首页 > 其他分享 >即时通信聊天工具的原理与设计

即时通信聊天工具的原理与设计

时间:2023-06-07 12:32:47浏览次数:36  
标签:string 通信 Text 用户 private 即时 聊天工具 splitString new


 该软件采用P2P方式,各个客户端之间直接发消息进行会话聊天,服务器在其中只扮演协调者的角色(混合型P2P)。

1.会话流程设计

       当一个新用户通过自己的客户端登陆系统后,从服务器获取当前在线的用户信息列表,列表信息包括了系统中每个用户的地址。用户就可以开始独立工作,自主地向其他用户发送消息,而不经过服务器。每当有新用户加入或在线用户退出时,服务器都会及时发消息通知系统中的所有其他用户,以便它们实时地更新用户信息列表。

      按照上述思路,设计系统会话流程如下:

      (1)用户通过客户端进入系统,向服务器发出消息,请求登陆。

      (2)服务器收到请求后,向客户端返回应答消息,表示同意接受该用户加入,并顺带将自己服务线程所在的监听端口号告诉用户。

      (3)客户端按照服务器应答中给出的端口号与服务器建立稳定的连接。

      (4)服务器通过该连接将当前在线用户的列表信息传给新加入的客户端。

      (5)客户端获得了在线用户列表,就可以独立自主地与在线的其他用户通信了。

      (6)当用户退出系统时要及时地通知服务器。

2.用户管理

    系统中,无论是服务器还是客户端都保存一份在线用户列表,客户端的用户表在一开始登陆时从服务器索取获得。在程序运行的过程中,服务器负责实时地将系统内用户的变动情况及时地通知在线的每个成员用户。

    新用户登录时,服务器将用户表传给他,同时向系统内每个成员广播“login”消息,各成员收到后更新自己的用户表。

    同样,在有用户退出系统时,服务器也会及时地将这一消息传给各个用户,当然这也就要求每个用户在自己想要退出之前,必须要先告诉服务器。

3.协议设计

3.1 客户端与服务器会话

    (1)登陆过程。

      客户端用匿名UDP向服务器发送消息:

      login,username,localIPEndPoint

      消息内容包括3个字段,各字段之间用“,”分隔:“login”表示请求登陆;“username”为用户名;“localIPEndPoint”是客户端本地地址。

      服务器收到后以匿名UDP返回如下消息:

      Accept,port

      其中,“Accept”表示服务器接受了请求;“port”是服务所在端口,服务线程在这个端口上监听可能的客户连接,该连接使用同步的TCP。

      连上服务器,获取用户列表:

      客户端从上一会话的“port”字段的值服务所在端口,于是向端口发起TCP连接,向服务器索取在线的用户列表,服务器接受连接后将用户列别传输给客户端。

      用户列表格式如下:

      username1,IPEndPoint1;username2,IPEndPoint2;.....;end

      username1,username2.....为用户名,IPEndPoint1,IPEndPoint2....为它们对应的端点。每个用户的信息都有个“用户名+端点”组成,用户信息之间以“;”隔开,整个用户列表以“end”结尾。

3.1 服务器协调管理用户

    (1)新用户加入通知。

      由于系统中已存在的每个用户都有一份当前用户表,因此当有新成员加入时,服务器无需重复给系统中的每个成员再传送用户表,只要将新加入成员的信息告诉系统内的其他用户,再由他们各自更新自己的用户表就行了。

      服务器向系统内用户广播发送如下消息:

      端点字段写为“remoteIPEndPoint”,表示是远程某个用户终端登陆了,本地客户线程据此更新用户列表。其实,在这个过程中,服务器只是将受到的“login”消息简单地转发而已。

     (2)用户退出。

      与新成员加入时一样,服务器将用户退出的消息直接进行广播转发:

      logout,username,remoteIPEndPoint

      其中,“remoteIPEndPoint”为退出系统的远程用户终端的端点地址。

3.1 用户终端之间聊天

      用户聊天时,他们各自的客户端之间是以P2P方式工作的,彼此地位对等,独立,不与服务器发生直接联系。

      聊天时发送的信息格式为:

      talk,longTime,selfUserName,message

      “talk”表明这是聊天内容;“longTime”是长时间格式的当前系统时间;“selfUserName”为自己的用户名;“message”是聊天的内容。

4.系统实现

4.1 服务线程

    系统运行后,先有服务器启动服务线程,只需单击“启动”按钮即可。

    “启动”按钮的事件过程:


1  //点击开始事件处理函数
 2         private void buttonStart_Click(object sender, EventArgs e)
 3         {
 4             //创建接收套接字
 5             serverIp = IPAddress.Parse(textBoxServerIp.Text);
 6             serverIPEndPoint = new IPEndPoint(serverIp, int.Parse(textBoxServerPort.Text));
 7             receiveUdpClient = new UdpClient(serverIPEndPoint);
 8            
 9             //启动接收线程
10             Thread threadReceive = new Thread(ReceiveMessage);
11             threadReceive.Start();
12             buttonStart.Enabled = false;
13             buttonStop.Enabled = true;
14 
15             //随机指定监听端口 N( P+1 ≤ N < 65536 )
16             Random random = new Random();
17             tcport = random.Next(port + 1, 65536);
18 
19             //创建监听套接字
20             myTcpListener = new TcpListener(serverIp, tcport);
21             myTcpListener.Start();
22            
23             //启动监听线程
24             Thread threadListen = new Thread(ListenClientConnect);
25             threadListen.Start();
26             AddItemToListBox(string.Format("服务线程({0})启动,监听端口{1}",serverIPEndPoint,tcport));
27         }


      可以看到,服务器先后启动了两个线程:一个是接收线程threadReceive,它在一个实名UDP端口上,时刻准备着接收客户端发来的会话消息;另一个是监听线程threadListen,它在某个随机指定的端口上监听。

      服务器接收线程关联的ReceiveMessage()方法:



