最近的一个项目中需要同时使用两块网卡收发UDP组播数据包,并且要求使用Socket的方式接收和发送网络数据包(我不会告诉你们我之前是直接使用SharpPcap来实现的)。在C#中Socket接触的比较早,但是用的不多,特别是在实现本次上网卡的收发过程中也是遇到了不少麻烦。其中最最头疼的就是不能同时接收两张网卡的数据,虽然这个问题不是致命的(大不了用SharpPcap呗!!),但是最为一个21世纪有志青年,怎么能干出这种半吊子的事情呢!于是,这两天我陷入了这个问题无法自拔,当然,最后还是解决了!哈~哈~哈哈哈~~
就在解决问题的那瞬间,眼前忽然出现了一个身影——“啊~~勤劳的(码)农夫啊~~,请问你丢的是这把金斧头呢,还是这…….”,“金斧头!金斧头!”(嘿嘿,把它卖了就能炒股票!赚大钱!出任CEO!迎娶白富美!走向人生巅峰!)
啊~~~呸!呸!呸!其实我想说,良心发现的我还是觉得如果有那么一群人还在纠结这个问题的,那么看看这里,也许能有帮助。(真的!绝对不是来装X的)
问题概述
言归正传,在我的应用中,我主要是想利用C#的Socket来接收组播的UDP数据包。当然,发送也需要,但不是我最关心的问题。因此,我首先想到的就是UdpClient这个类。这个类对Socket进行了很好的封装,用起来更加简单。那么问题来了,我始终只能收到一个网卡上的数据,这是很头疼的。我检查了网络,检查了数据包发现都没问题,但就是只能收到其中一个网卡的数据。
下面是我初始化Socket的代码,该代码为CapDevice类中的一段:
// 定义IPEndPoint private IPEndPoint localEP = null; private IPEndPoint remoteEP = null; // 定义UDP发送和接收Socket //private Socket udpReceive = null; private UdpClient udpReceive = null; private UdpClient udpSend = null; // 本机节点 localEP = new IPEndPoint(device, LOCAL_PORT); // 远程节点 remoteEP = new IPEndPoint(IPAddress.Parse(MASTER_IP), DES_PORT); // 实例化 udpReceive = new UdpClient(AddressFamily.InterNetwork); udpReceive.Client.ReceiveBufferSize = 320000; udpReceive.Client.Bind(localEP); udpReceive.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); udpSend = new UdpClient(); // 发送和接收加入组播组 udpReceive.JoinMulticastGroup(IPAddress.Parse(MASTER_TX_GROUP));
udpSend.JoinMulticastGroup(IPAddress.Parse(MASTER_RX_GROUP)); // 打开发送标志位 isOpen = true; // 打开接收线程 cap_thread = new Thread(new ThreadStart(ReceiveLoop)); cap_thread.Start(); cap_thread.Priority = ThreadPriority.Highest; cap_thread.IsBackground = true;
在使用过程中,我分别实例化了两个CapDevice对象,并且将所需IP和端口信息通过参数传入。
device为本机网卡的IP地址;
LOCAL_PORT为本机侦听端口;
MASTER_IP为待接收的远程设备的IP地址;
DES_PORT为待接收的远程设备的端口;
MASTER_TX_GROUP为接收数据绑定的组;
MASTER_RX_GROUP为发送数据绑定的组;
本人使用台式机开发,主板自带一张千兆网卡,然后外接了一张PCI接口的千兆网卡。在我调试和寻找问题的过程中遇到这样的情况:
1. 两张网卡的IP分别为192.168.30.33和192.168.30.34,33的为主板上的网卡,34的是外接的网卡。按照上述方式进行配置后我发现我永远只能收到33的网卡上的数据,除非我拔掉33的网卡上的网线,并且重新打开接收,此时34的网卡上才会有数据。
2. 我尝试使用同步、异步的方法,并且尝试使用Socket类而不是UdpClient类,但是都没有成功。
因此,我怀疑,即使我的Socket侦听的IP是192.168.30.34,实际Windows还是没有对34这张网卡的数据进行侦听。
问题解决
我花了很多时间去寻找答案,但是结果还是不太对。最终我找到一个讲述跟我一样问题的网站:
http://stackoverflow.com/questions/15265620/udp-read-data-from-all-network-interfaces
提出问题的人也遇到了同样的问题,并且进行了很多尝试。最终他得到的结论是:“把同步接收改成异步接收就行啦!”
我试了一下,发现问题不在那里,还是没有。但是我发现一个地方,那就是加入组的时候他们使用的JoinMulticastGroup函数有两个参数,然后我这么改了:
udpReceive.JoinMulticastGroup(IPAddress.Parse(MASTER_TX_GROUP), localEP.Address);
恩,其实看了这么多,我就想说这个,真不好意思,浪费大家这么多时间看废话,嘻嘻!!
第二个参数是本地地址,真正能够让我的第二张网卡也能够接收数据的也是这个参数,看来第二个参数才是通知系统将IP与本地网卡建立联系。测试了一下,异步接收和同步接收都没有问题。
总结
问题总结一句话,加入组播组需要使用带本地IP地址的重载函数。当然,写这么多就是想把问题描述清楚一点,同样的问题按照这里所述就能得到解决。如果是其它原因导致Socket通信失败,按照本文所述就不一定能解决咯。
下面是初始化代码(区别就在高亮部分代码):
// 定义IPEndPoint private IPEndPoint localEP = null; private IPEndPoint remoteEP = null; // 定义UDP发送和接收Socket //private Socket udpReceive = null; private UdpClient udpReceive = null; private UdpClient udpSend = null; // 本机节点 localEP = new IPEndPoint(device, LOCAL_PORT); // 远程节点 remoteEP = new IPEndPoint(IPAddress.Parse(MASTER_IP), DES_PORT); // 实例化 udpReceive = new UdpClient(AddressFamily.InterNetwork); udpReceive.Client.ReceiveBufferSize = 320000; udpReceive.Client.Bind(localEP); udpReceive.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); udpSend = new UdpClient(); // 发送和接收加入组播组 udpReceive.JoinMulticastGroup(IPAddress.Parse(MASTER_TX_GROUP), localEP.Address);
udpSend.JoinMulticastGroup(IPAddress.Parse(MASTER_RX_GROUP)); // 打开发送标志位 isOpen = true; // 打开接收线程 cap_thread = new Thread(new ThreadStart(ReceiveLoop)); cap_thread.Start(); cap_thread.Priority = ThreadPriority.Highest; cap_thread.IsBackground = true;
这里顺便把我同步接收线程函数也贴出来吧:
private void ReceiveLoop() { byte[] rcvData; while (isOpen) { rcvData = udpReceive.Receive(ref remoteEP); // 解析数据 UploadEndecoder.CapturePkgM(rcvData); } udpReceive.Close(); udpSend.Close(); }