2.1网络编程
2.2网络原理
2.3协程库
2.4dpdk
2.5高性能异步io机制
WebServer 是一种软件程序,用于处理客户端(如浏览器)的 HTTP 请求并返回响应。它是互联网的基础设施之一,主要功能包括:
常见的 WebServer 软件:
工作流程:
客户端(浏览器) → HTTP请求 → WebServer → 处理请求 → 返回HTTP响应 → 客户端
WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,由 RFC 6455 定义。它在 HTTP 协议基础上发展而来,但具有以下特点:
Origin
头处理跨域请求。应用场景:
WebSocket 连接通过 HTTP 请求发起,使用 Upgrade
头部升级协议:
客户端请求:
GET /ws HTTP/1.1
Host: example.com
Origin: http://localhost:3000
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
服务器响应:
HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Upgrade: websocket
:告知服务器升级到 WebSocket 协议。Sec-WebSocket-Key
:客户端生成的随机值,用于验证服务器合法性。Sec-WebSocket-Accept
:服务器根据客户端 Key 计算的响应值,确保双方理解 WebSocket 协议。握手成功后,双方通过帧(Frame)格式传输数据:
LT 和 ET 的核心区别
水平触发(LT)
触发条件:只要文件描述符(FD)处于就绪状态(如可读缓冲区有数据),就会持续触发事件。
特性:
边缘触发(ET)
触发条件:仅在 FD 状态变化时触发一次(如数据从无到有)。
特性:
epoll同时支持 LT 和 ET:默认是 LT 模式,通过 EPOLLET
标志可启用 ET 模式。
为什么 ET 模式要求非阻塞 I/O?
阻塞 I/O 与 ET 的矛盾:
read
调用中。正确做法:
fcntl(fd, F_SETFL, O_NONBLOCK)
)。
EAGAIN
(表示缓冲区已空 / 满)。场景 | LT 模式 | ET 模式 |
---|---|---|
编程复杂度 | 低(无需循环处理) | 高(必须循环处理 + 非阻塞 I/O) |
性能 | 中等(可能有冗余通知) | 高(减少系统调用次数) |
适用场景 | 简单应用(如小规模连接) | 高性能服务器(如 Nginx、Redis) |
数据处理要求 | 可部分处理数据 | 必须一次性处理完所有数据 |
适用场景:
水平触发 (LT) :
边缘触发模式下,仅在文件描述符状态变化时触发一次事件,要求应用程序必须一次性处理完所有数据。这种模式适合以下场景:
3. 典型案例对比
应用场景 | 推荐模式 | 理由 |
---|---|---|
Nginx 反向代理 | ET | 需处理数万并发连接,ET 模式可减少系统调用,提升吞吐量。 |
Redis 内存数据库 | ET | 单线程处理大量请求,ET 模式配合非阻塞 I/O 可最大化性能。 |
小型 Python Web 应用 | LT | 基于同步模型,使用阻塞 I/O,LT 模式更易实现。 |
实时视频流服务器 | ET | 需快速处理大量数据,避免缓冲区堆积,ET 模式适合一次性读取完整帧数据。 |
企业内部管理系统 | LT | 连接数少,业务逻辑复杂,LT 模式降低开发难度。 |
4. 选择建议
EAGAIN
。HTTP 是一个基于 TCP/IP 通信协议,在TCP连接,socket连接的基础上来传递数据的协议(首先要建立tcp连接)
GET /hello.txt HTTP/1.1
User-Agent: curl/7.16.3 libcurl/7.16.3 0penssL/0.9.7l zlib/1.2.3
Host: www.example.com
Accept-Language: en, mi
HTTP1.0 定义了三种请求方法:
HTTP1.1 新增了六种请求方法:
HTTP/1.1 200 OK
Date: Mon, 27 Jul 2009 12:28:53 GMT
Server: Apache
Last-Modified: Wed, 22 Jul 2009 19:15:56 GMT
ETag: "34aa387-d-1568eb00"
Accept-Ranges: bytes
Content-Length: 51
Vary: Accept-Encoding
Content-Type: text/plain
使用状态机来管理连接的不同状态,实现对连续数据的分阶段发送
状态 0(默认状态):
连接的初始状态,主要用于初始化和处理连接的基本读写操作。
else if (conn_list[fd].status == 0) {
if (conn_list[fd].wlength != 0) {
count = send(fd, conn_list[fd].wbuffer, conn_list[fd].wlength, 0);
}
set_event(fd, EPOLLIN, 0);
}
在状态 0 下,如果写缓冲区有数据,则发送数据。然后设置事件为EPOLLIN
,监听可读事件。
状态 1(主动发送数据):
当需要主动发送数据时,连接进入状态 1。
if (conn_list[fd].status == 1) {
count = send(fd, conn_list[fd].wbuffer, conn_list[fd].wlength, 0);
set_event(fd, EPOLLOUT, 0);
}
在状态 1 下,发送写缓冲区中的数据,然后设置事件为EPOLLOUT
,继续监听可写事件,以便处理可能的剩余数据。
状态 2(等待发送确认或准备发送):
连接在某些情况下进入状态 2,主要用于等待发送确认或准备发送数据。
else if (conn_list[fd].status == 2) {
set_event(fd, EPOLLOUT, 0);
}
在状态 2 下,不发送数据,仅设置事件为EPOLLOUT
,监听可写事件。
在http_response
函数中状态机的应用:
在http_response
函数中,根据连接的状态分阶段发送 HTTP 响应。
if (c->status == 0) {
c->wlength = sprintf(c->wbuffer,
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n"
"Accept-Ranges: bytes\r\n"
"Content-Length: %ld\r\n"
"Date: Tue, 30 Apr 2024 13:16:46 GMT\r\n\r\n",
stat_buf.st_size);
c->status = 1;
} else if (c->status == 1) {
int ret = sendfile(c->fd, filefd, NULL, stat_buf.st_size);
if (ret == -1) {
printf("errno: %d\n", errno);
}
c->status = 2;
} else if (c->status == 2) {
c->wlength = 0;
memset(c->wbuffer, 0, BUFFER_LENGTH);
c->status = 0;
}
sendfile
发送文件内容,并切换到状态 2。核心逻辑:代码实现了一个简易 HTTP 服务器的请求处理和响应生成功能,支持返回静态 HTML 页面或 PNG 图片,通过状态机管理不同阶段的响应生成,可根据宏定义选择不同的响应模式。
#include
#include
#include
#include
#include
#include
#include
#include "server.h"
// 定义Web服务器的根目录
#define WEBSERVER_ROOTDIR "./"
/**
* 处理HTTP请求
*
* 解析客户端发送的HTTP请求,但当前实现为空,
* 仅清空发送缓冲区并重置连接状态为0
*
* @param c 连接结构体指针,包含请求和响应信息
* @return 始终返回0
*/
int http_request(struct conn *c) {
// 打印请求内容(调试用,当前注释掉)
//printf("request: %s\n", c->rbuffer);
// 清空发送缓冲区并重置长度
memset(c->wbuffer, 0, BUFFER_LENGTH);
c->wlength = 0;
// 设置连接状态为0(初始状态)
c->status = 0;
}
/**
* 生成HTTP响应
*
* 根据预定义的宏选择不同的响应模式:
* 1. 直接返回固定HTML页面
* 2. 读取index.html文件并返回
* 3. 使用sendfile系统调用分阶段发送文件
* 4. 发送PNG图片文件
*
* @param c 连接结构体指针,包含请求和响应信息
* @return 响应数据的长度
*/
int http_response(struct conn *c) {
#if 1
// 模式1:直接返回固定HTML页面
c->wlength = sprintf(c->wbuffer,
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n"
"Accept-Ranges: bytes\r\n"
"Content-Length: 82\r\n"
"Date: Tue, 30 Apr 2024 13:16:46 GMT\r\n\r\n"
"0voice.king King
\r\n\r\n");
#elif 0
// 模式2:读取index.html文件并返回
int filefd = open("index.html", O_RDONLY);
struct stat stat_buf;
fstat(filefd, &stat_buf);
// 构造HTTP响应头
c->wlength = sprintf(c->wbuffer,
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n"
"Accept-Ranges: bytes\r\n"
"Content-Length: %ld\r\n"
"Date: Tue, 30 Apr 2024 13:16:46 GMT\r\n\r\n",
stat_buf.st_size);
// 读取文件内容到缓冲区
int count = read(filefd, c->wbuffer + c->wlength, BUFFER_LENGTH - c->wlength);
c->wlength += count;
close(filefd);
#elif 0
// 模式3:使用sendfile系统调用分阶段发送文件
int filefd = open("index.html", O_RDONLY);
struct stat stat_buf;
fstat(filefd, &stat_buf);
if (c->status == 0) {
// 状态0:构造HTTP响应头
c->wlength = sprintf(c->wbuffer,
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n"
"Accept-Ranges: bytes\r\n"
"Content-Length: %ld\r\n"
"Date: Tue, 30 Apr 2024 13:16:46 GMT\r\n\r\n",
stat_buf.st_size);
c->status = 1;
} else if (c->status == 1) {
// 状态1:使用sendfile发送文件内容
int ret = sendfile(c->fd, filefd, NULL, stat_buf.st_size);
if (ret == -1) {
printf("errno: %d\n", errno);
}
c->status = 2;
} else if (c->status == 2) {
// 状态2:重置缓冲区和状态
c->wlength = 0;
memset(c->wbuffer, 0, BUFFER_LENGTH);
c->status = 0;
}
close(filefd);
#else
// 模式4:发送PNG图片文件(与模式3类似,但发送图片)
int filefd = open("c1000k.png", O_RDONLY);
struct stat stat_buf;
fstat(filefd, &stat_buf);
if (c->status == 0) {
// 状态0:构造HTTP响应头(Content-Type为image/png)
c->wlength = sprintf(c->wbuffer,
"HTTP/1.1 200 OK\r\n"
"Content-Type: image/png\r\n"
"Accept-Ranges: bytes\r\n"
"Content-Length: %ld\r\n"
"Date: Tue, 30 Apr 2024 13:16:46 GMT\r\n\r\n",
stat_buf.st_size);
c->status = 1;
} else if (c->status == 1) {
// 状态1:使用sendfile发送图片内容
int ret = sendfile(c->fd, filefd, NULL, stat_buf.st_size);
if (ret == -1) {
printf("errno: %d\n", errno);
}
c->status = 2;
} else if (c->status == 2) {
// 状态2:重置缓冲区和状态
c->wlength = 0;
memset(c->wbuffer, 0, BUFFER_LENGTH);
c->status = 0;
}
close(filefd);
#endif
return c->wlength;
}
sendfile
系统调用,高效地将文件内容直接发送到套接字,避免用户空间拷贝。c->status
管理响应生成的不同阶段,确保头信息和内容分开发送。sendfile
是一个高效的系统调用,用于在文件描述符之间直接传输数据,避免了用户空间和内核空间之间的数据拷贝,从而显著提高了传输效率。
#include
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
参数说明:
out_fd
:输出文件描述符(如 socket)。in_fd
:输入文件描述符(如打开的文件),必须支持 mmap
(如普通文件)。offset
:文件读取的起始位置(若为 NULL
则从当前位置开始)。count
:传输的最大字节数。模式3使用状态机来管理连接的不同状态,实现对连续数据的分阶段发送
状态 0(默认状态):
连接的初始状态,主要用于初始化和处理连接的基本读写操作。
else if (conn_list[fd].status == 0) {
if (conn_list[fd].wlength != 0) {
count = send(fd, conn_list[fd].wbuffer, conn_list[fd].wlength, 0);
}
set_event(fd, EPOLLIN, 0);
}
在状态 0 下,如果写缓冲区有数据,则发送数据。然后设置事件为EPOLLIN
,监听可读事件。
状态 1(主动发送数据):
当需要主动发送数据时,连接进入状态 1。
if (conn_list[fd].status == 1) {
count = send(fd, conn_list[fd].wbuffer, conn_list[fd].wlength, 0);
set_event(fd, EPOLLOUT, 0);
}
在状态 1 下,发送写缓冲区中的数据,然后设置事件为EPOLLOUT
,继续监听可写事件,以便处理可能的剩余数据。
状态 2(等待发送确认或准备发送):
连接在某些情况下进入状态 2,主要用于等待发送确认或准备发送数据。
else if (conn_list[fd].status == 2) {
set_event(fd, EPOLLOUT, 0);
}
在状态 2 下,不发送数据,仅设置事件为EPOLLOUT
,监听可写事件。
在http_response
函数中状态机的应用:
在http_response
函数中,根据连接的状态分阶段发送 HTTP 响应。
if (c->status == 0) {
c->wlength = sprintf(c->wbuffer,
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n"
"Accept-Ranges: bytes\r\n"
"Content-Length: %ld\r\n"
"Date: Tue, 30 Apr 2024 13:16:46 GMT\r\n\r\n",
stat_buf.st_size);
c->status = 1;
} else if (c->status == 1) {
int ret = sendfile(c->fd, filefd, NULL, stat_buf.st_size);
if (ret == -1) {
printf("errno: %d\n", errno);
}
c->status = 2;
} else if (c->status == 2) {
c->wlength = 0;
memset(c->wbuffer, 0, BUFFER_LENGTH);
c->status = 0;
}
sendfile
发送文件内容,并切换到状态 2。如何在简历里写你的webserver项目,
sudo wrk -c 50 -d 10s -t 10 http://192.168.21.129:2000
核心逻辑: 代码实现了一个基于 OpenSSL 的 WebSocket 协议通信模块,包含握手、数据帧编解码功能,支持不同长度的消息处理及掩码操作。
openssl
开发库: sudo apt install libssl-dev
#include
#include
#include "server.h"
#include
#include
#include
#include
// WebSocket GUID常量,用于握手阶段计算Sec-WebSocket-Accept
#define GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
/*
key: "fUNa6rJwr4/VDpwcgvceYA=="
fUNa6rJwr4/VDpwcgvceYA==258EAFA5-E914-47DA-95CA-C5AB0DC85B11
SHA-1计算后得到20字节结果,再进行base64编码
WebSocket协议处理流程:
1. Handshark: 完成HTTP升级握手
2. Transmission: 数据传输阶段
- decode: 解码客户端消息
- encode: 编码服务端响应
*/
// WebSocket数据帧头部结构定义 - 基础头部
struct _nty_ophdr {
unsigned char opcode:4, // 操作码(4位): 0x01表示文本帧,0x08表示关闭帧
rsv3:1, // 保留位3(1位)
rsv2:1, // 保留位2(1位)
rsv1:1, // 保留位1(1位)
fin:1; // 结束标志(1位): 1表示当前为最后一帧
unsigned char payload_length:7, // 负载长度(7位): 0-125表示实际长度,126/127有特殊含义
mask:1; // 掩码标志(1位): 1表示数据经过掩码处理(客户端发送必须置1)
} __attribute__ ((packed));
// WebSocket数据帧头部结构定义 - 长度为126(2字节)的扩展头部
struct _nty_websocket_head_126 {
unsigned short payload_length; // 实际负载长度(2字节)
char mask_key[4]; // 掩码密钥(4字节)
unsigned char data[8]; // 数据起始位置
} __attribute__ ((packed));
// WebSocket数据帧头部结构定义 - 长度为127(8字节)的扩展头部
struct _nty_websocket_head_127 {
unsigned long long payload_length; // 实际负载长度(8字节)
char mask_key[4]; // 掩码密钥(4字节)
unsigned char data[8]; // 数据起始位置
} __attribute__ ((packed));
// 类型重定义,方便后续使用
typedef struct _nty_websocket_head_127 nty_websocket_head_127;
typedef struct _nty_websocket_head_126 nty_websocket_head_126;
typedef struct _nty_ophdr nty_ophdr;
/**
* 功能: Base64编码函数
* 参数:
* - in_str: 输入字符串
* - in_len: 输入字符串长度
* - out_str: 输出编码结果
* 返回值: 编码后的字符串长度,失败返回-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;
// 创建Base64编码的BIO链
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;
}
/**
* 功能: 从缓冲区读取一行数据(以\r\n结尾)
* 参数:
* - allbuf: 完整缓冲区
* - level: 起始位置
* - linebuf: 输出的行数据
* 返回值: 下一行的起始位置,失败返回-1
*/
int readline(char* allbuf, int level, char* linebuf) {
int len = strlen(allbuf);
for (; level < len; ++level) {
if (allbuf[level] == '\r' && allbuf[level+1] == '\n')
return level + 2;
else
*(linebuf++) = allbuf[level];
}
return -1;
}
/**
* 功能: 应用掩码解密/加密数据(WebSocket协议要求客户端发送的数据必须掩码)
* 参数:
* - data: 待处理数据
* - len: 数据长度
* - mask: 掩码密钥(4字节)
*/
void demask(char *data, int len, char *mask) {
int i;
for (i = 0; i < len; i++)
*(data + i) ^= *(mask + (i % 4)); // 异或操作实现加解密(掩码操作可逆)
}
/**
* 功能: 解码WebSocket数据帧
* 参数:
* - stream: 输入数据流
* - mask: 输出的掩码密钥
* - length: 流长度
* - ret: 输出的负载长度
* 返回值: 指向负载数据的指针
*/
char* decode_packet(unsigned char *stream, char *mask, int length, int *ret) {
nty_ophdr *hdr = (nty_ophdr*)stream;
unsigned char *data = stream + sizeof(nty_ophdr);
int size = 0;
int start = 0;
int i = 0;
// 根据payload_length字段的值判断扩展头部类型
if ((hdr->mask & 0x7F) == 126) {
// 情况1: 负载长度为126,表示实际长度存储在接下来的2字节中
nty_websocket_head_126 *hdr126 = (nty_websocket_head_126*)data;
size = hdr126->payload_length;
// 提取掩码密钥
for (i = 0; i < 4; i++) {
mask[i] = hdr126->mask_key[i];
}
start = 8; // 数据起始位置(基础头部+扩展头部)
} else if ((hdr->mask & 0x7F) == 127) {
// 情况2: 负载长度为127,表示实际长度存储在接下来的8字节中
nty_websocket_head_127 *hdr127 = (nty_websocket_head_127*)data;
size = hdr127->payload_length;
// 提取掩码密钥
for (i = 0; i < 4; i++) {
mask[i] = hdr127->mask_key[i];
}
start = 14; // 数据起始位置(基础头部+扩展头部)
} else {
// 情况3: 负载长度在0-125之间,表示实际长度直接存储在payload_length字段中
size = hdr->payload_length;
// 提取掩码密钥
memcpy(mask, data, 4);
start = 6; // 数据起始位置(基础头部+掩码)
}
*ret = size; // 返回负载长度
demask(stream + start, size, mask); // 应用掩码解密数据
return stream + start; // 返回指向负载数据的指针
}
/**
* 功能: 编码WebSocket数据帧
* 参数:
* - buffer: 输出缓冲区
* - mask: 掩码密钥
* - stream: 输入的负载数据
* - length: 负载长度
* 返回值: 编码后的总长度
*/
int encode_packet(char *buffer, char *mask, char *stream, int length) {
nty_ophdr head = {0};
head.fin = 1; // 设置FIN标志为1,表示这是最后一帧
head.opcode = 1; // 设置操作码为1,表示文本帧
int size = 0;
// 根据负载长度选择合适的头部格式
if (length < 126) {
// 情况1: 负载长度小于126,直接使用基础头部
head.payload_length = length;
memcpy(buffer, &head, sizeof(nty_ophdr));
size = 2; // 基础头部长度
} else if (length < 0xffff) {
// 情况2: 负载长度在126-65535之间,使用2字节扩展头部
nty_websocket_head_126 hdr = {0};
hdr.payload_length = length;
memcpy(hdr.mask_key, mask, 4); // 设置掩码密钥
// 构建完整头部
memcpy(buffer, &head, sizeof(nty_ophdr));
memcpy(buffer + sizeof(nty_ophdr), &hdr, sizeof(nty_websocket_head_126));
size = sizeof(nty_websocket_head_126);
} else {
// 情况3: 负载长度大于65535,使用8字节扩展头部
nty_websocket_head_127 hdr = {0};
hdr.payload_length = length;
memcpy(hdr.mask_key, mask, 4); // 设置掩码密钥
// 构建完整头部
memcpy(buffer, &head, sizeof(nty_ophdr));
memcpy(buffer + sizeof(nty_ophdr), &hdr, sizeof(nty_websocket_head_127));
size = sizeof(nty_websocket_head_127);
}
// 复制负载数据到缓冲区
memcpy(buffer + 2, stream, length);
return length + 2; // 返回总长度
}
// WebSocket握手阶段使用的密钥长度常量
#define WEBSOCK_KEY_LENGTH 19
/**
* 功能: 处理WebSocket握手请求
* 参数:
* - c: 连接结构体指针
* 返回值: 0表示成功
*/
int handshark(struct conn *c) {
char linebuf[1024] = {0};
int idx = 0;
char sec_data[128] = {0};
char sec_accept[32] = {0};
// 逐行解析HTTP请求头
do {
memset(linebuf, 0, 1024);
idx = readline(c->rbuffer, idx, linebuf);
// 查找Sec-WebSocket-Key字段
if (strstr(linebuf, "Sec-WebSocket-Key")) {
// 格式示例: Sec-WebSocket-Key: QWz1vB/77j8J8JcT/qtiLQ==
// 拼接WebSocket GUID
strcat(linebuf, GUID);
// 计算SHA-1哈希
SHA1(linebuf + WEBSOCK_KEY_LENGTH, strlen(linebuf + WEBSOCK_KEY_LENGTH), sec_data);
// 对SHA-1结果进行Base64编码
base64_encode(sec_data, strlen(sec_data), sec_accept);
// 构建WebSocket握手响应
memset(c->wbuffer, 0, BUFFER_LENGTH);
c->wlength = sprintf(c->wbuffer,
"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", c->wbuffer);
break;
}
// 循环直到遇到空行(表示HTTP头结束)
} while ((c->rbuffer[idx] != '\r' || c->rbuffer[idx+1] != '\n') && idx != -1);
return 0;
}
/**
* 功能: 处理WebSocket请求
* 参数:
* - c: 连接结构体指针
* 返回值: 0表示成功
*/
int ws_request(struct conn *c) {
printf("request: %s\n", c->rbuffer);
// 根据连接状态进行不同处理
if (c->status == 0) {
// 状态0: 初始状态,处理握手请求
handshark(c);
c->status = 1; // 更新状态为已握手
} else if (c->status == 1) {
// 状态1: 已握手,处理数据帧
char mask[4] = {0};
int ret = 0;
// 解码数据帧
c->payload = decode_packet(c->rbuffer, c->mask, c->rlength, &ret);
printf("data : %s , length : %d\n", c->payload, ret);
c->wlength = ret; // 设置响应长度
c->status = 2; // 更新状态为待响应
}
return 0;
}
/**
* 功能: 处理WebSocket响应
* 参数:
* - c: 连接结构体指针
* 返回值: 0表示成功
*/
int ws_response(struct conn *c) {
if (c->status == 2) {
// 状态2: 待响应,编码并发送响应
c->wlength = encode_packet(c->wbuffer, c->mask, c->payload, c->wlength);
c->status = 1; // 更新状态为已握手,继续等待下一个请求
}
return 0;
}
代码实现了 WebSocket 协议的核心功能,主要包括:
编译时记得链接 openssl
库,sudo gcc reactor.c webserver.c websocket.c -o websocket -lssl -lcrypto
https://github.com/0voice