C10K问题:高并发模型设计

一、循环服务器模型

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h> //*******//
#include <netinet/in.h> //*******//
#include <arpa/inet.h>  //*******//int sockfd;
void my_exit(int sig)
{shutdown(sockfd, SHUT_RDWR);close(sockfd);printf("shutdown socket done\n");exit(0);
}
void handle(int sig) // SIGPIPE的信号处理函数————以观察是否产生了SIGPIPE信号
{if (sig == SIGPIPE){printf("SIGPIPE is going\n");}
}int main(int argc, char **argv)
{signal(SIGINT, my_exit);signal(SIGPIPE, handle);signal(SIGPIPE, SIG_IGN);sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){perror("socked is error");exit(-1);}printf("socket success\n");int i;setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));struct sockaddr_in sockaddr_in1;sockaddr_in1.sin_family = AF_INET; sockaddr_in1.sin_port = htons(4443);                         // 正确的做法是使用htons函数将主机字节序转换为网络字节序————htons而非htonl因为端口号是16位sockaddr_in1.sin_addr.s_addr = inet_addr("192.168.106.128"); //*****//if (bind(sockfd, (struct sockaddr *)&sockaddr_in1, sizeof(sockaddr_in1)) < 0){perror("bind error");exit(-1);}printf("bind success\n");if (listen(sockfd, 20) < 0){perror("listen error");exit(-1);}printf("listen success\n");struct sockaddr_in addr2;int len_addr2 = sizeof(addr2);while (1){int sock_fd1 = accept(sockfd, (struct sockaddr *)&addr2, &len_addr2); // 每来一个客户端的连接请求,就会生成一个描述符,只要知道这个描述符,就能通过此通信if (sock_fd1 < 0){perror("accept error");exit(-1);}printf("client ip = %s ,port = %d\n", inet_ntoa(addr2.sin_addr), ntohs(addr2.sin_port)); // 1.inet_ntoa把ip地址转换为字符————2.把网络的转换为主机的char buffer[1024] = {0};int recv_t = recv(sock_fd1, buffer, sizeof(buffer) - 1, 0);printf("recv_t : %d   ", recv_t);if (recv_t < 0){perror("recv error");exit(-1);}else if (recv_t == 0) // recv的返回值为零的时候,证明客户端关闭了!{printf("client is closed\n");}else{printf("recv :%s\n", buffer);memset(buffer, 0, sizeof(buffer));scanf("%s", buffer);// int w_t = send(sock_fd1, buffer, strlen(buffer), 0);int w_t = send(sock_fd1, buffer, strlen(buffer), MSG_NOSIGNAL); // MSG_NOSIGNAL:表示此操作不愿被SIGPIPE信号断开;或注册信号处理函数if (w_t < 0){perror("send data error");exit(-1);}}shutdown(sock_fd1, SHUT_RDWR);}return 0;
}

循环服务器模型:

阻塞:

同一时间只能处理一客户端的请求(读写请求或者连接请求),accept和recv会阻塞

非阻塞:

1.accept非阻塞:

// flag标志位,置为sock_nonblock;int sockfd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);

就可以将accept自动设置为非阻塞 

2.recv非阻塞:

recv函数的flag位置为MSG_DONTWAIT;

int ret3 = recv(ret2, buffer, sizeof(buffer), MSG_DONTWAIT); // msg_dontwait非阻塞接收

3.read非阻塞:

fcntl设置文件描述符的flags位;

// read:int flags = fcntl(ret2, F_GETFL);flags = flags | O_NONBLOCK;fcntl(ret2, F_SETFL,flags);

4.例子: 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <errno.h>int main(int argc, char **argv)
{//*************************************************************//// flag标志位,置为sock_nonblock;int sockfd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);//*************************************************************//if (sockfd < 0){perror("socket error");exit(-1);}int j = 1;setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &j, sizeof(j));struct sockaddr_in addr1;addr1.sin_family = AF_INET;addr1.sin_addr.s_addr = inet_addr("127.0.0.1");addr1.sin_port = htons(5555);int ret1 = bind(sockfd, (struct sockaddr *)(&addr1), sizeof(addr1));if (ret1 < 0){perror("bind error");exit(-1);}if (listen(sockfd, 20) < 0){perror("listen error");exit(-1);}struct sockaddr_in addr2;int len;memset(&addr2, 0, sizeof(addr2));char buffer[1024] = {0};while (1){int ret2 = accept(sockfd, (struct sockaddr *)&addr2, &len);if (ret2 < 0){//-----------------------------------------------------------------//if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)//-----------------------------------------------------------------//{perror("accept error");exit(-1);}else{sleep(1);printf("accept going\n");continue;}}while (1){// read:int flags = fcntl(ret2, F_GETFD);flags = flags | O_NONBLOCK;fcntl(ret2, F_SETFD,flags);memset(buffer, 0, sizeof(buffer));//-----------------------           ret2!!!       ----------------------//// int ret3 = recv(ret2, buffer, sizeof(buffer), MSG_DONTWAIT); // msg_dontwait非阻塞接收//----------------------------------------------------------------------//int ret3 = read(ret2, buffer, sizeof(buffer));if (ret3 < 0){if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR){perror("recv error");exit(-1);}else{continue;}}else if (ret3 == 0){printf("client closed\n");break;}else{printf("receive message is : %s\n", buffer);memset(buffer, 0, sizeof(buffer));scanf("%s", buffer);sendto(ret2, buffer, sizeof(buffer), 0, (struct sockaddr *)&addr2, sizeof(addr2));}}}return 0;
}