1   //接收数据
 2         private void ReceiveMessage()
 3         {
 4             IPEndPoint remoteIPEndPoint = new IPEndPoint(IPAddress.Any, 0);
 5             while (true)
 6             {
 7                 try
 8                 {
 9                     //关闭receiveUdpClient时此句会产生异常
10                     byte[] receiveBytes = receiveUdpClient.Receive(ref remoteIPEndPoint);
11                     string message = Encoding.Unicode.GetString(receiveBytes, 0, receiveBytes.Length);
12                    
13                     //显示消息内容
14                     AddItemToListBox(string.Format("{0}:[{1}]", remoteIPEndPoint, message));
15                    
16                     //处理消息数据
17                     string[] splitString = message.Split(',');
18                    
19                     //解析用户端地址
20                     string[] splitSubString = splitString[2].Split(':');   //除去':'
21                     IPEndPoint clientIPEndPoint = new IPEndPoint(IPAddress.Parse(splitSubString[0]), int.Parse(splitSubString[1]));
22                     switch (splitString[0])
23                     {
24                         //收到注册关键字"login"
25                         case "login":
26                             User user = new User(splitString[1], clientIPEndPoint);
27                             userList.Add(user);
28                             AddItemToListBox(string.Format("用户{0}({1})加入", user.GetName(), user.GetIPEndPoint()));
29                             string sendString = "Accept," + tcport.ToString();
30                             SendtoClient(user, sendString);   //向该用户发送同意关键字
31                             AddItemToListBox(string.Format("向{0}({1})发出:[{2}]", user.GetName(), user.GetIPEndPoint(), sendString));
32                             for (int i = 0; i < userList.Count; i++)
33                             {
34                                 if (userList[i].GetName() != user.GetName())
35                                 {
36                                     //向除刚加入的所有用户发送更新消息
37                                     SendtoClient(userList[i], message);
38                                 }
39                             }
40                             AddItemToListBox(string.Format("广播:[{0}]", message));
41                             break;
42                      
43                         //收到关键字"logout"
44                         case "logout":
45                             for (int i = 0; i < userList.Count; i++)
46                             {
47                                 if (userList[i].GetName() == splitString[1])
48                                 {
49                                     AddItemToListBox(string.Format("用户{0}({1})退出", userList[i].GetName(), userList[i].GetIPEndPoint()));
50                                     userList.RemoveAt(i);
51                                 }
52                             }
53 
54                             //向所用用户发送更新消息
55                             for (int i = 0; i < userList.Count; i++)
56                             {
57                                 SendtoClient(userList[i], message);
58                             }
59                             AddItemToListBox(string.Format("广播:[{0}]", message));
60                             break;
61                     }
62                 }
63                 catch
64                 {
65                     break;
66                 }
67             }
68             AddItemToListBox(string.Format("服务线程({0})终止", serverIPEndPoint));
69         }


     接收线程执行该方法,进入while()循环,对每个收到的消息进行解析,根据消息头是“login”或“logout”转入相应的处理。

     监听线程对应ListenClientConnect()方法:


1 //接受客户端连接
 2         private void ListenClientConnect()
 3         {
 4             TcpClient newClient = null;
 5             while (true)
 6             {
 7                 try
 8                 {
 9                     //获得用于传递数据的TCP套接口
10                     newClient = myTcpListener.AcceptTcpClient();
11                     AddItemToListBox(string.Format("接受客户端{0}的 TCP 请求", newClient.Client.RemoteEndPoint));
12                 }
13                 catch
14                 {
15                     AddItemToListBox(string.Format("监听线程({0}:{1})终止", serverIp, tcport));
16                     break;
17                 }
18 
19                 //启动发送用户列表线程
20                 Thread threadSend = new Thread(SendData);
21                 threadSend.Start(newClient);
22             }
23         }

      当客户端请求到达后,与之建立TCP连接,然后创建一个新的线程threadSend,他通过执行SendData()方法传送用户列表。

      在服务器运行过程中,可随时通过点击“停止”按钮关闭服务线程。

      ”停止“按钮的事件过程:


1    //当点击关闭按钮的事件处理程序
2         private void buttonStop_Click(object sender, EventArgs e)
3         {
4             myTcpListener.Stop();
5             receiveUdpClient.Close();
6             buttonStart.Enabled = true;
7             buttonStop.Enabled = false;
8         }



     这里myTcpListener是TCP监听套接字,而receiveUdpClient是UDP套接字。当执行Stop()方法关闭监听套接字时,myTcpListener.AcceptTcpClient()会产生异常。

     运行服务器,先后单击”启动“和”停止“按钮,状态监控屏上就显示出服务线程的工作状态,如下图所示。

                                 

即时通信聊天工具的原理与设计_服务器

                                                        图1 服务线程的启动/停止状态

4.2 登陆/注销

 (1) 用户对象

     为了便于服务器对全体用户的管理,在服务器工程中添加自定义User类。代码如下:


1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 
 6 //添加的命名空间引用
 7 using System.Net;
 8 
 9 namespace Server
10 {
11     //用户信息类      蒲泓全(18/3/2012)
12     class User
13     {
14         private string userName;           //用户名
15         private IPEndPoint userIPEndPoint;  //用户地址 
16         public User(string name, IPEndPoint ipEndPoint)
17         {
18             userName = name;
19             userIPEndPoint = ipEndPoint;
20         }
21         public string GetName()
22         {
23             return userName;
24         }
25         public IPEndPoint GetIPEndPoint()
26         {
27             return userIPEndPoint;
28         }
29     }
30 }


      User类具有用户名和端点地址两个属性,这也正是用户列表中需要填写的信息项。
(2) 用户登录功能

      当服务器的两个服务线程运行起来之后,各用户就可以通过客户端程序登录到系统了。用户在客户端上单击“登录”按钮后,客户端就向服务器发出“login”请求。

     “登录”按钮的事件过程为:




即时通信聊天工具的原理与设计_服务器_02

即时通信聊天工具的原理与设计_服务器_03



