做了这么久的视觉一直没发现这个问题,直到前几天有网友问我为什么TCP通信会自动断开。当时我就懵了,我从来没遇到断开的情况啊。于是一通百度,发现很多网友都有这种情况,造成这种情况的原因是保持连接的通道如果长时间不通信就会被路由关闭连接,造成我们看到的自动断开现象。解决这个问题的办法也很简单,起一个线程,用tcp的连接状态做判断条件,只有有连接,就每隔一段时间发送一个数据过去,这样就会让路由觉得我们一直是活跃的,就不会被关闭了。一旦捕获到发送异常情况,那就尝试重新连接即可。
好了,整个过程就是这样,简称心跳包,每隔一段时间发送心跳,告诉对方自己还活着。为了方便大家理解,我随便写了一个例子,仅供参考,当然很多地方不够完善,比较是随手写的,大冬天的码字太恼火了。
服务端代码
为了方便使用,我将socket封装成了一个类,并提供了4个事件给外部使用,下面是这个类的代码,需要的可以直接拿来用。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Net; using System.Net.Sockets; using System.Threading; namespace TCPheart { public delegate void OnSocketReceive(String socket, String str);//声明接收TCP信息委托 public delegate void OnSocketEnter(String str);//声明Sockt连接委托 public delegate void OnSocketRemove(String str);//声明Sockt断开委托 public delegate void OnSocketError(String str);//声明Sockt发信错误委托 public class DMDSocket { public event OnSocketReceive OnSocketReceiveListener;//声明TCP接收委托类型的事件 public event OnSocketEnter OnSocketEnterListener;//声明TCP连接成功委托类型的事件 public event OnSocketRemove OnSocketRemoveListener;//声明Sockt断开委托类型的事件 public event OnSocketRemove OnSocketErrorListener;//声明Sockt发信错误委托类型的事件 Socket mysocket = null;//socket服务端对象 Dictionary<String, Socket> mydic = new Dictionary<string, Socket>();//已连接列表 Dictionary<String, Thread> mythread = new Dictionary<string, Thread>();//已连接监听线程列表 /// <summary> /// 初始化Socket对象 /// </summary> /// <param name="ip">ip</param> /// <param name="prots">端口</param> public DMDSocket(String ip, int prots) { mysocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); IPAddress ipsa = IPAddress.Parse(ip); IPEndPoint ipenp = new IPEndPoint(ipsa, prots); mysocket.Bind(ipenp); mysocket.Listen(10); } /// <summary> /// 开启Socket服务 /// </summary> public void StartScoketSever() { Thread thread1 = new Thread(new ThreadStart(waitconnet)); thread1.IsBackground = true; thread1.Start(); } /// <summary> /// 客户端连接处理 /// </summary> private void waitconnet() { while (true) { Socket socketconn = mysocket.Accept(); OnSocketEnterListener(socketconn.RemoteEndPoint.ToString()); mydic.Add(socketconn.RemoteEndPoint.ToString(), socketconn); ParameterizedThreadStart pts = new ParameterizedThreadStart(mesgthread); Thread threadmes = new Thread(pts); threadmes.IsBackground = true; threadmes.Start(socketconn); mythread.Add(socketconn.RemoteEndPoint.ToString(), threadmes); } } /// <summary> /// 消息处理 /// </summary> /// <param name="socketpra">客户端Socket连接对象</param> private void mesgthread(object socketpra) { int ststics = 0; Socket socketrec = socketpra as Socket; while (true) { byte[] argmesg = new byte[1024 * 1024]; int length = -1; try { length = socketrec.Receive(argmesg); String getstr = Encoding.UTF8.GetString(argmesg, 0, length); OnSocketReceiveListener(socketrec.RemoteEndPoint.ToString(), getstr); } catch (Exception ex) { if (ststics == 0) { mydic.Remove(socketrec.RemoteEndPoint.ToString()); mythread.Remove(socketrec.RemoteEndPoint.ToString()); OnSocketRemoveListener(socketrec.RemoteEndPoint.ToString()); } ststics = 1; } Thread.Sleep(100); } } /// <summary> /// 发送消息 /// </summary> /// <param name="clent">客户端连接名</param> /// <param name="msg">发送的消息内容</param> public void SendMsg(String clent, String msg) { try { byte[] strmes = Encoding.UTF8.GetBytes(msg); mydic[clent].Send(strmes); } catch (Exception ex) { //异常处理逻辑,这里触发错误事件 OnSocketErrorListener(ex.ToString()); } } } }
下面是窗体类,使用上面这个TCP类创建连接,并发起心跳包以及更新UI的一些操作。
public partial class Form1 : Form{public Form1(){InitializeComponent();}//发送心跳包private void button1_Click(object sender, EventArgs e){ParameterizedThreadStart pts = new ParameterizedThreadStart(heart);Thread threadmes = new Thread(pts);threadmes.IsBackground = true;threadmes.Start(TCP);}Boolean connect = true;public void heart(object socket){DMDSocket tcp = socket as DMDSocket;while (connect){tcp.SendMsg(socketname," ");Thread.Sleep(10000);}}DMDSocket TCP;//创建TCP连接private void Form1_Load(object sender, EventArgs e){TCP = new DMDSocket("127.0.0.1", 1234);TCP.OnSocketErrorListener += error;TCP.OnSocketEnterListener += enter;TCP.OnSocketReceiveListener += receive;TCP.OnSocketRemoveListener += remove;TCP.StartScoketSever();}//错误事件public void error(String str) {connect = false;MessageBox.Show(str);}String socketname;//连接事件public void enter(String str){socketname = str;MessageBox.Show(str);}//接收消息事件public void receive(String socket,String str){MessageBox.Show(str);}//连接断开事件public void remove(String str){MessageBox.Show(str);}}
客户端
C#的socket客户端前面已经有写过了,这里使用的客户端仅作为接收,所以和前面的代码没什么区别,不会的可以看看这篇文章。C#Socket套接字使用Tcp协议通信(客户端),如果你觉得我写的这个服务器端代码看不懂,可以看看以前写的简单版TCP服务器端代码,自己加上心跳包即可。C#Socket套接字使用Tcp协议通信(服务器端)
做了个简单测试,连接保持了2个小时没有断开,应该是可靠的。我之前没发现这个问题可能是我们的设备运行比较快,几乎没有休息的时候,并且出问题了我都是让他们直接重启,毕竟重启能解决99%的问题嘛哈哈哈。
原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/tech/pnotes/241249.html