二、阻塞IO+多进程

例子:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>void handler(int sig)
{if(sig==SIGCHLD){printf("child process is exit\n");waitpid(-1,NULL,0);}
}
int main(int argc, char **argv)
{signal(SIGCHLD,handler);int socket_fd = socket(AF_INET, SOCK_STREAM, 0);if (socket_fd < 0){perror("socket error");exit(-1);}int j = 1;setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &j, sizeof(j));struct sockaddr_in addr1;addr1.sin_addr.s_addr = inet_addr("127.0.0.1");addr1.sin_family = AF_INET;addr1.sin_port = htons(5555);int ret1 = bind(socket_fd, (struct sockaddr *)&addr1, sizeof(addr1));if (ret1 < 0){perror("bind error");exit(-1);}if (listen(socket_fd, 20) < 0){perror("listen error");exit(-1);}struct sockaddr_in addr2;int len = sizeof(addr2);while (1){char message[1024] = {0};memset(&addr2, 0, sizeof(addr2));int fd1 = accept(socket_fd, (struct sockaddr *)&addr2, &len);if (fd1 < 0){perror("accept error");exit(-1);}pid_t pid = fork();if (pid == 0){while (1){read(fd1, message, sizeof(message));printf("%s\n", message);char *p = message;for (; *p != '\0'; ++p){*p = *p - 'a' + 'A';}write(fd1, message, strlen(message) + 1);memset(message, 0, sizeof(message));}}if (pid > 0){close(fd1);continue;}}return 0;
}

waitpid 函数

是一个用于等待子进程终止并回收其资源的系统调用,它的函数原型如下:
#include <sys/types.h>
#include <sys/wait.h>

pid_t waitpid(pid_t pid, int *status, int options);
waitpid 函数用于等待指定的子进程终止,并且提供了更灵活的选项来控制等待的行为。下面是参数的含义:

pid:要等待的子进程的进程ID。可以使用以下值:

<-1:等待任何进程组ID等于pid的子进程。
-1:等待任何子进程,类似于 wait 函数。
0:等待与调用进程在同一个进程组的任何子进程。
> 0:等待指定进程ID等于pid的子进程。
status:用于存储子进程终止状态的整数指针。status 中将包含子进程的退出状态信息。

options:用于指定等待选项的整数。常用的选项包括:

WNOHANG:如果没有终止的子进程可用,则立即返回,不阻塞。
WUNTRACED:也等待已经停止但尚未报告的子进程。
WCONTINUED:等待已继续但尚未报告的子进程。
waitpid 函数之所以可以回收子进程资源,是因为它在等待子进程终止时会挂起当前进程,直到指定的子进程终止。一旦子进程终止,waitpid 会将子进程的退出状态信息(如退出码)存储在 status 参数中,然后返回子进程的进程ID。通过检查 status 中的信息,您可以确定子进程的终止状态,并根据需要采取相应的操作。

void handler(int sig)
{if(sig==SIGCHLD){printf("child process is exit\n");waitpid(-1,NULL,0);}
}

优缺点:

优点:

编码简单,不用考虑进程间的数据同步、服务器健壮;

缺点:

1.资源消耗大,启动一个进程消耗相对比启动一个线程消耗大得多;处理多个连接的时候,需要启动多个进程去处理

2.系统的进程数是有限制的

3.查看系统最多支持的进程数——ulimit -u命令;也可以修改

4.TCP服务器进程同时打开文件数的限制——ulimit -n命令;每一次accept就会产生一个文件描述符,数量限制在0-1024

参考:

Linux系统打开文件最大数量限制(进程打开的最大文件句柄数设置) - 废物大师兄 - 博客园 (cnblogs.com)icon-default.png?t=N7T8https://www.cnblogs.com/cjsblog/p/9367043.html

