一、Html5WebSocket介紹
WebSocket protocol 是HTML5一種新的協議(protocol)。它是實現了瀏覽器與服務器全雙工通信(full-duplex)。
現在,很多網站為了實現即時通訊(real-time),所用的技術都是輪詢(polling)。輪詢是在特定的的時間間隔(time interval)(如每1秒),由瀏覽器對服務器發出HTTP request,然后由服務器返回最新的數據給客服端的瀏覽器。這種傳統的HTTP request d的模式帶來很明顯的缺點 – 瀏覽器需要不斷的向服務器發出請求(request),然而HTTP request 的header是非常長的,里面包含的數據可能只是一個很小的值,這樣會占用很多的帶寬。
而最比較新的技術去做輪詢的效果是Comet – 用了AJAX。但這種技術雖然可達到全雙工通信,但依然需要發出請求(reuqest)。
在 WebSocket API,瀏覽器和服務器只需要要做一個握手的動作(實際上是tcp),然后,瀏覽器和服務器之間就形成了一條快速通道(這里走的是新的協議)。兩者之間就直接可以數據互相傳送。
二、IM系統的幾種通信方式
1.點對點通信(對等通信方式):客戶端A想要與客戶端B進行通信,首頁會與IM服務器進行一次握手,然后從IM服務器拿到客戶端B的地址。然后直接向客戶端B發送消息,然后客戶端B獲取到A客戶端的地址,也直接向客戶端A回復消息,這樣就不通過IM服務器來中轉,這樣雙方的即時文字消息就不通過 IM服務器中轉,而是通過網絡進行點對點的直接通訊,這稱為對等通訊方式(Peer To Peer) 。PS:這種方式需要做內網穿透或代理,不然無法獲取到對方的地址等信息。
2.代理通信:當客戶端A與客戶端B之間存在防火牆,網速很慢等原因,IM服務器可以提供消息中專的服務,客戶端A先把消息發送到IM服務器,然后再通過IM服務器把消息轉發給客戶端B,這樣無需得到客戶端的地址信息就能實現消息送達,這種方式叫做代理通信。
3.離線代理通信:當客戶端A想要與客戶端B通信的時候,發現客戶端B不在線,這樣IM服務器會把消息存起來,等到下一次客戶端B上線的時候,由客戶端B主動獲取到離線消息(這樣做好像可以降低服務器的壓力)。
三、利用Html5的WebSocket實現簡單的聊天室
1.服務端代碼如下,注釋那些都挺全的,就不一一多說:
1 private async Task WebSocketContext(AspNetWebSocketContext context) 2 { 3 try 4 { 5 WebSocket socket = context.WebSocket; 6 7 //獲取連接信息 8 string user_name = TDCMS.Common.TD_Request.GetQueryStringValue("user_name", ""); 9 10 //第一次open時,添加到連接池中 11 if (_userPool.Find(c => c.User_name== user_name) == null) 12 { 13 _userPool.Add(new UserPool() { User_name = user_name , Socket = socket }); 14 } 15 else 16 { 17 UserPool p = _userPool.Find(c => c.User_name == user_name); 18 if (socket != p.Socket)//當前對象不一致,更新 19 { 20 p.Socket = socket; 21 } 22 } 23 24 UserPool sourcePool= _userPool.Find(c => c.User_name == user_name);//獲取到發送者連接池 25 26 #region 對所有連接池中廣播 我上線了 27 foreach (var item in _userPool) 28 { 29 MessageModel model = new MessageModel() 30 { 31 Aim = item.User_name, 32 Contents = user_name + "上線了", 33 Source = sourcePool.User_name, 34 Status = 1 35 }; 36 await item.Socket.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes(JsonHelper.ObjectToJson(model))), WebSocketMessageType.Text, true, CancellationToken.None); 37 } 38 #endregion 39 40 bool isNext = true; 41 while (isNext) 42 { 43 if (socket.State == WebSocketState.Open) 44 { 45 ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[2048]); 46 WebSocketReceiveResult result = await socket.ReceiveAsync(buffer, CancellationToken.None); 47 48 #region 關閉Socket處理,刪除連接池 49 if (socket.State != WebSocketState.Open)//連接關閉 50 { 51 if (_userPool.Find(c => c.User_name == user_name) != null) 52 _userPool.Remove(_userPool.Find(c => c.User_name == user_name));//刪除連接池 53 //廣播當前在線的用戶 我下線了 54 foreach (var item in _userPool) 55 { 56 MessageModel offline = new MessageModel() 57 { 58 Aim = item.User_name, 59 Contents = user_name + "下線了", 60 Source = sourcePool.User_name, 61 Status = 1 62 }; 63 await item.Socket.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes(JsonHelper.ObjectToJson(offline))), WebSocketMessageType.Text, true, CancellationToken.None); 64 } 65 break; 66 } 67 68 #endregion 69 70 #region 如果連接沒有關閉,處理發送過來的消息 71 72 MessageModel model=new MessageModel(); 73 int messageCount = result.Count; 74 string messageStr= Encoding.UTF8.GetString(buffer.Array, 0, messageCount); 75 model = JsonHelper.JsonToObject<MessageModel>(messageStr);//這個是解析好的 消息 76 77 //發送消息到每個客戶端 78 foreach (var item in _userPool) 79 { 80 await item.Socket.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes(JsonHelper.ObjectToJson(model))), WebSocketMessageType.Text, true, CancellationToken.None); 81 } 82 83 #endregion 84 } 85 } 86 87 } 88 catch(Exception ex) 89 { 90 throw ex; 91 } 92 }
2.客戶端JS代碼如下:
1 <script> 2 var im;//WebSocket對象 3 function initIm() { 4 var user=$('#txtUserName').val(); 5 im = new MyIm(window.location.hostname, window.location.port, user); 6 $('.online').show(); 7 $('.offline').hide(); 8 im.Init(); 9 } 10 11 //創建一個對象 里面有3個方法,分別為Init:初始化Socket連接、Send:發送消息、Colse:關閉連接 12 var MyIm = function (path, prot, user_name) { 13 this.requestPath = 'ws://' + path + ':' + prot + '/tools/Handler.ashx'; 14 this.user_name = user_name; 15 this.param = '?user_name=' + this.user_name; 16 this.socekt; 17 18 } 19 MyIm.prototype = { 20 Init: function () { 21 this.socekt = new WebSocket(this.requestPath + this.param); 22 this.socekt.onopen = function () { 23 addSysMessage('連接成功','') 24 };//連接成功 25 this.socekt.onmessage = function (result) { 26 //這里返回的消息為json格式,里面的data為服務器返回的內容 27 var json = eval('(' + result.data + ')'); 28 if (Number(json.Status) == 1) { 29 addSysMessage(json.Contents,json.Time) 30 } else { 31 addMessage(json); 32 } 33 };//接收到消息的時候 34 this.socekt.onclose = function (result) { 35 addSysMessage('我的連接關閉了','') 36 }//連接關閉的時候 37 this.socekt.onerror = function (result) { 38 addSysMessage('網絡發生了錯誤', '');//當這一步被執行時,close會被自動執行,所以無需主動去執行關閉方法 39 }//當連接發生錯誤的時候 40 }, 41 Send: function (msg) { 42 //這里可以直接發送消息給服務器,但是為了讓服務器好區分我的消息是屬於通知還是普通消息還是其他,所以做成了json 43 //后台獲取到json,解析后針對不同的消息類型進行處理 44 var json = '{"Status":0,"Contents":"'+msg+'","Source":"'+this.user_name+'","Aim":""}'; 45 this.socekt.send(json); 46 }, 47 Close: function () { 48 if (this.socekt != null) { 49 this.socekt.close(); 50 return; 51 } 52 } 53 } 54 //把通知消息載入到通知列表 55 function addSysMessage(msg, time) { 56 if (time.length <= 0) { 57 time = new Date(); 58 } 59 $('.messageBox').append('<li><p class="time">' + time + '</p><p class=\"message\">' + msg + '</p></li>'); 60 } 61 //把聊天消息載入到聊天框 62 function addMessage(json) { 63 $('.mainBox').append('<li><p class="time">' + json.Time + '</p><p class=\"message\"><span>'+json.Source+'說:</span>' + json.Contents + '</p></li>'); 64 } 65 //發送消息 66 function sendMsg() { 67 var contents = $('#txtContents').val(); 68 im.Send(contents); 69 } 70 //關閉連接 71 function offLine() { 72 im.Close(); 73 74 $('.online').hide(); 75 $('.offline').show(); 76 } 77 </script>
3.客戶端HTML:
<div class="mainIm"> <div> <ul class="mainBox"> </ul> <ul class="messageBox"> </ul> </div> <div class="online" style="display:none;"> <input type="text" id="txtContents" placeholder="輸入要發送的內容" /> <input type="button" value="發送" onclick="sendMsg()" /><input type="button" value="斷開連接" onclick="offLine()" /> </div> <div class="offline"> <input type="text" id="txtUserName" placeholder="請輸入一個用戶名" /> <input type="button" value="連接" onclick="initIm()" /> </div> </div>
4.最后的效果如下:

三、總結
目前IE並不支持WebSocket,就目前來說,這種方式並不適用於大范圍使用。
這里只是實現了簡單的聊天室,如果想要一對一,只需找到用戶的連接池,向該連接池發送消息即可,如果用戶不存在,可以創建一個全局變量離線消息池來儲存離線消息。
如有大神發現寫的不會的地方,請多多指教!!!
