WebSocket
WebSocket協議是一種雙向通信協議,它建立在TCP之上,同http一樣通過TCP來傳輸數據,但是它和http最大的不同有兩點:
- WebSocket是一種雙向通信協議,在建立連接后,WebSocket服務器和Browser/UA都能主動的向對方發送或接收數據,就像Socket一樣,不同的是WebSocket是一種建立在Web基礎上的一種簡單模擬Socket的協議;
- WebSocket需要通過握手連接,類似於TCP它也需要客戶端和服務器端進行握手連接,連接成功后才能相互通信。
當Web應用程序調用new WebSocket(url)接口時,Browser就開始了與地址為url的WebServer建立握手連接的過程。
客戶端向服務器發送請求:
GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key:dGhlIHNhbXBsZSBub25jZQ== Origin: http://example.com Sec-WebSocket-Protocol: chat,superchat Sec-WebSocket-Version: 13
服務器端返回內容:
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: xsOSgr30aKL2GNZKNHKmeT1qYjA=
請求中的【Sec-WebSocket-Key】是隨機發送的。而服務器返回的【Sec-WebSocket-Accept】是將【Sec-WebSocket-Key】加上一個固定字符串【258EAFA5-E914-47DA-95CA-C5AB0DC85B11】,並使用SHA-1加密后,再進行BASE-64編碼生成的。
服務端簡單實例
新建一個Web MVC項目:
.net4.5中實現了對websocket的支持,所以直接在一個項目中新建一個一般處理程序 Handler1 :
using System; using System.Collections.Generic; using System.Linq; using System.Net.WebSockets; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Web; using System.Web.WebSockets; namespace WebApplicationWebsocketHandler { /// <summary> /// 離線消息 /// </summary> public class MessageInfo { public MessageInfo(DateTime _MsgTime, ArraySegment<byte> _MsgContent) { MsgTime = _MsgTime; MsgContent = _MsgContent; } public DateTime MsgTime { get; set; } public ArraySegment<byte> MsgContent { get; set; } } /// <summary> /// Handler1 的摘要說明 /// </summary> public class Handler1 : IHttpHandler { private static Dictionary<string, WebSocket> CONNECT_POOL = new Dictionary<string, WebSocket>();//用戶連接池 private static Dictionary<string, List<MessageInfo>> MESSAGE_POOL = new Dictionary<string, List<MessageInfo>>();//離線消息池 public void ProcessRequest(HttpContext context) { //context.Response.ContentType = "text/plain"; //context.Response.Write("Hello World"); if (context.IsWebSocketRequest) { context.AcceptWebSocketRequest(ProcessChat); } } private async Task ProcessChat(AspNetWebSocketContext context) { WebSocket socket = context.WebSocket; string user = context.QueryString["user"].ToString(); try { #region 用戶添加連接池 //第一次open時,添加到連接池中 if (!CONNECT_POOL.ContainsKey(user)) CONNECT_POOL.Add(user, socket);//不存在,添加 else if (socket != CONNECT_POOL[user])//當前對象不一致,更新 CONNECT_POOL[user] = socket; #endregion #region 離線消息處理 if (MESSAGE_POOL.ContainsKey(user)) { List<MessageInfo> msgs = MESSAGE_POOL[user]; foreach (MessageInfo item in msgs) { await socket.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes(item.MsgTime + ":" + item.MsgContent)), WebSocketMessageType.Text, true, CancellationToken.None); } MESSAGE_POOL.Remove(user);//移除離線消息 } #endregion string descUser = string.Empty;//目的用戶 while (true) { if (socket.State == WebSocketState.Open) { ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[2048]); WebSocketReceiveResult result = await socket.ReceiveAsync(buffer, CancellationToken.None); #region 消息處理(字符截取、消息轉發) try { #region 關閉Socket處理,刪除連接池 if (socket.State != WebSocketState.Open)//連接關閉 { if (CONNECT_POOL.ContainsKey(user)) CONNECT_POOL.Remove(user);//刪除連接池 break; } #endregion string userMsg = Encoding.UTF8.GetString(buffer.Array, 0, result.Count);//發送過來的消息 string[] msgList = userMsg.Split('|'); if (msgList.Length == 2) { if (msgList[0].Trim().Length > 0) descUser = msgList[0].Trim();//記錄消息目的用戶 buffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(msgList[1])); if (CONNECT_POOL.ContainsKey(descUser))//判斷客戶端是否在線 { WebSocket destSocket = CONNECT_POOL[descUser];//目的客戶端 if (destSocket != null && destSocket.State == WebSocketState.Open) await destSocket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None); } else { Task.Run(() => { if (!MESSAGE_POOL.ContainsKey(descUser))//將用戶添加至離線消息池中 MESSAGE_POOL.Add(descUser, new List<MessageInfo>()); MESSAGE_POOL[descUser].Add(new MessageInfo(DateTime.Now, buffer));//添加離線消息 }); } } else { buffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(userMsg)); foreach (KeyValuePair<string, WebSocket> item in CONNECT_POOL) { await item.Value.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None); } } } catch (Exception exs) { //消息轉發異常處理,本次消息忽略 繼續監聽接下來的消息 } #endregion } else { break; } }//while end } catch (Exception ex) { //整體異常處理 if (CONNECT_POOL.ContainsKey(user)) CONNECT_POOL.Remove(user); } } public bool IsReusable { get { return false; } } } }
客戶端測試代碼如下:
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0"/> <title></title> <script src="http://code.jquery.com/jquery-1.4.1.min.js"></script> <script> var ws; $().ready(function () { $('#conn').click(function () { //ws = new WebSocket('ws://' + window.location.hostname + ':' + window.location.port + '/Handler1.ashx?user=' + $("#user").val()); ws = new WebSocket('ws://127.0.0.1:80/Handler1.ashx?user=' + $("#user").val()); //var host = 'ws://192.168.85.128:8085/api/WSChat?user='+$("#user").val(); //var host = "ws://192.168.85.128:8085/api/WSChat"; //webSocket = new WebSocket(host); $('#msg').append('<p>正在連接</p>'); ws.onopen = function () { $('#msg').append('<p>已經連接</p>'); } ws.onmessage = function (evt) { $('#msg').append('<p>' + evt.data + '</p>'); } ws.onerror = function (evt) { $('#msg').append('<p>' + JSON.stringify(evt) + '</p>'); } ws.onclose = function () { $('#msg').append('<p>已經關閉</p>'); } }); $('#close').click(function () { ws.close(); }); $('#send').click(function () { if (ws.readyState == WebSocket.OPEN) { ws.send($("#to").val() + "|" + $('#content').val()); } else { $('#tips').text('連接已經關閉'); } }); }); </script> </head> <body> <div> <input id="user" type="text" /> <input id="conn" type="button" value="連接" /> <input id="close" type="button" value="關閉"/><br /> <span id="tips"></span> <input id="content" type="text" /> <input id="send" type="button" value="發送"/><br /> <input id="to" type="text" />目的用戶 <div id="msg"> </div> </div> </body> </html>
測試需要iis配置支持WebSocket協議,win7暫不支持。。。