private void buttonLogin_Click(object sender, EventArgs e)
        {
//创建接收套接字
            IPAddress clientIp = IPAddress.Parse(textBoxLocalIp.Text);
            clientIPEndPoint = new IPEndPoint(clientIp, int.Parse(textBoxLocalPort.Text));
            receiveUdpClient = new UdpClient(clientIPEndPoint);

//启动接收线程
            Thread threadReceive = new Thread(ReceiveMessage);
            threadReceive.Start();
            AddItemToListBox(string.Format("客户线程({0})启动", clientIPEndPoint));

//匿名发送
            sendUdpClient = new UdpClient(0);

//启动发送线程
            Thread threadSend = new Thread(SendMessage);
            threadSend.Start(string.Format("login,{0},{1}",textBoxUserName.Text,clientIPEndPoint));
            AddItemToListBox(string.Format("发出:[login,{0},{1}]", textBoxUserName.Text, clientIPEndPoint));
            buttonLogin.Enabled = false;
            buttonLogout.Enabled = true;
this.Text = textBoxUserName.Text;  //使当前窗体名字变为当前用户名
        }

     可以看到客户端在登录时也启动了两个线程,其中一个threadReceive是用实名UDP创建的接收线程,又称为客户线程,这是因为,它代表客户端程序处理与服务器的会话消息。另一个线程threadSend则是临时创建的,并以匿名UDP向服务器发出“login”消息。

     登陆请求发出之后,客户线程就循环执行ReceiveMessage()方法,以随时接受和处理服务器的应答消息。

     客户线程关联的ReceiveMessage()方法:


1  //接收数据
 2         private void ReceiveMessage()
 3         {
 4             IPEndPoint remoteIPEndPoint = new IPEndPoint(IPAddress.Any, 0);
 5             while (true)
 6             {
 7                 try
 8                 {
 9                     //关闭receiveUdpClient时此句会产生异常
10                     byte[] receiveBytes = receiveUdpClient.Receive(ref remoteIPEndPoint);
11                     string message = Encoding.Unicode.GetString(receiveBytes, 0, receiveBytes.Length);
12                    
13                     //显示消息内容
14                     AddItemToListBox(string.Format("{0}:[{1}]", remoteIPEndPoint, message));
15                   
16                     //处理消息数据
17                     string[] splitString = message.Split(',');   //除去','
18                     switch (splitString[0])
19                     {
20                         //若接收连接
21                         case "Accept":
22                             try
23                             {
24                                 AddItemToListBox(string.Format("连接{0}:{1}...", remoteIPEndPoint.Address, splitString[1]));
25                                 myTcpClient = new TcpClient();
26                                 myTcpClient.Connect(remoteIPEndPoint.Address, int.Parse(splitString[1]));
27                                 if (myTcpClient != null)
28                                 {
29                                     AddItemToListBox("连接成功!");
30                                     networkStream = myTcpClient.GetStream();
31                                     br = new BinaryReader(networkStream);
32                                 }
33                             }
34                             catch
35                             {
36                                 AddItemToListBox("连接失败!");
37                             }
38                             Thread threadGetList = new Thread(GetUserList);
39                             threadGetList.Start(); //请求获得用户列表信息线程启动
40                             break;
41 
42                             //若收到注册关键字"login",代表有新的用户加入,并更新用户列表
43                         case "login":
44                             AddItemToListBox(string.Format("新用户{0}({1})加入", splitString[1], splitString[2]));
45                             string userItemInfo = splitString[1] + "," + splitString[2];
46                             AddItemToListView(userItemInfo);
47                             break;
48 
49                         //若收到注册关键字"logout",代表有用户退出,并更新用户列表
50                         case "logout":
51                             AddItemToListBox(string.Format("用户{0}({1})退出", splitString[1], splitString[2]));
52                             RmvItemfromListView(splitString[1]);
53                             break;
54 
55                         //若收到回话关键字"talk",则表明有用户发起回话,并开始准备回话
56                         case "talk":
57                             for (int i = 0; i < chatFormList.Count; i++)
58                             {
59                                 if (chatFormList[i].Text == splitString[2])
60                                 {
61                                     chatFormList[i].ShowTalkInfo(splitString[2], splitString[1], splitString[3]);
62                                 }
63                             }
64                             break;
65                     }
66                 }
67                 catch
68                 {
69                     break;
70                 }
71             }
72             AddItemToListBox(string.Format("客户线程({0})终止", clientIPEndPoint));
73         }



    如下图所示:为第一个用户登陆系统时,从状态监控屏幕上看到的客户端与服务器程序的会话过程。

                      

即时通信聊天工具的原理与设计_即时聊天_04

                                                                               图2 登陆过程中双方的会话

(3) 用户注销

    当用用户需要下线退出时,单击客户端界面上的“注销”按钮。

    “注销”按钮的过程代码:

1    //当点击退出按钮的事件处理函数
 2         private void buttonLogout_Click(object sender, EventArgs e)
 3         {
 4             //匿名发送
 5             sendUdpClient = new UdpClient(0);
 6 
 7             //启动发送线程
 8             Thread threadSend = new Thread(SendMessage);
 9             threadSend.Start(string.Format("logout,{0},{1}", textBoxUserName.Text, clientIPEndPoint));
10             AddItemToListBox(string.Format("发出:[logout,{0},{1}]", textBoxUserName.Text, clientIPEndPoint));
11             receiveUdpClient.Close();
12             listViewOnline.Items.Clear();
13             buttonLogin.Enabled = true;
14             buttonLogout.Enabled = false;
15             this.Text = "Client";       //恢复到原来的名字
16         }


     注销操作很简单,凡要注销的用户只需向服务器发出“logou”消息,告知服务器就可以了,最好还要关闭客户端自身的UDP套接字。

     服务器在收到“logout”消息后,执行下面代码:



for (int i = 0; i < userList.Count; i++)
       {
if (userList[i].GetName() == splitString[1])
                  {
                                    AddItemToListBox(string.Format("用户{0}({1})退出", userList[i].GetName(), userList[i].GetIPEndPoint()));
                                    userList.RemoveAt(i);
                                }
                            }

//向所用用户发送更新消息
                            for (int i = 0; i < userList.Count; i++)
                            {
                                SendtoClient(userList[i], message);
                            }
                            AddItemToListBox(string.Format("广播:[{0}]", message));


     服务程序在自己维护的User对象列表中删除这个用户,并且将这个消息广播给所有的用户。

     在这个过程中,退出的客户端与服务器的会话记录如下图所示:

                                

即时通信聊天工具的原理与设计_List_05

                                                                          图3 注销时双方的会话

