.Net Socket通讯可以使用Socket类,也可以使用 TcpClient、 TcpListener 和 UdpClient类。我这里使用的是Socket类,Tcp协议。
程序很简单,一个命令行的服务端,一个命令行的客户端。服务端启动后根据输入的端口号绑定本机端口并启动侦听,客户端启动后根据输入的客户端数量、服务IP、服务端口号连接服务端。客户端连接成功后启动新线程随机发送消息到服务端并等待接收服务端返回的消息,服务端和客户端成功创建连接后启动新线程接收客户端消息并返回客户端一个消息,如此循环下去……
上图:
图1. 客户端运行界面
图2. 服务端运行界面
图3. 项目结构
服务端关键代码
启动侦听:
1 var endPoint = new IPEndPoint(0, port); 2 var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 3 socket.Bind(endPoint); 4 socket.Listen(int.MaxValue);
启动新线程接收客户端连接:
1 // 启动新线程负责接受客户端连接 2 var socketThread = new Thread(OnSocketAccept) {IsBackground = true}; 3 socketThread.Start(socket); 4 Show("服务准备就绪");
接收客户端连接细节:
1 /// <summary> 2 /// 建立Socket连接 3 /// </summary> 4 /// <param name="obj"></param> 5 private static void OnSocketAccept(object obj) 6 { 7 try 8 { 9 var socket = obj as Socket; 10 while (true) 11 { 12 try 13 { 14 Socket clientSocket = socket.Accept(); 15 ... 16 // 尝试获取锁,超时则关闭当前连接并继续下次循环 17 if (!Monitor.TryEnter(ClientDictLock, LockTimeOut)) 18 { 19 CloseSocket(socket, key); 20 continue; 21 } 22 try 23 { 24 // 当然连接已存在则先关闭再缓存新连接 25 if (ClientDict.ContainsKey(key)) 26 { 27 CloseSocket(ClientDict[key], key); 28 ClientDict[key] = clientSocket; 29 } 30 else 31 { 32 ClientDict.Add(key, clientSocket); 33 } 34 } 35 finally 36 { 37 Monitor.Exit(ClientDictLock); 38 } 39 // 启动线程池线程执行接收和发送操作 40 ThreadPool.QueueUserWorkItem(OnSendOrReceive, clientSocket); 41 } 42 catch (ThreadAbortException) 43 { 44 throw; 45 } 46 catch (Exception exception) 47 { 48 ... 49 } 50 } 51 } 52 catch (ThreadAbortException) 53 { 54 ... 55 } 56 catch (Exception exception) 57 { 58 ... 59 } 60 }
发送/接收数据:
1 /// <summary> 2 /// 接收/发送数据 3 /// </summary> 4 /// <param name="obj"></param> 5 private static void OnSendOrReceive(object obj) 6 { 7 try 8 { 9 var socket = obj as Socket; 10 ... 11 while (true) 12 { 13 try 14 { 15 ... 16 // 接收数据 17 var receiveLength = socket.Receive(receiveBuffer); 18 ... 19 // 发送数据 20 var sendLength = socket.Send(sendBuffer); 21 ... 22 } 23 catch (ThreadAbortException) 24 { 25 throw; 26 } 27 catch (SocketException exception) 28 { 29 CloseSocket(socket, key); 30 break; 31 } 32 catch (Exception exception) 33 { 34 ... 35 } 36 } 37 } 38 catch (ThreadAbortException) 39 { 40 } 41 catch (Exception exception) 42 { 43 ... 44 } 45 }
关闭连接:
1 /// <summary> 2 /// 关闭连接 3 /// </summary> 4 /// <param name="socket"></param> 5 /// <param name="key"></param> 6 private static void CloseSocket(Socket socket, IPEndPoint key) 7 { 8 socket.Shutdown(SocketShutdown.Both); 9 socket.Disconnect(true); 10 socket.Close(); 11 socket.Dispose(); 12 ... 13 }
客户端关键代码
连接服务端:
// 根据客户端数量建立Socket连接 for (int i = 1; i <= clientNum; i++) { ConnetServer(address, port, i); } /// <summary> /// 建立Socket连接 /// </summary> /// <param name="address"></param> /// <param name="port"></param> /// <param name="id"></param> static void ConnetServer(IPAddress address, int port, int id) { try { var clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // 连接服务端 clientSocket.Connect(address, port); ... // 启动线程池线程开始发送数据和接收数据 ThreadPool.QueueUserWorkItem(OnSendOrReceive, clientSocket); } catch (Exception exception) { ... } }
发送/接收数据、关闭连接代码同服务端一样。
优先使用线程池线程,服务端接受客户端连接使用独立线程(socketThread)是考虑到可能需要手工停止该线程。
使用 Monitor.TryEnter(ClientDictLock, LockTimeOut) 和 Monitor.Exit(ClientDictLock); 是考虑到 lock 可能形成死锁,使用lock需要注意。
本人才疏学浅,欢迎大家批评指正!