淺析C#中的套接字編程

   C#是微軟隨着VS.net新推出的一門語言。它作爲一門新興的語言,有着C++的強健,又有着VB等的RAD特性。而且,微軟推出C#主要的目的是爲了對抗Sun公司的Java。大家都知道Java語言的強大功能,尤其在網絡編程方面。於是,C#在網絡編程方面也自然不甘落後於人。本文就向大家介紹一下C#下實現套接字(Sockets)編程的一些基本知識,以期能使大家對此有個大致瞭解。首先,我向大家介紹一下套接字的概念。

套接字基本概念:

套接字是通信的基石,是支持TCP/IP協議的網絡通信的基本操作單元。可以將套接字看作不同主機間的進程進行雙向通信的端點,它構成了單個主機內及整個網絡間的編程界面。套接字存在於通信域中,通信域是爲了處理一般的線程通過套接字通信而引進的一種抽象概念。套接字通常和同一個域中的套接字交換數據(數據交換也可能穿越域的界限,但這時一定要執行某種解釋程序)。各種進程使用這個相同的域互相之間用Internet協議簇來進行通信。

套接字可以根據通信性質分類,這種性質對於用戶是可見的。應用程序一般僅在同一類的套接字間進行通信。不過只要底層的通信協議允許,不同類型的套接字間也照樣可以通信。套接字有兩種不同的類型:流套接字和數據報套接字。

套接字工作原理:

要通過互聯網進行通信,你至少需要一對套接字,其中一個運行於客戶機端,我們稱之爲ClientSocket,另一個運行於服務器端,我們稱之爲ServerSocket。

根據連接啓動的方式以及本地套接字要連接的目標,套接字之間的連接過程可以分爲三個步驟:服務器監聽,客戶端請求,連接確認。

所謂服務器監聽,是服務器端套接字並不定位具體的客戶端套接字,而是處於等待連接的狀態,實時監控網絡狀態。

所謂客戶端請求,是指由客戶端的套接字提出連接請求,要連接的目標是服務器端的套接字。爲此,客戶端的套接字必須首先描述它要連接的服務器的套接字,指出服務器端套接字的地址和端口號,然後就向服務器端套接字提出連接請求。

所謂連接確認,是指當服務器端套接字監聽到或者說接收到客戶端套接字的連接請求,它就響應客戶端套接字的請求,建立一個新的線程,把服務器端套接字的描述發給客戶端,一旦客戶端確認了此描述,連接就建立好了。而服務器端套接字繼續處於監聽狀態,繼續接收其他客戶端套接字的連接請求。

C#中的套接字編程實例:

通過向大家簡單的介紹套接字的基本概念和實現套接字編程的基本原理,我想大家對套接字編程已有了初步的瞭解。不過,上面介紹的僅僅是基本概念和原理,要真正運用還是需要一定的工作的。對基本概念和原理的真正理解的最好方法莫過於自己動手做一個實例,下面我就向大家介紹一個很好的用C#實現套接字編程的實例――聊天室程序。

本程序是基於C/S(服務器/客戶端)構架的,程序包含一個服務器端的應用程序和一個客戶端的應用程序。首先,在服務器上運行服務器端的應用程序,該程序一運行就開始服務器監聽。然後,在客戶機上就可以打開客戶端的應用程序。程序打開後可以與服務器端應用程序進行連接,即進行客戶端請求。在連接確認後,客戶端用戶可以和其他的客戶端用戶進行聊天。客戶端人數沒有限制,同時還支持“悄悄話”聊天模式,支持聊天記錄。所以這是一個學習套接字編程的相當不錯的例子。而且,程序中爲了處理每個客戶端的信息還用到了多線程機制。在每個客戶端與服務器端連接成功後,它們之間就建立一個線程。這樣運用了多線程之後,客戶端之間就不會相互影響,即使其中一個出了錯誤也不會影響到另一個。

