http://www.soaspx.com/dotnet/csharp/csharp_20100905_5563.html C#是微軟隨著VS.net新推出的一門語言。它作為一門新興的語言,有著C++的強健,又有著VB等的RAD特性。而且,微軟推出C#主要的目的是為了對抗Sun公司的Java。大家都知道Java語言的強大功能,尤其在網(wǎng)絡編程方面。于是,C#在網(wǎng)絡編程方面也自然不甘落后于人。本文就向大家介紹一下C#下實現(xiàn)套接字(Sockets)編程的一些基本知識,以期能使大家對此有個大致了解。首先,我向大家介紹一下套接字的概念。 ![]() 3. 服務器端程序的代碼編寫。 對于服務器端,主要的作用是監(jiān)聽客戶端的連接請求并確認其請求。程序一開始便打開一個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() ); } } } 該線程是一直處于運行狀態(tài)的。當服務器端接收到一個來自客戶端的連接請求后,它就打開一個ServiceClient()線程來服務客戶端。當一個連接被建立后,每個客戶端就被賦予一個屬于它自己的套接字。同時,一個Client類的對象被建立。該對象包含了客戶端的一些相關信息,該信息被保存在一個數(shù)組列表中。 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: 在此處添加構造函數(shù)邏輯 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()函數(shù)。該函數(shù)是一個獨立的線程,其主要部分是一個while循環(huán)。在循環(huán)體內(nèi),程序處理各種客戶端命令。服務器端接收來自客戶端的以ASCII碼給出的字符串,其中包含了一個“|”形式的分隔符。字符串中“|”以前的部分就是具體的命令,包括CONN、CHAT、PRIV、GONE四種類型。CONN命令建立一個新的客戶端連接,將現(xiàn)有的用戶列表發(fā)送給新用戶并告知其他用戶有一個新用戶加入。CHAT命令將新的信息發(fā)送給所有用戶。PRIV命令將悄悄話發(fā)送給某個用戶。GONE命令從用戶列表中除去一個已離開的用戶并告知其他的用戶某某已經(jīng)離開了。同時,GONE命令可以設置布爾型的變量keepalive為false從而結束與客戶端連接的線程。ServiceClient()函數(shù)如下: 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() +"\r\n"; 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控件(用于顯示聊天消息以及系統(tǒng)消息),一個TextBox控件(用于發(fā)送消息),一個CheckBox控件(確定是否為悄悄話),一個StatusBar控件以及四個Button控件(分別為“連接”、“斷開連接”、“開始記錄”、“發(fā)送”)。各個控件的屬性設置可以參見源代碼中的具體設置,這里從略。界面設計好后的圖象如下: ![]() 3. 客戶端程序的代碼編寫。 當客戶端試圖和服務器端進行連接時,一個連接必須建立而且得向服務器端進行注冊。 EstablishConnection()函數(shù)運用一個TcpClient來和服務器端取得連接,同時創(chuàng)建一個NetworkStream來發(fā)送消息。還有,端口號和服務器端的是保持一致的,均為5555。EstablishConnection()函數(shù)如下: 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()函數(shù)向服務器端發(fā)送一個CONN命令。該命令先是發(fā)送該用戶的名稱,然后從服務器端獲得其他所有用戶的列表,所有用戶列表是在ListBox控件中顯示的。該函數(shù)如下: 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[]{'\r','\n'})); this.Text = clientname + ":已連接到服務器"; } catch (Exception) { MessageBox.Show("注冊時發(fā)生錯誤!","錯誤", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); } } 在此之后,當然就是用戶之間的聊天了,由ReceiveChat()函數(shù)來完成。該函數(shù)是一個獨立的線程,它處理所有用戶獲得的消息和用戶發(fā)送的消息。它主要處理了CHAT、PRIV、JOIN、GONE、QU99v等命令,處理的方法和服務器端的類似。具體函數(shù)實現(xiàn)如下:
![]() 總結: 本文向大家初步介紹了套接字的基本概念和實現(xiàn)套接字編程的基本原理,還通過一個很好的實例向大家展示了在C#下進行套接字編程的實現(xiàn)方法和一些編程技巧。從中,我們不難發(fā)現(xiàn)運用C#進行套接字編程乃至網(wǎng)絡編程有許多優(yōu)越之處。實例程序實現(xiàn)的思路清晰明了而且通俗易懂,是一個相當不錯的例子,希望各位能好好研讀。同時還希望大家能進一步完善該程序,使之功能更強大、界面更友好。在此之后,當然就是用戶之間的聊天了,由ReceiveChat()函數(shù)來完成。該函數(shù)是一個獨立的線程,它處理所有用戶獲得的消息和用戶發(fā)送的消息。它主要處理了CHAT、PRIV、JOIN、GONE、QU99v等命令,處理的方法和服務器端的類似。具體函數(shù)實現(xiàn)如下: 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] + "\r\n"); if(logging) { logwriter.Write("Private from "); logwriter.Write(tokens[1].Trim() ); logwriter.WriteLine(tokens[2] + "\r\n"); } } if (tokens[0] == "JOIN") { rtbChatIn.AppendText(tokens[1].Trim() ); rtbChatIn.AppendText(" has joined the Chat\r\n"); if(logging) { logwriter.WriteLine(tokens[1]+" has joined the Chat"); } string newguy = tokens[1].Trim(new char[]{'\r','\n'}); lbChatters.Items.Add(newguy); } if (tokens[0] == "GONE") { rtbChatIn.AppendText(tokens[1].Trim() ); rtbChatIn.AppendText(" has left the Chat\r\n"); if(logging) { logwriter.WriteLine(tokens[1]+" has left the Chat"); } lbChatters.Items.Remove(tokens[1].Trim(new char[]{'\r','\n'})); } if (tokens[0] == "QU99v") { ns.Close(); clientsocket.Close(); keepalive = false; statusBar1.Text = "服務器端已停止"; connected= false; btnSend.Enabled = false; btnDisconnect.Enabled = false; } } catch(Exception){} } } 通過以上的一些函數(shù),客戶端程序之間就可以進行自由地聊天了,各個用戶之間還可以互相發(fā)送悄悄話。所以程序已經(jīng)實現(xiàn)了聊天室的基本功能了,不過最后各個用戶還要正常地退出,那就要用到QuitChat()函數(shù)了。該函數(shù)的具體實現(xiàn)如下: 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 = "客戶端"; } 到此為止,客戶端程序的主要部分都已經(jīng)介紹完畢。還有一些按鈕控件的消息處理函數(shù)可以參見源代碼。同時,程序中還有一個聊天記錄功能,該功能和現(xiàn)在流行的聊天軟件的記錄功能類似。不過限于篇幅,在這里就不一一介紹了,有興趣的讀者可以研究一下本文后面的源代碼。 這樣,客戶端程序就完成了。程序運行圖示如下:
![]() 總結: 本文向大家初步介紹了套接字的基本概念和實現(xiàn)套接字編程的基本原理,還通過一個很好的實例向大家展示了在C#下進行套接字編程的實現(xiàn)方法和一些編程技巧。從中,我們不難發(fā)現(xiàn)運用C#進行套接字編程乃至網(wǎng)絡編程有許多優(yōu)越之處。實例程序實現(xiàn)的思路清晰明了而且通俗易懂,是一個相當不錯的例子,希望各位能好好研讀。同時還希望大家能進一步完善該程序,使之功能更強大、界面更友好。 |