三、阻塞IO+多线程

例子:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <signal.h>
#include <pthread.h>void change(char *p) // 字符大写
{for (; *p != '\0'; ++p){*p = *p - 'a' + 'A';}
}
void *pthread_cs(void *arg)
{int fd = (int)arg;char message[1024] = {0};pthread_detach(pthread_self()); // 获取当前线程的id号,然后设置为分离态(结束后可以自动回收资源)while (1){memset(message, 0, sizeof(message));if (read(fd, message, sizeof(message)) == 0)// 如果读取结果是0,则证明客户端已经关闭;需要主动退出线程,否则服务器主程序也会异常退出{printf("client cloesd\n");pthread_exit(NULL);}printf("%s\n", message);change(message);write(fd, message, sizeof(message));}
}int main(int argc, char **argv)
{pthread_t id;int socket_fd = socket(AF_INET, SOCK_STREAM, 0);if (socket < 0){perror("socket error");exit(-1);}int j = 1;setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &j, sizeof(j));struct sockaddr_in addr0;addr0.sin_addr.s_addr = inet_addr("127.0.0.1");addr0.sin_family = AF_INET;addr0.sin_port = htons(5555);int len_addr0 = sizeof(addr0);int ret1 = bind(socket_fd, (struct sockaddr *)&addr0, len_addr0);if (ret1 < 0){perror("bind error");exit(-1);}int ret2 = listen(socket_fd, 20);if (ret2 < 0){perror("listen error");exit(-1);}while (1){struct sockaddr_in addr1;int len_addr1 = sizeof(addr1);int fd = accept(socket_fd, (struct sockaddr *)&addr1, &len_addr1);if (fd < 0){perror("accept error");exit(-1);}pthread_create(&id, NULL, pthread_cs, (void *)fd); //------------(void *)fd----------//}return 0;
}

注意事项:

1:

if (read(fd, message, sizeof(message)) == 0)// 如果读取结果是0,则证明客户端已经关闭;需要主动退出线程,否则服务器主程序也会异常退出{printf("client cloesd\n");pthread_exit(NULL);}

2:

pthread_detach(pthread_self()); // 获取当前线程的id号,然后设置为分离态(结束后可以自动回收资源)

3:

//------------(void *)fd----------//这里需要传值而不是传地址(每次循环,地址上的值会变化)pthread_create(&id, NULL, pthread_cs, (void *)fd);

优缺点:

优点:相对多进程,会节约一些资源,会更加高效一点

缺点:

1.相对于多进程,增加coding复杂度,因为需要考虑数据同步锁保护【锁也是很浪费资源的】

2.一个进程中不能启动太多的线程——【当前进程的资源有限,线程数量受到进程栈空间的限制:线程函数压栈】

3.ulimit -s查看栈的大小,算出最大线程的个数

总结:线程是进程的优化,优先选择线程——

【解决线程数量限制——线程池

【解决锁的开销——减小锁的颗粒度(上锁的代码越少越好)】

四、池化技术(线程池)

1.实现线程池

---------------------------------------------------------(抽空写)---------------------------------------------

2.利用线程池来实现创建多进程响应多个客户端的连接

五、I/O多路转接(复用)(单Reactor )

select

