用C# ASP.NET MVC 实现WebSocket ,对于WebSocket想必都很了解了,不多说.
东西做的很粗糙 只能实现基本的聊天功能,不过基本的通信实现了,那么后序的扩展应该也不难(个人这么认为...)
先看下效果
可同时支持群聊和私聊 源码下载地址
http://download.csdn.net/detail/formularz/4668280
首先介绍下ValueWebSocket.cs 这个文件 主要是对与客户端的通信进行集中控制
1.ValueServer: Socket服务端
2.ValueProtocol:对WebSocket通信的数据加以解析
3.SessionManager: 集中管理在线用户
1 public class ValueWebSocket 2 { 3 // WebSocket服务端 4 private ValueServer server; 5 // 解析协议 6 private ValueProtocol valueProtocol; 7 // 管理在线用户 8 private SessionManager sessionManager; 9 10 public ValueWebSocket(String ipAddress, Int32 port) 11 { 12 valueProtocol = new ValueProtocol(); 13 sessionManager = new SessionManager(); 14 15 server = new ValueServer(ipAddress, port, Encoding.UTF8); 16 server.OnReceive += new ValueHelper.ValueSocket.Infrastructure.ReceiveHandler(server_OnReceive); 17 } 18 19 private void server_OnReceive(ValueHelper.ValueSocket.SocketEvents.ReceiveEventArgs e) 20 { 21 // 分析用户是否已存在 22 if (sessionManager.CheckSessionExist(e.Socket)) 23 { 24 Message message = valueProtocol.Decode(e.Data); 25 if (message.header.Opcode == OperType.Close) 26 { 27 removeUser(e.Socket); 28 } 29 if (message.header.Opcode == OperType.Text) 30 { 31 String msg = message.Data.ToString(); 32 execMsg(e.Socket, msg); 33 } 34 } 35 else 36 { 37 // 用户不存在则添加用户 38 // 并发送握手信息与客户端建立连接 39 String request = Encoding.UTF8.GetString(e.Data); 40 Byte[] response = valueProtocol.GetResponse(request); 41 server.Send(e.Socket, response); 42 sessionManager.AddSession(e.Socket, request); 43 } 44 } 45 46 // 对消息进行的处理 47 private void execMsg(Socket socket, String message) 48 { 49 String name = String.Empty; 50 foreach (ValueSession session in SessionManager.Sessions) 51 { 52 Socket sk = session.Socket; 53 if (sk.Connected) 54 { 55 if (sk.RemoteEndPoint == socket.RemoteEndPoint) 56 { 57 name = session.Cookies["name"]; 58 break; 59 } 60 } 61 } 62 63 // 判断私聊还是公共 64 String[] separator = message.Split(new String[] { "<separator>" }, StringSplitOptions.None); 65 String msg = String.Concat(name, ": ", separator[1]); 66 if (separator[0] == "All") 67 SendToAll(msg); 68 else 69 { 70 foreach (ValueSession session in SessionManager.Sessions) 71 { 72 if (session.Cookies["name"] == separator[0]) 73 { 74 sendTo(session.Socket, msg); 75 break; 76 } 77 } 78 } 79 } 80 81 private void removeUser(Socket socket) 82 { 83 sessionManager.RemoveSession(socket); 84 } 85 86 private void SendToAll(String msg) 87 { 88 foreach (ValueSession session in SessionManager.Sessions) 89 { 90 sendTo(session.Socket, msg); 91 } 92 } 93 94 private Boolean sendTo(Socket socket, String msg) 95 { 96 Byte[] data = valueProtocol.Encode(msg); 97 return server.Send(socket, data); 98 } 99 100 public void Start() 101 { 102 server.Start(); 103 } 104 105 public void Dispose() 106 { 107 sessionManager.Dispose(); 108 server.Dispose(); 109 } 110 }
SessionManager: 该类就不多说了,集中管理用户类.详情查看源码.
ValueProtocol: 这个类其实只是一个接口类,
真正对数据进行解析的是ProtocolDraft10类(按草案10中介绍的规则对数据进行解析,注:对协议说明有兴趣的同学可以查看这位大牛的文章http://blog.csdn.net/fenglibing/article/details/6852497)
1 public class ProtocolDraft10 : IProtocol 2 { 3 private const String WebSocketKeyPattern = @"Sec\-WebSocket\-Key:\s+(?<key>.*)\r\n"; 4 private const String MagicKey = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; 5 private const Char charOne = '1'; 6 private const Char charZero = '0'; 7 8 #region Handshake 9 10 // 发送回复信息完成握手 11 public Byte[] ProduceResponse(string request) 12 { 13 String webSocketKey = Common.GetRegexValue(request, WebSocketKeyPattern)[0].Groups["key"].Value; 14 String acceptKey = produceAcceptKey(webSocketKey); 15 StringBuilder stringBuilder = new StringBuilder(); 16 stringBuilder.Append(String.Concat("HTTP/1.1 101 Web Socket Protocol Handshake", Environment.NewLine)); 17 stringBuilder.Append(String.Concat("Upgrade: WebSocket", Environment.NewLine)); 18 stringBuilder.Append(String.Concat("Connection: Upgrade", Environment.NewLine)); 19 stringBuilder.Append(String.Concat("Sec-WebSocket-Accept: ", acceptKey, Environment.NewLine, Environment.NewLine)); 20 String asd = stringBuilder.ToString(); 21 return Encoding.UTF8.GetBytes(stringBuilder.ToString()); 22 } 23 // 根据Sec-WebSocket-Key和MagicKey生成AcceptKey 24 private String produceAcceptKey(String webSocketKey) 25 { 26 Byte[] acceptKey = SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes(webSocketKey + MagicKey)); 27 return Convert.ToBase64String(acceptKey); 28 } 29 30 #endregion 31 32 #region Decode 33 // 对客户端发来的数据进行解析 34 public Message Decode(Byte[] data) 35 { 36 Byte[] buffer = new Byte[14]; 37 if (data.Length >= 14) 38 Buffer.BlockCopy(data, 0, buffer, 0, 14); 39 else 40 Buffer.BlockCopy(data, 0, buffer, 0, data.Length); 41 MessageHeader header = analyseHead(buffer); 42 Message msg = new Message(); 43 msg.header = header; 44 45 Byte[] payload; 46 if (header != null) 47 { 48 payload = new Byte[data.Length - header.PayloadDataStartIndex]; 49 Buffer.BlockCopy(data, header.PayloadDataStartIndex, payload, 0, payload.Length); 50 if (header.MASK == charOne) 51 { 52 for (int i = 0; i < payload.Length; i++) 53 { 54 payload[i] = (Byte)(payload[i] ^ header.Maskey[i % 4]); 55 } 56 } 57 } 58 else 59 { 60 msg.Data = Encoding.UTF8.GetString(data); 61 return msg; 62 } 63 64 if (header.Opcode == OperType.Text) 65 msg.Data = Encoding.UTF8.GetString(payload); 66 67 return msg; 68 } 69 private MessageHeader analyseHead(Byte[] buffer) 70 { 71 MessageHeader header = new MessageHeader(); 72 header.FIN = (buffer[0] & 0x80) == 0x80 ? charOne : charZero; 73 header.RSV1 = (buffer[0] & 0x40) == 0x40 ? charOne : charZero; 74 header.RSV2 = (buffer[0] & 0x20) == 0x20 ? charOne : charZero; 75 header.RSV3 = (buffer[0] & 0x10) == 0x10 ? charOne : charZero; 76 77 if ((buffer[0] & 0xA) == 0xA) 78 header.Opcode = OperType.Pong; 79 else if ((buffer[0] & 0x9) == 0x9) 80 header.Opcode = OperType.Ping; 81 else if ((buffer[0] & 0x8) == 0x8) 82 header.Opcode = OperType.Close; 83 else if ((buffer[0] & 0x2) == 0x2) 84 header.Opcode = OperType.Binary; 85 else if ((buffer[0] & 0x1) == 0x1) 86 header.Opcode = OperType.Text; 87 else if ((buffer[0] & 0x0) == 0x0) 88 header.Opcode = OperType.Row; 89 90 header.MASK = (buffer[1] & 0x80) == 0x80 ? charOne : charZero; 91 Int32 len = buffer[1] & 0x7F; 92 if (len == 126) 93 { 94 header.Payloadlen = (UInt16)(buffer[2] << 8 | buffer[3]); 95 if (header.MASK == charOne) 96 { 97 header.Maskey = new Byte[4]; 98 Buffer.BlockCopy(buffer, 4, header.Maskey, 0, 4); 99 header.PayloadDataStartIndex = 8; 100 } 101 else 102 header.PayloadDataStartIndex = 4; 103 } 104 else if (len == 127) 105 { 106 Byte[] byteLen = new Byte[8]; 107 Buffer.BlockCopy(buffer, 4, byteLen, 0, 8); 108 header.Payloadlen = BitConverter.ToUInt64(byteLen, 0); 109 if (header.MASK == charOne) 110 { 111 header.Maskey = new Byte[4]; 112 Buffer.BlockCopy(buffer, 10, header.Maskey, 0, 4); 113 header.PayloadDataStartIndex = 14; 114 } 115 else 116 header.PayloadDataStartIndex = 10; 117 } 118 else 119 { 120 if (header.MASK == charOne) 121 { 122 header.Maskey = new Byte[4]; 123 Buffer.BlockCopy(buffer, 2, header.Maskey, 0, 4); 124 header.PayloadDataStartIndex = 6; 125 } 126 else 127 header.PayloadDataStartIndex = 2; 128 } 129 return header; 130 } 131 132 #endregion 133 134 #region Encode 135 // 对要发送的数据进行编码一符合草案10的规则 136 public Byte[] Encode(String msg) 137 { 138 Byte[] data = Encoding.UTF8.GetBytes(msg); 139 Int32 dataLength = data.Length; 140 141 Byte[] head = packetHeader(OperType.Text, dataLength); 142 for (int i = 0; i < data.Length; i++) 143 { 144 data[i] = (Byte)(data[i] ^ maskKey[i % 4]); 145 } 146 147 Byte[] result = new Byte[head.Length + dataLength]; 148 Buffer.BlockCopy(head, 0, result, 0, head.Length); 149 Buffer.BlockCopy(data, 0, result, head.Length, dataLength); 150 return result; 151 } 152 private const Byte byte80 = 0x80; 153 private Byte[] maskKey = new Byte[] { 113, 105, 97, 110 }; 154 private Byte[] packetHeader(OperType operType, Int32 length) 155 { 156 Byte byteHead = (Byte)(byte80 | (Byte)operType); 157 Byte[] byteLen; 158 if (length < 126) 159 { 160 byteLen = new Byte[1]; 161 byteLen[0] = (Byte)(byte80 | (Byte)length); 162 } 163 else if (length < 65535) 164 { 165 byteLen = new Byte[3]; 166 byteLen[0] = (Byte)(byte80 | (Byte)126); 167 for (int i = 1; i < 3; i++) 168 byteLen[i] = (Byte)(length >> (8 * (2 - i))); 169 } 170 else 171 { 172 byteLen = new Byte[9]; 173 byteLen[0] = (Byte)(byte80 | (Byte)127); 174 for (int i = 1; i < 9; i++) 175 byteLen[i] = (Byte)(length >> (8 * (8 - i))); 176 } 177 178 Byte[] packet = new Byte[1 + byteLen.Length + maskKey.Length]; 179 packet[0] = byteHead; 180 Buffer.BlockCopy(byteLen, 0, packet, 1, byteLen.Length); 181 Buffer.BlockCopy(maskKey, 0, packet, 1 + byteLen.Length, maskKey.Length); 182 return packet; 183 } 184 185 #endregion 186 }
改类主要实现与客户端的握手级数据的解析,至于为什么这么解析,可访问上面那位大牛的文章,有详细说明值得一提的是有掩码的话接
通信时发送的数据都是真实数据与掩码按按异或处理过的.
其他类其实都是一些基础部件, 详情查看源码.
ValueServer: 该类其实就是用套接口编写了一个服务端实现Accept,Receive,Send等相关事件
至于Socket服务端的具体实现请查看这位大牛的文章,有详细说明http://www.cnblogs.com/tianzhiliang/archive/2010/09/08/1821623.html
这里就不赘述了
客户端的实现就比较简单了 只要调用WebSocket的API就行了
具体如下:
<script type="text/javascript"> var noSupportMessage = "您的浏览器不支持WebSocket!"; var ws; function connectSocketServer() { var messageBoard = $("#messageBoard"); var support = "MozWebSocket" in window ? 'MozWebSocket' : ('WebSocket' in window ? 'WebSocket' : null); if (support == null) { alert(noSupportMessage); messageBoard.append('*' + noSupportMessage + "<br />"); return; } messageBoard.append("* Connecting to server..<br />"); try { ws = new WebSocket('ws://localhost:3000'); } catch (e) { alert(e.Message); } ws.onmessage = function (event) { messageBoard.append(event.data + "<br />"); } ws.onopen = function () { messageBoard.append('* Connection open<br />'); } ws.onclose = function () { messageBoard.append('* Connection closed<br />'); } } function sendMessage() { if (ws) { var mssageBox = document.getElementById("messageBox"); var user = document.getElementById("users"); var msg = user.value + "<separator>" + mssageBox.value; ws.send(msg); mssageBox.value = ""; } else { alert(noSupportMessage); } } window.onload = function () { connectSocketServer(); } </script>
完毕,有不当言论,欢迎指正.