下面,我就向大傢俱體介紹該實例:

服務器端程序:

1. 打開VS.net,新建一個C#的模板爲“Windows 應用程序”的項目,不妨命名爲“ChatServer”。

2. 佈置界面。只需在界面上添加一個ListBox控件即可,該控件主要用於顯示客戶端的用戶的一些信息的。圖象如下:



3. 服務器端程序的代碼編寫。

對於服務器端,主要的作用是監聽客戶端的連接請求並確認其請求。程序一開始便打開一個StartListening()線程。

private void StartListening() 



  listener 
= new TcpListener(listenport); 

  listener.Start(); 

  
while (true

  


try 



  Socket s 
= listener.AcceptSocket(); 

  clientsocket 
= s; 

  clientservice 
= new Thread(new ThreadStart(ServiceClient)); 

  clientservice.Start(); 

}
 

catch(Exception e) 



  Console.WriteLine(e.ToString() ); 

}
 

  }
 

}
 


該線程是一直處於運行狀態的。當服務器端接收到一個來自客戶端的連接請求後,它就打開一個ServiceClient()線程來服務客戶端。當一個連接被建立後,每個客戶端就被賦予一個屬於它自己的套接字。同時,一個Client類的對象被建立。該對象包含了客戶端的一些相關信息,該信息被保存在一個數組列表中。 Client類如下:

using System; 

using System.Threading; 

namespace ChatServer 



using System.Net.Sockets; 

using System.Net; 

/// 

/// Client 的摘要說明。 

/// 


public class Client 



private Thread clthread; 

private EndPoint endpoint; 

private string name; 

private Socket sock; 

public Client(string _name, EndPoint _endpoint, Thread _thread, Socket _sock) 



// TODO: 在此處添加構造函數邏輯 

clthread 
= _thread; 

endpoint 
= _endpoint; 

name 
= _name; 

sock 
= _sock; 

}
 

public override string ToString() 



return endpoint.ToString()+ " : " + name; 

}
 

public Thread CLThread 



get{return clthread;} 

set{clthread = value;} 

}
 

public EndPoint Host 



get{return endpoint;} 

set{endpoint = value;} 

}
 

public string Name 



get{return name;} 

set{name = value;} 

}
 

public Socket Sock 



get{return sock;} 

set{sock = value;} 

}
 

}
 

}
 


程序的主體部分應是ServiceClient()函數。該函數是一個獨立的線程,其主要部分是一個while循環。在循環體內,程序處理各種客戶端命令。服務器端接收來自客戶端的以ASCII碼給出的字符串,其中包含了一個“|”形式的分隔符。字符串中“|”以前的部分就是具體的命令,包括CONN、CHAT、PRIV、GONE四種類型。CONN命令建立一個新的客戶端連接,將現有的用戶列表發送給新用戶並告知其他用戶有一個新用戶加入。CHAT命令將新的信息發送給所有用戶。PRIV命令將悄悄話發送給某個用戶。GONE命令從用戶列表中除去一個已離開的用戶並告知其他的用戶某某已經離開了。同時,GONE命令可以設置布爾型的變量keepalive爲false從而結束與客戶端連接的線程。ServiceClient()函數如下:

 

private void ServiceClient() 