例子:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <signal.h>
#include <pthread.h>
#include <sys/select.h>
#include <errno.h>int socket_creat() // 封装的套结字创建——————(socket、setsockopt、bind、listen)
{int socket_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);if (socket_fd < 0){perror("socket creat error");exit(-1);}int j = 1;setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &j, sizeof(j));struct sockaddr_in s_addr;int len_s_addr = sizeof(s_addr);s_addr.sin_addr.s_addr = inet_addr("127.0.0.1");s_addr.sin_family = AF_INET;s_addr.sin_port = htons(5555);if (bind(socket_fd, (struct sockaddr *)&s_addr, len_s_addr) < 0){perror("bind error");exit(-1);}listen(socket_fd, 20);return socket_fd;
}void change(char *p) // 字符大写
{for (; *p != '\0'; ++p){*p = *p - 'a' + 'A';}
}int main(int argc, char **argv)
{int n_r;int cfd;int sockfd;int maxfd;fd_set r_set;fd_set all_set;int client[FD_SETSIZE]; // 保存cfd——FD——SETSIZE = 1024;struct sockaddr_in c_addr;memset(&c_addr, 0, sizeof(c_addr));socklen_t c_size = sizeof(struct sockaddr_in);sockfd = socket_creat();printf("socketfd is %d\n", sockfd);for (int i = 0; i < FD_SETSIZE; i++) // 文件描述符集合全置为-1;{client[i] = -1;}FD_SET(sockfd, &all_set);maxfd = sockfd;char message[1024];while (1){r_set = all_set;int rnum = select(maxfd + 1, &r_set, NULL, NULL, NULL);if (FD_ISSET(sockfd, &r_set)){c_size = sizeof(struct sockaddr_in);printf("socketfd is %d\n", sockfd);cfd = accept(sockfd, (struct sockaddr *)&c_addr, &c_size);if (cfd < 0){if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR){perror("accept error! ");exit(1);}continue;}printf("info client: ip = %s port = %d\n",inet_ntoa(c_addr.sin_addr), ntohs(c_addr.sin_port));FD_SET(cfd, &all_set);printf("%d\n", __LINE__);if (maxfd < cfd){maxfd = cfd;}}for (int i = 0; i < FD_SETSIZE; i++){if (client[i] == -1){client[i] = cfd;printf("%d\n", __LINE__);break;}}if (--rnum > 0){printf("%d\n", __LINE__);continue;}for (int i = 0; i < FD_SETSIZE; i++){if ((cfd = client[i]) != -1){printf("%d\n", __LINE__);if (FD_ISSET(cfd, &r_set)){printf("%d\n", __LINE__);memset(message, 0, sizeof(message));n_r = read(cfd, message, sizeof(message));if (n_r < 0){perror("read cfd data error! ");FD_CLR(cfd, &all_set);client[i] = -1;}if (n_r == 0){printf("client is close! \n");FD_CLR(cfd, &all_set);client[i] = -1;}printf("%d\n", __LINE__);printf("recv data:%s\n", message);change(message);write(cfd, message, strlen(message) + 1);if (--rnum == 0){break;}}}}}return 0;
}

my_select_server:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <signal.h>
#include <pthread.h>
#include <sys/select.h>
#include <errno.h>int socket_creat() // 封装的套结字创建——————(socket、setsockopt、bind、listen)
{int socket_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);if (socket_fd < 0){perror("socket creat error");exit(-1);}int j = 1;setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &j, sizeof(j));struct sockaddr_in s_addr;int len_s_addr = sizeof(s_addr);s_addr.sin_addr.s_addr = inet_addr("127.0.0.1");s_addr.sin_family = AF_INET;s_addr.sin_port = htons(5555);if (bind(socket_fd, (struct sockaddr *)&s_addr, len_s_addr) < 0){perror("bind error");exit(-1);}listen(socket_fd, 20);return socket_fd;
}void change(char *p) // 字符大写
{for (; *p != '\0'; ++p){*p = *p - 'a' + 'A';}
}int main(int agrc, char **argv)
{int socket_fd = socket_creat();struct sockaddr_in c_addr;socklen_t c_len;memset(&c_addr, 0, sizeof(c_addr));fd_set set_1;fd_set set_all;FD_SET(socket_fd, &set_all);int maxfd = socket_fd;int fd_ary[FD_SETSIZE];for (int i = 0; i < FD_SETSIZE; ++i){fd_ary[i] = -1;}char message[1024];while (1){set_1 = set_all;int ret = select(maxfd + 1, &set_1, NULL, NULL, NULL);if (FD_ISSET(socket_fd, &set_1)){int cfd = accept(socket_fd, (struct sockaddr *)&c_addr, &c_len);if (cfd < 0){if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR){perror("accept error");exit(-1);}printf("%d\n", __LINE__);continue;}if (cfd > 0){printf("client id : %s , client opt is %d\n",inet_ntoa(c_addr.sin_addr), ntohs(c_addr.sin_port));FD_SET(cfd, &set_all);if (maxfd < cfd){maxfd = cfd;printf("%d\n", __LINE__);}for (int i = 0; i < FD_SETSIZE; ++i){if (fd_ary[i] == -1){fd_ary[i] = cfd;printf("%d\n", __LINE__);break;}}}}for (int i = 0; i < FD_SETSIZE; ++i){if (FD_ISSET(fd_ary[i], &set_1)){printf("%d\n", __LINE__);memset(message, 0, sizeof(message));int ret_r = read(fd_ary[i], message, sizeof(message));if (ret_r == 0){printf("client is closed\n");fd_ary[i] = -1;FD_CLR(fd_ary[i], &set_all);}if (ret_r < 0){perror("read error");exit(-1);}printf("%s\n", message);change(message);write(fd_ary[i], message, sizeof(message));}}}return 0;
}

select优缺点:

缺点:

1.select用到的文件描述符集合,包含的数量有限(宏定义:FD_SETSIZE为1024,虽然可以修改);

