虽然之前在项目中也有用过Socket,但始终不是自己搭建的,所以对Server,Clinet端以及心跳,断线重连总没有很深入的理解,现在自己搭建了一遍加深一下理解。
服务端使用WPF界面,客户端使用控制台。实现了心跳,断线重连,一个服务端对应多个客户端的功能。
一.服务端
1.1 先创建一个Socket实例,并绑定到20000端口号;通过Listen方法开始监听并设置最大监听数量。
//新建一个Socket服务端实例,并绑定到20000端口 socketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); socketServer.Bind(new IPEndPoint(IPAddress.Any, 20000)); //设置最大监听数量 socketServer.Listen(10);
1.2 当有客户端成功连接,则会通过Accept方法生产一个新的Socket实例;此时可开启心跳定时器,并开启一个线程接收消息。
//开启一个线程监听客户端 Thread thd = new Thread(new ThreadStart(ListenSocket)); thd.Start(); /// <summary> /// 开始监听 /// </summary> private void ListenSocket() { try { while (true) { Socket _socket = socketServer.Accept(); //开始心跳 System.Timers.Timer heartbeatTimer = new System.Timers.Timer(); heartbeatTimer.Interval = 60000; heartbeatTimer.Elapsed += new System.Timers.ElapsedEventHandler((s, e) => heartbeatTimerIsUp(s, e, _socket)); heartbeatTimer.Start(); Thread thdReceive = new Thread(new ParameterizedThreadStart(thdRevMethod)); thdReceive.Start(_socket); } } catch (Exception ex) { MessageBox.Show("程序出现异常:" + ex); } }
1.3 接收消息与发送消息的代码如下
/// <summary> /// 接收消息 /// </summary> /// <param name="obj"></param> private void thdRevMethod(object obj) { Socket _socket = obj as Socket; try { while (true) { byte[] resByte = new byte[1024]; int resInt = _socket.Receive(resByte); if (resInt > 0) { string res = Encoding.Default.GetString(resByte, 0, resInt);///接收消息后操作 } } } catch (SocketException sex) { if (sex.SocketErrorCode == SocketError.ConnectionReset) { //当客户端断开连接,从客户端列表中移除该客户端 } } catch (Exception ex) { MessageBox.Show("程序出现异常:" + ex); } } /// <summary> /// 发送消息 /// </summary> /// <param name="msg"></param> /// <param name="ipAndPord"></param> public bool sendMsg(string msg,string ipAndPord) { try { Socket _socket = socketTimerDic.SingleOrDefault(r => string.Equals(r.Key.RemoteEndPoint.ToString(), ipAndPord)).Key; if (_socket != null) { byte[] byteStr = Encoding.Default.GetBytes(msg); _socket.Send(byteStr); return true; } else { return false; } } catch (Exception) { return false; } }
二.客户端
2.1 先创建一个Socket实例,并连接到服务端所在的ip端口;连接成功后开启心跳定会器并开启发送和接收消息的两个线程。
// 新建客户端实例,并连接到服务端所在的端口号 socketClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); socketClient.Connect("127.0.0.1", 20000); Console.WriteLine("成功连接到服务端"); heartbeatTimer.Start(); //开启一个线程发送消息 Thread thdSend = new Thread(new ThreadStart(thdSendMethod)); thdSend.Start(); //开启一个线程接收信息 Thread thdRev = new Thread(new ThreadStart(thdRevMethod)); thdRev.Start();
2.2 发送消息和接收消息的代码如下,当服务端断开会在接收消息的线程中触发sex.SocketErrorCode == SocketError.ConnectionReset的异常,此时捕获到后开启重连定时器即可
/// <summary> /// 接收消息 /// </summary> private static void thdRevMethod() { try { while (true) { byte[] resByte = new byte[1024]; int resInt = socketClient.Receive(resByte); if (resInt > 0) { Console.WriteLine(Encoding.Default.GetString(resByte,0, resInt)); } } } catch (SocketException sex) { if (sex.SocketErrorCode == SocketError.ConnectionReset) { Console.WriteLine("服务端断开!5s后重连!"); reconnectTimer.Start(); heartbeatTimer.Stop(); } } catch (Exception ex) { Console.WriteLine("程序出现异常:" + ex); heartbeatTimer.Stop(); } } //发送信息 private static void thdSendMethod() { try { while (true) { string res = Console.ReadLine(); byte[] resByte = Encoding.Default.GetBytes(res); socketClient.Send(resByte); } } catch (Exception ex) { Console.WriteLine("程序出现异常:" + ex); heartbeatTimer.Stop(); } }
三.运行示例
四..总结
假如代码中有错误的地方,希望可以帮忙指出来改之。
其中要注意的地方如下:
1.WPF关闭窗口后,需要通过 Environment.Exit(0);来结束掉当前整个服务端进程,否则Socket开启的接收和发送进程将还在,客户端也不会检测到服务端断开。
2.在SocketException中捕获异常sex.SocketErrorCode == SocketError.ConnectionReset时说明客户端/服务端连接断开了,需要进行重连等操作。
3.在接收到消息时候需要用GetString(byte[] bytes, int index, int count)指定长度,而不该使用GetString(byte[] bytes),否则可能出现很多空格。
4.定时器假如需要传递参数,可以通过设置全局变量,或者(s, e) => heartbeatTimerIsUp(s, e, _socket)单独传递一个变量到方法中。
源码下载地址如下:SocketDemo.rar