{ Socket client = clientsocket; 

bool keepalive = true

while (keepalive) 



Byte[] buffer 
= new Byte[1024]; 

client.Receive(buffer); 

string clientcommand = System.Text.Encoding.ASCII.GetString(buffer); 

string[] tokens = clientcommand.Split(new Char[]{'|'}); 

Console.WriteLine(clientcommand); 

if (tokens[0== "CONN"



for(int n=0; n 



Client cl 
= (Client)clients[n]; 

SendToClient(cl, 
"JOIN|" + tokens[1]); 

}
 

EndPoint ep 
= client.RemoteEndPoint; 

Client c 
= new Client(tokens[1], ep, clientservice, client); 

clients.Add(c); 

string message = "LIST|" + GetChatterList() +" "

SendToClient(c, message); 

lbClients.Items.Add(c); 

}
 

if (tokens[0== "CHAT"



for(int n=0; n 



Client cl 
= (Client)clients[n]; 

SendToClient(cl, clientcommand); 

}
 

}
 

if (tokens[0== "PRIV"



string destclient = tokens[3]; 

for(int n=0; n 



Client cl 
= (Client)clients[n]; 

if(cl.Name.CompareTo(tokens[3]) == 0

SendToClient(cl, clientcommand); 

if(cl.Name.CompareTo(tokens[1]) == 0

SendToClient(cl, clientcommand); 

}
 

}
 

if (tokens[0== "GONE"



int remove = 0

bool found = false

int c = clients.Count; 

for(int n=0; n 



Client cl 
= (Client)clients[n]; 

SendToClient(cl, clientcommand); 

if(cl.Name.CompareTo(tokens[1]) == 0



remove 
= n; 

found 
= true

lbClients.Items.Remove(cl); 

}
 

}
 

if(found) 

clients.RemoveAt(remove); 

client.Close(); 

keepalive 
= false

}
 

}
 

}
 

 

這樣,服務器端程序就基本完成了。(其他略次要的代碼可以參見源代碼中的Form1.cs文件)程序運行圖示如下:

 

客戶端程序:

1. 打開VS.net,新建一個C#的模板爲“Windows 應用程序”的項目,不妨命名爲“ChatClient”。 

2. 佈置界面。往界面上添加一個ListBox控件(用於顯示用戶列表),一個RichTextBox控件(用於顯示聊天消息以及系統消息),一個TextBox控件(用於發送消息),一個CheckBox控件(確定是否爲悄悄話),一個StatusBar控件以及四個Button控件(分別爲“連接”、“斷開連接”、“開始記錄”、“發送”)。各個控件的屬性設置可以參見源代碼中的具體設置,這裏從略。界面設計好後的圖象如下:



3. 客戶端程序的代碼編寫。

當客戶端試圖和服務器端進行連接時,一個連接必須建立而且得向服務器端進行註冊。 EstablishConnection()函數運用一個TcpClient來和服務器端取得連接,同時創建一個NetworkStream來發送消息。還有,端口號和服務器端的是保持一致的,均爲5555。EstablishConnection()函數如下:

 

private void EstablishConnection() 



statusBar1.Text 
= "正在連接到服務器"

try 



clientsocket 
= new TcpClient(serveraddress,serverport); 

ns 
= clientsocket.GetStream(); 

sr 
= new StreamReader(ns); 

connected 
= true

}
 

catch (Exception) 



MessageBox.Show(
"不能連接到服務器!","錯誤"

MessageBoxButtons.OK, MessageBoxIcon.Exclamation); 

statusBar1.Text 
= "已斷開連接"

}
 }
 

 



在和服務器端連接成功後,程序就用RegisterWithServer()函數向服務器端發送一個CONN命令。該命令先是發送該用戶的名稱,然後從服務器端獲得其他所有用戶的列表,所有用戶列表是在ListBox控件中顯示的。該函數如下:

 

private void RegisterWithServer() 



try 



string command = "CONN|" + ChatOut.Text; 

Byte[] outbytes 
= System.Text.Encoding.ASCII.GetBytes(command.ToCharArray()); 

ns.Write(outbytes,
0,outbytes.Length); 

string serverresponse = sr.ReadLine(); 

serverresponse.Trim(); 

string[] tokens = serverresponse.Split(new Char[]{'|'}); 

if(tokens[0== "LIST"



statusBar1.Text 
= "已連接"

btnDisconnect.Enabled 
= true

}
 

for(int n=1; n 

lbChatters.Items.Add(tokens[n].Trim(
new char[]{' ',' '})); 

this.Text = clientname + ":已連接到服務器"

}
 

catch (Exception) 



MessageBox.Show(
"註冊時發生錯誤!","錯誤"

MessageBoxButtons.OK, MessageBoxIcon.Exclamation); 

}
 

}
 

 



在此之後,當然就是用戶之間的聊天了,由ReceiveChat()函數來完成。該函數是一個獨立的線程,它處理所有用戶獲得的消息和用戶發送的消息。它主要處理了CHAT、PRIV、JOIN、GONE、QUIT等命令,處理的方法和服務器端的類似。具體函數實現如下:

 

private void ReceiveChat() 



bool keepalive = true

while (keepalive) 



try 



Byte[] buffer 
= new Byte[2048]; 

ns.Read(buffer,
0,buffer.Length); 

string chatter = System.Text.Encoding.ASCII.GetString(buffer); 

string[] tokens = chatter.Split(new Char[]{'|'}); 

if (tokens[0== "CHAT"



rtbChatIn.AppendText(tokens[
1]); 

if(logging) 

logwriter.WriteLine(tokens[
1]); 

}
 

if (tokens[0== "PRIV"



rtbChatIn.AppendText(
"Private from "); 

rtbChatIn.AppendText(tokens[
1].Trim() ); 

rtbChatIn.AppendText(tokens[
2+ " "); 

if(logging) 



logwriter.Write(
"Private from "); 

logwriter.Write(tokens[
1].Trim() ); 

logwriter.WriteLine(tokens[
2+ " "); 

}
 

}
 

if (tokens[0== "JOIN"



rtbChatIn.AppendText(tokens[
1].Trim() ); 

rtbChatIn.AppendText(
" has joined the Chat "); 

if(logging) 



logwriter.WriteLine(tokens[
1]+" has joined the Chat"); 

}
 

string newguy = tokens[1].Trim(new char[]{' ',' '}); 

lbChatters.Items.Add(newguy); 

}
 

if (tokens[0== "GONE"



rtbChatIn.AppendText(tokens[
1].Trim() ); 

rtbChatIn.AppendText(
" has left the Chat "); 

if(logging) 



logwriter.WriteLine(tokens[
1]+" has left the Chat"); 

}
 

lbChatters.Items.Remove(tokens[
1].Trim(new char[]{' ',' '})); 

}
 

if (tokens[0== "QUIT"



ns.Close(); 

clientsocket.Close(); 

keepalive 
= false

statusBar1.Text 
= "服務器端已停止"

connected
= false

btnSend.Enabled 
= false

btnDisconnect.Enabled 
= false

}
 

}
 

catch(Exception){} 

}
 }
 

 



通過以上的一些函數,客戶端程序之間就可以進行自由地聊天了,各個用戶之間還可以互相發送悄悄話。所以程序已經實現了聊天室的基本功能了,不過最後各個用戶還要正常地退出,那就要用到QuitChat()函數了。該函數的具體實現如下:

 

private void QuitChat() 



if(connected) 



try 



string command = "GONE|" + clientname; 

Byte[] outbytes 
= System.Text.Encoding.ASCII.GetBytes(command.ToCharArray()); 

ns.Write(outbytes,
0,outbytes.Length); 

clientsocket.Close(); 

}
 

catch(Exception) 



}
 

}
 

if(logging) 

logwriter.Close(); 

if(receive != null && receive.IsAlive) 

receive.Abort(); 

this.Text = "客戶端"

}
 

 

到此爲止,客戶端程序的主要部分都已經介紹完畢。還有一些按鈕控件的消息處理函數可以參見源代碼。同時,程序中還有一個聊天記錄功能,該功能和現在流行的聊天軟件的記錄功能類似。不過限於篇幅,在這裏就不一一介紹了,有興趣的讀者可以研究一下本文後面的源代碼。

這樣,客戶端程序就完成了。程序運行圖示如下:

 
發佈了42 篇原創文章 · 獲贊 3 · 訪問量 16萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章