优化方面
- 多线程:创建多个线程来处理请求,每个线程负责处理一个或多个连接。主线程监听新连接,将新连接分配给空闲线程处理。
- 多路复用:
- select:通过监听多个文件描述符集合,阻塞等待其中有事件发生。但其支持的文件描述符数量有限,检查事件时需要遍历整个集合,时间复杂度为O(n)。
- poll:与select类似,但通过链表结构解决了文件描述符数量限制问题,不过检查事件仍需遍历,时间复杂度同样为O(n)。
- epoll:适用于高并发场景,采用事件驱动机制,通过epoll_ctl函数添加、修改、删除要监听的文件描述符,epoll_wait函数返回就绪的文件描述符,时间复杂度为O(1)。
- 优化算法:优化请求处理算法,减少不必要的计算和I/O操作。
- 内存管理:优化内存分配和释放,避免频繁的内存分配和碎片化。
- 缓存机制:设置请求和响应的缓存,减少重复处理。
选择epoll优化方式
// 优化前简单的单线程阻塞式处理
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#define PORT 8080
#define BUFFER_SIZE 1024
int main(int argc, char const *argv[]) {
int server_fd, new_socket;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
char buffer[BUFFER_SIZE] = {0};
// 创建套接字
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 设置套接字选项
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
perror("setsockopt");
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
// 绑定套接字
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
// 监听
if (listen(server_fd, 3) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
while (1) {
// 接受新连接
if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
perror("accept");
continue;
}
// 接收数据
read(new_socket, buffer, BUFFER_SIZE);
printf("Received: %s\n", buffer);
// 发送响应
char *response = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n<html><body>Hello, World!</body></html>";
send(new_socket, response, strlen(response), 0);
// 关闭连接
close(new_socket);
}
return 0;
}
// 优化后使用epoll的代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <sys/epoll.h>
#define PORT 8080
#define BUFFER_SIZE 1024
#define MAX_EVENTS 10
int main(int argc, char const *argv[]) {
int server_fd, new_socket;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
char buffer[BUFFER_SIZE] = {0};
int epoll_fd;
struct epoll_event event, events[MAX_EVENTS];
// 创建套接字
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 设置套接字选项
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
perror("setsockopt");
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
// 绑定套接字
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
// 监听
if (listen(server_fd, 3) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
// 创建epoll实例
epoll_fd = epoll_create1(0);
if (epoll_fd == -1) {
perror("epoll_create1");
exit(EXIT_FAILURE);
}
// 将监听套接字添加到epoll实例
event.data.fd = server_fd;
event.events = EPOLLIN;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event) == -1) {
perror("epoll_ctl: server_fd");
exit(EXIT_FAILURE);
}
while (1) {
// 等待事件发生
int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
if (nfds == -1) {
perror("epoll_wait");
exit(EXIT_FAILURE);
}
for (int i = 0; i < nfds; i++) {
if (events[i].data.fd == server_fd) {
// 处理新连接
new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen);
if (new_socket == -1) {
perror("accept");
continue;
}
// 将新连接添加到epoll实例
event.data.fd = new_socket;
event.events = EPOLLIN | EPOLLET;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, new_socket, &event) == -1) {
perror("epoll_ctl: new_socket");
close(new_socket);
}
} else {
// 处理已连接套接字的事件
int client_fd = events[i].data.fd;
int valread = read(client_fd, buffer, BUFFER_SIZE);
if (valread == -1) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
perror("read");
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client_fd, NULL);
close(client_fd);
}
} else if (valread == 0) {
// 客户端关闭连接
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client_fd, NULL);
close(client_fd);
} else {
// 处理请求并发送响应
buffer[valread] = '\0';
printf("Received: %s\n", buffer);
char *response = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n<html><body>Hello, World!</body></html>";
send(client_fd, response, strlen(response), 0);
}
}
}
}
close(server_fd);
close(epoll_fd);
return 0;
}
性能对比分析思路
- 请求响应时间:使用工具如ab(Apache Bench)或wrk,向优化前后的服务器发送大量请求,记录平均响应时间、最大响应时间和最小响应时间。优化后应能显著降低平均响应时间,特别是在高并发场景下。
- 吞吐量:同样使用上述工具,统计单位时间内服务器处理的请求数量。优化后,由于能更高效地处理并发请求,吞吐量应有所提升。
- 资源利用率:使用top、htop等工具监控服务器在处理高并发请求时的CPU和内存使用率。优化后,CPU和内存利用率应更加合理,避免过度消耗导致系统性能下降。
- 并发连接数:逐步增加并发连接数,观察优化前后服务器能够稳定处理的最大并发连接数。优化后,最大并发连接数应有所提高,表明服务器对高并发的处理能力增强。