handshake(握手)
client請求:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
server回覆:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
"dGhlIHNhbXBsZSBub25jZQ=="(Sec-WebSocket-Key)+"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"(因定GUID)
先SHA-1哈希,再用base64編碼,獲得"s3pPLMBiTxaQ9kYGzzhZRbK+xOo="
Framing Protocol(數據幀協議)
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+
opcode:
0x1 text frame
0x2 binary frame
0x8 connection close
Mask:是否有掩碼(client to server必須, server to client可選)
Payload length:負載長度
<=125 直接7位表示長度
126,表示長充大於125並小於0xFFFF,長度放在2Byte
127,長度放在後8Byte
Masking-key:若是有Mask,4Byte
Payload Data:若是有Mask,須要和Making-key作異或來還原數據
附WebSocket Protocol
http://datatracker.ietf.org/doc/rfc6455/?include_text=1
代碼 QByteArray handShake(QString secWebSocketKey) { Q_ASSERT(!secWebSocketKey.isEmpty()); secWebSocketKey += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; QString s= QCryptographicHash::hash(secWebSocketKey.toAscii(), QCryptographicHash::Sha1).toBase64(); QString respone = QString("HTTP/1.1 101 Switching Protocols\r\n" "Upgrade: websocket\r\n" "Connection: Upgrade\r\n" "Access-Control-Allow-Credentials:true\r\n" "Access-Control-Allow-Headers:content-type\r\n" "Sec-WebSocket-Accept: %1\r\n" "\r\n").arg(s); return respone.toAscii(); } void unmask (int pos, int len, unsigned char *buf) { int i = pos; //The position of payload data int n = pos - 4; //The position of masking-key for (; i<len; i++,n++) { if (n == pos) n = pos - 4; //back to the first masking-key buf[i] ^= buf[n]; //unmask: payload data XOR masking-key } } QByteArray parserData(QByteArray input) { unsigned char *buf=(unsigned char *)input.data(); QByteArray out; int len = input.length(); if (buf[0] == 0x88) { qDebug()<<"Received a Close frame"; out = "close"; return out; } buf[1] &= 0x7F; int payloadBegin = 0; if (buf[1] < 126) { payloadBegin = 6; } else if (buf[1] == 126) { payloadBegin = 8; //6+2 } else if (buf[1] == 127) { payloadBegin = 14;//6+8 } unmask(payloadBegin, len, buf); out = QByteArray::fromRawData((const char *)(input.data()+payloadBegin), input.size()-payloadBegin); return out; } void writeData(QTcpSocket *socket, const QByteArray &data) { QByteArray head(2, 0); if (data.length()<=125) { quint8 len = data.length(); head[1] = len; } else if (data.length()<=0xffff) { head[1] = 126; quint16 len = data.length(); head.resize(4); for (int i=3; i>1; i--) { head[i] = (byte)(len & 0xff); len = len >> 8; } } else{ head[1] = 127; quint64 len = data.length(); head.resize(10); for (int i=9; i>1; i--) { head[i] = (byte)(len & 0xff); len = len >> 8; } } socket->write(head); socket->write(data); socket->flush(); }