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暂不支持。。。