最近基于SNMP网络管理协议开发网管软件,SNMP协议传输层为UDP(port 161和162),网络上流行的C#版SNMP封装库SnmpSharpNet似乎不能够支持大规模(400台以上)网络设备管理,因为对udp通信模型封装的不够好。SnmpSharpNet虽然支持异步(Async)UDP传输,但是在业务上了利用_busy变量限制了发送接收同时并发的可能。
高性能的UDP传输,起码应该是多线程并发的,即多线程同时发送,多线程同时接收。本人对UDP编程的一些函数有些疑问,特做实验求解:
1> ReceiveFrom,作为阻塞模式,到底阻塞多久?
2> 多线程同时SendTo,可行吗?
3> 多线程同时ReceiveFrom,可行吗?
4> connect作用?
5> 再多线程同时send?
6> 再多线程同时receive?
1. ReceiveFrom
A.设置等待时间:_socket.SetSocketOption(SocketOptionLevel.Socket,
SocketOptionName.ReceiveTimeout, timeout);只要在timeout ms时间内,收到数据,程序立刻往下执行;否则timeout ms后,程序甩SocketException中的超时异常。
B.此函数每执行一次,只接收一个UDP数据包。
C.使用UDP编程,一般应用程序需要实现超时重传,即多长时间未收到响应请求的数据包,再次发送请求。注意某种情况:请求命令1第1次,超时,请求命令1第2次,读到命令1响应,处理;请求命令2第1次,读到命令响应,这个响应可能是上次命令1的响应,也可能是此次命令2的响应,要解包分析。
2. 多线程同时SendTo
3. 多线程同时ReceiveFrom
实验1:
服务器SendTo发送请求,客户机ReceiveFrom,客户机接着SendTo,服务器ReceiveFrom返回响应,局域网耗时15ms(程序另有一些Console.WriteLine及DateTime.Now.ToString函数调用)。两台服务器对同一个客户端通信,没出现数据包发错,肯能是服务器数量还不够?
实验2:
在服务端,把SendTo和ReceiveFrom封装到一个函数Request中,启动三个线程,同时调用Request发送不同的数据请求。
Thread1_Request(“1234567890ab”); //回送” 2234567890ab”
Thread2_Request(“abcefg123”); //回送” bbcefg123”
Thread3_Request(“abc987123”); //回送” bbc987123”
接收端在接收到数据后,把首个字符+1,发送回服务器。我们希望同一线程中请求与响应的数据包对应,很遗憾,实际多线程接收时,线程2可能ReceiveFrom线程3的响应。如下所示一次3个线程并发过程:
Thread2 21:24:24.156接收:bbcefg123
Thread3 21:24:24.156接收:bbc987123
21:24:24.156接收:2234567890ab
Thread3 21:24:24.156接收:bbcefg123 //不是期望数据包
Thread2 21:24:24.156接收:bbc987123 //不是期望数据包
21:24:24.156接收:2234567890ab
Thread2 21:24:24.171接收:bbcefg123
Thread3 21:24:24.171接收:bbc987123
21:24:24.171接收:2234567890ab
Thread2 21:24:24.187接收:bbcefg123
Thread3 21:24:24.187接收:bbc987123
21:24:24.187接收:2234567890ab
Thread3 21:24:24.187接收:bbc987123
21:24:24.203接收:2234567890ab
Thread2 21:24:24.203接收:bbcefg123
Thread2 21:24:24.375接收:bbcefg123
Thread3 21:24:24.375接收:bbc987123
21:24:24.375接收:2234567890ab
21:24:24.375接收:2234567890ab
Thread3 21:24:24.375接收:bbc987123
Thread2 21:24:24.375接收:bbcefg123
Thread2 21:24:24.390接收:bbc987123 //不是期望数据包
Thread3 21:24:24.390接收:bbcefg123 //不是期望数据包
21:24:24.390接收:2234567890ab
经实验得出:一台服务器多线程同时对一台客户端通信,客户端响应服务器请求的数据包,在服务器接收时,同一线程的请求与响应会不对应,即本地UDP接收,谁先ReceiveFrom,接收区的数据包就被谁取走,而不管哪个请求的。
UDP模式下,多线程通信是,SendTo、ReceiveFrom不能同时封装在一个线程内,应该有独立的ReceiveFrom接收线程,对每次ReceiveFrom的数据包解析,从业务上识别出是对哪次SendTo请求的回应。如果发送、接收封装在同一个线程,超时重传机制容易实现,但是只能单线程通信了,大规模采集通信情况下满足不了。如果发送、接收各个独立线程,采集通信就可并发了,然而响应包是哪个请求、超时重传机制实现麻烦一些。
实验3:
单服务器单线程与通信客户端通信,服务器端线程代码如下:
while (true)
{
try
{
++moniNum;
_socket.SendTo(buffer, bufferLength, SocketFlags.None, (EndPoint)netPeer);
sw.Start();
Console.WriteLine("执行{0}次,时间:{1}", moniNum,DateTime.Now.ToString(("HH:mm:ss.fff")));
recv = _socket.ReceiveFrom(inbuffer, ref remote);
sw.Stop();
Console.WriteLine("{1}执行此段代码耗时:{0} ms", sw.ElapsedMilliseconds, moniNum);
}
catch (SocketException ex)
{}
if (recv > 0)
{
sw.Stop();
Console.WriteLine("{1}成功接收耗时:{0} ms", sw.ElapsedMilliseconds, moniNum);
}
}
客户端ReceiveFrom监听,一收到数据马上SendTo回给服务器。
运行结果如下:
执行1次,时间:09:39:30.656
1执行此段代码耗时:75 ms //75ms
1成功接收耗时:75 ms
执行1次,时间:09:39:30.734
1执行此段代码耗时:0 ms
1成功接收耗时:0 ms
执行1次,时间:09:39:30.734
1执行此段代码耗时:0 ms
1成功接收耗时:0 ms
执行1次,时间:09:39:30.734
1执行此段代码耗时:0 ms
1成功接收耗时:0 ms
执行1次,时间:09:39:30.734
1执行此段代码耗时:0 ms
1成功接收耗时:0 ms
执行1次,时间:09:39:30.734
1执行此段代码耗时:0 ms
1成功接收耗时:0 ms
执行1次,时间:09:39:31.156
1执行此段代码耗时:0 ms
1成功接收耗时:0 ms
执行1次,时间:09:39:31.156
1执行此段代码耗时:196 ms //196ms
1成功接收耗时:196 ms
执行1次,时间:09:39:31.343
1执行此段代码耗时:0 ms
1成功接收耗时:0 ms
疑问:为什么服SendTo->客ReceiveFrom->客SendTo->服Receive过程有大量的耗时0ms?有那么快吗?即使sw.Start()语句放在服SendTo之前,也是如此。
实验4:
服务器单线程封装SendTo和ReceiveFrom,线程中SendTo轮询各个客户端,ReceiveFrom各个客户端的响应。客户端监听线程ReceiveFrom数据后,立刻SendTo至服务器。
服务器期望:任意客户端ReceiveFrom的数据包是对其上一次SendTo的响应。
但是,如果服务器请求客户端1数据,超时,重发,还是超时,最后超时次数已过,服务器接着请求客户端2的数据,等待客户端2的回复,然而,如果这时收到了客户端1的UDP数据包,ReceiveFrom接收后,丢弃此包。本来等待客户2的ReceiveFrom机会,错过了。按程序,再次对客户2执行SendTo和ReceiveFrom。这有几个问题:A.其实没必要对客户2再次SendTo;B.如果对客户2再次SendTo了,它会回复两次,那么这两次又有一次会成为后面处理的多余负担;C.那么好点的方案应该是ReceiveFrom与SendTo分离,不绑定在同一个线程内;D.方案C有前提,准备接收客户2的回复时,无法对UDP接收区清除(能,这也还不够),超时后,网络再也不可能收到此次请求的回复数据包。
4. connect作用
5. 再多线程同时send
6. 再多线程同时receive