2.select是一种轮询全盘扫描(时间复杂度是O(n) ),只知道多少个描述符发生变化——遍历文件描述符集合,看哪个发生变化,再采取操作

优点:

1.可以跨平台,几乎所有平台

2.可以监听所有的文件描述符:普通文件、套接字和设备文件等等

poll

基本原理与select一致,也是轮询+遍历;唯一的区别就是poll没有最大文件描述符限制(使用链表的方式存储fd)

函数原型
#include cpoll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

struct pollfd fds[POLL_SIZE];fds[0].fd = sock_fd;fds[0].events = POLLIN;fds[0].revents = 0;for (int i = 1; i < POLL_SIZE; ++i){fds[i].fd = -1;fds[i].events = POLLIN;fds[i].revents = 0;}

revents字段表示实际发生的事件,可能包括POLLIN(可读事件)、POLLOUT(可写事件)

功能
监听集合有没有动静,如果没有动静就阻塞
如果有动静就成功返回,返回值为集合中有动静的fd的数里。
参数

2) nfds :数组的元素个数。
3)timeout:超时时间,如果写的是

-1:不设置超时,如果集合没有动静就一直阻塞下去,直到pol函数被信号中断〈唤醒)或者集合有动静为止
非-1值:比如3000 ( 3000徵妙),表示将超时时间设置为3秒
也就是说poll超时时间的单位时微妙s