(3) 更新用户列表

     系统内的在线用户收到服务器发来的消息后,实时地更新自己的用户列表。当服务器发来“login”消息时,说明有新成员加入,客户端执行下面的代码:



1  AddItemToListBox(string.Format("新用户{0}({1})加入", splitString[1], splitString[2]));
2  string userItemInfo = splitString[1] + "," + splitString[2];
3  AddItemToListView(userItemInfo);
4         break;



     若收到的是“logout”,则执行下面的代码:



1 AddItemToListBox(string.Format("用户{0}({1})退出", splitString[1], splitString[2]));
2 RmvItemfromListView(splitString[1]);
3          break;



    为了是程序简单,客户端并没有使用特定的数据结构存储用户列表,而是直接将列表用ListView空间显示在界面上,并用委托机制定义了两个回调函数AddItemToListView()和RmvItemfromListView(),向空间中添加/删除用户信息。

 4.3 及时聊天

    带有聊天谈话内容的消息以“talk”为首部,采用点对点(P2P)方式发给对方。“talk”消息的发送,接收和显示都由专门的聊天子窗口负责,当客户端主程序收到“talk”的消息时,执行下面的代码:


1 for (int i = 0; i < chatFormList.Count; i++)
2 {
3         if (chatFormList[i].Text == splitString[2])
4        {
5               chatFormList[i].ShowTalkInfo(splitString[2], splitString[1], splitString[3]);
6        }
7 }
8         break;


    系统中的每个用户都对应一个聊天子窗体对象,上段代码的作用就是将一个“talk”消息定位到它的接受者的子窗体对象,再由该对象调用自身的ShwoTalkInfo()方法显示聊天内容。

    要打开对应某个用户的子窗口,只需双击在线用户列表中的该用户项即可,代码如下:


1  //当点击两次发起回话的事件处理函数
 2         private void listViewOnline_DoubleClick(object sender, EventArgs e)
 3         {            
 4             string peerName = listViewOnline.SelectedItems[0].SubItems[1].Text;
 5             if (peerName == textBoxUserName.Text)
 6             {
 7                 return;
 8             }
 9             string ipendp = listViewOnline.SelectedItems[0].SubItems[2].Text;
10             string[] splitString = ipendp.Split(':');   //除去':'
11             IPAddress peerIp = IPAddress.Parse(splitString[0]);
12             IPEndPoint peerIPEndPoint = new IPEndPoint(peerIp, int.Parse(splitString[1]));
13             ChatForm dlgChatForm = new ChatForm();
14             dlgChatForm.SetUserInfo(textBoxUserName.Text, peerName, peerIPEndPoint);
15             dlgChatForm.Text = peerName;
16             chatFormList.Add(dlgChatForm);
17             dlgChatForm.Show();            
18         }


    其中,chatFormList是客户端程序定义的数据结构,用于保存每一个在线用户的子窗口列表,它与服务器端的userList结构是相对应的。每当用户双击了列表中的某个用户项时,程序就用该项的信息创建一个新的子窗体对象并添加到chatFormList表中。子窗体的初始化使用其自身的SetUserInfo()方法。

    哈哈哈哈,现在整个通信聊天软件就完成了,我们看看下面运行的效果。

5. 运行效果

   同时运行一个服务器(Server)程序和三个客户端(Client)程序,启动服务线程,在三个客户端分别以用户名“泓全”,“爱田”,“爱盼”登陆服务器。如下图所示:

                  

即时通信聊天工具的原理与设计_服务器_06

                                                                                    图4 登陆服务器

      此时,每个在线用户两两之间就都可以即时聊天了。不过,在聊天之前,聊天双方要先打开对方的子窗口,操作方法很简单,只要在客户端界面上双击“在线”列表中相应的用户项即可。如下图所示:

                                    

即时通信聊天工具的原理与设计_即时聊天_07

                                                                                          图5 在线交谈

6. 源代码

6.1 服务器端

