這篇文章橙色的文字都是廢話,不耐煩的園友可以跳過那些文字。包括這句話。
最初接觸Socket編程的是在學校的java課上,可那時候沒心學java,老師講的Socket也沒怎么理會,上機操作時,上網拷了一段C#的客戶端和服務端代碼,分別與java寫的服務端和客戶端進行通信。至於整個通信流程是怎樣的沒理會,直到寫上一篇博文時才清楚。
還記得那時候上課老師問過如果一個服務端要跟兩個客戶端通信,那怎么辦?接着他復制粘貼了一下創建Socket,綁定,監聽那幾行代碼。
1 ServerSocket ss1 = new ServerSocket(8081); 2 Socket s1 = ss1.accept(); 3 ServerSocket ss2 = new ServerSocket(8082); 4 Socket s2 = ss2.accept();
其實這樣是多開了端口,的確是一個服務端對兩個客戶端通信了,但真正的一對多通信肯定不是這樣吧,否則作為一台服務器,面對那么大的並發量,要開多少個端口才完事。如果我沒理解錯的話,java的accept也是同步的,這樣就意味着一個客戶端1跟這個服務端連接了之后,需要再來一個客戶端2 (或者在那個客戶端1) 與另一個端口連接了,才能正常通信。這樣個人覺得不科學。
上網谷歌了一下,找到了一篇博文,是java的Socket的一對多通信,里面的一句話我一看眼發亮了:多執行一次Accept()。想C#與java類似的,於是嘗試了一下,果然行。唉!看來學Socket編程的,都是參考java那部分的文章。
結合了對多線程的皮毛理解,想了一個辦法實現一個服務端的與多個客戶端交互,而且是用同一端口。大致是這樣,在服務端的主線程執行一個循環去Accept客戶端的Connet。每Accept一次,就開一個線程去負責與這個客戶端通信。下面就上代碼
首先是一些需要用到的變量
1 static int socketCount; //已經開的線程 2 static object threadFlag; //多線程的鎖旗標 3 const int MAX_SOCKET_COUNT = 3; //最大接受客戶端數量
接着是Main方法里面的代碼
1 threadFlag = new object(); 2 socketCount = 0; 3 4 Socket serverScoket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp); 5 IPEndPoint serverPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8081); 6 7 serverScoket.Bind(serverPoint); 8 serverScoket.Listen(10);
這里跟往常的Socket通信一樣。建立終結點和Socket,綁定和監聽。下面的就涉及到多線程了。
1 Thread t = null; 2 Socket clientSocket = null; 3 try 4 { 5 while (true) 6 { 7 8 //判斷當前已開的線程數是否超出最大值,超出了則要等待 9 while (socketCount >= MAX_SOCKET_COUNT) Thread.Sleep(1000); 10 clientSocket = serverScoket.Accept(); 11 //累計變量自增 12 socketCount++; 13 IPEndPoint clientPoint = clientSocket.RemoteEndPoint as IPEndPoint; 14 Console.WriteLine("client {0}:{1} connect",clientPoint.Address,clientPoint.Port); 15 t = new Thread(new ParameterizedThreadStart(ThreadConnect)); 16 t.Start(clientSocket); 17 } 18 } 19 finally 20 { 21 serverScoket.Close(); 22 serverScoket.Dispose(); 23 Environment.Exit(0); 24 25 }
用了一個While循環去不斷地有限制地接受客戶端。每當接受了一個客戶端,就開一個線程去處理它的通信。同時已開啟線程數量累加一個。如果已開啟的線程數大於最大值的話是停止接受的。防止開的線程過多壓壞了服務端。
在接受時只是用了同步接受,沒采用異步,因為感覺沒必要,反正接受包在了一個死循環里面,還沒接收到就讓主線程一直卡在那句里面。等待接受了,才開始下一次接受等待。不過這里用的一個死循環覺得挺不妥的。沒辦法跳出循環,關閉Socket,釋放資源等一系列操作不知道在哪里執行好。
最后上一段線程執行方法代碼
1 static void ThreadConnect(object clientObj) 2 { 3 Socket clientSocket = clientObj as Socket; 4 IPEndPoint clientPoint = clientSocket.RemoteEndPoint as IPEndPoint; 5 if (clientSocket == null) 6 { 7 lock (threadFlag) 8 { 9 socketCount--; 10 } 11 return; 12 } 13 clientSocket.Send(Encoding.ASCII.GetBytes("Hello world"),SocketFlags.None); 14 byte[] datas; 15 int rec; 16 while (true) 17 { 18 datas = new byte[1024]; 19 rec = clientSocket.Receive(datas); 20 //當客戶端發來的消息長度為0時,表明結束通信 21 if (rec == 0) break; 22 23 string msg= "Msg has been receive length is "+rec; 24 clientSocket.Send(Encoding.ASCII.GetBytes(msg),SocketFlags.None); 25 } 26 Console.WriteLine("client {0}:{1} disconnect", clientPoint.Address, clientPoint.Port); 27 lock (threadFlag) 28 { 29 //減少當前已開的線程數 30 socketCount--; 31 clientSocket.Close(); 32 clientSocket.Dispose(); 33 } 34 }
整個消息接收和發送過程都放在了死循環里面,由客戶端發來的信息的長度來判定客戶端是否終止通信,從而斷定是否跳出循環。不過消息的收發可以用異步的,只不過這里沒用上而已,感覺這里應該用。在最后的地方還要關閉Socket,釋放資源。同時也要減少已開的線程數,畢竟這個線程已經完了。
在寫完這篇文章時突然想到 “Sokect連接池 ”這個名詞,接下來也嘗試一下。用那個來做應該會更好。這個小程序的確簡陋了,期待各位園友拍磚和吐槽。謝謝