代码改变世界

网络编程之Socket的TCP协议实现客户端与客户端之间的通信

2015-05-18 15:33  糯米粥  阅读(10693)  评论(12编辑  收藏  举报

     我认为当你学完某个知识点后,最好是做一个实实在在的小案例。这样才能更好对知识的运用与掌握

如果你看了我前两篇关于socket通信原理的入门文章。我相信对于做出我这个小案列是完全没有问题的!!

既然是小案列。就难免会有很多不足,所以还请大家见谅。先说下用此案例实现的功能

利用Socke的TCP协议实现了

1:端与端之间的通信(客户端和客户端)包括发送文字,小文件,抖动效果

2:同步实现好友下线与上线的效果,即有其他客户端上线会通知其他已经在线的客户端

3:实现自定义通信协议

 

服务器窗体布局

 

 

 

 

 

布局很明了。顶部一个DataGridView显示连接的客户端信息,来一个就新增一个。走一个就干掉一个。

下面就是显示服务器信息

客户端布局:

窗口布局是不是很大众呢。QQ啊。飞秋。类似的布局。

好了。现在启动服务器。连接两个客户端试试

服务器已经获取了客户端信息

 

客服端用户列表是空的?因为这里我的处理是不能跟自己聊天所以列表是不显示自己的ip的

那么在登陆一个客户端呢?

服务器


此时客户端都已经获取了对方上线的ip

 

此时就可以通信了。那来试试上面说的功能。发送文字,抖动,和图片

 

发送文字:

 

 

箭头的方向是发送的开始,反过来发送也是可以的

 

当有人下线,客户端会更新 "用户列表",同时服务器也会端口对下线者的通信

比如:当端口号为:6477下线。即端口连接

 

 

 

刚下线一个客户端。所以现在登陆一个客户端

发送文件: 这里仅仅是小文件。显示发送一个图片

当选择文件文件后。文件路径会显示在框中

从图中可以看出。是一个图片。名字是 2

单击发送。接收的用户会提示是否接受该文件。

 

当单击是的时候。则弹出保存对话框

 

从图片中可以看出来,这里获取到了文件名称。那么客户端是怎么获取到的呢?在下面的代码中会一一讲解

现在重新命名 new.jpg保存看看。已经成功了

 

抖动效果

为了让大家看到抖动效果。这里放一个gif图片。其实跟QQ抖动类似。当然没他那么优美。那么好看。

 

 

看看一个通信的流程图

 

 

图片中红色的标记框就是

首先:我第一次登陆成功,服务器发送在线好友给我,显示在用户列表中

同时通知其他在线用户。用人(我)上线了。

当然。当有人上线。服务器也会通知我。然后把上线的ip添加到用列表中

当我下线的时候。通知服务器我下线了。服务器则通知其他在线用户。更新列表

发送消息也是先发送到服务器。然后服务器在转发。

 

好了。具体功能就是这些了。带着这些实现的功能读下面的代码。思绪应该会比较清晰

 

还记得创建服务器的三个步骤吗?

第一:创建监听的socket

第二:绑定监听用个的ip和端口号

第三:开始监听

 

因为监听和获取消息都是要循环的。要么用多线程。要么用socket异步。所以这两个方法要独立出来

创建StartServer()方法。用来开启服务器

 1 /// <summary>
 2         /// 打开服务器
 3         /// </summary>
 4         void StartServer()
 5         {
 6             try
 7             {
 8                 string _ip = tbIp.Text;
 9                 int _point = int.Parse(tbPoint.Text);
10 
11                 //创建监听客户端请求的socket
12                 Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
13                 //监听用的ip和端口
14                 IPAddress address = IPAddress.Parse(_ip);
15                 IPEndPoint point = new IPEndPoint(address, _point);
16 
17                 //绑定
18                 socket.Bind(point);
19                 socket.Listen(10);
20 
21 
22                 //异步 开始监听
23                 socket.BeginAccept(new AsyncCallback(Listen), socket);
24 
25 
26                 //禁用当前按钮
27                 btnStart.Enabled = false;
28 
29                 //启动时间
30                 startTime.Text = DateTime.Now.ToString();
31 
32                 //底部提示消息
33                 tssMsg.Text = "服务器已经启动";
34             }
35             catch (Exception ex)
36             {
37                 MessageBox.Show(ex.Message);
38             }
39 
40         }

从代码可以看出来。我用到了异步通信 BeginAccept方法 回掉方法是Listen

Listen方法即上面说的要循环监听的代码

 

Listen方法

 1 /// <summary>
 2         /// 开始监听
 3         /// </summary>
 4         /// <param name="result"></param>
 5         void Listen(IAsyncResult result)
 6         {
 7 
 8             try
 9             {
10                 //获取监听的socket
11                 Socket clientSocket = result.AsyncState as Socket;
12                 //与服务器通信的socket
13                 Socket connSocket = clientSocket.EndAccept(result);
14 
15                 string ip = connSocket.RemoteEndPoint.ToString();
16                 //连接成功。保存信息
17                 if (!Common.connSocket.ContainsKey(ip))
18                     Common.connSocket.Add(ip, connSocket);
19 
20                 //连接成功,更新服务器信息
21                 changeList(connSocket);
22 
23 
24                 //等待新的客户端连接 ,相当于循环调用
25                 clientSocket.BeginAccept(new AsyncCallback(Listen), clientSocket);
26 
27                 byte[] buffer = new byte[1024 * 1024];
28 
29 
30                 //接收来自客户端信息 ,相当于循环调用
31                 connSocket.BeginReceive(Common.ReceiveBuffer, 0, Common.ReceiveBuffer.Length, 0, new AsyncCallback(Receive), connSocket);
32 
33                 //用户第一次登陆。获取所有在线用户  如果有好友功能。则获取他的好友
34                 SendesClient(connSocket);
35             }
36             catch (Exception ex)
37             {
38 
39                 //MessageBox.Show(ex.Message);
40             }
41 
42 
43         }

如果你在想为什么Listen方法要这样定义:

 void Listen(IAsyncResult result)

你可以去看下多线程,异步委托方面的知识

我博客也有这方面的入门:http://www.cnblogs.com/nsky/p/4425286.html

这里把连接的客服端保存在字典中

key:当前的ip value :当前的socke通信

//连接成功。保存信息
if (!Common.connSocket.ContainsKey(ip))
        Common.connSocket.Add(ip, connSocket);

 

 

当客户端连接。就获取所有在线的ip

//用户第一次登陆。获取所有在线用户  如果有好友功能。则获取他的好友
  SendesClient(connSocket);

 

SendesClient方法

 1 /// <summary>
 2         /// 把上线的人发送到客户端
 3         /// </summary>
 4         /// <param name="connSocket">当前连接的客户端</param>
 5         void SendesClient(Socket connSocket)
 6         {
 7             //自定义协议:[命令 2位]
 8             /*
 9              * 第一位:10代表是首次登陆获取所有好友,把自己的ip放最后一位
10              * 好像这里默认已经是最后一位了??
11              */
12             string key = connSocket.RemoteEndPoint.ToString();
13             //把自己的ip删除
14             if (Common.connSocket.ContainsKey(key))
15                 Common.connSocket.Remove(key);
16 
17             //把自己的key添加到最后一位
18             if (!Common.connSocket.ContainsKey(key))
19                 Common.connSocket.Add(key, connSocket);
20 
21             //发送到客户端
22             byte[] clientByte = Encoding.UTF8.GetBytes(string.Join(",", Common.connSocket.Keys));
23 
24 
25             //List<byte> bbb = new List<byte>();
26             //bbb.Add(1);
27             //bbb.AddRange(clientByte);
28 
29             List<byte> li = clientByte.ToList();
30             li.Insert(0, 10);//第一位插入10 代表是获取好友
31 
32             //把当前在线ip发送给自己
33             connSocket.Send(li.ToArray());
34 
35             //告诉其他在线的用户。我上线啦,求勾搭
36             //var online = from onn in Common.connSocket
37             //             where !onn.Key.Contains(connSocket.RemoteEndPoint.ToString())  //筛选,不包含自己的ip
38             //             select onn;
39 
40             //if (online.Count() <= 0) return; //当前没有上线的
41 
42             foreach (KeyValuePair<string, Socket> item in Common.connSocket)
43             {
44                 //不需要给自己发送。因为当自己上线的时候。就已经获取了在线的ip
45                 if (item.Key == connSocket.RemoteEndPoint.ToString()) continue;
46                 //多线程通知在线用户。
47                 Thread thread = new Thread(() =>
48                 {
49                     byte[] buffer = Encoding.UTF8.GetBytes(connSocket.RemoteEndPoint.ToString());
50                     List<byte> list = buffer.ToList();
51                     //有人上线
52                     //[命令(12)| ip(上线的ip)| ...]
53                     list.Insert(0, 12);//说明有人上线
54                     item.Value.Send(list.ToArray());
55                 });
56                 thread.IsBackground = true;
57                 thread.Start();
58             }
59 
60             //判断当前用户是否还处于连接状态
61             //if (Common.connSocket.ContainsKey(connSocket.RemoteEndPoint.ToString()))
62             //connSocket.Send(buffer);
63 
64             //有人下线。就通知所有在线的人数
65             //如果是qq我想应该是通知我的好友。不是知道是不是
66 
67             /*
68              * 如果在线有 A B C 
69              * 那么A下线
70              * 是不是要通知B C
71              * 还是在B C 定时访问服务器来获取在线人数呢?
72              * 待解决
73              */
74         }

 

 

那么Listen方法是怎么循环监听的呢?其实就是在Listen里面循环调用自己

 //等待新的客户端连接 ,相当于循环调用
  clientSocket.BeginAccept(new AsyncCallback(Listen), clientSocket);

 

 

监听有客服端连接成功。就开始接受客户端信息

1  //接收来自客户端信息 ,相当于循环调用
2   connSocket.BeginReceive(Common.ReceiveBuffer, 0, Common.ReceiveBuffer.Length, 0, new AsyncCallback(Receive), connSocket);

Common.ReceiveBuffer是什么??

这是我把公共的数据封装在了一个Common类里面。下面会给出

 

循环监听有了。那么写一个循环接收消息的也就很简单了。依葫芦画瓢

Receive方法:

 1  /// <summary>
 2         /// 接收来自客户端信息
 3         /// </summary>
 4         /// <param name="result"></param>
 5         void Receive(IAsyncResult result)
 6         {
 7 
 8             //与客户端通信的socket
 9             Socket clientSocket = result.AsyncState as Socket;
10 
11             try
12             {
13                 //获取实际的长度值
14                 int num = clientSocket.EndReceive(result);
15                 if (num > 0)
16                 {
17                     byte[] data = new byte[num];
18                     //复制实际的长度到data字节数组中
19                     Array.Copy(Common.ReceiveBuffer, 0, data, 0, num);
20 
21                     //判断协议位
22                     int command = data[0];
23 
24                     //获取内容
25                     string source = Encoding.UTF8.GetString(data, 1, num - 1);
26 
27                     //获取接收者的ip
28                     string receiveIp = source.Split(',')[0];
29 
30 
31                     if (command == 1) //说明发送的是文字
32                     {
33                         /*协议:
34                          * //[命令(1)|对方的ip和自己的ip 50位)| 内容(文字) | ...]
35                          */
36                         //获取接收者通信连接的socket
37                         if (Common.connSocket.ContainsKey(receiveIp))
38                         {
39                             Common.connSocket[receiveIp].Send(data);
40                         }
41                     }
42                     else if (command == 0) //说明是发送的文件
43                     {
44                         /*协议: 这里50位不知道是否理想。
45                          * [命令(0)| ip(对方的ip和自己的ip 50位)| 内容(文件大小和文件全名 30位)|响应(文件内容) | ...]
46                          */
47 
48                         //获取接收者通信连接的socket
49                         if (Common.connSocket.ContainsKey(receiveIp))
50                         {
51                             Common.connSocket[receiveIp].Send(data);
52                         }
53                     }
54                     else if (command == 2)//抖动一下
55                     {
56                         //协议
57                         //震动
58                         //[命令(2)| 对方的ip和自己的ip 50位| ...]
59 
60                         //获取接收者通信连接的socket
61                         if (Common.connSocket.ContainsKey(receiveIp))
62                         {
63                             Common.connSocket[receiveIp].Send(data);
64                         }
65                     }
66 
67                     //string msg = Encoding.UTF8.GetString(data);
68                     //MessageBox.Show(msg);
69 
70 
71                     //接收其他信息
72                     clientSocket.BeginReceive(Common.ReceiveBuffer, 0, Common.ReceiveBuffer.Length, 0, new AsyncCallback(Receive), clientSocket);
73 
74                 }
75                 else //客户端断开
76                 {
77                     clientOff(clientSocket);
78                 }
79             }
80             catch (Exception ex)
81             {
82                 clientOff(clientSocket);
83             }
84 
85         }

 

当客户端端口会执行clientOff方法。用来通知其他用户。更新列表

clientOff方法

 1  /// <summary>
 2         /// 客户端关闭
 3         /// </summary>
 4         void clientOff(Socket clientSocket)
 5         {
 6             //从集合删除下线的ip
 7             string outIp = clientSocket.RemoteEndPoint.ToString();
 8             if (Common.connSocket.ContainsKey(outIp))
 9                 Common.connSocket.Remove(outIp);
10 
11             //更新服务器在线人数
12             changOnlineCount(false);
13 
14             this.Invoke(new Action(() =>
15             {
16                 //更新列表
17                 //删除退出的ip
18                 for (int i = 0; i < dgvList.Rows.Count; i++)
19                 {
20                     if (dgvList.Rows[i].Tag.ToString() == outIp)
21                     {
22                         dgvList.Rows.RemoveAt(i);
23                         break;
24                     }
25                 }
26             }));
27 
28             clientSocket.Shutdown(SocketShutdown.Receive);
29             clientSocket.Close();
30 
31             //通知所有在线用户。有人下线了。需要更新列表,如果是qq是通知我的好友。不知道是不是这样
32             /*这里有点疑问:
33              * 是客户端定时到服务器获取在线用户?
34              * 还是服务器通知客户端
35              
36              */
37 
38             //有人下线 协议
39             //[命令(11)| ip(下线的ip)| ...]
40 
41             //我这里通知客户端吧
42             foreach (KeyValuePair<string, Socket> item in Common.connSocket)
43             {
44                 Thread thread = new Thread(() =>
45                 {
46                     byte[] buffer = Encoding.UTF8.GetBytes(outIp);
47                     List<byte> list = buffer.ToList();
48                     list.Insert(0, 11);//添加协议位
49                     item.Value.Send(list.ToArray());
50                 });
51                 thread.IsBackground = true;
52                 thread.Start();
53 
54 
55                 //多线程。通知每个在线用户。更新列表
56                 //ThreadPool.QueueUserWorkItem(new WaitCallback(new Action<object>((o) =>
57                 //{
58                 //    string result = string.Join(",", Common.connSocket.Keys);
59                 //    byte[] buffer = Encoding.UTF8.GetBytes(result);
60                 //    try
61                 //    {
62                 //        //客户端关闭,则发送报异常
63                 //        item.Value.Send(buffer);
64                 //    }
65                 //    catch (Exception ex)
66                 //    {
67 
68                 //    }
69                 //})));
70                 //string result = string.Join(",", Common.connSocket.Keys);
71                 //byte[] buffer = Encoding.UTF8.GetBytes(result);
72                 //item.Value.Send(buffer);
73 
74             }
75         }

 

有没有想过。服务器是怎么知道客户端发送的是文字。文件还是抖动呢?

这里就要提到自定义协议了。双方约定好。比如:

客户端发送0 就代表是文字。 发送1 则代表是文件 2 则代表是抖动。

 

我这里自定义的协议如下。不能说很好。只能说马马虎虎过得去

 1 /*********************协议说明***********************/
 2         //根据协议解码
 3         /*
 4          * 自定义协议规则
 5          *  [ 命令(1) | 内容(30) | ip(22)| 响应(....) | ...]
 6            命令:0-文件 1-文字 2-震动
 7          * 内容:
 8          * 文件(长度,文件名字+后缀名) 响应(文件的字节)
 9          * 文字(内容)
10          * 震动()
11          * ip(自己的ip和对方的ip)
12          * 
13          * [ 命令(1) | ip(22) | 内容(30)| 响应(....) | ...]
14          * 
15          * 文件:
16          *  [命令(0)| ip(自己的ip和对方的ip)| 内容(文件大小和文件全名)|响应(文件内容) | ...]
17          * 文字:
18          *  [命令(1)|对方的ip和自己的ip 50位)| 内容(文字) | ...]
19          * 震动
20          *  [命令(2)| ip(对方的ip)| ...]
21          * 更新在线人数
22          * [命令(3)| ip(自己的ip和对方的ip)| ...]
23          * 第一次登陆获取在线(好友)人数
24          * [命令(10)| ip(自己的ip和所有的ip)| ...]
25          * 有人下线
26          *  [命令(11)| ip(下线的ip)| ...]
27          * 有人上线
28          *  [命令(12)| ip(上线的ip)| ...]
29          *  0
30          *  1
31          *  2
32          *  3
33          *  4
34          *  5
35          * 
36          */

 

既然协议有了。那么就可以根据协议发送数据包。服务器和其他客户端就可以根据协议解包。

这里来说下发送文件的协议

从上面可以看到我定义的文件协议:

[命令(0)| ip(自己的ip和对方的ip)| 内容(文件大小和文件全名)|响应(文件内容) | ...]

 

先贴上发送文件的代码(客户端发送)。然后根据我的思路来分析

客户端发送文件代码

 1 /// <summary>
 2         /// 发送文件
 3         /// </summary>
 4         /// <param name="sender"></param>
 5         /// <param name="e"></param>
 6         void btnSendFile_Click(object sender, EventArgs e)
 7         {
 8             //判断是否有选择用户
 9             string sendUser = cbList.Text;
10 
11             if (string.IsNullOrEmpty(sendUser))
12             {
13                 //提示
14                 tbHis.AppendText("请选择要发送的用户...\n");
15                 return;
16             }
17             //判断是否选择了文件
18             else if (string.IsNullOrEmpty(tbFile.Text))
19             {
20                 tbHis.AppendText("请选择文件\n");
21                 tbHis.AppendText("\n");
22                 return;
23             }
24             //开始读取文件
25             using (FileStream fs = new FileStream(tbFile.Text, FileMode.Open, FileAccess.Read))
26             {
27                 //大文件会内存溢出
28                 //引发类型为“System.OutOfMemoryException”的异常。
29                 //所以大文件只能 续传。像QQ一样在线接收的方式。
30                 byte[] buffer = new byte[fs.Length];
31                 //获取实际的字节数,如果有需要的话。
32                 int num = fs.Read(buffer, 0, buffer.Length);
33 
34                 /*协议: 这里50位不知道是否理想??
35                  * 是不是可以修改为:第一位 协议 第二位标记ip的长度 第三位标记内容的长度??
36                  * [命令(0)| ip(对方的ip和自己的ip 50位)| 内容(文件大小和文件全名 30)|响应(文件内容) | ...]
37                  */
38                 string allIp = string.Format("{0},{1}", cbList.Text, myIp.Text);
39                 byte[] sendIp = Encoding.UTF8.GetBytes(allIp);
40 
41                 List<byte> list = sendIp.ToList();
42 
43                 //sendIp 不够50位
44                 if (sendIp.Length < 50)
45                 {
46                     for (int i = 0; i < 50 - sendIp.Length; i++)
47                     {
48                         list.Add(0);
49                     }
50                 }
51                 list.Insert(0, 0); //添加协议位
52                 //添加内容
53                 byte[] fileByte = Encoding.UTF8.GetBytes(Common.SafeFileName);
54                 list.AddRange(fileByte);
55                 //内容是否够30
56                 if (fileByte.Length < 30)
57                 {
58                     for (int i = 0; i < 30 - fileByte.Length; i++)
59                     {
60                         list.Add(0);
61                     }
62                 }
63                 //添加响应
64                 list.AddRange(buffer);
65 
66                 //开始发送
67                 Common.connSocket.Send(list.ToArray());
68             }
69         }

 

字节数组必须根据你定义的协议来拼接。这样在服务端才好解包

我这里把ip放到了List<byte>中

List<byte> list = sendIp.ToList();

然后在list前面插入协议位:0 ,因为我定义的协议是0代表发送文件

list.Insert(0, 0); //添加协议位

 

是否还记得上面提到的。客户端接收文件。打开保存对话框。自动补全了文件名。是怎么做的吗?

因为协议中发文件名发过去了

//添加内容
byte[] fileByte = Encoding.UTF8.GetBytes(Common.SafeFileName);
list.AddRange(fileByte);

还有因为不知道ip的固定长度。所以我这里限制是50位。不够的话就补加空,不知道这样是否划算

//sendIp 不够50位
  if (sendIp.Length < 50)
   {
     for (int i = 0; i < 50 - sendIp.Length; i++)
       {
            list.Add(0);
       }
  }

所以的数据根据协议打包后。就可以发送到服务器。在服务器解包。获取目的者的ip。开始转发

Receive方法中你可以找到解包的代码,部分如下:

 1                     byte[] data = new byte[num];
 2                     //复制实际的长度到data字节数组中
 3                     Array.Copy(Common.ReceiveBuffer, 0, data, 0, num);
 4 
 5                     //判断协议位
 6                     int command = data[0];
 7 
 8                     //获取内容
 9                     string source = Encoding.UTF8.GetString(data, 1, num - 1);
10 
11                     //获取接收者的ip
12                     string receiveIp = source.Split(',')[0];
13 
14 
15                     if (command == 1) //说明发送的是文字
16                     {
17                         /*协议:
18                          * //[命令(1)|对方的ip和自己的ip 50位)| 内容(文字) | ...]
19                          */
20                         //获取接收者通信连接的socket
21                         if (Common.connSocket.ContainsKey(receiveIp))
22                         {
23                             Common.connSocket[receiveIp].Send(data);
24                         }
25                     }

 

获取协议位:

int command = data[0];

注:

你会发现我这里在服务器解包判断协议毫无意义。因为第一位是命令。而后面接着是ip。这些长度都是固定的。

只有取出来ip发送到指定的客户端。让客户端解包判断协议就是了。没错。因为我这里本来加个功能。如果是文件则把文件缓存起来。所以就判断

了协议的命令。你可以根据自己的需求去做。

 

发送到了客户端,就理所当然的要判断协议位了。如果是抖动就抖一下。是文件。就提示是否接受。等等

看看客户端解包代码:

记住:下面的Receive方法是客户端中的方法。用力接收服务器发来的消息

  1 /// <summary>
  2         /// 接收来自服务器的消息
  3         /// </summary>
  4         /// <param name="result"></param>
  5         void Receive(IAsyncResult result)
  6         {
  7             Socket clientSocket = result.AsyncState as Socket;
  8 
  9             //byte[] b = new byte[1024 * 1024];
 10             try
 11             {
 12                 //获取实际的长度
 13                 int num = clientSocket.EndReceive(result);
 14                 if (num > 0)
 15                 {
 16                     //MessageBox.Show(num.ToString());
 17                     byte[] buffer = new byte[num];
 18                     Array.Copy(Common.buffer, 0, buffer, 0, num); //复制数据到data
 19                     //string ip = Encoding.UTF8.GetString(data);
 20 
 21 
 22                     //以下是客户端=》服务器==》服务器
 23                     /*
 24                      * 当if else 超过3个
 25                      * 
 26                      * 建议用switch case语句
 27                      */
 28 
 29                     //获取口令
 30                     int command = buffer[0];
 31                     //说明是获取好友
 32                     if (command == 10)
 33                     {
 34                         //协议说明
 35                         //第一次登陆获取在线(好友)人数
 36                         //[命令(10)| ip(自己的ip和所有好友的ip)| ...]
 37 
 38 
 39                         //其实用户本地ip也可以这样获取
 40                         //string cy = clientSocket.LocalEndPoint;
 41 
 42                         string allIp = Encoding.UTF8.GetString(buffer, 1, num - 1);
 43                         string[] temp = allIp.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
 44 
 45 
 46                         //跨线程操作UI
 47                         this.Invoke(new Action(() =>
 48                         {
 49                             myIp.Text = temp.Length > 0 ? temp[temp.Length - 1] : "";
 50 
 51                             //排除自己的ip
 52                             var other = from i in temp where !i.Contains(myIp.Text) select i;
 53 
 54                             cbList.Items.Clear();//清空
 55                             cbList.Items.AddRange(other.ToArray()); //绑定列表
 56 
 57                             if (cbList.Items.Count > 0)
 58                                 cbList.SelectedIndex = 0;//默认选中第一个
 59                         }));
 60 
 61                     }
 62                     else if (command == 11) //说明是有人下线
 63                     {
 64                         //协议说明
 65                         // 有人下线
 66                         //[命令(11)| ip(下线的ip)| ...]
 67 
 68                         //获取下线的ip
 69                         string outIp = Encoding.UTF8.GetString(buffer, 1, num - 1);
 70 
 71                         this.Invoke(new Action(() =>
 72                         {
 73                             //删除下线的ip
 74                             cbList.Items.Remove(outIp);
 75                             if (cbList.Items.Count > 0)
 76                                 cbList.SelectedIndex = 0;//默认选中第一个
 77                         }));
 78                     }
 79                     else if (command == 12) //有人上线了
 80                     {
 81                         //协议说明
 82                         // 有人上线
 83                         //[命令(12)| ip(上线的ip)| ...]
 84 
 85                         //获取上线的ip
 86                         string onlineIp = Encoding.UTF8.GetString(buffer, 1, num - 1);
 87                         //添加上线的ip
 88 
 89                         this.Invoke(new Action(() =>
 90                         {
 91                             //添加上线的ip
 92                             cbList.Items.Add(onlineIp);
 93                             if (cbList.Items.Count > 0)
 94                                 cbList.SelectedIndex = 0;//默认选中第一个
 95                         }));
 96                     }
 97 
 98                     //以下是客户端=》服务器==》客户端
 99 
100                     else if (command == 1)
101                     {
102                         //协议:
103                         //[命令(1)|对方的ip和自己的ip 50位)| 内容(文字) | ...]
104 
105                         //获取ip段
106                         string[] sourceIp = Encoding.UTF8.GetString(buffer, 1, 50).Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
107 
108                         //发消息来的ip
109                         string fromIp = sourceIp[1];
110 
111 
112 
113                         //获取内容
114                         string content = Encoding.UTF8.GetString(buffer, 50 + 1, num - 50 - 1);
115 
116                         this.Invoke(new Action(() =>
117                         {
118                             //列表框中选择当前的ip
119                             cbList.Text = fromIp.ToString();
120 
121                             //显示内容
122                             tbHis.AppendText(string.Format("时间:{0}\n", DateTime.Now.ToString()));
123                             //tbHis.AppendText(string.Format("提示{0}对我说:", fromIp)); //我操。这样怎么就不行
124                             tbHis.AppendText(fromIp + "\n");
125                             tbHis.AppendText("对你说:\n");
126                             tbHis.AppendText(content + "\n");
127                             tbHis.AppendText("\n");
128                         }));
129                     }
130                     else if (command == 0) //发送的文件
131                     {
132                         /*协议: 这里50位不知道是否理想。
133                          * [命令(0)| ip(对方的ip和自己的ip 50位)| 内容(文件大小和文件全名 30位)|响应(文件内容) | ...]
134                          */
135 
136                         //这里有冗余代码
137 
138                         //获取ip段
139                         string[] sourceIp = Encoding.UTF8.GetString(buffer, 1, 50).Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
140 
141                         //发消息来的ip
142                         string fromIp = sourceIp[1];
143 
144                         this.Invoke(new Action(() =>
145                         {
146                             //列表框中选择当前的ip
147                             cbList.Text = fromIp.ToString();
148                         }));
149 
150 
151                         //获取内容
152                         string content = Encoding.UTF8.GetString(buffer, 50 + 1, 30);
153 
154                         //获取响应
155                         //string pass = Encoding.UTF8.GetString(buffer, 50 + 30 + 1, num - 50 - 30 - 1);
156 
157                         //显示
158                         //tbHis.AppendText(string.Format("{0}给你发了一个文件:{1}\n", fromIp, content));
159                         tbHis.AppendText(fromIp);
160                         tbHis.AppendText("给你发了一个文件");
161                         tbHis.AppendText(content + "\n");
162                         tbHis.AppendText("\n");
163 
164 
165                         //提示用户是否接收文件
166                         if (MessageBox.Show("是否接受文件\n" + content, "接收文件", MessageBoxButtons.YesNo) == DialogResult.Yes)
167                         {
168                             //开始保存
169                             SaveFileDialog sfd = new SaveFileDialog();
170                             sfd.FileName = content;
171 
172                             //获取文件类型
173                             string ex = content.Split('.')[1];
174 
175                             //保存文件类型
176                             sfd.Filter = "|*." + ex;
177                             if (sfd.ShowDialog(this) == DialogResult.OK)
178                             {
179                                 using (FileStream fs = new FileStream(sfd.FileName, FileMode.Create))
180                                 {
181                                     fs.Write(buffer, 50 + 30 + 1, num - 50 - 30 - 1);
182                                 }
183                             }
184 
185                         }
186 
187                     }
188                     else if (command == 2) //发送抖动
189                     {
190                         //如果窗口在任务栏。则显示
191                         this.Show();
192                         this.WindowState = FormWindowState.Normal;
193                         this.Activate();
194 
195                         int n = -1;
196 
197                         for (int i = 0; i < 10; i++)
198                         {
199                             n = -n;
200                             this.Location = new Point(this.Location.X + 10 * n, this.Location.Y + 10 * n);
201                             this.TopMost = true;//在所有窗口的顶部
202                             System.Threading.Thread.Sleep(50);
203                         }
204 
205                         //抖动完成。结束顶层显示
206                         this.TopMost = false;
207                     }
208 
209                     //连接成功,再一次获取服务器发来的消息
210                     clientSocket.BeginReceive(Common.buffer, 0, Common.buffer.Length, 0, new AsyncCallback(Receive), clientSocket);
211 
212                 }
213             }
214             catch (Exception ex) //服务器端口
215             {
216                 MessageBox.Show(ex.Message);
217                 clientSocket.Shutdown(SocketShutdown.Receive);
218                 clientSocket.Close();
219             }
220         }

 

 

都是if else if判断的。着实看着会晕乎乎的。因为是小案列。就没去过多的封装。大家可以根据自己的喜好去封装

上面大部分都是讲的服务端口的代码。相比客户端就是一样的。只是少了一个监听的代码。直接连接服务器。接收和发送消息就ok了

这里就不分析了。稍后直接贴上参考代码

当然socket通信协议方面的知识不单单就我这点皮毛。大家自己可以深入研究。

比如:很多网站右下角都会有这样一个窗口,大家都见过吧。

 

我想这也是相同的原理。当打开浏览器。ajax异步去连接服务器,

获取消息发送到客户端。你说呢?

 

因为想做个打飞机的游戏。其实网上也有很多源码,可我认为别人的终究是别人的。只有自己做过那才叫曾经拥有。

可单机版的太没有挑战性,那就实现一个多人在线一起玩的。就涉及到了服务器

所以想用C#写一个服务器,unity3d做客户端,与之通信。所以才有了这篇博文的诞生。

 

最后把全部源码给大家参考下

服务端源码:

  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 using System.Net;
 10 using System.Net.Sockets;
 11 using System.Threading;
 12 
 13 namespace TopServer
 14 {
 15     public partial class mainServer : Form
 16     {
 17 
 18         /*********************协议说明***********************/
 19         //根据协议解码
 20         /*
 21          * 自定义协议规则
 22          *  [ 命令(1) | 内容(30) | ip(22)| 响应(....) | ...]
 23            命令:0-文件 1-文字 2-震动
 24          * 内容:
 25          * 文件(长度,文件名字+后缀名) 响应(文件的字节)
 26          * 文字(内容)
 27          * 震动()
 28          * ip(自己的ip和对方的ip)
 29          * 
 30          * [ 命令(1) | ip(22) | 内容(30)| 响应(....) | ...]
 31          * 
 32          * 文件:
 33          *  [命令(0)| ip(自己的ip和对方的ip)| 内容(文件大小和文件全名)|响应(文件内容) | ...]
 34          * 文字:
 35          *  [命令(1)|对方的ip和自己的ip 50位)| 内容(文字) | ...]
 36          * 震动
 37          *  [命令(2)| ip(对方的ip)| ...]
 38          * 更新在线人数
 39          * [命令(3)| ip(自己的ip和对方的ip)| ...]
 40          * 第一次登陆获取在线(好友)人数
 41          * [命令(10)| ip(自己的ip和所有的ip)| ...]
 42          * 有人下线
 43          *  [命令(11)| ip(下线的ip)| ...]
 44          * 有人上线
 45          *  [命令(12)| ip(上线的ip)| ...]
 46          *  0
 47          *  1
 48          *  2
 49          *  3
 50          *  4
 51          *  5
 52          * 
 53          */
 54 
 55         public mainServer()
 56         {
 57             InitializeComponent();
 58             Init();
 59         }
 60 
 61         /// <summary>
 62         /// 初始化datagridview属性
 63         /// </summary>
 64         public void Init()
 65         {
 66 
 67             #region datagridview一些属性设置
 68             dgvList.AllowUserToAddRows = false;
 69             dgvList.AllowUserToDeleteRows = false;
 70             dgvList.AllowUserToResizeColumns = false;
 71             dgvList.AllowUserToResizeRows = false;
 72             dgvList.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
 73             dgvList.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
 74             dgvList.MultiSelect = false;
 75             dgvList.ReadOnly = true;
 76             dgvList.RowHeadersVisible = false;
 77             dgvList.BackgroundColor = Color.White;
 78             dgvList.ScrollBars = ScrollBars.Vertical;
 79             dgvList.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
 80             #endregion
 81 
 82             //窗体加载事件
 83             //this.Load += new EventHandler(mainServer_Load);
 84 
 85             //启动服务器按钮
 86             this.btnStart.Click += new EventHandler(btnStart_Click);
 87 
 88             //关闭服务器按钮
 89             this.btnStop.Click += new EventHandler(btnStop_Click);
 90         }
 91 
 92         /// <summary>
 93         /// 关闭服务器
 94         /// </summary>
 95         /// <param name="sender"></param>
 96         /// <param name="e"></param>
 97         void btnStop_Click(object sender, EventArgs e)
 98         {
 99             if (Common.ListenSocket != null)
100             {
101                 Common.ListenSocket.Close();
102                 Common.ListenSocket = null;
103                 btnStart.Enabled = true;
104                 //底部提示消息
105                 tssMsg.Text = "服务器已经关闭";
106             }
107         }
108 
109         /// <summary>
110         /// 启动服务器
111         /// </summary>
112         /// <param name="sender"></param>
113         /// <param name="e"></param>
114         void btnStart_Click(object sender, EventArgs e)
115         {
116             StartServer();
117         }
118 
119         /// <summary>
120         /// 窗体加载事件
121         /// </summary>
122         /// <param name="sender"></param>
123         /// <param name="e"></param>
124         void mainServer_Load(object sender, EventArgs e)
125         {
126             //启动时间
127             startTime.Text = DateTime.Now.ToString();
128 
129             //启动服务器
130             StartServer();
131         }
132 
133         /// <summary>
134         /// 打开服务器
135         /// </summary>
136         void StartServer()
137         {
138             try
139             {
140                 string _ip = tbIp.Text;
141                 int _point = int.Parse(tbPoint.Text);
142 
143                 //创建监听客户端请求的socket
144                 Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
145                 //监听用的ip和端口
146                 IPAddress address = IPAddress.Parse(_ip);
147                 IPEndPoint point = new IPEndPoint(address, _point);
148 
149                 //绑定
150                 socket.Bind(point);
151                 socket.Listen(10);
152 
153 
154                 //异步 开始监听
155                 socket.BeginAccept(new AsyncCallback(Listen), socket);
156 
157 
158                 //禁用当前按钮
159                 btnStart.Enabled = false;
160 
161                 //启动时间
162                 startTime.Text = DateTime.Now.ToString();
163 
164                 //底部提示消息
165                 tssMsg.Text = "服务器已经启动";
166             }
167             catch (Exception ex)
168             {
169                 MessageBox.Show(ex.Message);
170             }
171 
172         }
173 
174         /// <summary>
175         /// 开始监听
176         /// </summary>
177         /// <param name="result"></param>
178         void Listen(IAsyncResult result)
179         {
180 
181             try
182             {
183                 //获取监听的socket
184                 Socket clientSocket = result.AsyncState as Socket;
185                 //与服务器通信的socket
186                 Socket connSocket = clientSocket.EndAccept(result);
187 
188                 string ip = connSocket.RemoteEndPoint.ToString();
189                 //连接成功。保存信息
190                 if (!Common.connSocket.ContainsKey(ip))
191                     Common.connSocket.Add(ip, connSocket);
192 
193                 //连接成功,更新服务器信息
194                 changeList(connSocket);
195 
196 
197                 //等待新的客户端连接 ,相当于循环调用
198                 clientSocket.BeginAccept(new AsyncCallback(Listen), clientSocket);
199 
200                 byte[] buffer = new byte[1024 * 1024];
201 
202 
203                 //接收来自客户端信息 ,相当于循环调用
204                 connSocket.BeginReceive(Common.ReceiveBuffer, 0, Common.ReceiveBuffer.Length, 0, new AsyncCallback(Receive), connSocket);
205 
206                 //用户第一次登陆。获取所有在线用户  如果有好友功能。则获取他的好友
207                 SendesClient(connSocket);
208             }
209             catch (Exception ex)
210             {
211 
212                 //MessageBox.Show(ex.Message);
213             }
214 
215 
216         }
217         /// <summary>
218         /// 接收来自客户端信息
219         /// </summary>
220         /// <param name="result"></param>
221         void Receive(IAsyncResult result)
222         {
223 
224             //与客户端通信的socket
225             Socket clientSocket = result.AsyncState as Socket;
226 
227             try
228             {
229                 //获取实际的长度值
230                 int num = clientSocket.EndReceive(result);
231                 if (num > 0)
232                 {
233                     byte[] data = new byte[num];
234                     //复制实际的长度到data字节数组中
235                     Array.Copy(Common.ReceiveBuffer, 0, data, 0, num);
236 
237                     //判断协议位
238                     int command = data[0];
239 
240                     //获取内容
241                     string source = Encoding.UTF8.GetString(data, 1, num - 1);
242 
243                     //获取接收者的ip
244                     string receiveIp = source.Split(',')[0];
245 
246 
247                     if (command == 1) //说明发送的是文字
248                     {
249                         /*协议:
250                          * //[命令(1)|对方的ip和自己的ip 50位)| 内容(文字) | ...]
251                          */
252                         //获取接收者通信连接的socket
253                         if (Common.connSocket.ContainsKey(receiveIp))
254                         {
255                             Common.connSocket[receiveIp].Send(data);
256                         }
257                     }
258                     else if (command == 0) //说明是发送的文件
259                     {
260                         /*协议: 这里50位不知道是否理想。
261                          * [命令(0)| ip(对方的ip和自己的ip 50位)| 内容(文件大小和文件全名 30位)|响应(文件内容) | ...]
262                          */
263 
264                         //获取接收者通信连接的socket
265                         if (Common.connSocket.ContainsKey(receiveIp))
266                         {
267                             Common.connSocket[receiveIp].Send(data);
268                         }
269                     }
270                     else if (command == 2)//抖动一下
271                     {
272                         //协议
273                         //震动
274                         //[命令(2)| 对方的ip和自己的ip 50位| ...]
275 
276                         //获取接收者通信连接的socket
277                         if (Common.connSocket.ContainsKey(receiveIp))
278                         {
279                             Common.connSocket[receiveIp].Send(data);
280                         }
281                     }
282 
283                     //string msg = Encoding.UTF8.GetString(data);
284                     //MessageBox.Show(msg);
285 
286 
287                     //接收其他信息
288                     clientSocket.BeginReceive(Common.ReceiveBuffer, 0, Common.ReceiveBuffer.Length, 0, new AsyncCallback(Receive), clientSocket);
289 
290                 }
291                 else //客户端断开
292                 {
293                     clientOff(clientSocket);
294                 }
295             }
296             catch (Exception ex)
297             {
298                 clientOff(clientSocket);
299             }
300 
301         }
302 
303         /// <summary>
304         /// 客户端关闭
305         /// </summary>
306         void clientOff(Socket clientSocket)
307         {
308             //从集合删除下线的ip
309             string outIp = clientSocket.RemoteEndPoint.ToString();
310             if (Common.connSocket.ContainsKey(outIp))
311                 Common.connSocket.Remove(outIp);
312 
313             //更新服务器在线人数
314             changOnlineCount(false);
315 
316             this.Invoke(new Action(() =>
317             {
318                 //更新列表
319                 //删除退出的ip
320                 for (int i = 0; i < dgvList.Rows.Count; i++)
321                 {
322                     if (dgvList.Rows[i].Tag.ToString() == outIp)
323                     {
324                         dgvList.Rows.RemoveAt(i);
325                         break;
326                     }
327                 }
328             }));
329 
330             clientSocket.Shutdown(SocketShutdown.Receive);
331             clientSocket.Close();
332 
333             //通知所有在线用户。有人下线了。需要更新列表,如果是qq是通知我的好友。不知道是不是这样
334             /*这里有点疑问:
335              * 是客户端定时到服务器获取在线用户?
336              * 还是服务器通知客户端
337              
338              */
339 
340             //有人下线 协议
341             //[命令(11)| ip(下线的ip)| ...]
342 
343             //我这里通知客户端吧
344             foreach (KeyValuePair<string, Socket> item in Common.connSocket)
345             {
346                 Thread thread = new Thread(() =>
347                 {
348                     byte[] buffer = Encoding.UTF8.GetBytes(outIp);
349                     List<byte> list = buffer.ToList();
350                     list.Insert(0, 11);//添加协议位
351                     item.Value.Send(list.ToArray());
352                 });
353                 thread.IsBackground = true;
354                 thread.Start();
355 
356 
357                 //多线程。通知每个在线用户。更新列表
358                 //ThreadPool.QueueUserWorkItem(new WaitCallback(new Action<object>((o) =>
359                 //{
360                 //    string result = string.Join(",", Common.connSocket.Keys);
361                 //    byte[] buffer = Encoding.UTF8.GetBytes(result);
362                 //    try
363                 //    {
364                 //        //客户端关闭,则发送报异常
365                 //        item.Value.Send(buffer);
366                 //    }
367                 //    catch (Exception ex)
368                 //    {
369 
370                 //    }
371                 //})));
372                 //string result = string.Join(",", Common.connSocket.Keys);
373                 //byte[] buffer = Encoding.UTF8.GetBytes(result);
374                 //item.Value.Send(buffer);
375 
376             }
377         }
378         /// <summary>
379         /// 把上线的人发送到客户端
380         /// </summary>
381         /// <param name="connSocket">当前连接的客户端</param>
382         void SendesClient(Socket connSocket)
383         {
384             //自定义协议:[命令 2位]
385             /*
386              * 第一位:10代表是首次登陆获取所有好友,把自己的ip放最后一位
387              * 好像这里默认已经是最后一位了??
388              */
389             string key = connSocket.RemoteEndPoint.ToString();
390             //把自己的ip删除
391             if (Common.connSocket.ContainsKey(key))
392                 Common.connSocket.Remove(key);
393 
394             //把自己的key添加到最后一位
395             if (!Common.connSocket.ContainsKey(key))
396                 Common.connSocket.Add(key, connSocket);
397 
398             //发送到客户端
399             byte[] clientByte = Encoding.UTF8.GetBytes(string.Join(",", Common.connSocket.Keys));
400 
401 
402             //List<byte> bbb = new List<byte>();
403             //bbb.Add(1);
404             //bbb.AddRange(clientByte);
405 
406             List<byte> li = clientByte.ToList();
407             li.Insert(0, 10);//第一位插入10 代表是获取好友
408 
409             //把当前在线ip发送给自己
410             connSocket.Send(li.ToArray());
411 
412             //告诉其他在线的用户。我上线啦,求勾搭
413             //var online = from onn in Common.connSocket
414             //             where !onn.Key.Contains(connSocket.RemoteEndPoint.ToString())  //筛选,不包含自己的ip
415             //             select onn;
416 
417             //if (online.Count() <= 0) return; //当前没有上线的
418 
419             foreach (KeyValuePair<string, Socket> item in Common.connSocket)
420             {
421                 //不需要给自己发送。因为当自己上线的时候。就已经获取了在线的ip
422                 if (item.Key == connSocket.RemoteEndPoint.ToString()) continue;
423                 //多线程通知在线用户。
424                 Thread thread = new Thread(() =>
425                 {
426                     byte[] buffer = Encoding.UTF8.GetBytes(connSocket.RemoteEndPoint.ToString());
427                     List<byte> list = buffer.ToList();
428                     //有人上线
429                     //[命令(12)| ip(上线的ip)| ...]
430                     list.Insert(0, 12);//说明有人上线
431                     item.Value.Send(list.ToArray());
432                 });
433                 thread.IsBackground = true;
434                 thread.Start();
435             }
436 
437             //判断当前用户是否还处于连接状态
438             //if (Common.connSocket.ContainsKey(connSocket.RemoteEndPoint.ToString()))
439             //connSocket.Send(buffer);
440 
441             //有人下线。就通知所有在线的人数
442             //如果是qq我想应该是通知我的好友。不是知道是不是
443 
444             /*
445              * 如果在线有 A B C 
446              * 那么A下线
447              * 是不是要通知B C
448              * 还是在B C 定时访问服务器来获取在线人数呢?
449              * 待解决
450              */
451         }
452 
453         /// <summary>
454         /// 更新列表
455         /// </summary>
456         /// <param name="socket"></param>
457         void changeList(Socket socket)
458         {
459             //获取客户端信息 ip和端口号
460             string ip = socket.RemoteEndPoint.ToString();
461             //客户端登陆时间
462             string time = DateTime.Now.ToString();
463 
464             //跨线程操作ui
465             this.Invoke(new Action(() =>
466             {
467                 //新增一行
468                 dgvList.Rows.Add();
469 
470                 //获取当前dgvList的行
471                 int rows = dgvList.Rows.Count;
472 
473                 //赋值
474                 dgvList.Rows[rows - 1].Cells[0].Value = ip;
475                 dgvList.Rows[rows - 1].Cells[1].Value = time;
476 
477                 //把ip当作当前行的tag标记一下,为了删除行的时候可以找到该行
478                 dgvList.Rows[rows - 1].Tag = ip;
479 
480                 //更新在线人数
481                 //lbCount.Text = int.Parse(lbCount.Text) + 1 + "";//后面加空字符串。转为字符串
482                 //或者
483                 //lbCount.Text = (int.Parse(lbCount.Text) + 1).ToString();
484 
485                 //foreach (DataGridViewRow item in dgvList.Rows)
486                 //{
487                 //   if(item.Tag==ip)item.
488 
489                 //}
490 
491 
492                 //dgvList.DataSource = Common.connSocket;
493 
494                 //更新在线人数
495                 changOnlineCount(true);
496             }));
497 
498         }
499 
500         /// <summary>
501         /// 更新在线人数
502         /// </summary>
503         /// <param name="tag">true=>+ false=>-</param>
504         void changOnlineCount(bool tag)
505         {
506             int num = 0;
507             if (tag) num = int.Parse(lbCount.Text) + 1;
508             else num = int.Parse(lbCount.Text) - 1;
509 
510             this.Invoke(new Action(() =>
511             {
512                 //更新在线人数
513                 lbCount.Text = num.ToString();
514                 if (num == 0) Common.connSocket.Clear();
515 
516             }));
517         }
518     }
519 
520     /// <summary>
521     /// 公共类
522     /// </summary>
523     public class Common
524     {
525         /// <summary>
526         /// 保存服务器来的消息
527         /// </summary>
528         public static byte[] ReceiveBuffer = new byte[1024 * 1024];
529 
530         /// <summary>
531         /// 监听用的socket
532         /// </summary>
533         public static Socket ListenSocket;
534 
535         /// <summary>
536         /// 保存所有负责通信用是socket
537         /// </summary>
538         public static Dictionary<string, Socket> connSocket = new Dictionary<string, Socket>();
539     }
540 }
View Code

 

 

客户端源码:

  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 using System.Net.Sockets;
 10 using System.Net;
 11 using System.Threading;
 12 using System.IO;
 13 
 14 namespace TopClient
 15 {
 16     public partial class UserList : Form
 17     {
 18         public UserList()
 19         {
 20             InitializeComponent();
 21             Init();
 22         }
 23         public void Init()
 24         {
 25             //窗体加载
 26             this.Load += new EventHandler(UserList_Load);
 27 
 28             //发送文字
 29             this.btnSender.Click += new EventHandler(btnSender_Click);
 30 
 31             //选择文件
 32             this.btnChangeFile.Click += new EventHandler(btnChangeFile_Click);
 33 
 34             //发送文件
 35             this.btnSendFile.Click += new EventHandler(btnSendFile_Click);
 36 
 37             //抖动对方
 38             this.btnDd.Click += new EventHandler(btnDd_Click);
 39 
 40             //创建time 
 41             /*
 42              * 用力监听服务器是否开启
 43              */
 44             //Common.time = new System.Windows.Forms.Timer();
 45             //Common.time.Interval = 1000; //如果服务器未开启。则一秒访问一次
 46             //Common.time.Tick += new EventHandler(time_Tick);
 47         }
 48         /// <summary>
 49         /// 发送抖动
 50         /// </summary>
 51         /// <param name="sender"></param>
 52         /// <param name="e"></param>
 53         void btnDd_Click(object sender, EventArgs e)
 54         {
 55             //判断是否有选择用户
 56             string sendUser = cbList.Text;
 57 
 58             if (string.IsNullOrEmpty(sendUser))
 59             {
 60                 //提示
 61                 tbHis.AppendText("请选择要抖动的用户...\n");
 62                 return;
 63             }
 64             //协议
 65             //震动
 66             //[命令(2)| 对方的ip和自己的ip 50位| ...]
 67             string allIp = string.Format("{0},{1}", cbList.Text, myIp.Text);
 68 
 69             byte[] sendIp = Encoding.UTF8.GetBytes(allIp);
 70 
 71             List<byte> list = sendIp.ToList();
 72             list.Insert(0, 2);//添加协议位
 73 
 74             //sendIp 不够50位
 75             if (sendIp.Length < 50)
 76             {
 77                 for (int i = 0; i < 50 - sendIp.Length; i++)
 78                 {
 79                     list.Add(0);
 80                 }
 81             }
 82             //开始发送
 83             Common.connSocket.Send(list.ToArray());
 84         }
 85 
 86         /// <summary>
 87         /// 发送文件
 88         /// </summary>
 89         /// <param name="sender"></param>
 90         /// <param name="e"></param>
 91         void btnSendFile_Click(object sender, EventArgs e)
 92         {
 93             //判断是否有选择用户
 94             string sendUser = cbList.Text;
 95 
 96             if (string.IsNullOrEmpty(sendUser))
 97             {
 98                 //提示
 99                 tbHis.AppendText("请选择要发送的用户...\n");
100                 return;
101             }
102             //判断是否选择了文件
103             else if (string.IsNullOrEmpty(tbFile.Text))
104             {
105                 tbHis.AppendText("请选择文件\n");
106                 tbHis.AppendText("\n");
107                 return;
108             }
109             //开始读取文件
110             using (FileStream fs = new FileStream(tbFile.Text, FileMode.Open, FileAccess.Read))
111             {
112                 //大文件会内存溢出
113                 //引发类型为“System.OutOfMemoryException”的异常。
114                 //所以大文件只能 续传。像QQ一样在线接收的方式。
115                 byte[] buffer = new byte[fs.Length];
116                 //获取实际的字节数,如果有需要的话。
117                 int num = fs.Read(buffer, 0, buffer.Length);
118 
119                 /*协议: 这里50位不知道是否理想??
120                  * 是不是可以修改为:第一位 协议 第二位标记ip的长度 第三位标记内容的长度??
121                  * [命令(0)| ip(对方的ip和自己的ip 50位)| 内容(文件大小和文件全名 30)|响应(文件内容) | ...]
122                  */
123                 string allIp = string.Format("{0},{1}", cbList.Text, myIp.Text);
124                 byte[] sendIp = Encoding.UTF8.GetBytes(allIp);
125 
126                 List<byte> list = sendIp.ToList();
127 
128                 //sendIp 不够50位
129                 if (sendIp.Length < 50)
130                 {
131                     for (int i = 0; i < 50 - sendIp.Length; i++)
132                     {
133                         list.Add(0);
134                     }
135                 }
136                 list.Insert(0, 0); //添加协议位
137                 //添加内容
138                 byte[] fileByte = Encoding.UTF8.GetBytes(Common.SafeFileName);
139                 list.AddRange(fileByte);
140                 //内容是否够30
141                 if (fileByte.Length < 30)
142                 {
143                     for (int i = 0; i < 30 - fileByte.Length; i++)
144                     {
145                         list.Add(0);
146                     }
147                 }
148                 //添加响应
149                 list.AddRange(buffer);
150 
151                 //开始发送
152                 Common.connSocket.Send(list.ToArray());
153             }
154         }
155 
156         /// <summary>
157         /// 选择文件
158         /// </summary>
159         /// <param name="sender"></param>
160         /// <param name="e"></param>
161         void btnChangeFile_Click(object sender, EventArgs e)
162         {
163             OpenFileDialog ofd = new OpenFileDialog();
164             if (ofd.ShowDialog() == DialogResult.OK)
165             {
166                 tbFile.Text = ofd.FileName;
167                 //保存文件名和扩展名
168                 Common.SafeFileName = ofd.SafeFileName;
169             }
170         }
171         /// <summary>
172         /// 
173         /// </summary>
174         /// <param name="sender"></param>
175         /// <param name="e"></param>
176         void time_Tick(object sender, EventArgs e)
177         {
178             connectServer();
179         }
180 
181         /// <summary>
182         /// 发送
183         /// </summary>
184         /// <param name="sender"></param>
185         /// <param name="e"></param>
186         void btnSender_Click(object sender, EventArgs e)
187         {
188             //判断是否有选择用户
189             string sendUser = cbList.Text;
190             //获取聊天的内容
191             string content = tbContent.Text;
192 
193             if (string.IsNullOrEmpty(sendUser))
194             {
195                 //提示
196                 tbHis.AppendText("请选择要发送的用户...\n");
197                 return;
198             }
199             else if (string.IsNullOrEmpty(content))
200             {
201                 //提示
202                 tbHis.AppendText("你不打算输入点什么咯...\n");
203                 return;
204             }
205 
206             try
207             {
208                 //文字: ip设置最大值为 50 位
209                 //[命令(1)|对方的ip和自己的ip 50位)| 内容(文字) | ...]
210                 //这里把对方的ip放前面是为了在服务端好获取
211                 //把自己的ip和对方的ip转为byte
212 
213                 //如果是独立电脑应该可以这样获取
214                 //Common.connSocket.LocalEndPoint
215                 //如果此处不发送自己的ip。那么也可以在服务端获取
216 
217                 string allIp = string.Format("{0},{1}", cbList.Text, myIp.Text);
218 
219 
220                 byte[] sendIp = Encoding.UTF8.GetBytes(allIp);
221                 byte[] buffer = Encoding.UTF8.GetBytes(content);
222 
223                 List<byte> list = sendIp.ToList();
224                 list.Insert(0, 1);//添加协议位
225 
226                 //sendIp 不够50位
227                 if (sendIp.Length < 50)
228                 {
229                     for (int i = 0; i < 50 - sendIp.Length; i++)
230                     {
231                         list.Add(0);
232                     }
233                 }
234 
235                 //把内容添加到末尾
236                 list.AddRange(buffer);
237 
238                 //开始发送
239                 Common.connSocket.Send(list.ToArray());
240                 tbContent.Clear();
241 
242                 //把发送的内容显示在上面
243                 tbHis.AppendText(string.Format("时间:{0}\n", DateTime.Now.ToString()));
244                 tbHis.AppendText(string.Format("我对{0}说:\n", cbList.Text));
245                 tbHis.AppendText(content + "\n");
246                 tbHis.AppendText("\n");
247                 //清空输入框
248                 tbContent.Clear();
249             }
250             catch (Exception ex)
251             {
252                 MessageBox.Show(ex.Message);
253             }
254 
255         }
256         //保存服务器来的byte
257         byte[] buffer = new byte[1024 * 1024];
258         //保存ip
259         public static string ip;
260         void UserList_Load(object sender, EventArgs e)
261         {
262             connectServer();
263         }
264 
265         void connectServer()
266         {
267             string ip = "192.168.1.2";
268             int _point = 8000;
269 
270             try
271             {
272                 Common.connSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
273                 IPAddress address = IPAddress.Parse(ip);
274                 IPEndPoint point = new IPEndPoint(address, _point);
275                 Common.connSocket.Connect(point);
276 
277                 //连接成功,获取服务器发来的消息
278                 Common.connSocket.BeginReceive(Common.buffer, 0, Common.buffer.Length, 0, new AsyncCallback(Receive), Common.connSocket);
279 
280                 //Thread t = new Thread(get);
281                 //t.IsBackground = true;
282                 //t.Start(Common.connSocket);
283 
284             }
285             catch (Exception ex)
286             {
287                 MessageBox.Show(ex.Message);
288             }
289         }
290 
291         void get(object o)
292         {
293             while (true)
294             {
295                 Socket clientSocket = o as Socket;
296 
297                 byte[] buffer = new byte[1024 * 1024];
298                 try
299                 {
300                     //获取实际的长度
301                     int num = clientSocket.Receive(buffer); //服务器关闭会报错
302                     if (num > 0)
303                     {
304                         //获取口令
305                         int command = buffer[0];
306                         //说明是获取好友
307                         if (command == 10)
308                         {
309                             //协议说明
310                             //第一次登陆获取在线(好友)人数
311                             //[命令(10)| ip(自己的ip和所有好友的ip)| ...]
312 
313 
314                             //其实用户本地ip也可以这样获取
315                             //string cy = clientSocket.LocalEndPoint;
316 
317                             string allIp = Encoding.UTF8.GetString(buffer, 1, num - 1);
318                             string[] temp = allIp.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
319 
320 
321                             //跨线程操作UI
322                             this.Invoke(new Action(() =>
323                             {
324                                 myIp.Text = temp.Length > 0 ? temp[temp.Length - 1] : "";
325 
326                                 //排除自己的ip
327                                 var other = from i in temp where !i.Contains(myIp.Text) select i;
328 
329                                 cbList.Items.Clear();//清空
330                                 cbList.Items.AddRange(other.ToArray()); //绑定列表
331                             }));
332 
333                         }
334                         else if (command == 11) //说明是有人下线
335                         {
336                             //协议说明
337                             // 有人下线
338                             //[命令(11)| ip(下线的ip)| ...]
339 
340                             //获取下线的ip
341                             string outIp = Encoding.UTF8.GetString(buffer, 1, num - 1);
342 
343                             this.Invoke(new Action(() =>
344                             {
345                                 //删除下线的ip
346                                 cbList.Items.Remove(outIp);
347                             }));
348                         }
349                         else if (command == 12) //有人上线了
350                         {
351                             //协议说明
352                             // 有人上线
353                             //[命令(12)| ip(上线的ip)| ...]
354 
355                             //获取上线的ip
356                             string onlineIp = Encoding.UTF8.GetString(buffer, 1, num - 1);
357                             //添加上线的ip
358 
359                             this.Invoke(new Action(() =>
360                             {
361                                 //添加上线的ip
362                                 cbList.Items.Add(onlineIp);
363                             }));
364 
365                         }
366 
367                         this.Invoke(new Action(() =>
368                         {
369                             if (cbList.Items.Count > 0)
370                                 cbList.SelectedIndex = 0;//默认选中第一个
371 
372                         }));
373                     }
374                 }
375                 catch (Exception ex)
376                 {
377                     MessageBox.Show(ex.Message);
378                     clientSocket.Shutdown(SocketShutdown.Receive);
379                     clientSocket.Close();
380                     break;
381                 }
382 
383 
384                 //string ip = Encoding.UTF8.GetString(buffer, 0, num);
385                 ////MessageBox.Show(ip);
386 
387                 ////第一个是自己的ip
388                 //string[] userIp = ip.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
389 
390                 //try
391                 //{
392 
393                 //    //cbList.Items.Clear();
394                 //    //MessageBox.Show(cbList.Items.Count.ToString());
395 
396                 //    //var m0 = from m in userIp
397                 //    //         where !m.Contains(userIp[0].ToString())
398                 //    //         select m;
399                 //    //cbList.Items.AddRange(userIp.ToArray());
400 
401                 //    //this.Invoke(new Action(() =>
402                 //    //{
403                 //    //    cbList.Items.Clear();
404                 //    //    //MessageBox.Show(cbList.Items.Count.ToString());
405 
406                 //    //    cbList.Items.AddRange(userIp.ToArray());
407 
408 
409                 //    //}));
410                 //}
411                 //catch (Exception ex)
412                 //{
413                 //    MessageBox.Show(ex.Message);
414                 //}
415 
416 
417                 //MessageBox.Show(string.Join(",",userIp));
418 
419                 //cbList.DataSource = userIp.ToList();
420 
421 
422 
423             }
424         }
425 
426         /// <summary>
427         /// 接收来自服务器的消息
428         /// </summary>
429         /// <param name="result"></param>
430         void Receive(IAsyncResult result)
431         {
432             Socket clientSocket = result.AsyncState as Socket;
433 
434             //byte[] b = new byte[1024 * 1024];
435             try
436             {
437                 //获取实际的长度
438                 int num = clientSocket.EndReceive(result);
439                 if (num > 0)
440                 {
441                     //MessageBox.Show(num.ToString());
442                     byte[] buffer = new byte[num];
443                     Array.Copy(Common.buffer, 0, buffer, 0, num); //复制数据到data
444                     //string ip = Encoding.UTF8.GetString(data);
445 
446 
447                     //以下是客户端=》服务器==》服务器
448                     /*
449                      * 当if else 超过3个
450                      * 
451                      * 建议用switch case语句
452                      */
453 
454                     //获取口令
455                     int command = buffer[0];
456                     //说明是获取好友
457                     if (command == 10)
458                     {
459                         //协议说明
460                         //第一次登陆获取在线(好友)人数
461                         //[命令(10)| ip(自己的ip和所有好友的ip)| ...]
462 
463 
464                         //其实用户本地ip也可以这样获取
465                         //string cy = clientSocket.LocalEndPoint;
466 
467                         string allIp = Encoding.UTF8.GetString(buffer, 1, num - 1);
468                         string[] temp = allIp.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
469 
470 
471                         //跨线程操作UI
472                         this.Invoke(new Action(() =>
473                         {
474                             myIp.Text = temp.Length > 0 ? temp[temp.Length - 1] : "";
475 
476                             //排除自己的ip
477                             var other = from i in temp where !i.Contains(myIp.Text) select i;
478 
479                             cbList.Items.Clear();//清空
480                             cbList.Items.AddRange(other.ToArray()); //绑定列表
481 
482                             if (cbList.Items.Count > 0)
483                                 cbList.SelectedIndex = 0;//默认选中第一个
484                         }));
485 
486                     }
487                     else if (command == 11) //说明是有人下线
488                     {
489                         //协议说明
490                         // 有人下线
491                         //[命令(11)| ip(下线的ip)| ...]
492 
493                         //获取下线的ip
494                         string outIp = Encoding.UTF8.GetString(buffer, 1, num - 1);
495 
496                         this.Invoke(new Action(() =>
497                         {
498                             //删除下线的ip
499                             cbList.Items.Remove(outIp);
500                             if (cbList.Items.Count > 0)
501                                 cbList.SelectedIndex = 0;//默认选中第一个
502                         }));
503                     }
504                     else if (command == 12) //有人上线了
505                     {
506                         //协议说明
507                         // 有人上线
508                         //[命令(12)| ip(上线的ip)| ...]
509 
510                         //获取上线的ip
511                         string onlineIp = Encoding.UTF8.GetString(buffer, 1, num - 1);
512                         //添加上线的ip
513 
514                         this.Invoke(new Action(() =>
515                         {
516                             //添加上线的ip
517                             cbList.Items.Add(onlineIp);
518                             if (cbList.Items.Count > 0)
519                                 cbList.SelectedIndex = 0;//默认选中第一个
520                         }));
521                     }
522 
523                     //以下是客户端=》服务器==》客户端
524 
525                     else if (command == 1)
526                     {
527                         //协议:
528                         //[命令(1)|对方的ip和自己的ip 50位)| 内容(文字) | ...]
529 
530                         //获取ip段
531                         string[] sourceIp = Encoding.UTF8.GetString(buffer, 1, 50).Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
532 
533                         //发消息来的ip
534                         string fromIp = sourceIp[1];
535 
536 
537 
538                         //获取内容
539                         string content = Encoding.UTF8.GetString(buffer, 50 + 1, num - 50 - 1);
540 
541                         this.Invoke(new Action(() =>
542                         {
543                             //列表框中选择当前的ip
544                             cbList.Text = fromIp.ToString();
545 
546                             //显示内容
547                             tbHis.AppendText(string.Format("时间:{0}\n", DateTime.Now.ToString()));
548                             //tbHis.AppendText(string.Format("提示{0}对我说:", fromIp)); //我操。这样怎么就不行
549                             tbHis.AppendText(fromIp + "\n");
550                             tbHis.AppendText("对你说:\n");
551                             tbHis.AppendText(content + "\n");
552                             tbHis.AppendText("\n");
553                         }));
554                     }
555                     else if (command == 0) //发送的文件
556                     {
557                         /*协议: 这里50位不知道是否理想。
558                          * [命令(0)| ip(对方的ip和自己的ip 50位)| 内容(文件大小和文件全名 30位)|响应(文件内容) | ...]
559                          */
560 
561                         //这里有冗余代码
562 
563                         //获取ip段
564                         string[] sourceIp = Encoding.UTF8.GetString(buffer, 1, 50).Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
565 
566                         //发消息来的ip
567                         string fromIp = sourceIp[1];
568 
569                         this.Invoke(new Action(() =>
570                         {
571                             //列表框中选择当前的ip
572                             cbList.Text = fromIp.ToString();
573                         }));
574 
575 
576                         //获取内容
577                         string content = Encoding.UTF8.GetString(buffer, 50 + 1, 30);
578 
579                         //获取响应
580                         //string pass = Encoding.UTF8.GetString(buffer, 50 + 30 + 1, num - 50 - 30 - 1);
581 
582                         //显示
583                         //tbHis.AppendText(string.Format("{0}给你发了一个文件:{1}\n", fromIp, content));
584                         tbHis.AppendText(fromIp);
585                         tbHis.AppendText("给你发了一个文件");
586                         tbHis.AppendText(content + "\n");
587                         tbHis.AppendText("\n");
588 
589 
590                         //提示用户是否接收文件
591                         if (MessageBox.Show("是否接受文件\n" + content, "接收文件", MessageBoxButtons.YesNo) == DialogResult.Yes)
592                         {
593                             //开始保存
594                             SaveFileDialog sfd = new SaveFileDialog();
595                             sfd.FileName = content;
596 
597                             //获取文件类型
598                             string ex = content.Split('.')[1];
599 
600                             //保存文件类型
601                             sfd.Filter = "|*." + ex;
602                             if (sfd.ShowDialog(this) == DialogResult.OK)
603                             {
604                                 using (FileStream fs = new FileStream(sfd.FileName, FileMode.Create))
605                                 {
606                                     fs.Write(buffer, 50 + 30 + 1, num - 50 - 30 - 1);
607                                 }
608                             }
609 
610                         }
611 
612                     }
613                     else if (command == 2) //发送抖动
614                     {
615                         //如果窗口在任务栏。则显示
616                         this.Show();
617                         this.WindowState = FormWindowState.Normal;
618                         this.Activate();
619 
620                         int n = -1;
621 
622                         for (int i = 0; i < 10; i++)
623                         {
624                             n = -n;
625                             this.Location = new Point(this.Location.X + 10 * n, this.Location.Y + 10 * n);
626                             this.TopMost = true;//在所有窗口的顶部
627                             System.Threading.Thread.Sleep(50);
628                         }
629 
630                         //抖动完成。结束顶层显示
631                         this.TopMost = false;
632                     }
633 
634                     //连接成功,再一次获取服务器发来的消息
635                     clientSocket.BeginReceive(Common.buffer, 0, Common.buffer.Length, 0, new AsyncCallback(Receive), clientSocket);
636 
637                 }
638             }
639             catch (Exception ex) //服务器端口
640             {
641                 MessageBox.Show(ex.Message);
642                 clientSocket.Shutdown(SocketShutdown.Receive);
643                 clientSocket.Close();
644             }
645         }
646     }
647 }
View Code

 

 

客户端的一个公共类 Common.cs

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Net;
 6 using System.Net.Sockets;
 7 
 8 namespace TopClient
 9 {
10     /****************************************
11      * 与服务器通信的类
12     ****************************************/
13     public class Common
14     {
15         /// <summary>
16         /// 与服务器通信的socket
17         /// </summary>
18         public static Socket connSocket;
19 
20         /// <summary>
21         /// 保存服务器来的byte
22         /// </summary>
23         public static byte[] buffer = new byte[1024 * 1024];
24 
25         /// <summary>
26         /// 保存当登陆成功后。从服务器获取的所有用ip
27         /// </summary>
28         public static string ip;
29 
30         /// <summary>
31         /// time计时器
32         /// </summary>
33         public static System.Windows.Forms.Timer time;
34 
35         /// <summary>
36         /// 当前是否连接到服务器
37         /// </summary>
38         public static bool isConnect = false;
39 
40         /// <summary>
41         /// 保存文件的文件名和扩展名 xxx.png
42         /// </summary>
43         public static string SafeFileName;
44     }
45 }
View Code