網絡編程之Socket的TCP協議實現客戶端與客戶端之間的通訊

     我認爲當你學完某個知識點後,最好是作一個實實在在的小案例。這樣才能更好對知識的運用與掌握html

若是你看了我前兩篇關於socket通訊原理的入門文章。我相信對於作出我這個小案列是徹底沒有問題的!!ajax

既然是小案列。就不免會有不少不足,因此還請你們見諒。先說下用此案例實現的功能數組

利用Socke的TCP協議實現了瀏覽器

1:端與端之間的通訊(客戶端和客戶端)包括髮送文字,小文件,抖動效果緩存

2:同步實現好友下線與上線的效果,即有其餘客戶端上線會通知其餘已經在線的客戶端服務器

3:實現自定義通訊協議多線程

 

服務器窗體佈局異步

 

 

 

 

 

佈局很明瞭。頂部一個DataGridView顯示鏈接的客戶端信息,來一個就新增一個。走一個就幹掉一個。socket

下面就是顯示服務器信息ide

客戶端佈局:

窗口布局是否是很大衆呢。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
相關文章
相關標籤/搜索