1 using System;
  2 using System.Collections.Generic;
  3 using System.ComponentModel;
  4 using System.Data;
  5 using System.Drawing;
  6 using System.Linq;
  7 using System.Text;
  8 using System.Windows.Forms;
  9 
 10 //添加的命名空间引用
 11 using System.Net;
 12 using System.Net.Sockets;
 13 using System.Threading;
 14 using System.IO;
 15 
 16 namespace Server
 17 {
 18     public partial class MainForm : Form
 19     {
 20         private List<User> userList = new List<User>(); //保存登录的所有用户
 21         int  port;                          //服务端口
 22         int tcport;                         //监听端口
 23         private UdpClient sendUdpClient;     //匿名发送套接口
 24         private UdpClient receiveUdpClient;  //实名接收套接口
 25         private IPEndPoint serverIPEndPoint; //服务器地址
 26         private TcpListener myTcpListener;   //服务器监听套接口
 27         private IPAddress serverIp;          //服务器IP
 28         private NetworkStream networkStream;  //网络流
 29         private BinaryWriter bw;             //避免出现网络边界问题的写入流
 30         string userListString;               //用户列表串
 31         public MainForm()
 32         {
 33             InitializeComponent();
 34             
 35             //服务器IP
 36             IPAddress[] ServerIP = Dns.GetHostAddresses("");
 37             IPAddress address = IPAddress.Any;
 38             for (int i = 0; i < ServerIP.Length; i++ )
 39             {
 40                 if (ServerIP[i].AddressFamily == AddressFamily.InterNetwork)
 41                 {
 42                     address = ServerIP[i];
 43                     break;
 44                 }
 45             }
 46             textBoxServerIp.Text = address.ToString();
 47           
 48             //随机选择服务端口 Port( Port > 1024 )
 49             port = new Random().Next(1024, 65535);
 50             textBoxServerPort.Text = port.ToString();
 51             buttonStop.Enabled = false;
 52         }
 53 
 54         //点击开始事件处理函数
 55         private void buttonStart_Click(object sender, EventArgs e)
 56         {
 57             //创建接收套接字
 58             serverIp = IPAddress.Parse(textBoxServerIp.Text);
 59             serverIPEndPoint = new IPEndPoint(serverIp, int.Parse(textBoxServerPort.Text));
 60             receiveUdpClient = new UdpClient(serverIPEndPoint);
 61            
 62             //启动接收线程
 63             Thread threadReceive = new Thread(ReceiveMessage);
 64             threadReceive.Start();
 65             buttonStart.Enabled = false;
 66             buttonStop.Enabled = true;
 67 
 68             //随机指定监听端口 N( P+1 ≤ N < 65536 )
 69             Random random = new Random();
 70             tcport = random.Next(port + 1, 65536);
 71 
 72             //创建监听套接字
 73             myTcpListener = new TcpListener(serverIp, tcport);
 74             myTcpListener.Start();
 75            
 76             //启动监听线程
 77             Thread threadListen = new Thread(ListenClientConnect);
 78             threadListen.Start();
 79             AddItemToListBox(string.Format("服务线程({0})启动,监听端口{1}",serverIPEndPoint,tcport));
 80         }
 81 
 82         
 83         //接受客户端连接
 84         private void ListenClientConnect()
 85         {
 86             TcpClient newClient = null;
 87             while (true)
 88             {
 89                 try
 90                 {
 91                     //获得用于传递数据的TCP套接口
 92                     newClient = myTcpListener.AcceptTcpClient();
 93                     AddItemToListBox(string.Format("接受客户端{0}的 TCP 请求", newClient.Client.RemoteEndPoint));
 94                 }
 95                 catch
 96                 {
 97                     AddItemToListBox(string.Format("监听线程({0}:{1})终止", serverIp, tcport));
 98                     break;
 99                 }
100 
101                 //启动发送用户列表线程
102                 Thread threadSend = new Thread(SendData);
103                 threadSend.Start(newClient);
104             }
105         }
106 
107         //向客户端发送在线用户列表信息
108         private void SendData(object userClient)
109         {
110             TcpClient newUserClient = (TcpClient)userClient;
111             userListString = null;
112             for (int i = 0; i < userList.Count; i++)
113             {
114                 userListString += userList[i].GetName() + "," + userList[i].GetIPEndPoint().ToString() + ";";
115             }
116             userListString += "end";
117             networkStream = newUserClient.GetStream();
118             bw = new BinaryWriter(networkStream);
119             bw.Write(userListString);
120             bw.Flush();      //不保留现在写入的数据
121             AddItemToListBox(string.Format("向{0}传送:[{1}]", newUserClient.Client.RemoteEndPoint, userListString));
122             bw.Close();
123             newUserClient.Close();
124         }
125 
126         //接收数据
127         private void ReceiveMessage()
128         {
129             IPEndPoint remoteIPEndPoint = new IPEndPoint(IPAddress.Any, 0);
130             while (true)
131             {
132                 try
133                 {
134                     //关闭receiveUdpClient时此句会产生异常
135                     byte[] receiveBytes = receiveUdpClient.Receive(ref remoteIPEndPoint);
136                     string message = Encoding.Unicode.GetString(receiveBytes, 0, receiveBytes.Length);
137                    
138                     //显示消息内容
139                     AddItemToListBox(string.Format("{0}:[{1}]", remoteIPEndPoint, message));
140                    
141                     //处理消息数据
142                     string[] splitString = message.Split(',');
143                    
144                     //解析用户端地址
145                     string[] splitSubString = splitString[2].Split(':');   //除去':'
146                     IPEndPoint clientIPEndPoint = new IPEndPoint(IPAddress.Parse(splitSubString[0]), int.Parse(splitSubString[1]));
147                     switch (splitString[0])
148                     {
149                         //收到注册关键字"login"
150                         case "login":
151                             User user = new User(splitString[1], clientIPEndPoint);
152                             userList.Add(user);
153                             AddItemToListBox(string.Format("用户{0}({1})加入", user.GetName(), user.GetIPEndPoint()));
154                             string sendString = "Accept," + tcport.ToString();
155                             SendtoClient(user, sendString);   //向该用户发送同意关键字
156                             AddItemToListBox(string.Format("向{0}({1})发出:[{2}]", user.GetName(), user.GetIPEndPoint(), sendString));
157                             for (int i = 0; i < userList.Count; i++)
158                             {
159                                 if (userList[i].GetName() != user.GetName())
160                                 {
161                                     //向除刚加入的所有用户发送更新消息
162                                     SendtoClient(userList[i], message);
163                                 }
164                             }
165                             AddItemToListBox(string.Format("广播:[{0}]", message));
166                             break;
167                      
168                         //收到关键字"logout"
169                         case "logout":
170                             for (int i = 0; i < userList.Count; i++)
171                             {
172                                 if (userList[i].GetName() == splitString[1])
173                                 {
174                                     AddItemToListBox(string.Format("用户{0}({1})退出", userList[i].GetName(), userList[i].GetIPEndPoint()));
175                                     userList.RemoveAt(i);
176                                 }
177                             }
178 
179                             //向所用用户发送更新消息
180                             for (int i = 0; i < userList.Count; i++)
181                             {
182                                 SendtoClient(userList[i], message);
183                             }
184                             AddItemToListBox(string.Format("广播:[{0}]", message));
185                             break;
186                     }
187                 }
188                 catch
189                 {
190                     break;
191                 }
192             }
193             AddItemToListBox(string.Format("服务线程({0})终止", serverIPEndPoint));
194         }
195 
196         private void SendtoClient(User user, string message)
197         {
198             //匿名发送
199             sendUdpClient = new UdpClient(0);
200             byte[] sendbytes = Encoding.Unicode.GetBytes(message);
201             IPEndPoint remoteIPEndPoint = user.GetIPEndPoint();
202             sendUdpClient.Send(sendbytes, sendbytes.Length, remoteIPEndPoint);
203             sendUdpClient.Close();
204         }
205 
206         //当点击关闭按钮的事件处理程序
207         private void buttonStop_Click(object sender, EventArgs e)
208         {
209             myTcpListener.Stop();
210             receiveUdpClient.Close();
211             buttonStart.Enabled = true;
212             buttonStop.Enabled = false;
213         }
214 
215         //用委托机制解决显示问题
216         private delegate void AddItemToListBoxDelegate(string str);
217         private void AddItemToListBox(string str)
218         {
219             if (listBoxStatus.InvokeRequired)
220             {
221                 AddItemToListBoxDelegate d = AddItemToListBox;
222                 listBoxStatus.Invoke(d, str);
223             }
224             else
225             {
226                 listBoxStatus.Items.Add(str);
227                 listBoxStatus.TopIndex = listBoxStatus.Items.Count - 1;
228                 listBoxStatus.ClearSelected();
229             }
230         }
231     }
232 }


