上篇文章讲述了http协议的一个简单业务流程实现,这个流程是浏览器通过tcp协议和服务器之间进行连接,然后浏览器向服务器发送http请求,服务器接收请求后给浏览器返回http响应的一个过程。
今天要讲的websocket协议和http协议不一样的是,只要客户端连接上了服务器,websocket协议并不需要浏览器向服务器发送请求,服务器才会返回响应,而是服务器可以主动向浏览器发送数据包。比如某些网页的消息推送,即时消息等,都是websocket协议实现的,当然 一样的是websocket和http一样,都是通过tcp协议来实现浏览器和服务器之间的连接的。
首先考虑四个问题:
1.websocket协议格式?
2.websocket如何验证客户端合法?
3.明文、密文如何传输?
4.websocket如何断开?
1.websocket协议格式,是有国际标准的websocket格式,其中对格式做了非常标准的规范。
引用协议中的原文如上图所示。
websocket协议主要分为两部分,一个是握手,一个是数据传输。上图也是给出了握手过程的请求和响应。
可以看出websocket其实也是一个http协议头,只是比http协议头还多了几个字段。 其中值得注意的一个字段为Sec-WebSocket-Key,这个字段就是解决第二个问题的。
2. 如何验证客户端的合法性?
协议里规定了一个唯一固定的GUID,"258EAFA5-E914-47DA- 95CA-C5AB0DC85B11"。
用上面Sec-WebSocket-Key和GUID相连接,即为:
"dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA- C5AB0DC85B11"
原文The server would then take the SHA-1 hash of this, giving the value 0xb3 0x7a 0x4f 0x2c 0xc0 0x62 0x4f 0x16 0x90 0xf6 0x46 0x06 0xcf 0x38 0x59 0x45 0xb2 0xbe 0xc4 0xea. This value is then base64-encoded (see Section 4 of [RFC4648]), to give the value "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=".
也就是说对红色这个字符串做SHA1哈希,然后再对这个结果做一个base64编码得出最后的结果:
"s3pPLMBiTxaQ9kYGzzhZRbK+xOo=".
这个结果需要由服务器发给客户端,客户端对此进行校验。
下面是websocket握手过程对客户端的连接请求进行解析及回复响应的过程。
int readline(char *allbuf, int idx, char *linebuf) {
int len = strlen(allbuf);
for(;idx < len;idx ++) {
if (allbuf[idx] == '\r' && allbuf[idx+1] == '\n') {
return idx+2;
} else {
*(linebuf++) = allbuf[idx];
}
}
return -1;
}
int base64_encode(char *in_str, int in_len, char *out_str) {
BIO *b64, *bio;
BUF_MEM *bptr = NULL;
size_t size = 0;
if (in_str == NULL || out_str == NULL)
return -1;
b64 = BIO_new(BIO_f_base64());
bio = BIO_new(BIO_s_mem());
bio = BIO_push(b64, bio);
BIO_write(bio, in_str, in_len);
BIO_flush(bio);
BIO_get_mem_ptr(bio, &bptr);
memcpy(out_str, bptr->data, bptr->length);
out_str[bptr->length-1] = '\0';
size = bptr->length;
BIO_free_all(bio);
return size;
}
int handshark(struct ntyevent *ev) {
//ev->buffer , ev->length
char linebuf[1024] = {0};
int idx = 0;
char sec_data[128] = {0};
char sec_accept[32] = {0};
do {
memset(linebuf, 0, 1024);
idx = readline(ev->buffer, idx, linebuf);
if (strstr(linebuf, "Sec-WebSocket-Key")) {
//linebuf: Sec-WebSocket-Key: QWz1vB/77j8J8JcT/qtiLQ==
strcat(linebuf, GUID);
//linebuf:
//Sec-WebSocket-Key: QWz1vB/77j8J8JcT/qtiLQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11
SHA1(linebuf + WEBSOCK_KEY_LENGTH, strlen(linebuf + WEBSOCK_KEY_LENGTH), sec_data); // openssl
base64_encode(sec_data, strlen(sec_data), sec_accept);
memset(ev->buffer, 0, BUFFER_LENGTH);
ev->length = sprintf(ev->buffer, "HTTP/1.1 101 Switching Protocols\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Accept: %s\r\n\r\n", sec_accept);
printf("ws response : %s\n", ev->buffer);
break;
}
} while((ev->buffer[idx] != '\r' || ev->buffer[idx+1] != '\n') && idx != -1 );
return 0;
}
握手过程结束后,下面再来介绍数据传输过程,这一过程也伴随着第三个问题,即如何进行明文、密文的传输?
3. 明文、密文的传输?
从协议中描述的数据帧格式如下: