我認爲當你學完某個知識點後,最好是作一個實實在在的小案例。這樣才能更好對知識的運用與掌握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 }
客戶端源碼:
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 }
客戶端的一個公共類 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 }