引自:http://ilewen.com/questions/514編程
在本教程中,我會向你展現如何用C#創建一個線程中的TCP服務端。若是你用過windows的sockets編寫程序,你就知道有多麻煩。感謝.net框架,使得網絡編程變得更容易了。windows
咱們將創建一個很是簡單的的服務器接受客戶端鏈接,並能夠發送和接收數據。服務器爲每個鏈接客戶端產生一個線程,從理論上講,能夠接受多個鏈接(雖在實踐中,Windows對此是有限制)。
下面看代碼:數組
using System; using System.Text; using System.Net.Sockets; using System.Threading; using System.Net; namespace TCPServerTutorial { class Server { private TcpListener tcpListener; private Thread listenThread; public Server() { this.tcpListener = new TcpListener(IPAddress.Any, 3000); this.listenThread = new Thread(new ThreadStart(ListenForClients)); this.listenThread.Start(); } } }
上面創建了一個基本的服務器類。咱們定義了一個TcpListener變量(TcpListener封裝了底層套接字通訊工做),同時定義了一個線程用於監聽客戶端的鏈接。接下來咱們定義了ThreadStart的委託函數:ListenForClients。
代碼以下:服務器
private void ListenForClients() { this.tcpListener.Start(); while (true) { //blocks until a client has connected to the server TcpClient client = this.tcpListener.AcceptTcpClient(); //create a thread to handle communication //with connected client Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClientComm)); clientThread.Start(client); } }
這個函數很是簡單。首先,它啓動TcpListener,而後循環接受鏈接。 AcceptTcpClient的調用將阻塞線程的執行,直到一個客戶端鏈接,在這裏,咱們觸發一個線程來處理與咱們的新客戶端的通訊。我用了ParameterizedThreadStart委託,因此我能夠傳遞AcceptTcpClient調用返回的TcpClient對象到新線程。網絡
ParameterizedThreadStart使用函數HandleClientComm。這個函數負責從客戶端讀取數據。讓咱們看看它。併發
private void HandleClientComm(object client) { TcpClient tcpClient = (TcpClient)client; NetworkStream clientStream = tcpClient.GetStream(); byte[] message = new byte[4096]; int bytesRead; while (true) { bytesRead = 0; try { //blocks until a client sends a message bytesRead = clientStream.Read(message, 0, 4096); } catch { //a socket error has occured break; } if (bytesRead == 0) { //the client has disconnected from the server break; } //message has successfully been received ASCIIEncoding encoder = new ASCIIEncoding(); System.Diagnostics.Debug.WriteLine(encoder.GetString(message, 0, bytesRead)); } tcpClient.Close(); }
第一步將client類型轉換爲TcpClient類型,由於ParameterizedThreadStart委託只能接受基本對象類型。下一步,從TcpClient獲得NetworkStream用來讀取數據。以後,經過一個while循環從客戶端讀取數據。 read調用會一直處於阻塞狀態,直到從客戶端接收到數據。若是從客戶端讀取到零字節,那麼說明客戶端已斷開。在該例子裏,我只是一個字符串轉換成字節數組,並將它輸出到控制檯。固然,你會作一些更復雜的工做。若是socket出現錯誤或客戶端斷開鏈接,你應該調用TcpClient對象的close函數關閉鏈接,釋放它使用的任何資源。框架
上面就是建立一個服務器線程,接受鏈接,並從客戶端讀取數據所需作的全部的事情。固然,若是服務端不能發送數據,那麼就沒什麼用了。下面,讓咱們看看如何將數據發送到咱們的鏈接的一個客戶端。socket
NetworkStream clientStream = tcpClient.GetStream(); ASCIIEncoding encoder = new ASCIIEncoding(); byte[] buffer = encoder.GetBytes("Hello Client!"); clientStream.Write(buffer, 0 , buffer.Length); clientStream.Flush();
從AcceptTcpClient返回的TcpClient對象用來發送數據給客戶端。所以須要在服務端保存這些對象。一般能夠建立一個TcpClient對象的集合。發送數據是很是簡單的,只須要獲得client的NetworkStream對象,而後調用它的write方法就能夠了。tcp
TCP服務端已經完成了。比較難的部分是定義一個協議用來在客戶端和服務端之間發送信息。應用層的協議一般對不一樣的應用都是不同的。因此我不打算講解更多,你只須要實現你本身的。函數
若是沒有一個客戶端鏈接到服務端,那麼這個服務端還有存在的意思嗎?本教程主要是講服務端編程,但這裏有一個簡短的代碼,說明了如何設置一個基本的TCP鏈接,併發送一段數據。
TcpClient client = new TcpClient(); IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 3000); client.Connect(serverEndPoint); NetworkStream clientStream = client.GetStream(); ASCIIEncoding encoder = new ASCIIEncoding(); byte[] buffer = encoder.GetBytes("Hello Server!"); clientStream.Write(buffer, 0 , buffer.Length); clientStream.Flush();
第一步工做是獲取客戶端到服務端的鏈接。使用TcpClient.Connect方法。它須要知道服務端的IPEndPoint,在這裏,我將鏈接到本地主機,端口號3000。而後發送字符串"hello Server!"
注意:從客戶端或服務器寫並不老是等於一個在接收端讀。例如,客戶端向服務器發送10個字節,但服務器可能沒法在第一次讀取的時候獲得全部10個字節。使用TCP,幾乎保證最終獲得全部10個字節,但它可能須要不止讀取一次。因此當設計交互協議的時候要注意這一點。