这一块总结程序的核心部分,局域网内的聊天,这部分我没有使用固定的服务器,而是通过UDP确定聊天的对象,然后双方通过TCP进行聊天,点对点式的聊天。
首先分析一段TCP的服务器和客户端的代码,基本这个代码看懂了就差不多
服务器部分
#region 通信协议说明 // CONNECT|客户端名称 服务器接收 发送不同的客户端 初始化聊天室 // LISTEN|聊天室其他用户名称 客户端接收 初始化聊天室 // CHAT|客户名称|内容 服务器接收 发送消息 // JOIN|客户端名称 客户端接收 增加显示聊天室新增人员 // LEAVE|客户端名称 客户端接收 和上面相反 // QUIT| 客户端接收 服务器关闭 #endregion
m_listener = new TcpListener(int.Parse(textBox2.Text)); //实例化监听对象 m_listener.Start(); //开启监听 while(true&&m_isStart) { Socket s = m_listener.AcceptSocket(); //连接客户端 clientSocket=s; //赋值 clientService = new Thread(new ThreadStart(ServiceClient));//开启客户端服务线程 每一个新的客户端都会分配一条新的线程 clientService.Start(); //开始服务 }
Socket client=clientSocket; bool isAlive = true; while(isAlive&&m_isStart) { Byte[] buffer = new Byte[100000]; client.Receive(buffer); //服务器接收消息 string messageinfo = System.Text.Encoding.Default.GetString(buffer); //默认编码 string[] tokens = messageinfo.Split(new char[] { ‘|‘ }); //分割字符串 switch(tokens[0]) //头部消息 { case "CONNECT": for(int i=0;i<m_clients.Count;i++) //存在一个客户端连接 将新的客户的名称发送给其他客户 { Client cl = (Client)m_clients[i]; SendToClient(cl, "JOIN|" + tokens[1]); //发送加入消息 } EndPoint ep = client.RemoteEndPoint; //获取该客户端的相关信息 Client c = new Client(tokens[1], ep, clientService, client); //实例化一个客户端 m_clients.Add(c); //增加到客户端列表 string message="LISTEN|"+GetNameLists()+"\r\n"; //发送其他客户端的名称给新的客户 初始化客户列表 SendToClient(c, message); //发送 break; case "CHAT": for(int i=0;i<m_clients.Count;i++) { Client cl = (Client)m_clients[i]; //发送聊天消息 SendToClient(cl,messageinfo); } break; case "LEAVE": int removeindex = 0; bool found = false; for (int i = 0; i < m_clients.Count; i++) { Client cl = (Client)m_clients[i]; //某个客户离开 匹配寻找并向其他客户端发送离开消息 SendToClient(cl, messageinfo); if (cl.Name.CompareTo(tokens[1])==0) { removeindex=i; found = true; } } if (found) m_clients.RemoveAt(removeindex); //客户列表移除 client.Close(); isAlive = false; //关闭连接 关闭这个客户的线程 break; } }
#region 变量声明 private Thread client_thread; //客户端服务线程 private EndPoint endpoint; //客户端电脑相关信息 private string name; //客户名称 private Socket sock; //客户端Socket实例 #endregion #region 构造函数 public Client(string _name, EndPoint _endpoint, Thread _thread, Socket _sock) { name = _name; endpoint = _endpoint; client_thread = _thread; sock = _sock; } #endregion #region CLR属性 public Thread Client_Thread { get { return client_thread; } set { client_thread = 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; } } #endregion
客户端和上面差不多,基本都是分析协议头,然后执行相关的操作。
private void ReceiveChat() { bool alive = true; while (m_isConnected && alive) { Byte[] buffer = new Byte[100000]; //接收服务器消息 m_ns.Read(buffer, 0, buffer.Length); string time = System.DateTime.Now.ToLongTimeString(); //获取当前时间 string chatter = System.Text.Encoding.Default.GetString(buffer); //默认编码 主要是针对中文字符 string[] tokens = chatter.Split(new Char[] {‘|‘}); //分离获取消息 switch (tokens[0]) //头部消息 { case "CHAT": allmessage.AppendText(tokens[1].Trim()); //增加客户名称和时间 allmessage.AppendText(" " + time + " \r\n"); allmessage.AppendText(tokens[2]); //增加消息内容 break; case "JOIN": allmessage.AppendText(time + " "); allmessage.AppendText(tokens[1].Trim()); allmessage.AppendText("加入聊天室" + "\r\n"); string newcome = tokens[1].Trim(new char[] {‘\r‘,‘\n‘}); //新增用户 listBox1.Items.Add(newcome); //在列表增加名称 break; case "LEAVE": allmessage.AppendText(time + " "); allmessage.AppendText(tokens[1].Trim()); allmessage.AppendText("退出了聊天室" + "\r\n"); string leaver = tokens[1].Trim(new char[] { ‘\r‘,‘\n‘}); for (int n = 0; n < listBox1.Items.Count; n++) { if (listBox1.Items[n].ToString().CompareTo(leaver) == 0) //匹配 去除相关项 { listBox1.Items.RemoveAt(n); break; } } break; case "QUIT": m_ns.Close(); m_clientSocket.Close(); m_isConnected = false; alive = false; this.Text = "服务器断开"; button2.Enabled = false; button1.Text = "连接"; listBox1.Items.Clear(); MessageBox.Show("服务器断开!"); break; } }
这里还是给出下载链接,源程序可能还需要修改 http://download.csdn.net/detail/zhoupeng39/8104695
http://download.csdn.net/detail/zhoupeng39/8104687
上面的程序是我从网上看到然后修改的,TCP部分就差不多了,下面说说UDP部分。
UDP就相对简单了,主要是广播和单播,当然还必须加上和TCP差不多的协议头来区分操作。
代码如下
if (state==1) message = string.Format("Online|{0}", m_UserName); //1 表示上线 else message = string.Format("Outline|{0}", m_UserName); //0 表示下线 byte[] messageByte = Encoding.Unicode.GetBytes(message); m_BroSendClient.Send(messageByte, messageByte.Length, m_BroEndPoint); //广播 m_BroSendClient.Close();
IPEndPoint RemotePoint = new IPEndPoint(IPAddress.Any, 0); while(true) { byte[] receiveByte = m_UdpRecClient.Receive(ref RemotePoint); string receiveMessage = Encoding.Unicode.GetString(receiveByte); string[] tokens = receiveMessage.Split(new char[] { ‘|‘ }); //分离字符串 string sendmessage; switch(tokens[0]) { case "Online": //上线消息 if (tokens[1] != m_UserName) //开始全网广播 { for (int i = 0; i < m_OtherUserName.Length; i++) { if (m_OtherUserName[i] == tokens[1]) { m_OtherUserState[i] = 1; //改变状态 m_OtherUserIp[i] = RemotePoint.Address.ToString(); //存储IP ShowOnlineWindowMehtod(tokens[1]); //显示上线窗口 sendmessage = string.Format("Inline|{0}", m_UserName); UdpSendMessage(sendmessage, i); //发送在线消息 break; } } } break; case "Inline": //在线消息 for (int i = 0; i < m_OtherUserName.Length; i++) { if (m_OtherUserName[i]==tokens[1]) { m_OtherUserState[i] = 1; m_OtherUserIp[i] = RemotePoint.Address.ToString(); //存储IP break; } } break; case "Outline": //离线消息 for (int i = 0; i < m_OtherUserName.Length; i++) { if (m_OtherUserName[i] == tokens[1]) { m_OtherUserState[i] = 0; m_OtherUserIp[i] = ""; break; } } break; case "Connect": //请求TCP连接消息 客户端接收 for (int i = 0; i < m_OtherUserName.Length; i++) { if (m_OtherUserName[i] == tokens[1]) { TcpClient s= new TcpClient(m_OtherUserIp[i], int.Parse(tokens[2])); m_ServerCliListocket=s; m_IsInChatting[i] = 1; m_ClientRecIndex=i; m_ClientRecThread = new Thread(new ThreadStart(RecClient)); m_ClientRecThread.Start(); break; } } break; } }
其实在使用TCP和UDP写程序的时候困难的不是接口的调用,困难的是线程如何分配,如何管理这些线程。
在编写过程中我遇到一个问题,我是在子线程里面显示上线提示窗口,结果窗口假死,这是子线程结束窗口资源就会被回收导致的,解决的方法也很简单,就是通过
委托将显示窗口挂到主线程上面,这样窗口就不会假死了
代码如下
void ShowOnlineWindowMehtod(string name) { //委托到主窗体线程上 this.Invoke(new ShowOtherUserOnlineHandle(ShowOtherUserOnline), name); } delegate void ShowOtherUserOnlineHandle(string name); private void ShowOtherUserOnline(string name) { Online formOnline = new Online(name); formOnline.Show(this); }
想想编写的整个过程,我是想到哪儿就写到哪儿,之前没有设计好整个的布局,导致最终代码错误很多,代码也是乱成一块,很不美观,等考试之后我会给这个程序加上发送图片和文件的功能,然后重写整个的框架,到时再发代码。