6.2 客户端

1 using System;
  2 using System.Collections.Generic;
  3 using System.ComponentModel;
  4 using System.Data;
  5 using System.Drawing;
  6 using System.Linq;
  7 using System.Text;
  8 using System.Windows.Forms;
  9 
 10 //添加的命名空间引用
 11 using System.Net;
 12 using System.Net.Sockets;
 13 using System.Threading;
 14 using System.IO;
 15 
 16 namespace Client
 17 {
 18     public partial class MainForm : Form
 19     {
 20         int  port;                           //端口号                 
 21         private UdpClient sendUdpClient;       //匿名发送套接口
 22         private UdpClient receiveUdpClient;    //实名接收套接口
 23         private IPEndPoint clientIPEndPoint;   //客户端地址
 24         private TcpClient myTcpClient;        //TCP套接字
 25         private NetworkStream networkStream;  //网络流
 26         private BinaryReader br;             //避免网络边界问题的读数据流
 27         string userListString;               //用户名字串
 28         private List<ChatForm> chatFormList = new List<ChatForm>();   //用户窗体列表
 29         public MainForm()
 30         {
 31             InitializeComponent();
 32 
 33             //本地IP和端口号的初始化
 34             IPAddress[] LocalIP = Dns.GetHostAddresses("");
 35             IPAddress address = IPAddress.Any;
 36             for (int i = 0; i < LocalIP.Length; i++)
 37             {
 38                 if (LocalIP[i].AddressFamily == AddressFamily.InterNetwork)
 39                 {
 40                     address = LocalIP[i];
 41                     break;
 42                 }
 43             }
 44             textBoxServerIp.Text = address.ToString();
 45             textBoxLocalIp.Text = address.ToString();
 46 
 47             //获得随机端口号
 48             port = new Random().Next(1024, 65535);
 49             textBoxLocalPort.Text = port.ToString();
 50 
 51             //随机生成用户名
 52             Random r = new Random((int)DateTime.Now.Ticks);   //类似于与C++中的种子
 53             textBoxUserName.Text = "user" + r.Next(100, 999);
 54             buttonLogout.Enabled = false;
 55         }
 56 
 57         private void buttonLogin_Click(object sender, EventArgs e)
 58         {
 59             //创建接收套接字
 60             IPAddress clientIp = IPAddress.Parse(textBoxLocalIp.Text);
 61             clientIPEndPoint = new IPEndPoint(clientIp, int.Parse(textBoxLocalPort.Text));
 62             receiveUdpClient = new UdpClient(clientIPEndPoint);
 63 
 64             //启动接收线程
 65             Thread threadReceive = new Thread(ReceiveMessage);
 66             threadReceive.Start();
 67             AddItemToListBox(string.Format("客户线程({0})启动", clientIPEndPoint));
 68            
 69             //匿名发送
 70             sendUdpClient = new UdpClient(0);
 71 
 72             //启动发送线程
 73             Thread threadSend = new Thread(SendMessage);
 74             threadSend.Start(string.Format("login,{0},{1}",textBoxUserName.Text,clientIPEndPoint));
 75             AddItemToListBox(string.Format("发出:[login,{0},{1}]", textBoxUserName.Text, clientIPEndPoint));
 76             buttonLogin.Enabled = false;
 77             buttonLogout.Enabled = true;
 78             this.Text = textBoxUserName.Text;  //使当前窗体名字变为当前用户名
 79         }
 80 
 81         //发送数据
 82         private void SendMessage(object obj)
 83         {
 84             string message = (string)obj;
 85             byte[] sendbytes = Encoding.Unicode.GetBytes(message);
 86 
 87             //服务器端的IP和端口号
 88             IPAddress remoteIp = IPAddress.Parse(textBoxServerIp.Text);
 89             IPEndPoint remoteIPEndPoint = new IPEndPoint(remoteIp, int.Parse(textBoxServerPort.Text));
 90             sendUdpClient.Send(sendbytes, sendbytes.Length, remoteIPEndPoint);  //匿名发送
 91             sendUdpClient.Close();
 92         }
 93 
 94         //接收数据
 95         private void ReceiveMessage()
 96         {
 97             IPEndPoint remoteIPEndPoint = new IPEndPoint(IPAddress.Any, 0);
 98             while (true)
 99             {
100                 try
101                 {
102                     //关闭receiveUdpClient时此句会产生异常
103                     byte[] receiveBytes = receiveUdpClient.Receive(ref remoteIPEndPoint);
104                     string message = Encoding.Unicode.GetString(receiveBytes, 0, receiveBytes.Length);
105                    
106                     //显示消息内容
107                     AddItemToListBox(string.Format("{0}:[{1}]", remoteIPEndPoint, message));
108                   
109                     //处理消息数据
110                     string[] splitString = message.Split(',');   //除去','
111                     switch (splitString[0])
112                     {
113                         //若接收连接
114                         case "Accept":
115                             try
116                             {
117                                 AddItemToListBox(string.Format("连接{0}:{1}...", remoteIPEndPoint.Address, splitString[1]));
118                                 myTcpClient = new TcpClient();
119                                 myTcpClient.Connect(remoteIPEndPoint.Address, int.Parse(splitString[1]));
120                                 if (myTcpClient != null)
121                                 {
122                                     AddItemToListBox("连接成功!");
123                                     networkStream = myTcpClient.GetStream();
124                                     br = new BinaryReader(networkStream);
125                                 }
126                             }
127                             catch
128                             {
129                                 AddItemToListBox("连接失败!");
130                             }
131                             Thread threadGetList = new Thread(GetUserList);
132                             threadGetList.Start(); //请求获得用户列表信息线程启动
133                             break;
134 
135                             //若收到注册关键字"login",代表有新的用户加入,并更新用户列表
136                         case "login":
137                             AddItemToListBox(string.Format("新用户{0}({1})加入", splitString[1], splitString[2]));
138                             string userItemInfo = splitString[1] + "," + splitString[2];
139                             AddItemToListView(userItemInfo);
140                             break;
141 
142                         //若收到注册关键字"logout",代表有用户退出,并更新用户列表
143                         case "logout":
144                             AddItemToListBox(string.Format("用户{0}({1})退出", splitString[1], splitString[2]));
145                             RmvItemfromListView(splitString[1]);
146                             break;
147 
148                         //若收到回话关键字"talk",则表明有用户发起回话,并开始准备回话
149                         case "talk":
150                             for (int i = 0; i < chatFormList.Count; i++)
151                             {
152                                 if (chatFormList[i].Text == splitString[2])
153                                 {
154                                     chatFormList[i].ShowTalkInfo(splitString[2], splitString[1], splitString[3]);
155                                 }
156                             }
157                             break;
158                     }
159                 }
160                 catch
161                 {
162                     break;
163                 }
164             }
165             AddItemToListBox(string.Format("客户线程({0})终止", clientIPEndPoint));
166         }
167 
168         //获得用户列表
169         private void GetUserList()
170         {
171             while (true)
172             {
173                 userListString = null;
174                 try
175                 {
176                     userListString = br.ReadString();
177                     if (userListString.EndsWith("end"))
178                     {
179                         AddItemToListBox(string.Format("收到:[{0}]", userListString));
180                         string[] splitString = userListString.Split(';');
181                         for (int i = 0; i < splitString.Length - 1; i++)
182                         {
183                             AddItemToListView(splitString[i]);
184                         }
185                         br.Close();
186                         myTcpClient.Close();
187                         break;
188                     }
189                 }
190                 catch
191                 {
192                     break;
193                 }
194             }
195         }
196 
197         //当点击退出按钮的事件处理函数
198         private void buttonLogout_Click(object sender, EventArgs e)
199         {
200             //匿名发送
201             sendUdpClient = new UdpClient(0);
202 
203             //启动发送线程
204             Thread threadSend = new Thread(SendMessage);
205             threadSend.Start(string.Format("logout,{0},{1}", textBoxUserName.Text, clientIPEndPoint));
206             AddItemToListBox(string.Format("发出:[logout,{0},{1}]", textBoxUserName.Text, clientIPEndPoint));
207             receiveUdpClient.Close();
208             listViewOnline.Items.Clear();
209             buttonLogin.Enabled = true;
210             buttonLogout.Enabled = false;
211             this.Text = "Client";       //恢复到原来的名字
212         }
213 
214         //当点击两次发起回话的事件处理函数
215         private void listViewOnline_DoubleClick(object sender, EventArgs e)
216         {            
217             string peerName = listViewOnline.SelectedItems[0].SubItems[1].Text;
218             if (peerName == textBoxUserName.Text)
219             {
220                 return;
221             }
222             string ipendp = listViewOnline.SelectedItems[0].SubItems[2].Text;
223             string[] splitString = ipendp.Split(':');   //除去':'
224             IPAddress peerIp = IPAddress.Parse(splitString[0]);
225             IPEndPoint peerIPEndPoint = new IPEndPoint(peerIp, int.Parse(splitString[1]));
226             ChatForm dlgChatForm = new ChatForm();
227             dlgChatForm.SetUserInfo(textBoxUserName.Text, peerName, peerIPEndPoint);
228             dlgChatForm.Text = peerName;
229             chatFormList.Add(dlgChatForm);
230             dlgChatForm.Show();            
231         }
232 
233         //利用委托机制显示信息
234         private delegate void AddItemToListBoxDelegate(string str);
235         private void AddItemToListBox(string str)
236         {
237             if (listBoxStatus.InvokeRequired)
238             {
239                 AddItemToListBoxDelegate d = AddItemToListBox;
240                 listBoxStatus.Invoke(d, str);
241             }
242             else
243             {
244                 listBoxStatus.Items.Add(str);
245                 listBoxStatus.TopIndex = listBoxStatus.Items.Count - 1;
246                 listBoxStatus.ClearSelected();
247             }
248         }
249 
250         private delegate void AddItemToListViewDelegate(string str);
251         private void AddItemToListView(string str)
252         {
253             if (listViewOnline.InvokeRequired)
254             {
255                 AddItemToListViewDelegate d = AddItemToListView;
256                 listViewOnline.Invoke(d, str);
257             }
258             else
259             {
260                 string[] splitString = str.Split(',');
261                 ListViewItem item = new ListViewItem();
262                 item.SubItems.Add(splitString[0]);
263                 item.SubItems.Add(splitString[1]);
264                 listViewOnline.Items.Add(item);
265             }
266         }
267 
268         private delegate void RmvItemfromListViewDelegate(string str);
269         private void RmvItemfromListView(string str)
270         {
271             if (listViewOnline.InvokeRequired)
272             {
273                 RmvItemfromListViewDelegate d = RmvItemfromListView;
274                 listViewOnline.Invoke(d, str);
275             }
276             else
277             {
278                 for (int i = 0; i < listViewOnline.Items.Count; i++)
279                 {
280                     if (listViewOnline.Items[i].SubItems[1].Text == str)
281                     {
282                         listViewOnline.Items[i].Remove();
283                     }
284                 }
285             }
286         }
287     }
288 }

