0. 虽然之前在项目中也有用过 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) 单独传递一个变量到方法中.
源码下载地址如下: https://files.cnblogs.com/files/liyijin/SocketDemo.rar
来源: https://www.cnblogs.com/liyijin/p/8733453.html