网站首页的动态视频怎么做的,今天济南刚刚发生的新闻,网站开发动静分离实践,晋江网站有什么职业做http服务器的实现 本文使用上一篇博文实现的epollreactor百万并发的服务器实现了一个使用http协议和WebSocket协议的WebServer。 完整代码请看我的github项目
1. 水平触发(Level Trigger)与边沿触发(Edge Trigger)
1.1 水平触发
水平触发是一种状态驱动机制。当文件描述符reactor百万并发的服务器实现了一个使用http协议和WebSocket协议的WebServer。 完整代码请看我的github项目
1. 水平触发(Level Trigger)与边沿触发(Edge Trigger)
1.1 水平触发
水平触发是一种状态驱动机制。当文件描述符如套接字处于可读或可写状态时内核会持续通知应用程序直到应用程序处理完所有数据或资源。
优点 容易编写通常可以简单处理因为内核会持续通知应用程序事件。 不容易丢失事件通知。
缺点
对于高并发场景水平触发可能会造成不必要的系统调用。因为即使数据或资源已经读取过内核还是会通知文件描述符仍然处于可读/可写状态。
使用场景
典型的阻塞式 I/O 使用水平触发较为合适。适用于那些可以容忍一定的事件重复通知的应用程序。
1.2 边沿触发
边沿触发是一种状态变化驱动机制。只有当文件描述符的状态从不可读/不可写到可读/可写时内核才会通知应用程序。ET 只在状态变化的那一刻通知不会持续通知。
优点
触发次数更少减少了系统调用开销适合高性能、高并发场景。
缺点
容易出现遗漏事件的情况。应用程序需要一次性读取或写入尽可能多的数据以确保没有遗漏。实现更为复杂。
使用场景
非阻塞IO通常配合边沿触发使用以避免阻塞和提高性能。边沿触发适用于高并发、追求性能的场景。如果数据包大小变化较大适合使用边沿触发。
2. httpserver
2.1 调整内核tcp缓冲区大小 如果文件块太大而用户层buffer太小或者内核tcp缓冲区太小会导致需要多次发送从而导致发送速度变慢。
可以尝试扩大TCP缓冲区在/etc/sysctl.conf中设置
net.ipv4.tcp_wmem 8192 8192 16384
net.ipv4.tcp_rmem 8192 8192 163842.2 IO层和协议层
IO层包含负责管理IO事件的epoll和进行事件处理的reactor。
协议层就是实现http请求处理和发送http响应的函数。
2.3 使用状态机保存连接状态信息
可以在连接中保存一个status字段表示当前连接的状态当status为0表示还没有发送任何信息为1表示已经发送了头部正在发送文件块为2表示已经全部发送完毕。
显然我们需要在status为1时将整个文件分块发送因此就需要保存该文件描述符的上下文信息。
2.4 分块发送大文件保存被发送文件的上下文信息
大文件传输中显然不能一次性把整个文件读出然后写入用户缓冲区再写入内核缓冲区。我们需要把文件分块利用水平触发分多次写入这样就绪要在connection中保存当前文件描述符在status为0时打开文件在status为2时关闭文件。
2.5 可选择使用sendfile函数减少内存复制
senfile函数可以在两个文件描述符之间直接传输数据数据流不需要经过用户空间。它利用mmap指令直接将文件内容读取到系统缓冲区因此性能更好。
缺点是由于不经过用户空间无法对文件分块发送在阻塞IO模式下发送大文件可能长时间陷入阻塞。在非阻塞IO模式下尽管不会陷入阻塞但会可能导致其他连接饥饿。
2.6 性能测试qps
wrk是一款针对 Http 协议的基准测试工具它能够在单机多核 CPU 的条件下使用系统自带的高性能 I/O 机制如 epollkqueue 等通过多线程和事件模式对目标机器产生大量的负载。
下载wrk。
这篇文章详细介绍了如何安装和使用wrk进行性能测试。
特点
轻量级简单易用只用于单机压测
测试结果:
对于每个http请求都返回一个738KB大小的图片测试结果如下
(base) fylia431:~/programs/sockets/course1 network_programs$ wrk -t12 -c400 -d30s http://localhost:2000
Running 30s test http://localhost:200012 threads and 400 connectionsThread Stats Avg Stdev Max /- StdevLatency 17.69ms 14.40ms 1.68s 99.92%Req/Sec 143.45 153.46 600.00 83.31%25494 requests in 30.10s, 17.80GB readSocket errors: connect 0, read 25499, write 0, timeout 1
Requests/sec: 847.08
Transfer/sec: 605.61MB可以看到qps是847.08
对于每个http请求都返回一个600字节的html文件测试结果如下
(base) fylia431:~/programs/sockets/course1 network_programs$ wrk -c400 -t12 -d30 http://localhost:2000
Running 30s test http://localhost:200012 threads and 400 connectionsThread Stats Avg Stdev Max /- StdevLatency 2.72ms 33.82ms 1.79s 99.38%Req/Sec 1.62k 1.02k 6.07k 72.66%461290 requests in 30.09s, 318.94MB readSocket errors: connect 0, read 461294, write 0, timeout 21
Requests/sec: 15327.85
Transfer/sec: 10.60MB可以看到因为数据传输量变少qps上升到了15327
2.7 代码实现
这里只展现了协议层和业务层的代码IO层和事件回调的底层代码请看完整项目reactor.c。
webserver.h
#pragma once#include stdio.h#define BUFFER_LENGTH 819200
#define CONNECTION_LENGTH 256
#define READY_LENFTH 1024
#define PORT_NUM 2typedef int (*RCallBack)(int fd);struct Conn
{int fd;char rbuffer[BUFFER_LENGTH];char wbuffer[BUFFER_LENGTH];int rlength;int wlength;RCallBack send_callback;RCallBack recv_callback;int status;int file_fd;
};int http_request(struct Conn *);
int http_response(struct Conn *);int set_event(int fd, int event, int flag);void error_handling(const char *message);void log_error(const char *message);webserver.c
#include fcntl.h
#include unistd.h
#include sys/epoll.h
#include sys/stat.h
#include sys/types.h#include webserver.hint http_request(struct Conn *conn)
{set_event(conn-fd, EPOLLOUT, EPOLL_CTL_MOD);conn-status 0;conn-wlength 0;return 0;
}int http_response(struct Conn *conn)
{const char *file pic.png;int file_fd;if (conn-status 0){file_fd open(file, O_RDONLY);if (file_fd -1){log_error(open() fails);return 1;}conn-file_fd file_fd;}else{file_fd conn-file_fd;}if (conn-status 0){struct stat filestat {0};fstat(file_fd, filestat);int sended snprintf(conn-wbuffer, BUFFER_LENGTH,HTTP/1.1 200 OK\r\nContent-Type: image/png\r\nAccept-Ranges: bytes\r\nContent-Length: %ld\r\n\r\n,filestat.st_size);conn-wlength sended;conn-status 1;}else if (conn-status 1){ssize_t recved read(file_fd, conn-wbuffer, BUFFER_LENGTH);if (recved 0){close(file_fd);conn-status 2;}if (recved 0){close(file_fd);log_error(read() fails);conn-status 2;}conn-wlength recved;}return 0;
}3. 可能出现的问题及解决 connection reset recv()函数可能由于对端reset连接而返回-1这是正常现象关闭对应的fd即可。 服务器程序在客户端关闭后直接退出 可能是由于服务器程序向已经被关闭的socket写数据时会接收到一个SIGPIPE默认情况下没有设置该信号的处理函数的话就会导致该进程直接被kill。 可以设置忽略该信号。 signal(SIGPIPE, SIG_IGN);也可以自定义信号处理函数 struct sigaction sa;
sa.sa_handler handle_sigpipe;
sigemptyset(sa.sa_mask);
sa.sa_flags 0;sigaction(SIGPIPE, sa, NULL); // 设置信号处理程序也可以在send函数参数中设置不发出信号 send(fd, buffer, length, MSG_NOSIGNAL);学习参考
学习更多请前往零声github。