6.3 聊天子窗口的代码


1 using System;
 2 using System.Collections.Generic;
 3 using System.ComponentModel;
 4 using System.Data;
 5 using System.Drawing;
 6 using System.Linq;
 7 using System.Text;
 8 using System.Windows.Forms;
 9 
10 //添加的命名空间引用
11 using System.Net;
12 using System.Net.Sockets;
13 using System.Threading;
14 
15 namespace Client
16 {
17     public partial class ChatForm : Form
18     {
19         private string selfUserName;          //自己的用户名
20         private string peerUserName;          //对方的用户名
21         private IPEndPoint peerUserIPEndPoint; //对方的地址
22         private UdpClient sendUdpClient;      //匿名发送套接口
23         public ChatForm()
24         {
25             InitializeComponent();
26         }
27 
28         //类似于构造函数
29         public void SetUserInfo(string selfName,string peerName,IPEndPoint peerIPEndPoint)
30         {            
31             selfUserName = selfName;
32             peerUserName = peerName;
33             peerUserIPEndPoint = peerIPEndPoint;
34         }
35 
36         //点击发送按钮的事件处理程序
37         private void buttonSend_Click(object sender, EventArgs e)
38         {
39             //匿名发送
40             sendUdpClient = new UdpClient(0);
41 
42             //启动发送线程
43             Thread threadSend = new Thread(SendMessage);
44             threadSend.Start(string.Format("talk,{0},{1},{2}", DateTime.Now.ToLongTimeString(), selfUserName, textBoxSend.Text));            
45             richTextBoxTalkInfo.AppendText(selfUserName + "" + DateTime.Now.ToLongTimeString() + Environment.NewLine + textBoxSend.Text);
46             richTextBoxTalkInfo.AppendText(Environment.NewLine);
47             richTextBoxTalkInfo.ScrollToCaret();            
48             textBoxSend.Text = "";
49             textBoxSend.Focus();
50         }
51 
52         //数据发送函数
53         private void SendMessage(object obj)
54         {
55             string message = (string)obj;
56             byte[] sendbytes = Encoding.Unicode.GetBytes(message);
57             sendUdpClient.Send(sendbytes, sendbytes.Length, peerUserIPEndPoint);
58             sendUdpClient.Close();
59         }
60 
61         //显示通话内容
62         public void ShowTalkInfo(string peerName, string time, string content)
63         {
64             richTextBoxTalkInfo.AppendText(peerName + "" + time + Environment.NewLine + content);
65             richTextBoxTalkInfo.AppendText(Environment.NewLine);
66             richTextBoxTalkInfo.ScrollToCaret();
67         }
68 
69         //当点击关闭时的事件处理程序
70         private void buttonClose_Click(object sender, EventArgs e)
71         {
72             this.Close();
73         }
74     }
75 }


