基于事件驱动的websocket简单实现

打印 上一主题 下一主题

主题 995|帖子 995|积分 2985

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x


websocket的实现

什么是websocket?

WebSocket 是一种网络通讯协议,旨在为客户端和服务器之间提供全双工、实时的通讯通道。它是在 HTML5 规范中引入的,可以让浏览器与服务器进行持久化毗连,以便实现低延迟的数据交换。
WebSocket 的特点:

  • 全双工通讯:客户端和服务器可以同时发送和接收消息,而不必等候对方完成操作。
  • 轻量级:相较于传统的 HTTP 协议,WebSocket 头部信息更小,这淘汰了网络开销。
  • 持久毗连:一旦创建毗连,双方可以一直保持这个毗连,直到主动关闭。如许克制了频繁创建和关闭毗连带来的性能消耗。
  • 实时性:适合需要即时数据更新的应用,如在线谈天、游戏、股票行情等

通讯过程

websocket通讯协议是基于http的,客户端首先发送毗连请求request,在该request中包含了基本的HTTP头信息:
  1. GET /chat HTTP/1.1
  2. Host: server.example.com
  3. Upgrade: websocket
  4. Connection: Upgrade
  5. Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
  6. Sec-WebSocket-Version: 13
复制代码
以上这些信息以字符串的形式发送至服务端的rbuffer里,当服务端识别到这些字符串信息后,需要发送相应response进行确认后才能创建websocket毗连。确认信息的response应该如下:
接收到客户端的key->
key与“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”进行拼接,得到新的key-->
使用SHA1算法加密-->
再使用base64加密-->
加上http头信息,以字符串形式的发送至客户端。当客户端收到后,websocket创建。
  1. int response_websock(struct conn *c){
  2.         char* key_head = "Sec-WebSocket-Key";
  3.         char* start = strstr(c->rbuffer, key_head);
  4.         start += 19;
  5.         char key[1024] = {0};
  6.         int set = 0;
  7.         while (*start != '='){
  8.             key[set] = *start;
  9.             start++;
  10.             set++;
  11.         }
  12.         key[set] = '\0';
  13.         char* result = strcat(key, GUID);
  14.         unsigned char hash[SHA_DIGEST_LENGTH] = {0};
  15.         SHA1((unsigned char*)result, strlen(result), hash);
  16.         char* base = base64_encode(hash, SHA_DIGEST_LENGTH);
  17.         //strcpy(c->wbuffer, base) ;
  18.         snprintf(c->wbuffer, sizeof(c->wbuffer), "HTTP/1.1 101 Switching Protocols\r\n"
  19.                                                  "Upgrade: websocket\r\n"
  20.                                                  "Connection: Upgrade\r\n"
  21.                                                  "Sec-WebSocket-Accept: %s\r\n\r\n", base);
  22.         c->wlength = strlen(c->wbuffer);
  23.         free(base);
  24. }
复制代码

创建websocket毗连后,可以互发信息,但是信息是以websocket帧,字节流的形式传送的,所以需要进行编码和解码。
websocket帧结构如下:


发送信息(编码):
  1. int encoding(struct conn *c){
  2.         //写入rbuffer
  3.         int message_len = strlen(payload);
  4.         int frame_len = 0;
  5.         // 设置 FIN 位和 Opcode(文本帧)
  6.         c->wbuffer[0] = 0x81; // FIN=1, Opcode=0x1(文本帧)
  7.         // 设置 Payload Length
  8.         if (message_len <= 125) {
  9.             c->wbuffer[1] = message_len; // 不需要额外长度字段
  10.             memcpy(&c->wbuffer[2], payload, message_len);
  11.             frame_len = 2 + message_len;
  12.         }
  13.         else if(message_len <= 65535){
  14.             c->wbuffer[1] = 126; // 16 位扩展长度
  15.             c->wbuffer[2] = (message_len >> 8) & 0xFF; // 高字节
  16.             c->wbuffer[3] = message_len & 0xFF;        // 低字节
  17.             memcpy(&c->wbuffer[4], payload, message_len);
  18.             frame_len = 4 + message_len;  
  19.         }
  20.         else{
  21.             c->wbuffer[1] = 127; // 64 位扩展长度
  22.             // 这里假设消息长度小于 2^32,因此高 4 字节为 0
  23.             memset(&c->wbuffer[2], 0, 4);
  24.             c->wbuffer[6] = (message_len >> 24) & 0xFF;
  25.             c->wbuffer[7] = (message_len >> 16) & 0xFF;
  26.             c->wbuffer[8] = (message_len >> 8) & 0xFF;
  27.             c->wbuffer[9] = message_len & 0xFF;
  28.             memcpy(&c->wbuffer[10], payload, message_len);
  29.             frame_len = 10 + message_len;
  30.         }
  31. }
