1. 程式人生 > >Socket程式設計實現服務端和客戶端的互動

Socket程式設計實現服務端和客戶端的互動

用Socket實現網路程式設計首先要建立一個Socket物件,Socket類位於System.Net.Socket名稱空間,需要先行匯入。建立Socket物件需要以下三個引數,這些引數都是列舉型別:

①AddressFamily成員指定Socket用來解析地址的定址方案,例如:InternetWork指示當Socket使用一個IP版本4地址連線;

②SocketType定義一個要開啟的Socket型別;

③Socket類使用ProtocolType列舉向Windows Sockets API通知所請求的協議。

如下程式碼為服務端建立Socket的程式碼:

  1. public partial class
     FrmService : Form     //服務端窗體  
  2.     {  
  3.         Socket listenSocket = null;   //伺服器端負責監聽的套接字  
  4.         Thread connThread = null;   //負責監聽客戶端連線請求的執行緒  
  5.         public FrmService()  
  6.         {  
  7.             InitializeComponent();  
  8.             TextBox.CheckForIllegalCrossThreadCalls = false;  //關閉對文字框的跨執行緒操作檢查  
  9.         }  
  10.         private void btnBeginListen_Click(object sender, EventArgs e)  
  11.         {  
  12.             //建立伺服器端負責監聽的套接字,引數(使用IP4定址協議,使用流式連線,使用TCP協議傳輸資料)  
  13.             listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);  
  14.             //用文字框中的IP地址建立一個IP地址物件IPAddress  
  15.             IPAddress address = IPAddress.Parse(txtIP.Text.Trim());  
  16.             //建立一個包含IP地址和埠的網路節點物件  
  17.             IPEndPoint endpoint = new IPEndPoint(address, int.Parse(txtPort.Text.Trim()));  
  18.             //把網路節點物件繫結到負責監聽的Socket上  
  19.             listenSocket.Bind(endpoint);  
  20.             //設定監聽佇列的長度,也就是當前可以同時監聽的請求次數  
  21.             listenSocket.Listen(10);  
  22.             //建立負責監聽的執行緒物件  
  23.             connThread = new Thread(WatchConnection);  
  24.             //把該執行緒設定為後臺執行緒  
  25.             connThread.IsBackground = true;  
  26.             //開啟執行緒  
  27.             connThread.Start();  
  28.             txtShow.Text = "服務端啟動監聽成功!";  
  29.         }  
  30.         /// <summary>  
  31.         /// 監聽客戶端連線事件  
  32.         /// </summary>  
  33.         private void WatchConnection()  
  34.         {  
  35.             while (true)  //使用一個死迴圈持續不斷的監聽新的客戶端的連線請求  
  36.             {  
  37.                 //開始監聽客戶端的連線請求  
  38.                 Socket connSocket = listenSocket.Accept();  
  39.                 ShowMsg("客戶端連線成功!");  
  40.             }  
  41.         }  
  42.         private void ShowMsg(string message)  
  43.         {  
  44.             txtShow.Text = message + "\r\n";  
  45.         }  
  46.     }  
由於Socket監聽客戶端連線請求的Accept()方法會阻斷當前執行緒的執行,直到接收到客戶端的連線資訊為止,所以我們要另外建立一個後臺執行緒來實現監聽功能而又不影響我們對主窗體的操作。在這個後臺執行緒的委託事件中,需要持續不斷地呼叫Accept()方法實現不斷監聽。在上面程式碼中,有一句:listenSocket.Listen(10); ,這句程式碼的意思是設定當前監聽Socket的監聽佇列,每次只能同時監聽10個客戶端的連線請求。

服務端負責監聽的Socket在使用Accept()方法監聽到客戶端的連線請求之後,會馬上返回一個新的Socket來用於和這個客戶端進行通訊。而又由於客戶端的數量不確定,因此這裡需要一個集合來專門儲存Accept()方法返回的與客戶端進行通訊的Socket。這樣才能讓該服務端同時連線上多個客戶端。