标签:string,通信,Text,用户,private,即时,聊天工具,splitString,new
From: https://blog.51cto.com/u_2650279/6430902

相关文章

  • 几种跨进程通信的机制
    概述由于不同的进程在运行过程中处于不同的用户空间,无法相互感知,因此就诞生IPC;信息的传播需要介质,几种跨进程通信的机制就是使用了不同的介质,由于介质的不同,所以传输的方式,传输的频率、传输的数据和适用范围都有不同;文件放在物理磁盘上的文件作为不同进程都能访问到的东西,可以......
  • 通过redis学网络(1)-用go基于epoll实现最简单网络通信框架
    本系列主要是为了对redis的网络模型进行学习,我会用golang实现一个reactor网络模型,并实现对redis协议的解析。系列源码已经上传githubhttps://github.com/HobbyBear/tinyredis/tree/chapter1redis的网络模型是基于epoll实现的,所以这一节让我们先基于epoll,实现一个最简单的服......
  • 进程间的通信
    进程间的通信1.进程的简单通信既然是进程间的通信,那至少得具备两方,根据信息的流动,可以分为发送方和接收方,在网络通信中也有客户端,服务端之称,我们只需要笼统地理解这种设计观念就行通信的方式有什么??单向双向(还有半双工)同步(会阻塞,有前置条件,一问一答)异步(发......
  • Vue——计算属性、监听属性、Vue生命周期、组件介绍和使用、组件间通信、ref属性
    计算属性//1计算属性是基于他们的依赖变量进行缓存的//2计算属性只有在它的相关依赖变量发生改变时才会重新求值,否则不会变(函数只要页面变化,就会重新运算)//3计算属性就像python中的property装饰器,可以把方法/函数伪装成属性//4计算属性,必须有返回值<body><divid......
  • flink源码分析--RPC通信过程分析
    flink的通信框架基于akka,但是不懂akka也关系不大。首先介绍几个概念,大家记住名字和对应的作用:xxxGateway:在flink中就是一个用来告诉调用者,xxx具有哪些方法可以调用的一个接口类。比如JobMasterGateway就是用来告诉所有需要调用JobMaster的用户,我JobMaster类只有比如10个方法,假设......
  • KingbaseES V8R6集群运维系列 -- 修改ssh通信为 sys_securecmdd 通信
    一、适用于:本文档使用于KingbaseESV008R006版本。二、关于SYS_SECURECMDD:sys_securecmdd是KingbaseES集群自带的工具,集群监控、管理集群时通过sys_securecmdd安全执行命令而不使用ssh服务。sys_securecmdd主要包含以下文件:服务端sys_securecmdd默认监听8890端口,接受客......
  • 单片机+WiFi模块和主流物联网平台实现MQTT协议通信视频教程
    单片机+WiFi模块和主流物联网平台实现MQTT协议通信视频教程1、单片机+WiFi模块和阿里云物联网平台实现MQTT协议通信视频教程单片机+WiFi模块和阿里云物联网平台实现MQTT协议通信,阿里云物联网平台可以对单片机数字量输出、保持寄存器进行设置操作,单片机可以实时上报数字量输入、数......
  • 8种品牌PLC单片机实现自由格式协议串口通信主站视频教程
    8种品牌PLC单片机实现自由格式协议串口通信主站视频教程一、罗克韦尔ABMicro850​系列PLC实现自由格式协议串口通信主站视频教程:罗克韦尔ABMicro850系列PLC做ASCII串口通信主站、串口调试助手做从站,程序实现PLC和串口调试助手相互发送和接收8个字节数据功能,视频详细讲解了ASCII......
  • 8种品牌PLC单片机实现自由格式协议串口通信从站视频教程
    8种品牌PLC单片机实现自由格式协议串口通信从站视频教程一、罗克韦尔ABMicro850​系列PLC实现自由格式协议串口通信从站视频教程:罗克韦尔ABMicro850系列PLC做ASCII串口通信从站、串口调试助手做主站,程序实现PLC和串口调试助手相互发送和接收8个字节数据功能,视频详细讲解了ASCII......
  • 系统化学习前端之Vue(vue2 组件通信)
    前言前文vue2基础中聊过,页面本质是DOM树,而在vue2中组件=vm实例对象=DOM。因此,页面其实也是组件树构成,组件之间形成父子关系,兄弟关系等,相互之间通信也是组件树的必须要求。vue2组件通信组件通信即组件之间的数据传递。props和$emit$attrs和$listener$parent......