.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需要注意。
本人才疏學淺,歡迎大家批評指正!