管理高并发Socket连接的技术和策略
- I/O复用:使用
select
、poll
或epoll
等I/O复用机制,能够在一个线程中同时监控多个Socket的状态变化,避免为每个连接创建单独的线程或进程,减少资源消耗。
- 线程池或进程池:预先创建一定数量的线程或进程,当有新连接到来时,从池中分配一个线程或进程处理,处理完毕后再放回池中,减少线程或进程创建销毁的开销。
- 事件驱动编程模型:基于事件(如连接建立、数据可读、数据可写等)来驱动程序的执行逻辑,提高程序的响应性和灵活性。
- 非阻塞I/O:将Socket设置为非阻塞模式,使得在执行I/O操作(如
read
、write
)时,不会因为没有数据而阻塞线程,提高系统的并发处理能力。
使用epoll机制实现高并发Socket服务器示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#define MAX_EVENTS 10
#define BUF_SIZE 1024
int main(int argc, char *argv[]) {
int serv_sock, clnt_sock;
struct sockaddr_in serv_adr, clnt_adr;
socklen_t clnt_adr_sz;
struct epoll_event *ep_events;
struct epoll_event event;
int epfd, event_cnt;
char buf[BUF_SIZE];
serv_sock = socket(PF_INET, SOCK_STREAM, 0);
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family = AF_INET;
serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_adr.sin_port = htons(atoi(argv[1]));
bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr));
listen(serv_sock, 5);
epfd = epoll_create(MAX_EVENTS);
ep_events = (struct epoll_event *)malloc(sizeof(struct epoll_event) * MAX_EVENTS);
event.events = EPOLLIN;
event.data.fd = serv_sock;
epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event);
while (1) {
event_cnt = epoll_wait(epfd, ep_events, MAX_EVENTS, -1);
if (event_cnt == -1) {
perror("epoll_wait error");
break;
}
for (int i = 0; i < event_cnt; i++) {
if (ep_events[i].data.fd == serv_sock) {
clnt_adr_sz = sizeof(clnt_adr);
clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &clnt_adr_sz);
event.events = EPOLLIN | EPOLLET;
event.data.fd = clnt_sock;
epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);
printf("connected client: %d \n", clnt_sock);
} else {
int fd = ep_events[i].data.fd;
int str_len = read(fd, buf, BUF_SIZE - 1);
if (str_len == 0) {
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
close(fd);
printf("closed client: %d \n", fd);
} else {
buf[str_len] = 0;
printf("message from client: %s", buf);
write(fd, buf, str_len);
}
}
}
}
close(serv_sock);
close(epfd);
free(ep_events);
return 0;
}
- 创建Socket并绑定监听:使用
socket
函数创建TCP套接字,bind
函数绑定IP地址和端口,listen
函数开始监听连接。
- 创建epoll实例:使用
epoll_create
创建一个epoll实例,并返回一个文件描述符epfd
。
- 添加监听Socket到epoll:构造一个
epoll_event
结构体,设置监听Socket的事件为EPOLLIN
(可读事件),并使用epoll_ctl
函数将其添加到epoll实例中。
- 进入事件循环:通过
epoll_wait
函数等待事件发生,epoll_wait
会阻塞,直到有事件发生或者超时。
- 处理新连接:当
epoll_wait
返回且事件对应的文件描述符是监听Socket时,调用accept
函数接受新连接,并将新连接的Socket也添加到epoll实例中,同时设置其事件为EPOLLIN
(可读事件)和EPOLLET
(边缘触发模式,提高效率)。
- 处理客户端数据:当事件对应的文件描述符是客户端Socket时,调用
read
函数读取数据,若读取到0,表示客户端关闭连接,从epoll实例中删除该Socket并关闭;否则将读取到的数据回显给客户端。