简介: 本文探索了在Linux环境下实现WebSocket服务器的网络设计,将WebSocket服务器作为连接世界的纽带,为读者介绍了如何掌握Linux网络设计中的关键技术。文章从实现WebSocket协议到优化服务器性能和稳定性等方面进行了深入讲解。通过学习本文,读者将能够全面了解WebSocket服务器的原理和工作机制,并获得构建高效、可靠的Linux WebSocket服务器的实用技巧和最佳实践。无论是初学者还是有经验的开发人员,都能从本文中获得宝贵的知识和启发,进一步提升在Linux网络设计中的能力。让我们一同打造连接世界的纽带,掌握Linux网络设计中WebSocket服务器的精髓。
websocket描述
websocket是在单个TCP连接上进行全双工通信的协议,允许Server主动向Client推送数据。 客户端和服务器只需要完成一次握手,就可以创建持久性的连接,进行双向数据传输。 websocket是独立的,作用在TCP上的协议。 为了向前兼容, WebSocket 协议使用 HTTP Upgrade 协议升级机制来进行 WebSocket 握手, 当握手完成之后, 客户端和服务端就可以依据WebSocket 协议的规范格式进行数据传输。
websocket相对HTTP协议的优点
1、支持双向通信,数据的实时性更新更强。 2、开销小;客户端和服务端进行数据通信时,websocket的header(数据头)较小。服务端到客户端的header只有2~10 Bytes,客户端到服务端的需要加上额外的4 Bytes的masking-key。而HTTP协议每次通信都需要携带完整的数据头。 3、扩展性。 4、二进制数据支持更好。
websocket的应用场景
从websocket的优点可以知道,主要应用场景有: 1、视频弹幕 2、媒体即时通讯 3、需要实时位置/数据的应用 5、金融行业的股票基金价格实时更新 等。
websocket握手
1、客户端:Upgrade(申请升级到websocket协议)
协议包含两个部分:握手和数据传输。WebSocket复用了HTTP的握手通道。 客户端通过HTTP请求与WebSocket服务端协商升级到websocket协议。协议升级完成后,后续的数据传输按照WebSocket的data frame进行。 WebSocket 握手采用 HTTP Upgrade 机制,使用标准的HTTP报文格式,只支持使用HTTP的GET方法,客户端发送如下所示的结构发起握手:
代码语言:javascript复制GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://fly.example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
说明:
参数 | 值 | 含义 |
---|---|---|
Upgrade: | websocket | 升级到websocket协议 |
Connection: | Upgrade | 升级协议 |
Sec-WebSocket-Key: | (key value) | 与服务端响应的sec-websocket-accept对应,提供安全防护 |
Sec-WebSocket-Version: | 13 | 指示websocket的版本 |
2、服务器:响应协议升级
服务端如果支持 WebSocket 协议,则返回 101 的 HTTP 状态码。返回如下所示的结构:
代码语言:javascript复制HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK xOo=
Sec-WebSocket-Protocol: chat
Sec-WebSocket-Version: 13
参数说明:
参数 | 说明 |
---|---|
Sec-WebSocket-Accept | 必须有,与客户端的Sec-WebSocket-Key对应 |
Sec-WebSocket-Version | 必须有, 返回服务端和客户端都支持的 WebSocket 协议版本。如果服务端不支持客户端的协议版本则立即终止握手, 并返回 HTTP 426 状态码,同时设置 Sec-WebSocket-Version 说明服务端支持的 WebSocket 协议版本列表 |
Sec-WebSocket-Protocol | 可选, 是否支持 WebSocket 子协议 |
Sec-WebSocket-Extensions | 可选, 是否支持拓展列表 |
注意:每个HTTP的header都以rn结尾,并且最后一行要加上一个额外的rn。这是由于http协议制定的时候,就是用分隔符进行分包。
3、Sec-WebSocket-Accept值的计算
客户端发起握手时通过 Sec-WebSocket-Key 传递了一个安全防护字符串,服务端将该值与 WebSocket 魔数 "258EAFA5-E914-47DA- 95CA-C5AB0DC85B11" 进行字符串拼接,将得到的字符串做 SHA-1 哈希, 将得到的哈希值再做 base64 编码,最后得到的值就是Sec-WebSocket-Accept值。 计算公式为: (1)将Sec-WebSocket-Key的值与258EAFA5-E914-47DA-95CA-C5AB0DC85B11魔数进行字符串拼接; (2)使用SHA1对拼接的字符串做哈希,得到一个哈希值; (3)将哈希值做base64编码得到Sec-WebSocket-Accept值。 伪代码:
代码语言:javascript复制//......
// 字符串拼接
char *str=Sec-WebSocket-Key "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
// 计算sha1哈希
char sec_data[64];
SHA1(str,strlen(str),sec_data);
// 编码成base64
char sec_accept[64];
base64_encode(sec_data,strlen(sec_data),sec_accept);
//......
base64_encode函数实现:
代码语言:javascript复制#include <openssl/sha.h>
#include <openssl/pem.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
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] = '