經過改善後的程式碼如下:

  1. public partial class FrmService : Form     //服務端窗體  
  2.     {  
  3.         Socket listenSocket = null;   //伺服器端負責監聽的套接字  
  4.         Thread connThread = null;   //負責監聽連線的執行緒  
  5.         Dictionary<string, Thread> recThreads = new Dictionary<string, Thread>();    //專門負責接收訊息的執行緒  
  6.         //Socket connSocket = null;  //服務端負責與客戶端通訊的Socket  
  7.         //專門用於儲存//服務端負責與客戶端通訊的Socket的集合  
  8.         Dictionary<string, Socket> connSockets = new Dictionary<string, Socket>();  
  9.         IPEndPoint endpoint = null;   //服務端的IP埠  
  10.         public FrmService()  
  11.         {  
  12.             InitializeComponent();  
  13.             TextBox.CheckForIllegalCrossThreadCalls = false;  //關閉對文字框的跨執行緒操作檢查  
  14.         }  
  15.         private void btnBeginListen_Click(object sender, EventArgs e)  
  16.         {  
  17.             //建立伺服器端負責監聽的套接字,引數(使用IP4定址協議,使用流式連線,使用TCP協議傳輸資料)  
  18.             listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);  
  19.             //用文字框中的IP地址建立一個IP地址物件IPAddress  
  20.             IPAddress address = IPAddress.Parse(txtIP.Text.Trim());  
  21.             //建立一個包含IP地址和埠的網路節點物件  
  22.             endpoint = new IPEndPoint(address, int.Parse(txtPort.Text.Trim()));  
  23.             //把網路節點物件繫結到負責監聽的Socket上  
  24.             listenSocket.Bind(endpoint);  
  25.             //設定監聽佇列的長度,也就是當前可以同時監聽的請求次數  
  26.             listenSocket.Listen(10);  
  27.             //建立負責監聽的執行緒物件  
  28.             connThread = new Thread(WatchConnection);  
  29.             //把該執行緒設定為後臺執行緒  
  30.             connThread.IsBackground = true;  
  31.             //開啟執行緒  
  32.             connThread.Start();  
  33.             ShowMsg("服務端啟動監聽成功!");  
  34.         }  
  35.         /// <summary>  
  36.         /// 監聽客戶端連線事件  
  37.         /// </summary>  
  38.         private void WatchConnection()  
  39.         {  
  40.             while (true)  //使用一個死迴圈持續不斷的監聽新的客戶端的連線請求  
  41.             {  
  42.                 //開始監聽客戶端的連線請求  
  43.                 Socket connSocket = listenSocket.Accept();  
  44.                 //向ListBox中新增一個IP埠字串,作為訪問該客戶端的唯一標誌  
  45.                 lbUniqueSign.Items.Add(connSocket.RemoteEndPoint.ToString());  
  46.                 //將與客戶端通訊的Socket新增到集合中  
  47.                 connSockets.Add(connSocket.RemoteEndPoint.ToString(), connSocket);  
  48.                 Thread thread = new Thread(ReciveMessage);  
  49.                 thread.IsBackground = true;  
  50.                 //以IP埠字串為Key值,把接收訊息的執行緒新增到recThread集合中。  
  51.                 recThreads.Add(connSocket.RemoteEndPoint.ToString(), thread);  
  52.                 thread.Start(connSocket);   //傳入引數,這個引數是當前負責與這個客戶端進行通訊的Socket  
  53.                 ShowMsg("客戶端連線成功!" + connSocket.RemoteEndPoint.ToString());  
  54.             }  
  55.         }  
  56.         private void ShowMsg(string message)  
  57.         {  
  58.             txtShow.AppendText(message + "\r\n");  
  59.         }  
  60.         /// <summary>  
  61.         /// 傳送訊息按鈕  
  62.         /// </summary>  
  63.         /// <param name="sender"></param>  
  64.         /// <param name="e"></param>  
  65.         private void btnSendMsg_Click(object sender, EventArgs e)  
  66.         {  
  67.             //把傳送的訊息轉換為位元組陣列  
  68.             byte[] arrSendMsg = Encoding.UTF8.GetBytes(txtMessage.Text.Trim());  
  69.             //按照選定的IP埠,把訊息傳送到該客戶端上  
  70.             connSockets[lbUniqueSign.SelectedItem.ToString()].Send(arrSendMsg);  
  71.             //顯示訊息  
  72.             ShowMsg(endpoint.ToString() + "傳送訊息:");  
  73.             ShowMsg("\t"+txtMessage.Text.Trim());  
  74.         }          
  75.         /// <summary>  
  76.         /// 迴圈接收客戶端傳送過來的資料  
  77.         /// </summary>  
  78.         private void ReciveMessage(object socketParam)       //接收訊息的方法需要一個object型別的引數  
  79.         {  
  80.             Socket socketClient = socketParam as Socket;   //把object型別轉換為Socket型別  
  81.             while (true)  
  82.             {  
  83.                 //宣告一個2M空間的位元組陣列  
  84.                 byte[] arrRecMsg = new byte[1024 * 1024 * 2];  
  85.                 //把接收到的位元組存入位元組陣列中,並獲取接收到的位元組數  
  86.                 int length = socketClient.Receive(arrRecMsg);  
  87.                 //按照接收到的實際位元組數獲取傳送過來的訊息  
  88.                 ShowMsg(socketClient.RemoteEndPoint.ToString() + ":");  
  89.                 ShowMsg("\t" + Encoding.UTF8.GetString(arrRecMsg, 0, length));  
  90.             }  
  91.         }  
  92.     }  

以上程式碼在改善之後,又添加了一個傳送訊息方法。傳送訊息需要呼叫Socket的Send()方法,它需要一個byte型別的陣列引數。我們需要先把要傳送的訊息轉換為byte型別的陣列,使用Encoding.UTF8.GetBytes()方法,傳入訊息引數即可,然後把這個陣列傳入Send()方法中。上面程式碼還實現了接收客戶端資訊的功能,我們把接收訊息也放在一個單獨的後臺執行緒中。(具體原因在下面)

我們需要從客戶端去連線服務端,因此在客戶端,我們需要建立一個專門連線服務端的Socket,並傳入服務端的IP埠。程式碼如下:

  1. public partial class FrmClient : Form     //客戶端窗體  
  2.     {  
  3.         private Thread recThread = null;   //負責持續獲取服務端發來的訊息  
  4.         private Socket socketClient = null;  //建立客戶端負責連線的Socket物件  
  5.         public FrmClient()  
  6.         {  
  7.             InitializeComponent();  
  8.             TextBox.CheckForIllegalCrossThreadCalls = false;  
  9.         }  
  10.         private void btnConnectService_Click(object sender, EventArgs e)  
  11.         {  
  12.             //建立一個IPAddress物件  
  13.             IPAddress address = IPAddress.Parse(txtIP.Text.Trim());  
  14.             //建立一個包含IP地址和埠的網路節點IPEndPoint物件  
  15.             IPEndPoint endpoint = new IPEndPoint(address, int.Parse(txtPort.Text.Trim()));  
  16.             //建立Socket物件  
  17.             socketClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);  
  18.             //傳入網路節點,連線服務端  
  19.             socketClient.Connect(endpoint);  
  20.             //建立獲取訊息的後臺執行緒物件  
  21.             recThread = new Thread(ReciveMessage);  
  22.             recThread.IsBackground = true;  
  23.             //開啟執行緒  
  24.             recThread.Start();  
  25.         }  
  26.         /// <summary>  
  27.         /// 迴圈接收服務端傳送過來的資料  
  28.         /// </summary>  
  29.         private void ReciveMessage()  
  30.         {  
  31.             while (true)  
  32.             {  
  33.                 //宣告一個2M空間的位元組陣列  
  34.                 byte[] arrRecMsg = new byte[1024 * 1024 * 2];  
  35.                 //把接收到的位元組存入位元組陣列中,並獲取接收到的位元組數  
  36.                 int length = socketClient.Receive(arrRecMsg);  
  37.                 //按照接收到的實際位元組數獲取傳送過來的訊息  
  38.                 ShowMsg(socketClient.RemoteEndPoint.ToString() + ":");  
  39.                 ShowMsg("\t" + Encoding.UTF8.GetString(arrRecMsg, 0, length));  
  40.             }  
  41.         }  
  42.         private void ShowMsg(string message)  
  43.         {  
  44.             txtShow.AppendText(message + "\r\n");  
  45.         }  
  46.         /// <summary>  
  47.         /// 傳送訊息按鈕  
  48.         /// </summary>  
  49.         /// <param name="sender"></param>  
  50.         /// <param name="e"></param>  
  51.         private void btnSendMsg_Click(object sender, EventArgs e)  
  52.         {  
  53.             byte[] arrSendMsg = Encoding.UTF8.GetBytes(txtMessage.Text.Trim());  
  54.             socketClient.Send(arrSendMsg);  
  55.             ShowMsg("客戶端" + "傳送訊息:");  
  56.             ShowMsg("\t"+txtMessage.Text.Trim());  
  57.         }  
  58.     }  
由於Socket的Recive()方法會阻礙執行緒的執行,因此也需要新建一個後臺執行緒來接收服務端的資訊。這個方法需要一個byte型別的陣列來儲存接收的位元組,然後返回總的位元組數。我們可以事先宣告一個2M的byte型別的陣列。然後我們需要把這些位元組轉換為字串,可以使用Encoding.UTF8.GetString()方法,這個方法需要3個引數,一個是儲存資訊的byte型別的陣列,一個是開始轉換的位元組數,最後一個是需要轉換的位元組長度。如果不設定需要轉換的位元組長度,那麼程式就會把2M的空間的byte型別陣列全部轉換,這樣會導致資源的浪費和接收訊息的不正確。在上面的程式碼中我們還實現了從客戶端向服務端傳送資訊。由於客戶端已經有一個連線服務端的Socket物件,因此直接呼叫該物件的Send()方法就可以了。

同時啟動服務端和客戶端兩個程式(先啟動其中一個專案,然後在解決方案管理器的另外一個專案名上單擊右鍵,在彈出的快捷選單中選擇“除錯”裡面的“啟動新例項”項並單擊),執行結果如下圖:

博文地址:http://blog.csdn.net/u011416044/article/details/12004819