(1)什么是WebSocket
WebSocket是HTML5开始提供的一种浏览器与服务器间进行全双工通讯的网络技术。
在WebSocket API中,浏览器和服务器只需要要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。
优点:
a、服务器与客户端之间交换的标头信息很小,大概只有2字节
b、服务器可以主动传送数据给客户端
(2)WebSocket(13)握手
WebSocket握手由客户端发起,报文样例:
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
这里Sec-WebSocket-Version表明版本号是13;注意Sec-WebSocket-Key,这是客户端发送的密钥,服务端需要对该密钥进行处理,反馈给客户端,客户端验证密钥正确后就开始通信,这之后该密钥就没用了。
服务端反馈样例:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
C#服务端首先提取Sec-WebSocket-Key的字符串,加上“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”(是一个固定的GUID),用SHA1计算哈希码,用base64加密,最终生成Sec-WebSocket-Accept的内容。握手代码:
private void handShake(byte[] recBytes, int recByteLength) { string recStr = Encoding.UTF8.GetString(recBytes, 0, recByteLength); string[] ss = recStr.Split(Environment.NewLine.ToCharArray()); string key = ss[10].Replace("Sec-WebSocket-Key: ", ""); key += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; SHA1 sha1 = SHA1.Create(); byte[] sha1bytes = sha1.ComputeHash(Encoding.UTF8.GetBytes(key)); string acceptStr = Convert.ToBase64String(sha1bytes); string sendStr = "HTTP/1.1 101 Switching Protocols" + NewLine + "Upgrade: websocket" + NewLine + "Connection: Upgrade" + NewLine + "Sec-WebSocket-Accept: " + acceptStr + NewLine + "Sec-WebSocket-Protocol: chat" + NewLine + NewLine; client.Send(System.Text.Encoding.UTF8.GetBytes(sendStr)); isHandshaked = true; }
(3)接收客户端数据
客户端调用send方法将字符窜发送到服务端。服务端要以二进制(bit)解析frame的前两个byte,过程如下:
1byte
1bit: frame-fin,x0表示该message后续还有frame;x1表示是message的最后一个frame
3bit: 分别是frame-rsv1、frame-rsv2和frame-rsv3,通常都是x0
4bit: frame-opcode,x0表示是延续frame;x1表示文本frame;x2表示二进制frame;x3-7保留给非控制frame;x8表示关闭连接;x9表示ping;xA表示pong;xB-F保留给控制frame
2byte
1bit: Mask,1表示该frame包含掩码;0,表示无掩码
7bit、7bit+2byte、7bit+8byte: 7bit取整数值,若在0-125之间,则是负载数据长度;若是126表示,后两个byte取无符号16位整数值,是负载长度;127表示后8个byte,取64位无符号整数值,是负载长度
3-6byte: 这里假定负载长度在0-125之间,并且Mask为1,则这4个byte是掩码
7-end byte: 长度是上面取出的负载长度,包括扩展数据和应用数据两部分,通常没有扩展数据;若Mask为1,则此数据需要解码,解码规则为1-4byte掩码循环和数据byte做异或操作。
C#接收数据代码如下:
private bool recData(byte[] recBytes, int recByteLength) { if (recByteLength < 2) return false; bool fin = (recBytes[0] & 0x80) == 0x80; // 1bit,1表示最后一帧 if (!fin) { Console.WriteLine("recData exception: 超过一帧"); // 超过一帧暂不处理 return false; } bool mask_flag = (recBytes[1] & 0x80) == 0x80; // 是否包含掩码 if (!mask_flag) { Console.WriteLine("recData exception: 没有Mask"); // 不包含掩码的暂不处理 return false; } int payload_len = recBytes[1] & 0x7F; // 数据长度 byte[] masks = new byte[4]; byte[] payload_data; if (payload_len == 126) { Array.Copy(recBytes, 4, masks, 0, 4); payload_len = (UInt16)(recBytes[2] << 8 | recBytes[3]); payload_data = new byte[payload_len]; Array.Copy(recBytes, 8, payload_data, 0, payload_len); } else if (payload_len == 127) { Array.Copy(recBytes, 10, masks, 0, 4); byte[] uInt64Bytes = new byte[8]; for (int i = 0; i < 8; i++) { uInt64Bytes[i] = recBytes[9 - i]; } UInt64 len = BitConverter.ToUInt64(uInt64Bytes, 0); payload_data = new byte[len]; for (UInt64 i = 0; i < len; i++) payload_data[i] = recBytes[i + 14]; } else { Array.Copy(recBytes, 2, masks, 0, 4); payload_data = new byte[payload_len]; Array.Copy(recBytes, 6, payload_data, 0, payload_len); } for (var i = 0; i < payload_len; i++) payload_data[i] = (byte)(payload_data[i] ^ masks[i % 4]); string content = Encoding.UTF8.GetString(payload_data); Console.WriteLine("client: {0}", content); return true; }
(3)发送数据到客户端
服务器发送的数据以0x81开头,紧接发送内容的长度(若长度在0-125,则1个byte表示长度;若长度不超过0xFFFF,则后2个byte作为无符号16位整数表示长度;若超过0xFFFF,则后8个byte作为无符号64位整数表示长度),最后是内容的byte数组。
C#发送代码:
private void sendData(string content) { byte[] contentBytes = null; byte[] temp = Encoding.UTF8.GetBytes(content); if (temp.Length < 126) { contentBytes = new byte[temp.Length + 2]; contentBytes[0] = 0x81; contentBytes[1] = (byte)temp.Length; Array.Copy(temp, 0, contentBytes, 2, temp.Length); } else if (temp.Length < 0xFFFF) { contentBytes = new byte[temp.Length + 4]; contentBytes[0] = 0x81; contentBytes[1] = 126; contentBytes[2] = (byte)(temp.Length & 0xFF); contentBytes[3] = (byte)(temp.Length >> 8 & 0xFF); Array.Copy(temp, 0, contentBytes, 4, temp.Length); } else { // 暂不处理超长内容 } client.Send(contentBytes); }
(4)html客户端代码
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>WebSocket</title>
<script type="text/javascript">
if (!window.WebSocket)
alert("WebSocket not supported by this browser!");
var ws;
function connectWS() {
if (ws == null) {
ws = new WebSocket("ws://127.0.0.1:5001");
ws.onmessage = function (evt) {
alert("接收到信息:" + evt.data);
};
ws.onclose = function () {
alert("连接已关闭。");
ws = null;
};
ws.onerror = function (evt) {
alert("连接出错:" + evt.data);
ws = null;
}
ws.onopen = function (evt) {
alert("连接已打开。");
};
}
}
function sendWS() {
if (ws != null) {
try {
var sendstr = document.getElementById("txtSend").value;
if (sendstr == "")
return;
ws.send(sendstr);
} catch (err) {
alert(err.Data);
}
} else {
alert("连接失效。");
}
}
function closeWS() {
ws.send("exit");
ws.close();
}
</script>
</head>
<body style="text-align: center;">
<input type="button" value="连接" οnclick="connectWS()" />
<input type="button" value="断开" οnclick="closeWS()" />
<br />
<input type="text" id="txtSend" /><input type="button" id="btnSend" value="发送" οnclick="sendWS()" />
</body>
</html>
附件是服务端代码,以及html测试网页
以上