上個星期,特別想寫一個點對點聊天的小程序,就上網查了一下有關C#網絡編程的知識,用到最多的就是TcpClient和TcpListener,使用這兩個類就可以完成主機之間的通信,當然,做這個程序的過程中也用到了多線程和事件與委托,這是我第一次將這些高級特性加入到程序中,通過參考
《C#和.net 3.0第一步》,我學會了如何使用事件,然后照個上面的例子寫出了這個多人聊天程序。
定義一個客戶端類:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Net.Sockets; using System.Windows.Forms; using System.IO; using System.Net; namespace TCPClient { class P2PClient { public TcpClient tcpClientObj;//收發數據 private Thread receiveThread; //接收數據的線程 public delegate void receiveDelegate(string receiveData);//處理接收數據事件的方法類型 public event receiveDelegate receiveEvent; //接收數據的事件 public void SendConnection(string ip, int port) //通過IP地址和端口號發送連接 { IPAddress ipaddr = IPAddress.Parse(ip);//轉為IP地址后在連接會加快速度 tcpClientObj = new TcpClient(); //連接客戶端 tcpClientObj.Connect(ipaddr, port);//連接 receiveThread = new Thread(Receiver); //啟動接收數據線程 receiveThread.Start(); } public void Send(string message) //發送信息 { if (tcpClientObj == null) { return; } NetworkStream ns = this.tcpClientObj.GetStream();//得到網絡流 StreamWriter sw = new StreamWriter(ns); sw.WriteLine(message);//發送信息 sw.Flush();//使所有的緩沖數據寫入基礎數據流 ns.Flush(); } private void Receiver() //接收數據對應的線程(接收到數據以后觸發事件) { while (true) //一直接受 { NetworkStream ns = this.tcpClientObj.GetStream(); StreamReader sr = new StreamReader(ns); string receivedata = sr.ReadLine(); receiveEvent(receivedata);//觸發事件 } } } }
客戶端窗體對應代碼如下:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; namespace TCPClient { public partial class ClientForm : Form { private P2PClient ClientObj = new P2PClient(); //客戶對象,對於一個對象一定要new啊 public ClientForm() { InitializeComponent(); } private void ClientForm_Load(object sender, EventArgs e) { btnSend.Enabled = false; //沒有連接不允許發送數據 this.AcceptButton = btnSend; } private void btnConnect_Click(object sender, EventArgs e) { string nickName = tbName.Text; string ip = tbIP.Text; string port = tbPort.Text; if(string.IsNullOrEmpty(nickName) || string.IsNullOrEmpty(ip) || string.IsNullOrEmpty(port)) { MessageBox.Show("請將昵稱、IP填寫完整"); return ; } try { ClientObj.SendConnection(ip, Convert.ToInt32(port)); //連接 ClientObj.receiveEvent += new P2PClient.receiveDelegate(ClientObj_receiveEvent); //訂閱事件的處理方法 ClientObj.Send(tbName.Text + "登陸成功!"); btnSend.Enabled = true; btnConnect.Enabled = false; } catch (Exception ex) { MessageBox.Show("連接時出錯:" + ex.Message); return; } } void ClientObj_receiveEvent(string receiveData) { try { if (this.InvokeRequired) //指示是否需要在這個線程上調用方法 { P2PClient.receiveDelegate update = new P2PClient.receiveDelegate(ClientObj_receiveEvent);//當把消息傳遞給控件線程時重復調用該方法就會調用else this.Invoke(update, new object[] { receiveData });//將消息發送給控件線程處理 } else { lbMessage.Items.Add(receiveData);//添加數據 } } catch (Exception ex) { MessageBox.Show("接收數據錯誤:" + ex.Message); return; } } private void btnSend_Click(object sender, EventArgs e) { try { if (string.IsNullOrEmpty(tbMessage.Text)) { return; } ClientObj.Send(tbName.Text + "說:" + tbMessage.Text);//發送信息 tbMessage.Clear();//清除原來的文本 } catch (Exception ex) { MessageBox.Show("發送數據出錯:" + ex.Message); return; } } private void ClientForm_FormClosing(object sender, FormClosingEventArgs e) { // ClientObj.Send(this.tbName.Text + "下線了"); //ClientObj.tcpClientObj.Close();//關閉連接 this.Close();//關閉窗體,讓程序自動釋放資源 } } }
客戶端完成,下面定義一個服務器端類
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Net.Sockets; using System.Threading; using System.Net; using System.Windows.Forms; using System.IO; namespace TCPServer { class P2PServer { public TcpListener listenObj;//監聽對象 public Dictionary<string,TcpClient> clientMem = new Dictionary<string,TcpClient>(); //客戶端列表一定要初始化,new private Thread listenThread; //監聽線程 public delegate void ConnectDelegate(); //連接成功后處理事件的方法類型 public event ConnectDelegate ConnectEvent;//連接事件 public delegate void ReceiveDelegate(string message); //接收數據后處理事件方法類型 public event ReceiveDelegate ReceiveEvent; //接收數據事件 public void Listen(int port) //監聽方法,啟動監聽線程 { IPAddress[] localIP = Dns.GetHostAddresses(Dns.GetHostName()); //通過主機名得到本地IP this.listenObj = new TcpListener(localIP[0], port); //監聽對象 this.listenThread = new Thread(ListenClient); //這個線程僅僅用來監聽客戶 listenThread.Start();//啟動這個線程方法 } public void ListenClient() //監聽線程對應的方法,監聽到信息后向所有的客戶端發送數據 { while (true) //一直監聽,可以有多個客戶端請求連接 { listenObj.Start();//開始偵聽請求 ;注意在線程start之后才可以。 TcpClient acceptClientObj = listenObj.AcceptTcpClient();//接收掛起的連接請求,這是一個阻塞方法 this.ConnectEvent();//觸發連接事件 Thread receiveThread = new Thread(Receiver); //這個線程處理接收的數據 string connectTime = DateTime.Now.ToString(); receiveThread.Name = connectTime;//設置線程的名字 this.clientMem.Add(connectTime, acceptClientObj); //將 客戶 添加到列表 receiveThread.Start();//接收到的連接包含數據 } } public void Send(string message) //發送信息 { foreach (KeyValuePair<string, TcpClient> var in clientMem) //向所有客戶發送數據 { if (var.Value == null || var.Value.Connected == false) { clientMem.Remove(var.Key); //刪除斷開的連接???這個地方有待改進 continue; } NetworkStream ns = var.Value.GetStream();//得到網絡流 StreamWriter sw = new StreamWriter(ns); sw.WriteLine(message); sw.Flush();//刷新數據流 ns.Flush(); } } public void Receiver() //接收 數據 對應的方法 { //所有的TcpClient都對應一個線程,用來接收客戶端發來的數據,通過線程名,找到對應的TcpClient while (true) { //收到一個TcpClient時,都有一個命名的Thread對應, NetworkStream ns = clientMem[Thread.CurrentThread.Name].GetStream(); StreamReader sr = new StreamReader(ns); string message = sr.ReadLine();//讀取消息 this.ReceiveEvent(message);//接收過數據 就觸發接收消息的事件 } } } }
下面是服務器端窗體代碼:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Net.Sockets; namespace TCPServer { public partial class ServerForm : Form { P2PServer serverObj = new P2PServer(); public ServerForm() { InitializeComponent(); } private void ServerForm_Load(object sender, EventArgs e) { try { serverObj.ConnectEvent += new P2PServer.ConnectDelegate(serverObj_ConnectEvent); //訂閱連接事件 serverObj.ReceiveEvent += new P2PServer.ReceiveDelegate(serverObj_ReceiveEvent); //訂閱接收數據事件 serverObj.Listen(Convert.ToInt32(tbPort.Text)); //啟動監聽 } catch (Exception ex) { MessageBox.Show("加載失敗:" + ex.Message); return; } } void serverObj_ReceiveEvent(string message) { try { if (this.InvokeRequired) { P2PServer.ReceiveDelegate update = new P2PServer.ReceiveDelegate(serverObj_ReceiveEvent); this.Invoke(update, new object[] { message }); } else { this.lbMessage.Items.Add(message);//添加到顯示欄 serverObj.Send(message); } } catch (Exception ex) { MessageBox.Show("處理事件方法錯誤:" + ex.Message); return; } } void serverObj_ConnectEvent() { try { if (this.InvokeRequired) { P2PServer.ConnectDelegate update = new P2PServer.ConnectDelegate(serverObj_ConnectEvent); this.Invoke(update); } else { this.lbMessage.Items.Add("連接成功"); } } catch (Exception ex) { MessageBox.Show("處理連接事件方法錯誤:" + ex.Message); return; } } private void ServerForm_FormClosed(object sender, FormClosedEventArgs e) { } private void ServerForm_FormClosing(object sender, FormClosingEventArgs e) { this.Close(); } } }
運行效果如下:

顯示消息的是一個ListBox控件。


