看过上期的都知道,我是搞java的,所以对这些可能理解不是很清楚,各位看完可以尽情发言。
事件循环和非阻塞IO
在服务器端网络编程中,有三种处理并发连接的方法。
它们是:分叉、多线程和事件循环。分叉为每个客户端连接创建新进程,以实现并发性。多线程使用线程而不是进程。事件循环使用轮询和非阻塞IO,通常在单个线程上运行。由于进程和线程的开销,大多数现代生产级软件使用事件循环进行网络连接。
我们服务器的事件循环的简化伪代码是:
all_fds = [...]
while True:
active_fds = poll(all_fds)
for each fd in active_fds:
do_something_with(fd)
def do_something_with(fd):
if fd is a listening socket:
add_new_client(fd)
elif fd is a client connection:
while work_not_done(fd):
do_something_to_client(fd)
def do_something_to_client(fd):
if should_read_from(fd):
data = read_until_EAGAIN(fd)
process_incoming_data(data)
while should_write_to(fd):
write_until_EAGAIN(fd)
if should_close(fd):
destroy_client(fd)
-
all_fds
是一个包含所有需要监控的文件描述符的列表。 -
while True:
是一个无限循环,它会一直运行,直到程序被外部中断。 -
active_fds = poll(all_fds)
调用poll函数,返回一个包含所有准备好进行I/O操作的文件描述符的列表。 -
for each fd in active_fds:
遍历这个列表,对每个文件描述符执行相应的操作。 -
do_something_with(fd)
是一个函数,根据文件描述符的类型执行相应的操作。 -
def do_something_with(fd):
定义了这个函数。 -
if fd is a listening socket:
如果文件描述符是一个监听套接字,那么调用add_new_client(fd)
函数添加新的客户端连接。 -
elif fd is a client connection:
如果文件描述符是一个客户端连接,那么进入一个循环,直到工作完成为止。在这个循环中,调用do_something_to_client(fd)
函数处理客户端的数据。 -
def do_something_to_client(fd):
定义了这个函数。 -
if should_read_from(fd):
如果应该从文件描述符读取数据,那么调用read_until_EAGAIN(fd)
函数读取数据,然后调用process_incoming_data(data)
函数处理数据。 -
while should_write_to(fd):
如果应该向文件描述符写入数据,那么调用write_until_EAGAIN(fd)
函数写入数据。 -
if should_close(fd):
如果应该关闭文件描述符,那么调用destroy_client(fd)
函数销毁客户端连接。
我们不只是对fd做一些事情(读、写或接受),而是使用轮询操作来告诉我们哪些fd可以立即操作而不阻塞。当我们在fd上执行IO操作时,该操作应该在非阻塞模式下执行。
epoll的使用
在Linux上,除了poll系统调用,还有select和epoll。古老的select系统调用基本上与轮询相同,只是最大fd数被限制为一个小数目,这使得它在现代应用程序中已经过时了。epoll API由3个系统调用组成:epoll_create、epoll_wait和epoll_ctl。epoll API是有状态的,而不是提供一组fd作为系统调用参数,epoll_ctl用于操作由epoll_create创建的fd集,epoll_wait正在对其进行操作。
epoll API执行与poll()类似的任务:监视多个文件描述符,以查看其中任何一个文件是否可以进行I/O操作。epoll API可以用作边缘触发或水平触发接口,并且可以很好地扩展到大量监视的文件描述符。以下提供系统调用来创建和管理epoll实例:
epoll_create()创建一个epoll实例并返回一个引用该实例的文件描述符。
然后通过epoll_ctl()注册对特定文件描述符的兴趣。当前在epoll上注册的文件描述符集,实例有时称为epoll集。
epoll_wait()等待I/O事件,如果当前没有事件可用,则阻塞调用线程。文章来源:https://www.toymoban.com/news/detail-789857.html
#define MAX_EVENTS 10
struct epoll_event ev, events[MAX_EVENTS];
int listen_sock, conn_sock, nfds, epollfd;
/* Set up listening socket, 'listen_sock' (socket(),
bind(), listen()) */
epollfd = epoll_create(10);
if (epollfd == -1) {
perror("epoll_create");
exit(EXIT_FAILURE);
}
ev.events = EPOLLIN;
ev.data.fd = listen_sock;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) {
perror("epoll_ctl: listen_sock");
exit(EXIT_FAILURE);
}
for (;;) {
nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
if (nfds == -1) {
perror("epoll_pwait");
exit(EXIT_FAILURE);
}
for (n = 0; n < nfds; ++n) {
if (events[n].data.fd == listen_sock) {
conn_sock = accept(listen_sock,
(struct sockaddr *) &local, &addrlen);
if (conn_sock == -1) {
perror("accept");
exit(EXIT_FAILURE);
}
setnonblocking(conn_sock);
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = conn_sock;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock,
&ev) == -1) {
perror("epoll_ctl: conn_sock");
exit(EXIT_FAILURE);
}
} else {
do_use_fd(events[n].data.fd);
}
}
}
这段代码的主要功能是创建一个监听套接字,并使用epoll机制来处理客户端连接请求。以下是代码的详细解析:文章来源地址https://www.toymoban.com/news/detail-789857.html
-
#define MAX_EVENTS 10
:定义了一个宏常量MAX_EVENTS
,表示最多可以处理的事件数量为10。 -
struct epoll_event ev, events[MAX_EVENTS];
:定义了一个结构体数组events
,用于存储epoll事件。每个事件由一个epoll_event
结构体表示。 -
int listen_sock, conn_sock, nfds, epollfd;
:定义了四个整型变量,分别表示监听套接字、新连接的套接字、返回的文件描述符数量和epoll实例的文件描述符。 -
epollfd = epoll_create(10);
:创建了一个epoll实例,最大文件描述符数量为10。如果创建失败,将打印错误信息并退出程序。 -
ev.events = EPOLLIN;
:设置事件类型为可读事件(EPOLLIN)。 -
ev.data.fd = listen_sock;
:将监听套接字的文件描述符设置为事件的关联数据。 -
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1)
:将监听套接字添加到epoll实例中,以便接收新的连接请求。如果添加失败,将打印错误信息并退出程序。 -
for (;;)
:无限循环,不断处理事件。 -
nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
:调用epoll_wait
函数等待事件发生。参数-1
表示等待所有类型的事件。返回值表示实际发生的事件数量。如果发生错误,将打印错误信息并退出程序。 -
for (n = 0; n < nfds; ++n)
:遍历所有发生的事件。 -
if (events[n].data.fd == listen_sock)
:判断事件是否与监听套接字相关联。如果是,表示有新的连接请求。 -
conn_sock = accept(listen_sock, (struct sockaddr *) &local, &addrlen);
:接受新的连接请求,并将新连接的套接字赋值给conn_sock
。如果接受失败,将打印错误信息并退出程序。 -
setnonblocking(conn_sock);
:将新连接的套接字设置为非阻塞模式,以便后续处理。 -
ev.events = EPOLLIN | EPOLLET;
:设置事件类型为可读事件(EPOLLIN),并启用边缘触发(EPOLLET)。 -
ev.data.fd = conn_sock;
:将新连接的套接字的文件描述符设置为事件的关联数据。 -
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock, &ev) == -1)
:将新连接的套接字添加到epoll实例中,以便后续处理。如果添加失败,将打印错误信息并退出程序。 -
else
:如果事件与监听套接字无关,表示是一个已连接的客户端发送的数据。调用do_use_fd
函数进行处理。
到了这里,关于Redis的实现二: c、c++的网络通信编程技术,让服务器处理多个client的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!