复制代码

接收信息(解码):
  1. int encoding(struct conn *c){
  2.         int fin = (c->rbuffer[0] & 0X80) >> 7;
  3.         int opcode = c->rbuffer[0] & 0x0F;              // 操作码
  4.         int masked = (c->rbuffer[1] & 0x80) >> 7;       // 是否有掩码
  5.         int payload_len = c->rbuffer[1] & 0x7F;
  6.         unsigned char *mask = NULL;                 // 掩码键
  7.         unsigned char *payload = NULL;              // 数据指针
  8.         if (payload_len <= 125) {
  9.             mask = &c->rbuffer[2];
  10.             payload = &c->rbuffer[6];
  11.         } else if (payload_len == 126) {
  12.             payload_len = ntohs(*(uint16_t *)&c->rbuffer[2]);
  13.             mask = &c->rbuffer[4];
  14.             payload = &c->rbuffer[8];
  15.         } else if (payload_len == 127) {
  16.             payload_len = ntohl(*(uint64_t *)&c->rbuffer[2]);
  17.             mask = &c->rbuffer[10];
  18.             payload = &c->rbuffer[14];
  19.         }
  20.         for (int i = 0; i < payload_len; i++) {  //解析数据(去除掩码)
  21.             payload[i] ^= mask[i % 4];
  22.         }
  23.         // 输出解码后的消息
  24.         payload[payload_len] = '\0';
  25.         printf("Message from client: %s\n", payload);
  26. }
复制代码

流程总结

由于在创建毗连阶段和通讯阶段发送的数据形式不同,所以需要在结构体中引入状态机,用于记录是哪种请求,根据不同的状态机,做出不同的response。
  1. int ws_request(struct conn *c){
  2.     //判断建立请求连接还是数据帧
  3.     if (strstr(c->rbuffer, "Sec-WebSocket-Key") != NULL) {
  4.         printf("HTTP handshake request detected.\n");
  5.         printf("request: %s", c->rbuffer);
  6.         c->wlength = 0;
  7.         c->status = 0;
  8.     }
  9.     else {
  10.         printf("WebSocket frame detected.\n");
  11.         c->wlength = 0;
  12.         c->status = 1;
  13.     }
  14.     return 0;
  15. }
复制代码
客户端发送request -> 服务端读取数据,判定是请求毗连还是发送websocket帧 ->根据不同status做出相应反应
  1. int ws_response(struct conn *c){
  2.     //返回建立连接
  3.     if(c->status == 0){
  4.         response_websock(xxx);
  5.     }
  6.     else if (c->status == 1){
  7.         //解码
  8.         encoding(xxx);
  9.         
  10.         //编码
  11.         decoding(xxx);
  12.     }
  13.     return c->wlength;
  14. }
复制代码
整体流程如下:
conn_list数组相当于一个用户和内核的中介,用来存放内核创建的毗连以及用于拷贝内核接收到的数据。在websocket时,还额外引入了status的状态。
这个图可以清楚的显示出reactor的优点,即将业务和网络io管理分开。websocket用来实现业务,reactor用来实现网络io的管理。


课程地址:www.github.com/0voice

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

我爱普洱茶

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表