C# 多人聊天程序
上个星期,特别想写一个点对点聊天的小程序,就上网查了一下有关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控件。