C#聊天程序服務(wù)端與客戶端完整實例代碼
本文所述為基于C#實現(xiàn)的多人聊天程序服務(wù)端與客戶端完整代碼。本實例省略了結(jié)構(gòu)定義部分,服務(wù)端主要是邏輯處理部分代碼,因此使用時需要完善一些窗體按鈕之類的。
先看服務(wù)端代碼如下:
using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; using System.Net; using System.Net.Sockets; using System.Threading; namespace 多人聊天程序Server端 { /// <summary> /// 應(yīng)用程序的主入口點(diǎn)。 /// </summary> [STAThread] static void Main() { Application.Run(new Form1()); } // 啟動服務(wù)按鈕 private void button2_Click(object sender, System.EventArgs e) { try { // 必須填寫端口 if(txtPort.Text == "") { MessageBox.Show("請先填寫服務(wù)端口號!", "提示"); return; } Int32 port = Int32.Parse(txtPort.Text); // 獲得端口號 // 創(chuàng)建偵聽的Socket mainSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); IPEndPoint localEP = new IPEndPoint(IPAddress.Any, port); // 將 Socket 綁定到本地的終結(jié)點(diǎn)上 mainSocket.Bind(localEP); // 開始偵聽,最大的連接數(shù)是 5 mainSocket.Listen(5); // 開始一個異步操作接受客戶的連接請求 mainSocket.BeginAccept(new AsyncCallback(OnClientConnect), null); // 啟動服務(wù)按鈕不可用,停止服務(wù)按鈕可用 UpdateControls(true); } catch(SocketException se) { MessageBox.Show(se.Message, "提示"); } } // 更新“啟動服務(wù)按鈕”和“停止服務(wù)”按鈕的狀態(tài):是否可用; // 注意:兩個按鈕的狀態(tài)是互斥的 private void UpdateControls(bool onServe) { button2.Enabled = !onServe; button3.Enabled = onServe; if(onServe) { status.Text = "已啟動服務(wù)"; } else { status.Text = "未啟動服務(wù)"; } } // 回調(diào)函數(shù),當(dāng)客戶連接上時,將會被調(diào)用 public void OnClientConnect(IAsyncResult asyn) { try { // 調(diào)用EndAccept完成BeginAccept異步調(diào)用,返回一個新的Socket處理與客戶的通信 Socket workerSocket = mainSocket.EndAccept(asyn); // 增加客戶數(shù)目 Interlocked.Increment(ref clientNum); // 將 workerSocket Socket加入到 ArrayList 中 workerSocketList.Add(workerSocket); // 發(fā)送歡迎信息給連接上服務(wù)器的客戶 string msg = "歡迎客戶 " + clientNum + " 登錄服務(wù)器\n"; SendWelcomeToClient(msg, clientNum); // 在線客戶數(shù)目改變,必須更新客戶列表 UpdateClientListControl(); // 連接上的客戶接收數(shù)據(jù) WaitForData(workerSocket, clientNum); // 主 Socket 返回,繼續(xù)等待其它的連接請求 mainSocket.BeginAccept(new AsyncCallback(OnClientConnect), null); } catch(ObjectDisposedException) { System.Diagnostics.Debugger.Log(0,"1","\n OnClientConnection: Socket已經(jīng)關(guān)閉!\n"); } catch(SocketException se) { MessageBox.Show(se.Message, "提示"); } } // 發(fā)送歡迎信息給客戶 void SendWelcomeToClient(string msg, int clientNumber) { // 用UTF8格式來將string信息轉(zhuǎn)化成byte數(shù)組形式 byte[] byData = System.Text.Encoding.UTF8.GetBytes(msg); // 獲得客戶clientNumber對應(yīng)的Socket Socket workerSocket = (Socket)workerSocketList[clientNumber - 1]; // 將數(shù)據(jù)發(fā)給客戶 workerSocket.Send(byData); } // 該類保存當(dāng)前的socket,它的客戶號還有發(fā)送給服務(wù)器的數(shù)據(jù) public class SocketPacket { public System.Net.Sockets.Socket currentSocket; // 當(dāng)前的Socket public int clientNumber; // 客戶號 public byte[] dataBuffer = new byte[1024]; // 發(fā)給服務(wù)器的數(shù)據(jù) // 構(gòu)造函數(shù) public SocketPacket(System.Net.Sockets.Socket socket, int clientNumber) { currentSocket = socket; this.clientNumber = clientNumber; } } // 開始等待客戶發(fā)送數(shù)據(jù) public void WaitForData(System.Net.Sockets.Socket socket, int clientNumber) { try { if(pfnWorkerCallBack == null) { // 當(dāng)連接上的客戶有寫的操作的時候,調(diào)用回調(diào)函數(shù) pfnWorkerCallBack = new AsyncCallback(OnDataReceived); } SocketPacket socketPacket = new SocketPacket(socket, clientNumber); socket.BeginReceive(socketPacket.dataBuffer, 0, socketPacket.dataBuffer.Length, SocketFlags.None, pfnWorkerCallBack, socketPacket); } catch(SocketException se) { MessageBox.Show (se.Message, "提示"); } } // 當(dāng)客戶寫數(shù)據(jù)時,調(diào)用以下方法 public void OnDataReceived(IAsyncResult asyn) { SocketPacket socketData = (SocketPacket)asyn.AsyncState ; try { // EndReceive完成BeginReceive異步調(diào)用,返回客戶寫入流的字節(jié)數(shù) int iRx = socketData.currentSocket.EndReceive(asyn); // 加 1 是因為字符串以 '\0' 作為結(jié)束標(biāo)志符 char[] chars = new char[iRx + 1]; // 對客戶發(fā)來的信息進(jìn)行UTF8解碼,存入chars字符數(shù)組中 System.Text.Decoder decoder = System.Text.Encoding.UTF8.GetDecoder(); int charLen = decoder.GetChars(socketData.dataBuffer, 0, iRx, chars, 0); System.String szData = new System.String(chars); string msg = "客戶 " + socketData.clientNumber + " 發(fā)的信息:" + szData; // 將客戶發(fā)的數(shù)據(jù)加入到信息列表中 AppendToRichEditControl(msg); // 等待數(shù)據(jù) WaitForData(socketData.currentSocket, socketData.clientNumber); } catch (ObjectDisposedException ) { System.Diagnostics.Debugger.Log(0,"1","\nOnDataReceived: Socket已經(jīng)關(guān)閉!\n"); } catch(SocketException se) { if(se.ErrorCode == 10054) { // 將客戶斷開連接的信息寫入信息列表中 string msg = "客戶 " + socketData.clientNumber + " 已斷開了連接!" + "\n"; AppendToRichEditControl(msg); // 移走已關(guān)閉的socket workerSocketList[socketData.clientNumber - 1] = null; // 更新客戶列表 UpdateClientListControl(); } else { MessageBox.Show (se.Message, "提示"); } } } // 更新信息列表,該方法可由主線程或其他工作線程所調(diào)用 private void AppendToRichEditControl(string msg) { // 測試看是哪個線程調(diào)用了該方法 if (InvokeRequired) { // We cannot update the GUI on this thread. // All GUI controls are to be updated by the main (GUI) thread. // Hence we will use the invoke method on the control which will // be called when the Main thread is free // Do UI update on UI thread object[] pList = {msg}; txtRecvMsg.BeginInvoke(new UpdateRichEditCallback(OnUpdateRichEdit), pList); } else { // 創(chuàng)建該控件的主線程直接更新信息列表 OnUpdateRichEdit(msg); } } // 添加信息到 txtRecvMsg 中 private void OnUpdateRichEdit(string msg) { // txtRecvMsg.AppendText(msg); txtRecvMsg.Text = txtRecvMsg.Text + msg; } // 更新客戶列表 private void UpdateClientListControl() { if (InvokeRequired) // Is this called from a thread other than the one created // the control { clientList.BeginInvoke(new UpdateClientListCallback(UpdateClientList), null); } else { // 創(chuàng)建該控件的主線程直接更新信息列表 UpdateClientList(); } } // 更新客戶列表 void UpdateClientList() { clientList.Items.Clear(); // 清空客戶列表 for(int i = 0; i < workerSocketList.Count; i++) { // 加1,是因為數(shù)組從下標(biāo)0開始,而我們的客戶標(biāo)號是從1開始 string clientKey = Convert.ToString(i + 1); Socket workerSocket = (Socket)workerSocketList[i]; if(workerSocket != null) { // 將連接著服務(wù)器的客戶添加到客戶列表中 if(workerSocket.Connected) { clientList.Items.Add(clientKey); } } } } // 停止服務(wù)按鈕 private void button3_Click(object sender, System.EventArgs e) { CloseSockets(); UpdateControls(false); // 更新客戶列表 UpdateClientListControl(); } // 發(fā)送信息按鈕 private void button1_Click(object sender, System.EventArgs e) { // 如果在線客戶列表不為空,則允許發(fā)送信息 if (clientList.Items.Count != 0 ) { try { string msg = txtSendMsg.Text; msg = "服務(wù)器信息: " + msg + "\n"; byte[] byData = System.Text.Encoding.UTF8.GetBytes(msg); Socket workerSocket = null; for(int i = 0; i < workerSocketList.Count; i++) { workerSocket = (Socket)workerSocketList[i]; if(workerSocket!= null) { // 發(fā)給所有連接上服務(wù)器的客戶 if(workerSocket.Connected) { workerSocket.Send(byData); } } } } catch(SocketException se) { MessageBox.Show(se.Message, "提示"); } } else { MessageBox.Show("沒有在線客戶,不能發(fā)送信息!", "提示"); } } // 清空信息按鈕 private void button4_Click(object sender, System.EventArgs e) { txtRecvMsg.Clear(); // 清空從客戶發(fā)來的信息 } // 關(guān)閉窗體按鈕 private void button5_Click(object sender, System.EventArgs e) { CloseSockets(); Close(); } // 關(guān)閉Socket void CloseSockets() { // 關(guān)閉主Socket if(mainSocket != null) { mainSocket.Close(); } Socket workerSocket = null; // 關(guān)閉客戶 Socket 數(shù)組 for(int i = 0; i < workerSocketList.Count; i++) { workerSocket = (Socket)workerSocketList[i]; if(workerSocket != null) { workerSocket.Close(); workerSocket = null; } } } private void Form1_Load(object sender, System.EventArgs e) { try { // 獲得本機(jī)的IP地址 txtIP.Text = Dns.Resolve(Dns.GetHostName()).AddressList[0].ToString(); // 啟動時,啟動服務(wù)按鈕可用,停止服務(wù)按鈕不可用 UpdateControls(false); } catch(Exception exc) { MessageBox.Show(exc.Message, "提示"); } } } }
客戶端主要實現(xiàn)接收來自服務(wù)端返回的消息、實現(xiàn)發(fā)送消息的操作界面,創(chuàng)建Socket實例,得到服務(wù)器的IP地址,更新控件,連接和斷開等,具體代碼如下:
using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; using System.Net; using System.Net.Sockets; namespace 多人聊天程序Client端 { public class Form1 : System.Windows.Forms.Form { private System.Windows.Forms.Label label1; private System.Windows.Forms.TextBox txtIP; private System.Windows.Forms.Label label2; private System.Windows.Forms.Label label3; private System.Windows.Forms.RichTextBox txtSendMsg; private System.Windows.Forms.Label label4; private System.Windows.Forms.Button button1; private System.Windows.Forms.Button button2; private System.Windows.Forms.Button button3; private System.Windows.Forms.Button button4; private System.Windows.Forms.RichTextBox txtRecvMsg; private System.Windows.Forms.TextBox txtPort; private System.Windows.Forms.Button button5; private System.ComponentModel.Container components = null; byte[] m_dataBuffer = new byte[10]; IAsyncResult result; public AsyncCallback pfnCallBack ; private System.Windows.Forms.Label status; private System.Windows.Forms.Label label5; public Socket clientSocket; public Form1() { InitializeComponent(); } private void InitializeComponent() { this.label1 = new System.Windows.Forms.Label(); this.txtIP = new System.Windows.Forms.TextBox(); this.label2 = new System.Windows.Forms.Label(); this.txtPort = new System.Windows.Forms.TextBox(); this.label3 = new System.Windows.Forms.Label(); this.txtSendMsg = new System.Windows.Forms.RichTextBox(); this.label4 = new System.Windows.Forms.Label(); this.status = new System.Windows.Forms.Label(); this.txtRecvMsg = new System.Windows.Forms.RichTextBox(); this.button1 = new System.Windows.Forms.Button(); this.button2 = new System.Windows.Forms.Button(); this.button3 = new System.Windows.Forms.Button(); this.button4 = new System.Windows.Forms.Button(); this.label5 = new System.Windows.Forms.Label(); this.button5 = new System.Windows.Forms.Button(); this.SuspendLayout(); // label1 this.label1.AutoSize = true; this.label1.Location = new System.Drawing.Point(16, 24); this.label1.Name = "label1"; this.label1.Size = new System.Drawing.Size(60, 17); this.label1.TabIndex = 0; this.label1.Text = "服務(wù)器IP:"; // txtIP this.txtIP.Location = new System.Drawing.Point(80, 24); this.txtIP.Name = "txtIP"; this.txtIP.Size = new System.Drawing.Size(160, 21); this.txtIP.TabIndex = 1; this.txtIP.Text = ""; // label2 this.label2.AutoSize = true; this.label2.Location = new System.Drawing.Point(16, 56); this.label2.Name = "label2"; this.label2.Size = new System.Drawing.Size(35, 17); this.label2.TabIndex = 2; this.label2.Text = "端口:"; // txtPort this.txtPort.Location = new System.Drawing.Point(80, 56); this.txtPort.Name = "txtPort"; this.txtPort.Size = new System.Drawing.Size(40, 21); this.txtPort.TabIndex = 3; this.txtPort.Text = ""; // label3 this.label3.AutoSize = true; this.label3.Location = new System.Drawing.Point(16, 96); this.label3.Name = "label3"; this.label3.Size = new System.Drawing.Size(110, 17); this.label3.TabIndex = 4; this.label3.Text = "發(fā)送信息給服務(wù)器:"; // txtSendMsg this.txtSendMsg.Location = new System.Drawing.Point(16, 120); this.txtSendMsg.Name = "txtSendMsg"; this.txtSendMsg.Size = new System.Drawing.Size(224, 88); this.txtSendMsg.TabIndex = 5; this.txtSendMsg.Text = ""; // label4 this.label4.AutoSize = true; this.label4.Location = new System.Drawing.Point(16, 248); this.label4.Name = "label4"; this.label4.Size = new System.Drawing.Size(60, 17); this.label4.TabIndex = 6; this.label4.Text = "連接狀態(tài):"; // status this.status.Location = new System.Drawing.Point(80, 248); this.status.Name = "status"; this.status.Size = new System.Drawing.Size(192, 23); this.status.TabIndex = 7; // txtRecvMsg this.txtRecvMsg.Location = new System.Drawing.Point(264, 80); this.txtRecvMsg.Name = "txtRecvMsg"; this.txtRecvMsg.ReadOnly = true; this.txtRecvMsg.Size = new System.Drawing.Size(224, 144); this.txtRecvMsg.TabIndex = 8; this.txtRecvMsg.Text = ""; // button1 this.button1.Location = new System.Drawing.Point(280, 16); this.button1.Name = "button1"; this.button1.Size = new System.Drawing.Size(88, 32); this.button1.TabIndex = 9; this.button1.Text = "連接"; this.button1.Click += new System.EventHandler(this.button1_Click); // button2 this.button2.Location = new System.Drawing.Point(384, 16); this.button2.Name = "button2"; this.button2.Size = new System.Drawing.Size(88, 32); this.button2.TabIndex = 10; this.button2.Text = "斷開"; this.button2.Click += new System.EventHandler(this.button2_Click); // button3 this.button3.Location = new System.Drawing.Point(280, 232); this.button3.Name = "button3"; this.button3.Size = new System.Drawing.Size(88, 32); this.button3.TabIndex = 11; this.button3.Text = "清空信息"; this.button3.Click += new System.EventHandler(this.button3_Click); // button4 this.button4.Location = new System.Drawing.Point(384, 232); this.button4.Name = "button4"; this.button4.Size = new System.Drawing.Size(88, 32); this.button4.TabIndex = 12; this.button4.Text = "關(guān)閉"; this.button4.Click += new System.EventHandler(this.button4_Click); // label5 this.label5.AutoSize = true; this.label5.Location = new System.Drawing.Point(264, 64); this.label5.Name = "label5"; this.label5.Size = new System.Drawing.Size(134, 17); this.label5.TabIndex = 13; this.label5.Text = "收到服務(wù)器發(fā)來的信息:"; // button5 this.button5.Location = new System.Drawing.Point(16, 208); this.button5.Name = "button5"; this.button5.Size = new System.Drawing.Size(224, 32); this.button5.TabIndex = 14; this.button5.Text = "發(fā)送信息"; this.button5.Click += new System.EventHandler(this.button5_Click); // Form1 this.AutoScaleBaseSize = new System.Drawing.Size(6, 14); this.ClientSize = new System.Drawing.Size(512, 277); this.Controls.Add(this.button5); this.Controls.Add(this.label5); this.Controls.Add(this.button4); this.Controls.Add(this.button3); this.Controls.Add(this.button2); this.Controls.Add(this.button1); this.Controls.Add(this.txtRecvMsg); this.Controls.Add(this.status); this.Controls.Add(this.label4); this.Controls.Add(this.txtSendMsg); this.Controls.Add(this.label3); this.Controls.Add(this.txtPort); this.Controls.Add(this.label2); this.Controls.Add(this.txtIP); this.Controls.Add(this.label1); this.Name = "Form1"; this.Text = "多人聊天程序Client端"; this.Load += new System.EventHandler(this.Form1_Load); this.ResumeLayout(false); } #endregion [STAThread] static void Main() { Application.Run(new Form1()); } // 連接按鈕 private void button1_Click(object sender, System.EventArgs e) { // IP地址和端口號不能為空 if(txtIP.Text == "" || txtPort.Text == "") { MessageBox.Show("請先完整填寫服務(wù)器IP地址和端口號!", "提示"); return; } try { // 創(chuàng)建Socket實例 clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // 得到服務(wù)器的IP地址 IPAddress ipAddress = IPAddress.Parse(txtIP.Text); Int32 port = Int32.Parse(txtPort.Text); // 創(chuàng)建遠(yuǎn)程終結(jié)點(diǎn) IPEndPoint remoteEP = new IPEndPoint(ipAddress, port); // 連接到遠(yuǎn)程服務(wù)器 clientSocket.Connect(remoteEP); if(clientSocket.Connected) { UpdateControls(true); WaitForData(); // 異步等待數(shù)據(jù) } } catch(SocketException se) { MessageBox.Show (se.Message, "提示"); UpdateControls(false); } } // 等待數(shù)據(jù) public void WaitForData() { try { if(pfnCallBack == null) { // 當(dāng)連接上的客戶有寫的操作的時候,調(diào)用回調(diào)函數(shù) pfnCallBack = new AsyncCallback(OnDataReceived); } SocketPacket socketPacket = new SocketPacket(); socketPacket.thisSocket = clientSocket; result = clientSocket.BeginReceive(socketPacket.dataBuffer, 0, socketPacket.dataBuffer.Length, SocketFlags.None, pfnCallBack, socketPacket); } catch(SocketException se) { MessageBox.Show(se.Message, "提示"); } } // 該類保存Socket以及發(fā)送給服務(wù)器的數(shù)據(jù) public class SocketPacket { public System.Net.Sockets.Socket thisSocket; public byte[] dataBuffer = new byte[1024]; // 發(fā)給服務(wù)器的數(shù)據(jù) } // 接收數(shù)據(jù) public void OnDataReceived(IAsyncResult asyn) { try { SocketPacket theSockId = (SocketPacket)asyn.AsyncState ; // EndReceive完成BeginReceive異步調(diào)用,返回服務(wù)器寫入流的字節(jié)數(shù) int iRx = theSockId.thisSocket.EndReceive(asyn); // 加 1 是因為字符串以 '\0' 作為結(jié)束標(biāo)志符 char[] chars = new char[iRx + 1]; // 用UTF8格式來將string信息轉(zhuǎn)化成byte數(shù)組形式 System.Text.Decoder decoder = System.Text.Encoding.UTF8.GetDecoder(); int charLen = decoder.GetChars(theSockId.dataBuffer, 0, iRx, chars, 0); System.String szData = new System.String(chars); // 將收到的信息顯示在信息列表中 txtRecvMsg.Text = txtRecvMsg.Text + szData; // 等待數(shù)據(jù) WaitForData(); } catch (ObjectDisposedException) { System.Diagnostics.Debugger.Log(0,"1","\nOnDataReceived: Socket已經(jīng)關(guān)閉!\n"); } catch(SocketException se) { if(se.ErrorCode == 10054) { string msg = "服務(wù)器" + "停止服務(wù)!" + "\n"; txtRecvMsg.Text = txtRecvMsg.Text + msg; clientSocket.Close(); clientSocket = null; UpdateControls(false); } else { MessageBox.Show(se.Message, "提示"); } } } // 更新控件。連接和斷開(發(fā)送信息)按鈕的狀態(tài)是互斥的 private void UpdateControls(bool connected) { button1.Enabled = !connected; button2.Enabled = connected; button5.Enabled = connected; if(connected) { status.Text = "已連接"; } else { status.Text = "無連接"; } } // 斷開按鈕 private void button2_Click(object sender, System.EventArgs e) { // 關(guān)閉Socket if(clientSocket != null) { clientSocket.Close(); clientSocket = null; UpdateControls(false); } } // 發(fā)送信息按鈕 private void button5_Click(object sender, System.EventArgs e) { try { // 如果客戶與服務(wù)器有連接,則允許發(fā)送信息 if(clientSocket.Connected) { string msg = txtSendMsg.Text + "\n"; // 用UTF8格式來將string信息轉(zhuǎn)化成byte數(shù)組形式 byte[] byData = System.Text.Encoding.UTF8.GetBytes(msg); if(clientSocket != null) { // 發(fā)送數(shù)據(jù) clientSocket.Send(byData); } } } catch(Exception se) { MessageBox.Show(se.Message, "提示"); } } // 清空按鈕 private void button3_Click(object sender, System.EventArgs e) { txtRecvMsg.Clear(); // 清空信息列表 } // 關(guān)閉按鈕 private void button4_Click(object sender, System.EventArgs e) { // 關(guān)閉Socket if(clientSocket != null) { clientSocket.Close(); clientSocket = null; } Close(); // 關(guān)閉窗體 } private void Form1_Load(object sender, System.EventArgs e) { UpdateControls(false); // 初始化時,只有連接按鈕可用 } } }
相關(guān)文章
C#中XmlTextWriter讀寫xml文件詳細(xì)介紹
.NET中包含了很多支持XML的類,這些類使得程序員使用XML編程就如同理解XML文件一樣簡單。在這篇文章中,我將給出這樣的一個類的使用示例,這個類就是XmlTextWriter類2013-04-04c# WPF中System.Windows.Interactivity的使用
這篇文章主要介紹了c# WPF中System.Windows.Interactivity的使用,幫助大家更好的理解和學(xué)習(xí)使用c#,感興趣的朋友可以了解下2021-03-03C#實現(xiàn)在網(wǎng)頁中根據(jù)url截圖并輸出到網(wǎng)頁的方法
這篇文章主要介紹了C#實現(xiàn)在網(wǎng)頁中根據(jù)url截圖并輸出到網(wǎng)頁的方法,涉及C#網(wǎng)頁瀏覽器及圖片操作的相關(guān)技巧,需要的朋友可以參考下2016-01-01