当前位置: 首页 > news >正文

掌握Linux网络设计中的WebSocket服务器

websocket握手

1、客户端:Upgrade(申请升级到websocket协议)

协议包含两个部分:握手和数据传输。WebSocket复用了HTTP的握手通道。 客户端通过HTTP请求与WebSocket服务端协商升级到websocket协议。协议升级完成后,后续的数据传输按照WebSocket的data frame进行。 WebSocket 握手采用 HTTP Upgrade 机制,使用标准的HTTP报文格式,只支持使用HTTP的GET方法,客户端发送如下所示的结构发起握手:

代码语言:javascript

AI代码解释

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

AI代码解释

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都以\r\n结尾,并且最后一行要加上一个额外的\r\n。这是由于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

AI代码解释

//...... // 字符串拼接 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

AI代码解释

#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] = '\0'; size = bptr->length; BIO_free_all(bio); return size; }

WebSocket 数据帧 (data frame)

WebSocket 协议以 frame 为最小单位传输数据,当一条message(消息)过长时,发送方可以将message(消息)拆分成多个 frame 发送,接收方收到以后再重新拼接、解码还原出一条完整的message(消息)。 WebSocket 协议的data frame 的结构如下所示(从左到右,单位是比特):

代码语言:javascript

AI代码解释

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 ... | +---------------------------------------------------------------+

说明:

字段

bit占用

语义

FIN

1 bit

指示当前的 frame是否是消息的最后一个切片。1 表示这是消息(message)的最后一个分片(fragment);0 表示不是是消息(message)的最后一个分片(fragment)

RSV1~3

1 bit

一般情况下全为0。使用WebSocket扩展时,这三个标志位可以非0,由扩展进行定义。注意,如果这三个数是非零的值,并且并没有使用WebSocket扩展,接收方应该立刻终止websocket的连接。

opcode

4 bit

操作代码,指示data frame 的类型,决定了数据载荷(data payload)的解析方式。如果操作代码是非法的,那么接收端应该断开连接

mask

1 bit

指示是否要对数据载荷(data payload)进行掩码操作。从客户端向服务端发送数据时,需要对数据进行掩码操作;从服务端向客户端发送数据时,不需要对数据进行掩码操作。即所有客户端发送到服务端的数据帧,Mask必须为1,如果服务端接收到的数据没有进行掩码操作,服务端应该断开连接。

Payload len

7 bit

指示数据载荷的长度,单位是字节。该字段的长度有三种可能:7 bit ,7 + 16 bit ,7 + 64 bit。当 数据载荷(Payload )的实际长度 <126 时, 则 此字段的长度为 7bit, 直接代表了数据载荷的实际长度;当 此字段为 126 时, 则其后跟随的 16 bit将被解释为 16-bit 的无符号整数, 该整数的值指示数据载荷的实际长度;当 此字段为 127 时, 其后的 64 bit将被解释为 64-bit 的无符号整数, 该整数的值指示数据载荷的实际长度。注意,如果payload length占用了多个字节的话,payload length的二进制表达采用网络序(需要解决大小端问题)

Masking-key

32 bit

可选字段,如果 Mask 为 1 ,Masking-key 字段存在,长度为 32 bit(4字节),所有由客户端发往服务端的data frame 都必须使用掩码覆盖;如果Mask为0,则没有Masking-key。注意,载荷数据的长度,不包括masking-key的长度

Payload

0~64bit

数据载荷,长度不固定,是 fram的数据部分。如果使用了 WebSocket 扩展,扩展数据 (Extension data) 也将存放在这里, 扩展数据 + 应用数据, Payload Len 字段指示的值等于它们的长度和

opcode可选操作代码:

操作码

含义

0x0

特殊,表示一个延续帧。本次数据传输采用了数据分片,当前收到的数据帧为其中一个数据分片

0x1

表示这是一个文本帧

0x2

表示这是一个二进制帧

0x3-0x7

保留

0x8

连接断开

0x9

ping操作

0xA

pong操作

0xB-0xF

保留

掩码算法unmask

Masking-key是由客户端发送过来的32位的随机数。Masking-key不影响数据载荷的长度。掩码、反掩码操作都采用如下算法: 以字节为步长遍历 Payload, 对于 Payload 的第 i 个字节, 首先做 i 对 4 取模得到 j, 则掩码覆盖后的 Payload 的第 i 个字节的值为原先 Payload 第 i 个字节与 Masking-Key 的第 j 个字节做按位异或操作。

http://www.jsqmd.com/news/855536/

相关文章:

  • 港科大沈劭劼、谭平团队最新成果:开源280万全景数据集,实现零样本立体匹配
  • 测试经理为保障项目按期交付,主动规划核心内容
  • 我开发了一个 AI 表单填写 Chrome 插件:AutoFormX,提升 Web 测试和表单联调效率
  • 3步搞定OFD兼容难题:Ofd2Pdf实战手册
  • Cursor试用限制终极解决方案:3分钟快速重置设备标识实战指南
  • STM32 HAL库驱动中景园0.96寸OLED(SSD1306)避坑指南:从IIC地址到GRAM刷新的完整流程
  • 别再傻傻分不清:一张图看懂BLDC六步换相与PMSM FOC的本质区别与应用选型
  • 不止是省9.9刀:解锁特斯拉Model 3的‘行驶中保持WiFi’功能,打造家庭移动娱乐中心
  • 告别臃肿UI!5K行代码的GuiLite在STM32 HAL库上跑起来了(附工程源码)
  • 避开这3个坑,你的C# + VisionPro相机采集程序才算稳定(WinForm实战)
  • 告别接线混乱!用ESP32的I2C接口驱动LCD1602,5分钟搞定温湿度显示(附完整代码)
  • 从音箱分频到电源净化:聊聊RLC低通滤波器那些意想不到的实用场景
  • 操作系统概述(4)--操作系统运行机制(1):处理机双重模式与中断
  • FPGA管脚不够用?手把手教你用74HC595级联驱动8位数码管(附Verilog代码与仿真)
  • C++ STL常用函数一览表(快速记忆版本)
  • 多模态协作:文本、图像、语音Agent配合
  • Odrive运动控制实战:用STM32的CAN总线读取电机位置和发送位置指令
  • Perplexity历史资料搜索效率提升300%:实测验证的5步精准检索法(附2024最新API调用参数)
  • 构建AI应用时如何借助Taotoken实现模型的灵活选型与降级
  • 《Linux系统编程》Linux基础开发工具 (三):从零实现动态进度条(附回车、换行与缓冲区详解)
  • TPU核心引擎的‘血管网络’:用RTL仿真动画可视化脉动阵列数据流
  • 顶尖销售都在读什么?这三本书揭示理解客户的奥秘
  • Rockchip设备USB通信协议解析:rkdeveloptool的3种高效调试模式实战指南
  • 动态关节镜导航系统在ACL重建手术中的应用与实现
  • 从芯片上电到Wi-Fi连接:手把手调试ESP32-S3启动全流程(附日志分析)
  • AOCODARC-F7MINI飞控固件编译踩坑记:从‘make arm_sdk_install’失败到成功编译
  • AI时代学习转型
  • 告别MIUI!用PixelExperience给小米8 SE刷上纯净安卓13,体验到底香不香?
  • 射灯轨道灯怎么选?看完这篇不花冤枉钱! 这几家射灯轨道灯公司靠谱吗?老师傅偷偷告诉你! 装修小白必看:射灯轨道灯避坑指南,这家公司口碑最好!
  • Flutter依赖管理完全指南:从pubspec到Flutter Pub