返回值
-1:说明函数调失败,errno被设置。
如果是被信号中断从而导致出错返回-1时.errno被设置为EINTR,·如果不想o中断,要么重启poll的调用:要么忽略或者屏蔽下这些信号。
0:超时时间到,而且没有文件描述符有动静。
>0:返回有响应的文件描述符的数量。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <poll.h>
#include <fcntl.h>
#define POLL_SIZE 1024int socket_creat() // 封装的套结字创建——————(socket、setsockopt、bind、listen)
{int socket_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);if (socket_fd < 0){perror("socket creat error");exit(-1);}int j = 1;setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &j, sizeof(j));struct sockaddr_in s_addr;int len_s_addr = sizeof(s_addr);s_addr.sin_addr.s_addr = inet_addr("127.0.0.1");s_addr.sin_family = AF_INET;s_addr.sin_port = htons(5555);if (bind(socket_fd, (struct sockaddr *)&s_addr, len_s_addr) < 0){perror("bind error");exit(-1);}listen(socket_fd, 20);return socket_fd;
}void change(char *p) // 字符大写
{for (; *p != '\0'; ++p){*p = *p - 'a' + 'A';}
}int main(int argc, char **argv)
{int sock_fd = socket_creat();struct sockaddr_in c_addr;socklen_t len_c_addr = sizeof(c_addr);memset(&c_addr, 0, sizeof(c_addr));struct pollfd fds[POLL_SIZE];fds[0].fd = sock_fd;fds[0].events = POLLIN;fds[0].revents = 0;for (int i = 1; i < POLL_SIZE; ++i){fds[i].fd = -1;fds[i].events = POLLIN;fds[i].revents = 0;}char message[1024] = "0";while (1){int ret_p = poll(fds, POLL_SIZE, -1);if (ret_p < 0){perror("poll error");exit(-1);}if (ret_p > 0){if (fds[0].events == fds[0].revents){fds[0].revents = 0;int c_fd = accept(sock_fd, (struct sockaddr *)&c_addr, &len_c_addr);if (c_fd < 0){perror("accept error");exit(-1);}if (c_fd > 0){for (int i = 1; i < POLL_SIZE; ++i){if (fds[i].fd == -1){fds[i].fd = c_fd;break;//---------------       注意---------------//}}printf("cilent ip is %s , cilent port is %d\n",inet_ntoa(c_addr.sin_addr), ntohs(c_addr.sin_port));}}for (int i = 1; i < POLL_SIZE; ++i){if (fds[i].events == fds[i].revents){int ret_r = read(fds[i].fd, message, sizeof(message));if (ret_r < 0){perror("read error");fds[i].fd = -1;}if (ret_r == 0){printf("client is closed\n");fds[i].fd = -1;}if (ret_r > 0){printf("receive message is %s\n", message);change(message);write(fds[i].fd, message, sizeof(message));memset(message, 0, sizeof(message));}fds[i].revents = 0;}}}}return 0;
}

epoll—重点

使用步骤

1.epoll_create

size:

epoll上能关注的最大描述符数量。(真正用的时候随便设一个就行,epoll在容里不够的时候会做自动扩展)

返回值:返回红黑树的描述符epollfd ;出错时返回-1,错误代码置在ernno

epoll在内部是一个红黑制结构。文件描述符默认0—2被终端占用,每次有一个新的文件描述符就创建一个树节点。epoll认设置的是树上最多的节点数是2000:但是如果敌里超过2000:则大小会被自动扩展。

2.epoll_ctl

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

3.epoll_wait 
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <poll.h>
#include <fcntl.h>
#include <sys/epoll.h>#define POLL_SIZE 1024int socket_creat() // 封装的套结字创建——————(socket、setsockopt、bind、listen)
{int socket_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);if (socket_fd < 0){perror("socket creat error");exit(-1);}int j = 1;setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &j, sizeof(j));struct sockaddr_in s_addr;int len_s_addr = sizeof(s_addr);s_addr.sin_addr.s_addr = inet_addr("127.0.0.1");s_addr.sin_family = AF_INET;s_addr.sin_port = htons(5555);if (bind(socket_fd, (struct sockaddr *)&s_addr, len_s_addr) < 0){perror("bind error");exit(-1);}listen(socket_fd, 20);return socket_fd;
}void change(char *p) // 字符大写
{for (; *p != '\0'; ++p){*p = *p - 'a' + 'A';}
}int main(int argc, char **argv)
{int sock_fd = socket_creat();struct sockaddr_in c_addr;socklen_t len_c_addr = sizeof(c_addr);memset(&c_addr, 0, sizeof(c_addr));int epfd = epoll_create(2000); // 初始化红黑书,返回值是红黑树的标识符 ; 红黑书大小——暂定输入参数为2000;//int epfd = epoll_create1(0);//也是一样的效果    
if (epfd < 0){perror("epoll_creat error");exit(-1);}struct epoll_event event_0; // 先定义一个结构体,存放第一个sock_fd————以便于后面处理“新连接”的请求;event_0.events = EPOLLIN;   // 监听的是可读——也就是有新连接了event_0.data.fd = sock_fd;  // 存放sock_fd,以便于后面遍历的时候,通过event.data.fd找到sock_fd这个,而非处理新连接所新加的eventstruct epoll_event event_a[2000];    // 定义一个总的,而且空的struct epoll_event数组,作为epoll_wait的传出参数,用以保存所有变化了的文件描述符memset(event_a, 0, sizeof(event_a)); // 清空它int ret1 = epoll_ctl(epfd, EPOLL_CTL_ADD, sock_fd, &event_0); // 把第一个含有sock_fd的结构体加到红黑里;if (ret1 < 0){perror("epoll add error");exit(-1);}char message[1024] = {0};while (1){int ret = epoll_wait(epfd, event_a, 2000, -1); // 监听,-1代表阻塞,死等;for (int i = 0; i < 2000; ++i)                 // 遍历这个“包含所有变化的”的struct epoll_event数组,以处理每一个请求{if (event_a[i].data.fd == sock_fd) // 这个对应的是有新连接的请求{int c_fd = accept(sock_fd, (struct sockaddr *)&c_addr, &len_c_addr);if (c_fd < 0){perror("accept error");exit(-1);}if (c_fd > 0){printf("cilent ip is %s , cilent port is %d\n",inet_ntoa(c_addr.sin_addr), ntohs(c_addr.sin_port));event_0.data.fd = c_fd; // 有新连接,就要把新的结构体(结点),挂到红黑树里 👇epoll_ctl(epfd, EPOLL_CTL_ADD, c_fd, &event_0);}}else // 处理的是已经连接,有收发消息的请求{int fd = event_a[i].data.fd;int ret_r = read(fd, message, sizeof(message));if (ret_r < 0){perror("read error");close(fd);// 需要关闭这个文件描述符;}if (ret_r == 0){close(fd);// 需要关闭这个文件描述符;printf("client is closed\n");int ret2 = epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
//删除,第四个参数置为NULL;}if (ret_r > 0){printf("receive message is %s\n", message);change(message);write(fd, message, sizeof(message));memset(message, 0, sizeof(message));}}if (--ret <= 0){break;}}}return 0;
}

注意:上图中虽然套接字已经是非阻塞的了,但是可以不需要处理accept的errno,因为,如果不是新客户端连接的话,是不会进入accept语句里的

支持操作:

水平触发LT

        只要有数据都会触发,缓冲区剩余未读尽的数据会导致epoll_wait返回——(数据没读完就会一直发出请求给服务器,占用资源,降低效率)

边缘触发ET

        1.ET模式(边缘触发)只有数据到来才触发,不管缓存区中是否还有数据,缓冲区剩未读尽的数据不会导致epoll_wait返回

        2.边缘触发需要一次性的把缓冲区的数据读完为止,也就是一直读,直到读到EGAIN(EGAIN说明缓冲区已经空了)为止

        3.边缘触发需要设置文件句柄为非阻塞

                    // event_0.events = EPOLLIN | EPOLLET;//设置为边缘触发模式//  int flags=fcntl(c_fd,F_GETFL);//  flags=flags|O_NONBLOCK;//  fcntl(cfd,F_SETFL,flags);

        4.epoll为什么要有EPOLLET触发模式?

ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高,epoll工作在ET模式的时候。必须使用非阻塞接口;以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死

优缺点


select内部使用数组实现,poll是链表。他们需要做内核区到用户区的转换,还需要做数据拷贝,因此效率低。
epoll不需要做内核区到用户区的转换,因为数据存在共享内存中。epoll维护的树在共享内存中,内核区和用户区去操作共享内存
因此不需要区域转换,也不需要拷贝操作


六、Reactor模式

七、I/O多路复用进阶(单Reactor +线程/进程):线程及线程池使用

八、I/O多路复用进阶(多Reactor +线程/进程) ——主-从 Reactor模式

九、异步I/O探索

十、Proactor异步网络模型

十一、总结
 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/149198.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

头戴式耳机怎么戴好看?头戴式耳机正确代法

走在大街上总能看到那么一些人&#xff0c;他们眼神时而朦胧涣散&#xff0c;时而精神奕奕&#xff0c;全身上下始终散发着#请勿打扰#的气息&#xff0c;因为他们都戴着头&#xff01;戴&#xff01;式&#xff01;耳&#xff01;机&#xff01;但是头戴式耳机把头压得扁扁的&a…

《C和指针》笔记31:多维数组的数组名、指向多维数组的指针、作为函数参数的多维数组

文章目录 1. 指向多维数组的数组名2. 指向多维数组的指针3. 作为函数参数的多维数组 1. 指向多维数组的数组名 我们知道一维数组名的值是一个指针常量&#xff0c;它的类型是“指向元素类型的指针”&#xff0c;它指向数组的第1个元素。那么多维数组的数组名代表什么呢&#x…

[管理与领导-113]:IT人看清职场中的隐性规则 - 10 - 看清人的行动、行为、手段、方法背后的动机与背景条件

目录 前言&#xff1a; 一、冰山模型 1.1 冰山模型&#xff0c;系统思考的工具 1.2 冰山模型&#xff1a;发现人行为背后的动机 二、动机、行为模型 "说一套"&#xff1a; "做一套"&#xff1a; "演一套"&#xff1a; "学一套&quo…

【已解决】 Expected linebreaks to be ‘LF‘ but found ‘CRLF‘.

问题描述 团队都是用mac&#xff0c;只有我自己是windows&#xff0c;启动项目一直报错 Expected linebreaks to be ‘LF‘ but found ‘CRLF‘. 但我不能因为自己的问题去改团队配置&#xff0c;也尝试过该vscode配置默认是LF还是报错 思路 看文章vscode如何替换所有文件的…

深入剖析红黑树:优雅地平衡二叉搜索树

目录 一.红黑树的概念二.插入操作三.与AVL树的比较 一.红黑树的概念 在之前的学习中&#xff0c;我们了解了二叉搜索平衡树&#xff0c;AVL树通过控制每个结点中的平衡因子的绝对值不超过1&#xff0c;实现了一个高性能的树。而相较于AVL的高度平衡&#xff0c;红黑树觉得AVL为…

传输层协议—UDP协议

传输层协议—UDP协议 文章目录 传输层协议—UDP协议传输层再谈端口号端口号范围划分pidofnetstat UDP协议端格式UDP报文UDP特点UDP缓冲区基于UDP的应用层协议 传输层 在学习HTTP/HTTPS等应用层协议时&#xff0c;为了方便理解&#xff0c;可以简单认为HTTP将请求和响应直接发送…

JMeter性能分析实战一:日常登录接口

负载测试 日常需求&#xff1a;负载测试&#xff01; 对于桥的负载测试&#xff1a;我给你20t的一排车辆&#xff0c;看你能不能撑得住20t&#xff01; 对于系统的负载测试&#xff1a; 逐步增加负载&#xff0c;便于问题的发现和定位&#xff0c;不要操之过急。逐步增加负载…

Stable Diffusion云服务器部署完整版教程

Stable Diffusion云服务器部署完整版教程 2023年07月04日 22:30 3607浏览 18喜欢 22评论 <span class"bili-avatar-icon bili-avatar-right-icon "></span> </div>薯片_AI 粉丝&#xff1a; 1513 文章&#xff1a; 1 设置分组取消关注 已关注 …

【MySql】3- 实践篇(一)

文章目录 1. 普通索引和唯一索引的选择1.1 查询过程1.2 更新过程1.2.1 change buffer1.2.2 change buffer 的使用场景 1.3 索引选择和实践1.4 change buffer 和 redo log2. MySQL为何有时会选错索引?2.1 优化器的逻辑2.1.1 扫描行数是怎么判断的?2.1.2 重新统计索引信息 2.2 …

C语言中柔性数组的讲解与柔性数组的优势

前言:也许你从来没有听说过柔性数组&#xff08;flexible array&#xff09;这个概念&#xff0c;但是它确实是存在的。C99 中&#xff0c;结构中的最后一个元素允许是未知大小的数组&#xff0c;这就叫做"柔性数组"成员。 目录标题 柔性数组什么是柔性数组呢&#…

【C语言】八大排序算法

文章目录 一、冒泡排序1、定义2、思想及图解3、代码 二、快速排序1、hoare版本2、挖坑法3、前后指针法4、非递归快排5、快速排序优化1&#xff09;三数取中选key值2&#xff09;小区间优化 三、直接插入排序1、定义2、代码 四、希尔排序1、定义2、图解3、代码 五、选择排序1、排…

sheng的学习笔记-【中文】【吴恩达课后测验】Course 2 - 改善深层神经网络 - 第二周测验

课程2_第2周_测验题 目录&#xff1a;目录 第一题 1.当输入从第8个mini-batch的第7个的例子的时候&#xff0c;你会用哪种符号表示第3层的激活&#xff1f; A. 【  】 a [ 3 ] { 8 } ( 7 ) a^{[3]\{8\}(7)} a[3]{8}(7) B. 【  】 a [ 8 ] { 7 } ( 3 ) a^{[8]\{7\}(3)} a…

代码随想录 Day11 二叉树 LeetCode T144,145,94 前中后序遍历 (递归解法)

题解及更详细解答来自于:代码随想录 (programmercarl.com) 前言: 递归三要素 确定递归函数的参数和返回值&#xff1a; 确定哪些参数是递归的过程中需要处理的&#xff0c;那么就在递归函数里加上这个参数&#xff0c; 并且还要明确每次递归的返回值是什么进而确定递归函数的返…

【Redis】基础数据结构-skiplist跳跃表

有序集合Sorted Set zadd zadd用于向集合中添加元素并且可以设置分值&#xff0c;比如添加三门编程语言&#xff0c;分值分别为1、2、3&#xff1a; 127.0.0.1:6379> zadd language 1 java (integer) 1 127.0.0.1:6379> zadd language 2 c (integer) 1 127.0.0.1:6379…

【Java-LangChain:使用 ChatGPT API 搭建系统-2】语言模型,提问范式与 Token

第二章 语言模型&#xff0c;提问范式与 Token 在本章中&#xff0c;我们将和您分享大型语言模型&#xff08;LLM&#xff09;的工作原理、训练方式以及分词器&#xff08;tokenizer&#xff09;等细节对 LLM 输出的影响。我们还将介绍 LLM 的提问范式&#xff08;chat format…

【图像处理】使用各向异性滤波器和分割图像处理从MRI图像检测脑肿瘤(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

实验3.2 分期付款计算器

目录 实验目的‪‬‪‬‪‬‪‬‪‬‮‬‭‬‪‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‭‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‪‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‪‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‪‬ 实验内容‪‬‪‬‪‬‪‬‪‬‮‬‭‬‪‬‪‬‪‬…

20231005使用ffmpeg旋转MP4视频

20231005使用ffmpeg旋转MP4视频 2023/10/5 12:21 百度搜搜&#xff1a;ffmpeg 旋转90度 https://zhuanlan.zhihu.com/p/637790915 【FFmpeg实战】FFMPEG常用命令行 https://blog.csdn.net/weixin_37515325/article/details/127817057 FFMPEG常用命令行 5.视频旋转 顺时针旋转…

python爬虫基于管道持久化存储操作

文章目录 基于管道持久化存储操作scrapy的使用步骤1.先转到想创建工程的目录下&#xff1a;cd ...2.创建一个工程3.创建之后要转到工程目录下4.在spiders子目录中创建一个爬虫文件5.执行工程setting文件中的参数 基于管道持久化存储的步骤&#xff1a;持久化存储1&#xff1a;保…

集合(容器)-List接口及实现类

容器的特征&#xff1a;①数据长度可变&#xff1b;②数据保存方式不同。 集合体系概述&#xff1a;JAVA的集合框架是由很多接口、抽象类、具体类组成。都位于java.util包中。 Java中集合类中默认可以存储任意数据类型&#xff0c;Java中的集合提供泛型机制&#xff0c;在定义…