C#之网络通信你不得不踩的坑

先介绍故事背景,本人是一名15年老程序猿,从Logo到Basic到猥琐Basic到C++到C++++(C#)

最终使用C#已经13个年头,做网络通信也有10个年头了,自己写的tcp、udp各种rtu协议,modbus协议的封装类都有过,一开始用Socket自己封装类,后来发现TCPClient和UDPClient也挺好用的,项目中也用了无数,不过以前一直是机机交互,从来没出现过问题。

最近我看不惯公司的软件,申请立案重新做一个,用了CS架构,全UDPmodbus通信,UDPmodbus也是我这15年来的经验表明这是最好用的网络通信(不要跟我讲什么TCP三次握手,老夫用网都是UDP一把梭),历经一年制作完成了初代版本,虽然界面操作等各种bug层出不穷,但是我一直以为,网络部分是最稳定的,毕竟是我最新研发的UDP多线程上传下载并行异步modbus封装类。

结果上了一个大项目,服务器CPU负载异常的高,经常卡死(服务器程序完全死掉无响应)。

先展示代码,

UDP初始化
                UDPClient udpcon = new UdpClient(一个写了端口的 IPEndPoint);
                udpcon.BeginReceive(new AsyncCallback(ReadCallback), 0);

然后是那个ReadCallback异步回调函数

 static void ReadCallback(IAsyncResult ar)
{
            IPEndPoint dqip = new IPEndPoint(IPAddress.Any, 1);
            byte[] buf = new byte[1];
            try
            {
                buf = udpcon.EndReceive(ar, ref dqip);
            }
            catch
            {
            }

            restart:

            try
            {
                udpcon.BeginReceive(new AsyncCallback(ReadCallback), 0);
            }
            catch
            {

                  goto restar; 

       //这个操作也是以前总是监听中断收不到数据了,后来在网上找到的大神解决方案,

       //当时也是觉得这个操作666,果然加了这个goto以后,我的服务器再也没中断过了。

            }

           调个处理函数进行数据处理(buf);

}

//=============================================================

上面这个简单的逻辑我用了大约六七年,没有任何问题,包括做在线采集,上百采集器,服务器每秒好几千个udp包都没有问题

发现经常卡死之后,断点跟踪,开一大群客户端测试,结果发现,我以前觉得666的操作,goto restart每次都失败,就每次都goto,这等于一个死循环线程,只要出现1次异步回调失败就会增加这么一个死循环,任务管理器一看妈耶!那个线程数卡卡增长,很快就上万了,然后这个服务器就卡死了,于是我进行了一次重大升级,不废话,直接上代码


            try
            {
                udpcon.BeginReceive(new AsyncCallback(ReadCallback), 0);
            }
            catch
            {
                try
                {
                    udpcon = new UdpClient(ipbd本地地址);
                    udpcon.BeginReceive(new AsyncCallback(ReadCallback), 0);
                }
                catch
                {

                }
            }

那个goto改成了这样,果然,立刻就解决了服务器卡死问题。

结果挂了几天,发现服务器总是登陆的人一多,就收不到数据了,程序没死,界面按钮等操作都正常,就是UDP收不到了,找了几个人帮我一起测试了一整天,终于发现,在catch里面重新监听还是有可能会失败,我给第一次BeginReceive的catch加了个Exception看了一下错误原因,居然是【远程主机强迫关闭了一个现有的连接

我擦!凸(艹皿艹 )UDP是无连接协议,这地球人都知道,从上学第一次学网络通信就讲过,为什么会出现这个情况呢?而且用了这么多年网络通信的我居然没遇到过,结果发现,这是因为通信没有完成之前,客户端突然断开,服务器就会收到这个消息,特别是在客户端网络不好的情况下,就更容易出现,我自己测试因为都是本机,几乎不存在这个情况,而且以前用的机机通信也不存在客户端会关闭一说,都是长开的,所以我一直没遇到这个问题。

经过大约4个小时的搜索,终于找到了解决方案……

创建UDP监听之后,一定要做下列操作

在初始化对象后设置属性如下:

uint IOC_IN = 0x80000000; 
uint IOC_VENDOR = 0x18000000; 
uint SIO_UDP_CONNRESET = IOC_IN | IOC_VENDOR | 12; 
你的udp.Client.IOControl((int)SIO_UDP_CONNRESET, new byte[] {Convert.ToByte(false)}, null);

大概意思就是给当前连接创建基于Socket的最底层的通信过程。下面是搜索到的那篇文章,那文章我发现好多人转载,我就不转载了,直接贴个地址。有兴趣的自己去看看。

地址:https://www.cnblogs.com/liuslayer/p/7867239.html

 

* * MODBUS协议 * * * 介绍: * 此modbus上位机 协议类 具有较强的通用性 * 本协议类最主要的思想是 把所有向下位机送的指令 先存放在缓冲区中(命名为管道) * 再将管道中的指令逐个送出去。 * 管道遵守FIFO的模式。管道中所存放指令的个数 在全局变量中定义。 * 管道内主要分为两部分:1,定时循环送指令。2,一次性送指令。 * 定时循环送指令:周期性间隔时间送指令,一般针对“输入寄存器”或“输入线圈”等实时更新的变量。 * 这两部分的长度由用户所添加指令个数决定(所以自由性强)。 * 指令的最大送次数,及管道中最大存放指令的个数在常量定义中 可进行设定。 * * 使用说明: * 1,首先对所定义的寄存器或线圈进行分组定义,并定义首地址。 * 2,在MBDataTable数组中添加寄存器或线圈所对应的地址。 注意 寄存器:ob = new UInt16()。线圈:ob = new byte()。 * 3,对所定义的地址 用属性进行定义 以方便在类外进行访问及了解所对应地址的含义。 * 4,GetAddressValueLength函数中 对使用说明的"第一步"分组 的元素个数进行指定。 * 5,在主程序中调用MBConfig进行协议初始化(初始化内容参考函数)。 * 6,在串口中断函数中调用MBDataReceive()。 * 7,定时器调用MBRefresh()。(10ms以下) * 指令送间隔时间等于实时器乘以10。 例:定时器5ms调用一次 指令送间隔为50ms。 * 8,在主程序初始化中添加固定实时送的指令操作 用MBAddRepeatCmd函数。 * 9,在主程序运行过程中 根据需要添加 单个的指令操作(非固定重复送的指令)用MBAddCmd函数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值