文章目录
目录
前言
本文根据本系列上一篇文章《网络编程(六)》的代码改进而来,为了便于理解,可以先移步上一篇文章。
一、http服务器是什么?
HTTP 服务器 是一种用于处理和响应 HTTP 请求的服务器,它是基于 HTTP 协议(超文本传输协议)工作的。HTTP 服务器接受客户端(通常是浏览器或其他客户端应用程序)发起的请求,并返回所请求的资源或信息。
HTTP 服务器的基本功能:
-
接收请求:HTTP 服务器监听来自客户端的 HTTP 请求。这些请求通常是通过浏览器或其他客户端发起的,包含了客户端所请求的资源信息(如网页、图片、文件等)。
-
处理请求:服务器根据请求的内容、请求头和请求方法,判断客户端请求的资源是静态的(如 HTML 文件、图片)还是动态的(如数据库查询结果、处理后的页面内容)。服务器还会根据请求的 URL 路径和查询参数来决定如何处理。
-
返回响应:服务器根据客户端的请求,生成相应的 HTTP 响应,通常包括:
- 状态码(例如:200 OK 表示成功,404 Not Found 表示资源未找到,500 Internal Server Error 表示服务器错误)
- 响应头(如 Content-Type、Date 等信息)
- 响应体(即实际返回的内容,例如 HTML 页面、JSON 数据、图片等)
-
支持静态和动态内容:
- 静态内容:例如存储在服务器上的 HTML 文件、CSS 文件、图片、视频等资源。
- 动态内容:通过服务器端脚本(如 PHP、Python、Node.js 等)动态生成的页面或数据,通常是与数据库进行交互并返回生成的内容。
HTTP 服务器的工作流程:
- 客户端(如浏览器)向服务器发送 HTTP 请求。
- 服务器接收并解析请求,查看请求的路径、方法、参数等。
- 根据请求的内容,服务器获取资源或调用后端程序生成动态内容。
- 服务器返回一个 HTTP 响应,包含相应的状态码、头部信息和响应体。
- 客户端(浏览器)接收到响应并显示结果。
HTTP 服务器的类型:
- 静态服务器:只提供静态资源(如 HTML 文件、CSS、JS 文件、图片等)的服务。
- 动态服务器:处理客户端请求并生成动态内容。通常与后端语言(如 PHP、Python、Ruby、Java 等)结合使用。
- 反向代理服务器:将客户端请求转发给其他服务器处理,常用于负载均衡和提高可用性。
- API 服务器:专门处理 API 请求,通常返回 JSON 或 XML 格式的数据,广泛用于前后端分离的应用。
HTTP 请求和响应的示例:
HTTP 请求示例:
GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0
Accept: text/html
HTTP 响应示例:
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Content-Length: 132<html>
<head><title>Example</title></head>
<body><h1>Hello, world!</h1></body>
</html>
总结:
- HTTP 服务器 是一个应用程序,负责接受和处理来自客户端的 HTTP 请求,并返回相应的 HTTP 响应。
- 它的主要功能是提供静态和动态资源,支持客户端的访问请求。
- 常见的 HTTP 服务器包括 Apache、Nginx、LiteSpeed、Node.js 等。
- HTTP 服务器是 Web 应用的核心组件,用于实现客户端与服务器之间的通信。
二、websocket服务器是什么
WebSocket 服务器 是一种专门用于处理 WebSocket 协议连接的服务器,它支持全双工、实时、持久化的双向通信。这使得 WebSocket 服务器与传统的基于 HTTP 协议的服务器有所不同,后者通常是请求-响应模式,每次交互都需要重新建立连接。
WebSocket 服务器的主要特点:
1.双向通信:
- WebSocket 提供了 全双工通信,这意味着客户端和服务器可以在任何时候彼此发送数据,不必等待对方的请求或响应。与 HTTP 协议的请求-响应模型不同,WebSocket 允许服务器主动向客户端发送数据。
2.持久化连接:
- 在 WebSocket 协议中,一旦建立了连接,客户端和服务器之间的连接将保持打开状态,直到显式关闭。这样,服务器和客户端之间可以维持一个长期有效的连接,避免了 HTTP 请求/响应模式中每次都需要建立连接的开销。
3.低延迟:
- WebSocket 的持久连接和双向通信特性使得它比 HTTP 更适合处理需要低延迟和实时交互的应用场景,如在线游戏、聊天应用、实时金融数据等。
4.节省资源:
- 在传统的 HTTP 协议中,每次客户端向服务器发送请求时,都需要建立和关闭一个连接,而 WebSocket 协议只需要在初始握手时建立连接,后续的数据交换通过同一个连接进行。这使得 WebSocket 在高频次消息交换的应用中更为高效。
WebSocket 协议的握手过程:
1.客户端发起握手请求:
- 客户端向服务器发送一个 HTTP 请求,带有特定的
Sec-WebSocket-Key
头和其他 WebSocket 相关的头部,表示希望与服务器建立 WebSocket 连接。
请求示例:
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlbG9vb3B5Y29tcGxleG9u
Sec-WebSocket-Version: 13
2.服务器接受握手并建立连接:
- 如果服务器支持 WebSocket 协议,它会返回一个 HTTP 101 状态码("Switching Protocols"),并且在响应头中包含
Sec-WebSocket-Accept
头。此时,WebSocket 连接被成功建立,之后的通信就不再使用 HTTP,而是通过 WebSocket 协议进行。
响应示例:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGz7wF3QXAmQK6vKpmkGk0cQ==
3.通信过程:
- 连接建立后,客户端和服务器之间的通信将使用 WebSocket 协议进行,而不再是 HTTP 协议。此时,双方可以随时互相发送消息。
4.关闭连接:
- WebSocket 连接可以由任何一方(客户端或服务器)发起关闭,通过发送一个控制帧来完成关闭过程。
WebSocket 服务器的常见用途:
1.即时通讯应用:
如在线聊天、即时消息(IM)系统,通过 WebSocket,消息可以实时传输,不需要不断的轮询。
2.在线游戏:
WebSocket 使得多人在线游戏能够实时交换状态信息和动作。
3.实时金融数据:
金融市场中的实时股价、汇率等数据可以通过 WebSocket 推送给客户端。
4.协作工具:
像 Google Docs、GitHub 等在线协作工具使用 WebSocket 进行实时的多用户协作和更新。
5.物联网(IoT):
WebSocket 可用于实时数据交换,适用于物联网设备的通信。
总结:
- WebSocket 服务器 是一种专门处理 WebSocket 协议连接的服务器,支持持久连接、低延迟、全双工通信。
- 它通过 WebSocket 握手 与客户端建立连接,在后续的通信中可以实时、双向交换数据,适用于实时应用场景。
- 常见的使用场景包括 即时通讯、在线游戏、金融数据、物联网 等。
三、源码展示
reactor.c
#include <errno.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <poll.h>
#include <sys/epoll.h>
#include <errno.h>
#include <sys/time.h>
#include "server.h"
#define CONNECTION_SIZE 1048576 // 1024 * 1024
#define MAX_PORTS 20
#define TIME_SUB_MS(tv1, tv2) ((tv1.tv_sec - tv2.tv_sec) * 1000 + (tv1.tv_usec - tv2.tv_usec) / 1000)
int accept_cb(int fd);
int recv_cb(int fd);
int send_cb(int fd);
int epfd = 0;
struct timeval begin;
struct conn conn_list[CONNECTION_SIZE] = {0};
// fd
int set_event(int fd, int event, int flag) {
if (flag) { // non-zero add
struct epoll_event ev;
ev.events = event;
ev.data.fd = fd;
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
} else { // zero mod
struct epoll_event ev;
ev.events = event;
ev.data.fd = fd;
epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);
}
}
int event_register(int fd, int event) {
if (fd < 0) return -1;
conn_list[fd].fd = fd;
conn_list[fd].r_action.recv_callback = recv_cb;
conn_list[fd].send_callback = send_cb;
memset(conn_list[fd].rbuffer, 0, BUFFER_LENGTH);
conn_list[fd].rlength = 0;
memset(conn_list[fd].wbuffer, 0, BUFFER_LENGTH);
conn_list[fd].wlength = 0;
set_event(fd, event, 1);
}
// listenfd(sockfd) --> EPOLLIN --> accept_cb
int accept_cb(int fd) {
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
int clientfd = accept(fd, (struct sockaddr*)&clientaddr, &len);
//printf("accept finshed: %d\n", clientfd);
if (clientfd < 0) {
printf("accept errno: %d --> %s\n", errno, strerror(errno));
return -1;
}
event_register(clientfd, EPOLLIN); // | EPOLLET
if ((clientfd % 1000) == 0) {
struct timeval current;
gettimeofday(¤t, NULL);
int time_used = TIME_SUB_MS(current, begin);
memcpy(&begin, ¤t, sizeof(struct timeval));
printf("accept finshed: %d, time_used: %d\n", clientfd, time_used);
}
return 0;
}
int recv_cb(int fd) {
memset(conn_list[fd].rbuffer, 0, BUFFER_LENGTH );
int count = recv(fd, conn_list[fd].rbuffer, BUFFER_LENGTH, 0);
if (count == 0) { // disconnect
printf("client disconnect: %d\n", fd);
close(fd);
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL); // unfinished
return 0;
} else if (count < 0) { //
printf("count: %d, errno: %d, %s\n", count, errno, strerror(errno));
close(fd);
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
return 0;
}
conn_list[fd].rlength = count;
//printf("RECV: %s\n", conn_list[fd].rbuffer);
#if 0 // echo
conn_list[fd].wlength = conn_list[fd].rlength;
memcpy(conn_list[fd].wbuffer, conn_list[fd].rbuffer, conn_list[fd].wlength);
printf("[%d]RECV: %s\n", conn_list[fd].rlength, conn_list[fd].rbuffer);
#elif 0
http_request(&conn_list[fd]);
#else
ws_request(&conn_list[fd]);
#endif
set_event(fd, EPOLLOUT, 0);
return count;
}
int send_cb(int fd) {
#if 0
http_response(&conn_list[fd]);
#else
ws_response(&conn_list[fd]);
#endif
int count = 0;
#if 0
if (conn_list[fd].status == 1) {
//printf("SEND: %s\n", conn_list[fd].wbuffer);
count = send(fd, conn_list[fd].wbuffer, conn_list[fd].wlength, 0);
set_event(fd, EPOLLOUT, 0);
} else if (conn_list[fd].status == 2) {
set_event(fd, EPOLLOUT, 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);
}
#else
if (conn_list[fd].wlength != 0) {
count = send(fd, conn_list[fd].wbuffer, conn_list[fd].wlength, 0);
}
set_event(fd, EPOLLIN, 0);
#endif
//set_event(fd, EPOLLOUT, 0);
return count;
}
int init_server(unsigned short port) {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0
servaddr.sin_port = htons(port); // 0-1023,
if (-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))) {
printf("bind failed: %s\n", strerror(errno));
}
listen(sockfd, 10);
//printf("listen finshed: %d\n", sockfd); // 3
return sockfd;
}
int main() {
unsigned short port = 2000;
epfd = epoll_create(1);
int i = 0;
for (i = 0;i < MAX_PORTS;i ++) {
int sockfd = init_server(port + i);
conn_list[sockfd].fd = sockfd;
conn_list[sockfd].r_action.recv_callback = accept_cb;
set_event(sockfd, EPOLLIN, 1);
}
gettimeofday(&begin, NULL);
while (1) { // mainloop
struct epoll_event events[1024] = {0};
int nready = epoll_wait(epfd, events, 1024, -1);
int i = 0;
for (i = 0;i < nready;i ++) {
int connfd = events[i].data.fd;
#if 0
if (events[i].events & EPOLLIN) {
conn_list[connfd].r_action.recv_callback(connfd);
} else if (events[i].events & EPOLLOUT) {
conn_list[connfd].send_callback(connfd);
}
#else
if (events[i].events & EPOLLIN) {
conn_list[connfd].r_action.recv_callback(connfd);
}
if (events[i].events & EPOLLOUT) {
conn_list[connfd].send_callback(connfd);
}
#endif
}
}
}
server.h
#ifndef __SERVER_H__
#define __SERVER_H__
#define BUFFER_LENGTH 1024
typedef int (*RCALLBACK)(int fd);
struct conn {
int fd;
char rbuffer[BUFFER_LENGTH];
int rlength;
char wbuffer[BUFFER_LENGTH];
int wlength;
RCALLBACK send_callback;
union {
RCALLBACK recv_callback;
RCALLBACK accept_callback;
} r_action;
int status;
#if 1 // websocket
char *payload;
char mask[4];
#endif
};
int http_request(struct conn *c);
int http_response(struct conn *c);
int ws_request(struct conn *c);
int ws_response(struct conn *c);
#endif
server.c
#include <errno.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <poll.h>
#include <sys/epoll.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/sendfile.h>
#include <openssl/sha.h>
#include <openssl/pem.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
#include "reactor.h"
int http_request(struct conn *c) {
memset(c->wbuffer, 0, BUFFER_LENGTH);
c->wlength = 0;
c->status = 0;
return 0;
}
int http_response(struct conn *c) {
//Tue Apr 30 06:41:52 AM UTC 2024
#if 0
c->wlength = sprintf(c->wbuffer,
"HTTP/1.1 200 OK\r\n"
"Accept-Ranges: bytes\r\n"
"Content-Length: 82\r\n"
"Content-Type: text/html\r\n"
"Date: Tue, 30 Apr 2024 13:16:46 GMT\r\n\r\n"
"<html><head><title>0voice.king</title></head><body><h1>King</h1></body></html>\r\n\r\n");
#elif 0
int filefd = open("index.html", O_RDONLY);
struct stat stat_buf;
fstat(filefd, &stat_buf);
c->wlength = sprintf(c->wbuffer,
"HTTP/1.1 200 OK\r\n"
"Accept-Ranges: bytes\r\n"
"Content-Length: %ld\r\n"
"Content-Type: text/html\r\n"
"Date: Tue, 30 Apr 2024 13:16:46 GMT\r\n\r\n",
stat_buf.st_size);
//printf("header: %s\n", c->wbuffer);
int count = read(filefd, c->wbuffer + c->wlength, BUFFER_LENGTH - c->wlength);
if (count < 0) {
printf("read: %d, errno: %d, %s\n", filefd, errno, strerror(errno));
}
c->wlength += count;
close(filefd);
//printf("body: %s\n", c->wbuffer);
#elif 0
int filefd = open("index.html", O_RDONLY);
struct stat stat_buf;
fstat(filefd, &stat_buf);
if (c->status == 0) {
c->wlength = sprintf(c->wbuffer,
"HTTP/1.1 200 OK\r\n"
"Accept-Ranges: bytes\r\n"
"Content-Length: %ld\r\n"
"Content-Type: text/html\r\n"
"Date: Tue, 30 Apr 2024 13:16:46 GMT\r\n\r\n",
stat_buf.st_size);
c->status = 1;
printf("header: %s\n", c->wbuffer);
} else {
int ret = sendfile(c->fd, filefd, NULL, stat_buf.st_size);
if (ret == -1) {
printf("errno: %d\n", errno);
}
c->status = 0;
printf("sendfile\n");
c->wlength = 0;
}
close(filefd);
#else
int filefd = open("c1000k.png", O_RDONLY);
struct stat stat_buf;
fstat(filefd, &stat_buf);
if (c->status == 0) {
c->wlength = sprintf(c->wbuffer,
"HTTP/1.1 200 OK\r\n"
"Accept-Ranges: bytes\r\n"
"Content-Length: %ld\r\n"
"Content-Type: image/png\r\n"
"Date: Tue, 30 Apr 2024 13:16:46 GMT\r\n\r\n",
stat_buf.st_size);
c->status = 1;
printf("header: %s\n", c->wbuffer);
} else {
int ret = sendfile(c->fd, filefd, NULL, stat_buf.st_size);
if (ret == -1) {
printf("errno: %d\n", errno);
}
c->status = 0;
printf("sendfile\n");
c->wlength = 0;
}
close(filefd);
#endif
return c->wlength;
}
// websocket
#define GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
struct _nty_ophdr {
unsigned char opcode:4,
rsv3:1,
rsv2:1,
rsv1:1,
fin:1;
unsigned char payload_length:7,
mask:1;
} __attribute__ ((packed));
struct _nty_websocket_head_126 {
unsigned short payload_length;
char mask_key[4];
unsigned char data[8];
} __attribute__ ((packed));
struct _nty_websocket_head_127 {
unsigned long long payload_length;
char mask_key[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;
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 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;
}
void demask(char *data,int len,char *mask){
int i;
for (i = 0;i < len;i ++)
*(data+i) ^= *(mask+(i%4));
}
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;
//char mask[4] = {0};
int i = 0;
if ((hdr->mask & 0x7F) == 126) {
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) {
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 {
size = hdr->payload_length;
memcpy(mask, data, 4);
start = 6;
}
*ret = size;
demask(stream+start, size, mask);
return stream + start;
}
int encode_packet(char *buffer,char *mask, char *stream, int length) {
nty_ophdr head = {0};
head.fin = 1;
head.opcode = 1;
int size = 0;
if (length < 126) {
head.payload_length = length;
memcpy(buffer, &head, sizeof(nty_ophdr));
size = 2;
} else if (length < 0xffff) {
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 {
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;
}
#define WEBSOCK_KEY_LENGTH 19
int handshark(struct conn *c) {
//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(c->rbuffer, 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(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;
}
} while((c->rbuffer[idx] != '\r' || c->rbuffer[idx+1] != '\n') && idx != -1 );
return 0;
}
int ws_request(struct conn *c) {
if (c->status == 0) {
handshark(c);
c->status = 1;
} else if (c->status == 1) {
char mask[4] = {0};
int ret = 0;
char *data = decode_packet(c->rbuffer, mask, c->rlength, &ret);
printf("data : %s , length : %d\n", data, ret);
ret = encode_packet(c->wbuffer, mask, data, ret);
c->wlength = ret;
}
return 0;
}
int ws_response(struct conn *c) {
return 0;
}
reactor.h
#ifndef __REACTOR_H__
#define __REACTOR_H__
#define BUFFER_LENGTH 1024
typedef int (*RCALLBACK)(int fd);
struct conn {
int fd;
char rbuffer[BUFFER_LENGTH];
int rlength;
char wbuffer[BUFFER_LENGTH];
int wlength;
RCALLBACK send_callback;
union {
RCALLBACK recv_callback;
RCALLBACK accept_callback;
} r_action;
int status; // { header, body}
};
#endif
编译指令
gcc -o server reactor.c server.c -lssl -lcrypto -mcmodel=medium
编译结果如下图所示即为成功编译
四、代码分析
(一)整体架构
这是一个基于epoll
事件驱动的高性能服务器程序,可以同时处理HTTP和WebSocket协议。主要特点如下:
-
多端口监听:同时监听20个连续端口(2000-2019)
-
高并发处理:使用epoll实现IO多路复用,支持百万级连接
-
模块化设计:通过回调函数处理不同事件(接收连接、读取数据、发送数据)
-
协议支持:同时支持HTTP和WebSocket协议
(二)核心数据结构
struct conn {
int fd; // 文件描述符
char rbuffer[1024]; // 读缓冲区
int rlength; // 已读数据长度
char wbuffer[1024]; // 写缓冲区
int wlength; // 待写数据长度
int (*send_callback)(int); // 发送回调函数
union {
int (*recv_callback)(int); // 普通连接的读回调
int (*accept_callback)(int); // 监听socket的接受回调
} r_action;
int status; // 连接状态
};
这个结构体为每个连接保存了所有必要信息,就像给每个客人分配了一个专属服务员,记录客人的所有需求。
(三)工作流程
1. 初始化阶段
int main() {
// 创建epoll实例
epfd = epoll_create(1);
// 创建20个监听端口
for (i = 0; i < MAX_PORTS; i++) {
int sockfd = init_server(port + i);
// 注册监听socket到epoll
set_event(sockfd, EPOLLIN, 1);
}
// 事件循环
while (1) {
int nready = epoll_wait(...);
// 处理每个就绪的事件
}
}
就像开了一家20个接待窗口的银行,每个窗口都有一个接待员(监听socket)。
2. 接受连接
int accept_cb(int fd) {
int clientfd = accept(...);
event_register(clientfd, EPOLLIN);
}
当有新客户到来时:
-
接待员(监听socket)通过
accept
发放排队号码(clientfd) -
给新客户分配专属服务员(注册到epoll)
-
开始等待客户的具体业务需求(监听EPOLLIN事件)
3. 处理数据
int recv_cb(int fd) {
// 读取客户端数据
int count = recv(...);
// 根据协议类型处理
if (WebSocket模式) {
ws_request(...);
} else {
http_request(...);
}
// 准备回复
set_event(fd, EPOLLOUT, 0);
}
就像服务员拿到客户的业务单据:
-
读取客户需求
-
根据业务类型(HTTP/WebSocket)进行处理
-
准备回复材料
4. 发送回复
int send_cb(int fd) {
if (WebSocket模式) {
ws_response(...);
} else {
http_response(...);
}
send(...);
// 切换回等待读取状态
set_event(fd, EPOLLIN, 0);
}
服务员将处理结果反馈给客户后,继续等待客户的下一个需求。
(四)关键协议处理
HTTP协议处理
int http_response(struct conn *c) {
// 构造HTTP头
sprintf(c->wbuffer, "HTTP/1.1 200 OK\r\nContent-Length: ...");
// 发送文件内容
sendfile(c->fd, filefd, ...);
}
典型的HTTP响应流程:
-
构造响应头
-
发送文件内容(使用sendfile高效传输)
WebSocket协议处理
int ws_request(struct conn *c) {
if (握手阶段) {
// 生成加密响应
SHA1(...);
base64_encode(...);
} else {
// 处理数据帧
decode_packet(...);
encode_packet(...);
}
}
WebSocket处理关键点:
-
握手阶段使用SHA1和Base64加密验证
-
数据帧需要处理掩码和长度编码
-
支持分片传输(FIN标志位)
(五)重要技术细节
1.epoll使用:
EPOLLIN
:数据可读事件EPOLLOUT
:数据可写事件- 边缘触发(ET) vs 水平触发(LT):本代码使用水平触发
2.高效文件传输:
sendfile(c->fd, filefd, ...);
- 直接在操作系统内核完成文件传输,无需用户空间拷贝
Q:sendfile
有什么优势?
A:零拷贝技术,减少数据在内核态和用户态的复制次数
3.WebSocket帧结构:
struct _nty_ophdr {
unsigned char opcode:4,
fin:1;
unsigned char payload_length:7,
mask:1;
};
处理各种长度的数据帧:
-
普通帧(<126字节)
-
扩展帧(126/127字节长度头)
总结
总结需要讲清楚一个事情,那就是这篇文章体现了reactor设计模式的一个优势:网络与业务隔离
Reactor 设计模式 可以通过其 事件驱动 的机制有效地实现 网络层和业务逻辑层的隔离。这一点在开发高性能网络应用时尤为重要,因为它可以简化代码结构,提高系统的可维护性,并减少不同层次之间的耦合度。
1. 网络层和业务层的定义:
- 网络层:负责处理所有与网络相关的任务,例如监听连接、接收和发送数据等。这些操作通常是 I/O 密集型的,并且可能会有大量的并发请求。
- 业务层:负责处理应用程序的具体业务逻辑,例如处理用户请求、计算、存储和返回数据等。这些操作通常是 CPU 密集型的,并且依赖于特定的业务需求。
2. Reactor 模式如何实现隔离:
Reactor 模式通过 事件驱动机制 将网络 I/O 操作和业务逻辑的处理分开,避免了它们之间的直接耦合。具体来说,Reactor 模式的核心是一个 事件循环,它负责监听和分发事件。
事件循环(Event Loop)
- Reactor 模式的核心是一个事件循环,它会监听多个 I/O 事件(例如网络连接的到达、数据的读取或写入等)。当事件发生时,事件循环将这些事件分发给相应的 事件处理器。
I/O 多路复用(I/O Multiplexing)
- 使用 I/O 多路复用技术(如 epoll、select、poll),Reactor 模式可以在单个线程中高效地处理多个 I/O 操作,而不需要为每个连接创建一个线程或进程。
事件处理器(Event Handlers)
- 事件处理器是 Reactor 模式中的核心组件,它们会针对不同的 I/O 事件执行相应的操作(例如,处理接收到的数据或发送数据)。在 Reactor 模式中,网络事件处理和业务逻辑处理是分开的。
- 网络事件处理:比如客户端连接建立、数据接收等网络相关操作,这些通常由专门的 I/O 处理器来处理。
- 业务逻辑处理:一旦数据被接收到,业务层逻辑会通过事件处理回调来处理这些数据。业务层代码并不会直接关心网络层的细节(如如何接收数据、如何发送数据),而只关心如何根据接收到的数据做出相应的业务处理。
如何实现隔离:
- I/O 事件分发:网络层的 I/O 事件(如连接请求、数据到达等)由 Reactor(事件循环)管理,它会将这些事件分发给特定的 事件处理器。这些处理器只关心如何处理网络事件,例如接收数据、建立连接等。
- 回调机制:事件处理器通常使用 回调函数 来通知业务层执行特定的操作。当网络事件处理完成(例如数据被接收到),网络层会调用相应的业务逻辑处理函数(回调),而业务层则负责对这些数据进行处理和响应。
- 代码解耦:通过这种方式,网络层和业务层可以完全解耦。网络层只负责监听和处理网络 I/O 操作,业务层只负责处理实际的业务逻辑。两者之间没有直接的依赖关系,这样就实现了 网络和业务的隔离。
示例:
假设你正在构建一个 Web 服务器:
- 网络层:负责监听传入的 HTTP 请求、解析 HTTP 请求头、从客户端读取数据等。
- 业务层:在接收到 HTTP 请求后,解析请求内容并返回适当的响应内容,可能涉及到数据库查询或业务计算。
在 Reactor 模式下,网络层(Reactor)负责处理客户端的连接和数据接收事件,当收到请求时,它会将请求传递给 HTTP 请求处理器。这个处理器只负责解析请求并将请求的数据交给 业务层 进行进一步处理。这样,网络和业务逻辑被清晰地分开,网络层不关心业务细节,业务层不关心网络通信的细节。
3. 优点:
- 代码解耦:通过将网络操作和业务逻辑分离,系统的可维护性大大增强。你可以独立地修改网络层或业务层,而不影响另一个层。
- 灵活性:你可以根据需求更改或扩展网络层和业务层。例如,如果你想将系统迁移到不同的网络框架或协议,只需修改网络层部分即可。
- 清晰的结构:每个模块(网络层和业务层)职责明确,避免了复杂的代码交织,使得程序结构更加清晰。
总结:
Reactor 设计模式能够通过 事件驱动和回调机制 实现 网络层和业务层的隔离,即网络层只负责接收、发送数据和管理连接,而业务层则专注于处理业务逻辑。这样设计的优点是 解耦、灵活性 和 清晰的代码结构,使得系统更加容易扩展和维护。