探秘Linux网络设计:Reactor模型与高效http静态服务器构建
1、Reactor简介
Reactor意译“反应堆”,是一种事件驱动机制,程序需要提供回调函数,注册到reactor中。
Reactor模型包含三个重要组件:多路复用器,事件分离器,事件处理器。多路复用器在Linux上一般是select、poll、epoll;事件分离器是调用对应注册的回调函数;事件处理器是回调函数的执行,如读、写数据操作。
一个程序监控多个IO,所有要处理的事件注册到一个中心IO多路复用器epoll上,由epoll进行管理,当epoll检测到一个IO事件到来或准备就绪时,epoll触发相对应的IO事件;reactor就是对所有的事件做成反应堆模式,当事件触发时,调用相对应的回调函数。比如,IO的可读事件调用读数据的回调函数,IO的可写事件调用发送数据回调函数。
每个IO相互独立,都有自己相对应的回调函数,互不影响。
2、Reactor存在的理由
1、epoll是对IO的管理,检测接入的IO,触发IO事件;reactor是对事件的管理,不同的事件调用不同的回调函数;这样带来的好处是每个事件对应不同的回调函数,每个事件数据互不影响。
2、事件封装;对未处理的事件放在独立的buffer中;对于http服务器可以优先处理一部分数据。
3、sendfile()说明
在HTTP服务器中,返回资源文件时可以使用sendfile函数来发送body部分,提高效率。
函数原型:
#include <sys/sendfile.h>
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
说明:
sendfile在一个文件描述符和另一个文件描述符之间复制数据。由于这种复制是在内核中完成的,因此sendfile比读和写的组合更有效,后者需要在用户空间之间传输数据。
参数说明:
参数 | 含义 |
---|---|
out_fd | 为读取而打开的文件描述符,通常是文件的fd |
in_fd | 为写入而打开的文件描述符 |
out_fd | 如果offset不为NULL,则它指向保存文件偏移量的变量,将从该变量开始从in_fd读取数据。如果偏移量为NULL,则从当前文件偏移量开始从in_ fd读取数据,并通过调用更新文件偏移量。 |
count | 在文件描述符之间复制的字节数 |
返回值:
如果传输成功,则返回写入out_ fd的字节数。请注意,成功调用sendfile()可能会写入比请求更少的字节;如果存在未发送的字节,调用方应准备重试调用。传输失败返回负数。
错误代码:
错误码 | 含义 |
---|---|
EAGAIN | 已使用ONONBLOCK选择非阻塞I/O,sendfile需要写入阻塞。 |
EBADF | 未打开输入文件进行读取,或未打开输出文件进行写入。 |
EFAULT | 地址不正确。 |
EINVAL | 描述符无效或已锁定,或者类似mmap的操作不可用于in_fd,或者计数为负。 |
EINVAL | out_ fd设置了O_APPEND。sendfile()当前不支持此操作。 |
EIO | 从in_ fd读取时出现未指定错误。 |
ENOMEM | 内存不足,无法从in_ fd读取。 |
EOVERFLOW | 计数太大,该操作将导致超过输入文件或输出文件的最大大小。 |
ESPIPE | 偏移量不为NULL,但输入文件不可搜索(2)。 |
4、HTTP常用的请求方法
方法 | 含义 |
---|---|
GET | 获取资源 |
POST | 提交数据 |
PUT | 更新数据 |
DELETE | 删除数据 |
5、HTTP静态服务器
HTTP静态服务器可以基于Reactor,实现高并发,reactor的实现可参考上一篇:开启极速之旅:了解Linux网络设计中的Reactor模型与百万级并发实践
5.1 接收数据逻辑 – recv
HTTP服务器接受请求时,不必接收完所有的数据才进行数据解析,可以读一部分数据先进行解析,再解析后面的数据。
可以按行解析,如HTTP请求时,可以先解析GET方法所在行的数据,里面就包含了请求资源的位置。
5.2 发送数据逻辑 – send
HTTP服务器返回数据和接收数据的类似,可以先发送http的头数据,再发送内容。
数据拷贝到自己的buffer,发送数据,发送完重新设置事件,设为读就绪。
5.3 HTTP服务器实现
HTTP请求包中GET所在行包含的数据有三部分:请求方法(GET),请求的资源,协议版本号。
5.3.1、HTTP请求包解析
业务分析是recv完之后的操作,一般recv最好是按行读取数据,方便数据分析和处理。
简单示例:
// 读取一行数据
int readline(char *buffer,int idx,char *linebuffer)
{
int len=strlen(buffer);
for(;idx<len;idx++)
{
if(buffer[idx]=='\r' && buffer[idx+1]=='\n')
return idx+2;
*(linebuffer++)=buffer[i];
}
return -1;
}
int nty_http_request(struct ntyevent *ev/*用于解析的结构体*/)
{
char linebuffer[1024];
readline(ev->buffer,0,linebuffer);
if(strstr(linebuffer,"GET "))
{
ev->method=HTTP_METHOD_GET;//标识为HTTP的GET方法
//解析资源包
int i = strlen("GET ");
while (linebuffer[i] != ' ')
i++;
linebuffer[i] = '\0';
sprintf(ev->resource, "%s%s", HTTP_WEB_ROOT,linebuffer + 4);//将请求资源保存
; }
else if(strstr(linebuffer,"POST "))
{
/*处理POST方法*/
}
return 0;
}
nty_http_request函数在recv之后调用。
5.3.2、HTTP打包发送
HTTP服务器应答需要打包数据,包括HTTP Header和body。在send之前操作,一般将HTTP header和body分开发送。
简单示例:
// http header 打包
int nty_http_response_get_method(struct ntyevent *ev)
{
int len;
int filefd = open(ev->resource, O_RDONLY);
if (filefd == -1)
{
len = sprintf(ev->wbuffer,
"HTTP/1.1 200 OK\r\n"
"Accept-Ranges: bytes\r\n"
"Content-Length: 78\r\n"
"Content-Type: text/html\r\n"
"Date: Sat, 06 Aug 2022 13:16:46 GMT\r\n\r\n"
"<html><head><title>FLY.TestWeb</title></head><body><h1>FLY.</h1><body/></html>");
ev->wlength = len;
}
else
{
struct stat stat_buf;
fstat(filefd, &stat_buf);
close(filefd);
len = sprintf(ev->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: Sat, 06 Aug 2022 13:16:46 GMT\r\n\r\n"
,stat_buf.st_size);
ev->wlength = len;
}
return len;
}
int nty_http_response(struct ntyevent *ev/*用于解析的结构体*/)
{
if(ev->method==HTTP_METHOD_GET)//获取HTTP的请求方法
{
nty_http_response_get_method(ev);//http头打包
}
else if(ev->method==HTTP_METHOD_POST)//获取HTTP的请求方法
{
}
}
// 发送数据回调函数
int send_cb(struct ntyevent *ev)
{
......
send(fd,ev->wbuffer,ev->length,0);//发送http的头数据
int filefd=open(ev->resource,O_RDONLY);//只读方式打开文件
struct stat stat_buff;
// 设置阻塞模式
fstat(filefd,&stat_buff);
int flag = fcntl(fd, F_GETFL, 0);
flag &= ~O_NONBLOCK;
fcntl(fd, F_SETFL, flag);
sendfile(filefd,fd,NULL,st_size);// 发送http body
// 还原非阻塞模式
flag |= O_NONBLOCK;
fcntl(fd, F_SETFL, flag);
close(filefd);
send(fd, "\r\n", 2, 0);
......
}
6 content-type常用类型
参数 | 含义 |
---|---|
text/html | HTML格式 |
application/x-www-form-urlencoded form | 表单数据被编码 |
application/json | JSON数据格式 |
text/plain | 纯文本格式 |
text/xml | XML格式 |
image/gif | gif图片格式 |
image/jpeg | jpg图片格式 |
image/png | png图片格式 |
application/octet-stream | 二进制流数据 |
- 点赞
- 收藏
- 关注作者
评论(0)