寫在開始 上面一篇寫了一篇使用WebSocket做客戶端,然後服務端是socke代碼實現的。傳送門:webSocket和Socket實現聊天群發 本來我是打算寫到一章上的,畢竟實現的都是一樣的功能,後來想了想就沒寫在一起,主要是兩個方面, 一個原因是這是另一種實現服務方式,放在一起看著有點亂。單獨寫 ...
寫在開始
上面一篇寫了一篇使用WebSocket做客戶端,然後服務端是socke代碼實現的。傳送門:webSocket和Socket實現聊天群發
本來我是打算寫到一章上的,畢竟實現的都是一樣的功能,後來想了想就沒寫在一起,主要是兩個方面,
一個原因是這是另一種實現服務方式,放在一起看著有點亂。單獨寫也方便查閱。二是寫是分開寫的回家晚上寫一點,不能直接在原文上寫,就重新起來一個草稿,但是寫完就感覺有點懶,不想整合到一塊了。嘿嘿,,,
所以對開頭說的不明白的同學可以先看一下前面的東西。看一下基礎,事半功倍哦。
這一篇不做功能的更改,既然我們使用了WebSocket為什麼不使用到底哪,我不喜歡socket的裡面出現的打包請求連接數據處理和發送數據處理。可以沒有問題啊。那你繼續往下看吧。
首先WebSocket伺服器這篇我們還是實現的6個功能:
- 單聊:可以指定人進行聊天。
- 群發:這個的意思就是當前伺服器內的所有人包含自己,這個就跟一個推送效果一樣。
- 開啟連接(客戶端):通知除自己以外的所有用戶
- 關閉連接(客戶端):通知除自己以外的所有用戶
- 群組A:實現一個群組名字為A
- 群組B:實現一個群組名字為B
技術點
前端寫法都是一樣的我就不做過多的敘述了,這裡只要是針對socket協議的方法進行修改成WebSocket形式。
首先我這次是把服務寫成了一般處理程式進行掛載的。(有些有強迫症的小伙伴想改訪問路由路徑可以參考一下:mvc中路由的映射和實現IHttpHandler掛載
我在本示例就是把放在model下的一個一般處理程式,改寫成了socket路徑.
原來訪問是:http“//http://localhost:埠號/文件夾位置/SocketServer.ashx
改完之後是:http“//http://localhost:埠號/socket/SocketServer.ashx
在實際項目中可以不暴露文件的真是路徑位置,還是有點用處的。
不得不說WebSocket確實不錯,比如接受發送數據解析方法都給封裝好了。
接受方式
既然使用WebSocket做協議當然接受就不用用socket而是使用WebSocket啦。通過在接受到請求後獲取上下文中的WebSocket。
//創建新WebSocket實例 WebSocket myClientSocket = context.WebSocket;
string userId = context.QueryString["userId"];
在這裡我們有一點變化就是socke用戶是通過socket隨機獲取的,這裡我修改成了頁面傳輸。前臺代碼:
var userId = parseInt(Math.random() * (999999 - 100000 + 1) + 100000, 10); console.log(userId) ws = new WebSocket('ws://' + window.location.hostname + ':' + window.location.port + '/socket?userId=' + userId);
判讀線上方式
WebSocket有單獨的狀態來進行線上的判斷,不用我們自己寫判斷處理還是比較好的。
#region 關閉Socket處理,刪除連接池 if (myClientSocket.State != WebSocketState.Open)//連接關閉 { if (ListUser.ContainsKey(userId)) ListUser.Remove(userId);//刪除連接池 break; } #endregion
接受數據
WebSocket也沒有辜負我們的期望,接受數據的處理也不需要我們處理的,使用ReceiveAsync方法可以得到消息位元組,我們只需要定義一個位元組數組段用來接受即可,例如:
ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[2048]);//定義位元組數組 WebSocketReceiveResult result = await myClientSocket.ReceiveAsync(buffer, CancellationToken.None);//獲得位元組
string userMsg = Encoding.UTF8.GetString(buffer.Array, 0, result.Count);//發送過來的消息
是不是感覺特別的方便,沒有了那些亂七八糟的處理了。看著還是挺舒心的。
發送數據
既然接受數據都有單獨的方法封裝,發送消息沒有道理沒有的,是的發送使用SendAsync方法,使用形式和ReceiveAsync類似,首先定義一個位元組數組段用來存放內容,例如:
ArraySegment<byte> buffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes($"用戶({userIdA}=>{userIdB}):{msg}")); socket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None);
這樣子就是一個發送過程,先把要發送的字元串轉換成位元組數組段,然後把這個數組段使用SendAsync發送出去就可以了。
註:在上面的兩個方法中我們都看到了ArraySegment這個東西,他到底是個什麼哪,他是一個命名在System命名空間下的一個結構體。類似與Array數組但是他又不是數組,為什麼這麼說,因為他可以接受數組段,他可以只保存內容中的一部分而不是全部。就像別人說的小抽屜一樣。我是把他理解成先把他放到這裡當作數據緩存區,等真實發送的時候進行發送數據。WebSocketMessageType是一個枚舉類型,通過F12可以看到:
// 摘要: // 指示消息類型: public enum WebSocketMessageType { // // 摘要: // 該消息是明文形式。 Text = 0, // // 摘要: // 消息採用二進位格式。 Binary = 1, // // 摘要: // 因為收到關閉的消息,接受已完成。 Close = 2 }
敬上代碼
入口函數
一般處理程式中判斷只接受WebSocket協議連接進入的運行:
if (context.IsWebSocketRequest) { context.AcceptWebSocketRequest(Accept); } else { }
消息處理
下麵就是同意連接後的主要方法,類似上一篇寫的ReceiveMessage方法(接受消息),這裡的處理存在一些改動,所以我就把所有代碼貼上來了。
1 #region 處理客戶端連接請求 2 /// <summary> 3 /// 處理客戶端連接請求 4 /// </summary> 5 /// <param name="result"></param> 6 private async Task Accept(AspNetWebSocketContext context) 7 { 8 //創建新WebSocket實例 9 WebSocket myClientSocket = context.WebSocket; 10 string userId = context.QueryString["userId"]; 11 12 try 13 { 14 15 string descUser = string.Empty;//目的用戶 16 while (true) 17 { 18 if (myClientSocket.State == WebSocketState.Open) 19 { 20 ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[2048]); 21 WebSocketReceiveResult result = await myClientSocket.ReceiveAsync(buffer, CancellationToken.None); 22 23 #region 消息處理(字元截取、消息轉發) 24 try 25 { 26 #region 關閉Socket處理,刪除連接池 27 if (myClientSocket.State != WebSocketState.Open)//連接關閉 28 { 29 30 if (ListUser.ContainsKey(userId)) 31 { 32 //退出 33 SignOut(userId); 34 ListUser.Remove(userId);//刪除連接池 35 Debug.WriteLine("當前退出用戶:" + userId); 36 } 37 break; 38 } 39 #endregion 40 string userMsg = Encoding.UTF8.GetString(buffer.Array, 0, result.Count);//發送過來的消息 41 string[] resultList = userMsg.Split(','); 42 if (resultList[0] == "login") 43 { 44 //登錄 45 Login(userId); 46 #region 用戶添加連接池 47 //第一次open時,添加到連接池中 48 if (!ListUser.ContainsKey(userId)) 49 ListUser.Add(userId, myClientSocket);//不存在,添加 50 else 51 if (myClientSocket != ListUser[userId])//當前對象不一致,更新 52 ListUser[userId] = myClientSocket; 53 #endregion 54 Debug.WriteLine("當前登錄用戶:" + userId); 55 } 56 else if (resultList[0] == "all") 57 { 58 //群發所有用戶 59 GroupChat(userId, resultList[1]); 60 } 61 else if (resultList[0] == "groupA") 62 { 63 //群組發送 64 GroupChatA("groupA", userId, resultList[1]); 65 } 66 else if (resultList[0] == "groupB") 67 { 68 //群組發送 69 GroupChatA("groupB", userId, resultList[1]); 70 } 71 else 72 { 73 //單聊 74 SingleChat(userId, resultList[0], resultList[1]); 75 } 76 77 } 78 catch (Exception exs) 79 { 80 //消息轉發異常處理,本次消息忽略 繼續監聽接下來的消息 81 } 82 #endregion 83 } 84 else 85 { 86 break; 87 } 88 }//while end 89 } 90 catch (Exception ex) 91 { 92 Console.WriteLine("Error : " + ex.ToString()); 93 } 94 } 95 #endregion
單聊實現
這裡我就不把寫的所有單聊,群里,實現群組方法貼上來了,實現的思路還是和以前一樣,只是寫法不同,我就寫一個單聊作為代表示例貼上來。想看全部在下麵下載源碼就好了。
#region 單聊 public void SingleChat(string userIdA, string userIdB, string msg) { WebSocket socket = ListUser[userIdB]; if (socket != null) { if (socket != null && socket.State == WebSocketState.Open) { ArraySegment<byte> buffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes($"用戶({userIdA}=>{userIdB}):{msg}")); socket.SendAsync(buffer, WebSocketMessageType.Binary, true, CancellationToken.None); } } } #endregion
傳送門:
基礎版本實現簡單的websocket:實現服務端webSocket連接通訊
完善websocket實現聊天示例:WebSocket和Socket實現聊天群發
最後在送上github源碼:https://github.com/Yanbigfeng/WebSocketToSocket