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)https://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异步网络模型
十一、总结
相关文章:
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 &l…...
哈希/散列--哈希表[思想到结构][==修订版==]
文章目录 1.何为哈希?1.1百度搜索1.2自身理解1.3哈希方法/散列方法1.4哈希冲突/哈希碰撞1.5如何解决?哈希函数的设计 2.闭散列和开散列2.1闭散列/开放定址法2.2开散列/链地址法/开链法1.概念2.容量问题3.字符串问题4.开散列性能测试5.开散列与闭散列比较 3.代码实现[配备详细…...
成都建筑模板批发市场在哪?
成都作为中国西南地区的重要城市,建筑业蓬勃发展,建筑模板作为建筑施工的重要材料之一,在成都也有着广泛的需求。如果您正在寻找成都的建筑模板批发市场,广西贵港市能强优品木业有限公司是一家值得关注的供应商。广西贵港市能强优…...
亨元模式 结构型模式之六
1.定义 享元模式是一种结构型设计模式, 它允许你在消耗少量内存的情况下支持大量对象。 2.滑滑梯问题 在说明亨元模式之前,我们先看看关于滑滑梯的程序设计。小区的楼下只有三个滑滑梯,但是想玩的小朋友却非常多。怎么设计计滑滑梯资源的管理…...
面试题: Spring中Bean的实例化和Bean的初始化有什么区别?
Spring中Bean的实例化和Bean的初始化有什么区别? 背景答案扩展知识什么是实例化什么是初始化 个人评价我的回答 背景 想换工作, 看了图灵周瑜老师的视频想记录一下, 算是学习结果的一个输出. 答案 Spring 在创建一个Bean对象时, 会先创建出一个Java对象, 会通过反射来执行…...
阻塞队列,生产者消费者模型
目标: 1. 认识与使用阻塞队列 2. 认识与实现消费者模型 目录 阻塞队列的特点 生产者消费者模型 生产者消费者模型的优点 阻塞队列实现该模型 阻塞队列的特点 1. 线程安全 2. 带有阻塞特性 (1)如果队列为空,继续出队列&a…...
【RCRL充放电时间相关计算】
一. 基础知识 L、C元件称为“惯性元件”,即电感中的电流、电容器两端的电压,都有一定的“电惯性”,不能突然变化。充放电时间,不光与L、C的容量有关,还与充/放电电路中的电阻R有关。RC电路的时间常数:τRC…...
C++ primer plus--输入、输出和文件
17 输入、输出和文件 17.1 C 输入和输出概述 C 把输入和输出看做字节流。输入时,程序从输入流中抽取字节;输出时,程序将字节插到输出流中。 缓冲区是内存中的临时存储区域,是程序与文件或其他 I/O 设备之间的桥梁。 17.2 使用…...
案例题--Web应用考点
案例题--Web应用考点 负载均衡技术微服务XML和JSON无状态和有状态真题 在选择题中没有考察过web的相关知识,主要就是在案例分析题中考察 负载均衡技术 应用层负载均衡技术 传输层负载均衡技术 就近的找到距离最近的服务器,并进行分发 使用户就近获取…...
MySQL的SQL 优化:提升数据库性能
1. 插入操作优化 1.1 使用多值插入 通常情况下,插入大量数据时,使用多值插入语句比逐行插入更高效。例如,将多个数据行打包成一个 INSERT 语句: INSERT INTO users (name, email) VALUES (Alice, aliceexample.com), (Bob, bob…...
【匠心打造】从0打造uniapp 可视化拖拽设计 c_o 第十篇
一、click one for uniapp置顶: 全部免费开源 (你商业用途也没关系,不过可以告诉我公司名或者项目名,放在官网上好看点。哈哈-_-) 二、写在之前 距离上一篇更新已经大约4个月了,公司的事情,自己的一些琐事一直没时间…...
BIT-5-操作符详解(C语言初阶学习)
1. 各种操作符的介绍。 2. 表达式求值 1. 操作符分类: 算术操作符 移位操作符 位操作符 赋值操作符 单目操作符 关系操作符 逻辑操作符 条件操作符 逗号表达式 下标引用、函数调用和结构成员 2. 算术操作符 - * / % 除了 % 操作符…...
【重拾C语言】三、分支程序设计(双分支和单分支程序设计、逻辑判断、多分支程序设计、枚举类型表示;典型例题:判断闰年和求一元二次方程根)
目录 前言 三、分支程序设计 3.1 判断成绩是否及格——双分支程序设计 3.2 成绩加上获奖信息—单分支程序设计 3.3 逻辑判断——布尔类型 3.4 获奖分等级——多分支程序设计 3.5 表示汽车种类——枚举类型 3.6 例题 3.6.1 例题——判断某个年份是否闰年 3.6.2 例题—…...
Shiro应用到Web Application
一、权限基础 a) 认证(你是谁?) 判断你(被认证者)是谁的过程。通常被认证者提供用户名和密码。 常见的认证包含如下几种: 匿名认证:允许访问资源,不做任何类型的安全检查。表单认证:访问资源之前,需要提…...
【POST请求-腾讯翻译君-爬虫案例】
原因:尝试多个在线翻译平台,由于返回数据存在加密原因(暂时不会解密),最总找到 ”腾讯翻译君“ 完成爬虫案例POST请求测试 案例测试网址 腾讯翻译 :https://fanyi.qq.com/ import requests import jsoncla…...
多卡片效果悬停效果
效果展示 页面结构 从页面的结构上看,在默认状态下毛玻璃卡片是有层次感的效果叠加在一起,并且鼠标悬停在卡片区域后,卡片整齐排列。 CSS3 知识点 transform 属性的 rotate 值运用content 属性的 attr 值运用 实现页面整体布局 <div …...
首饰饰品经营商城小程序的作用是什么
首饰如耳钉、戒指、手镯等除了高价值产品外,还有很多低价产品,市场需求客户众多,在实际经营中,商家们也会面临一些痛点。 私域话题越来越多加之线上线下同行竞争、流量匮乏等,更对商家选择自建商城经营平台。 通过【…...
华为OD机试真题【服务器能耗统计】
1、题目描述 【服务器能耗统计】 服务器有三种运行状态:空载、单任务、多任务,每个时间片的能耗的分别为1、3、4; 每个任务由起始时间片和结束时间片定义运行时间; 如果一个时间片只有一个任务需要执行,则服务器处于单任务状志; 如果一个时间片有多个任务需要执行,则服务器处于…...
ubuntu按下del却出现空格(命令行下键盘错乱)
问题: 有一天远程我的ubuntu 20.04,发现为何按 del 会产生空格后移的效果,up键也会重叠显示,首先感觉是这个远程软件有问题,于是又换了xshell,发现还是不行,只能打开积灰已久的笔记本࿰…...
Go开始:Go基本元素介绍
目录 标识符与关键字Go中的标识符Go关键字关键字示例 具名的函数常规函数代码示例 方法代码示例 高阶函数代码示例 匿名函数与Lambda表达式代码示例 闭包代码示例 具名的值变量基本数据类型复合数据类型指针类型 常量基本常量类型枚举常量常量表达式 定义类型和类型别名类型定义…...
十二、【漏洞复现】Rails任意文件读取(CVE-2019-5418)
十二、【漏洞复现】Rails任意文件读取(CVE-2019-5418) 12.1、漏洞原理 Ruby on Rails是一个使用 Ruby 语言写的开源 Web 应用框架,它是严格按照 MVC 结构开发的。它努力使自身保持简单,来使实际的应用开发时的代码更少,使用最少…...
【计算机视觉|人脸建模】学习从4D扫描中获取的面部形状和表情的模型
本系列博文为深度学习/计算机视觉论文笔记,转载请注明出处 标题:Learning a model of facial shape and expression from 4D scans 链接:Learning a model of facial shape and expression from 4D scans | ACM Transactions on Graphics Pe…...
【ADB】蓝牙总结
ADB 打开蓝牙关闭蓝牙打开Setting查看蓝牙地址查看蓝牙名称查看蓝牙是否开启车机蓝牙Setting配置路径wifi 打开蓝牙 adb root adb shell svc bluetooth enable 关闭蓝牙 adb root adb shell bluetooth disable 打开Setting adb shell am start -n com.android.settings/.S…...
嵌入式系统设计与应用---ARM处理器体系结构(学习笔记)
ARM处理器概述 Cortex-A8处理器工作模式 ps:除用户模式以外的其他模式被称为非用户模式或特权模式;除用户模式及系统模式以外的其他模式可称为异常模式 Cortex-A8存储器管理 ARM的基本数据类型 字节(Byte)&#…...
计算机竞赛 身份证识别系统 - 图像识别 深度学习
文章目录 0 前言1 实现方法1.1 原理1.1.1 字符定位1.1.2 字符识别1.1.3 深度学习算法介绍1.1.4 模型选择 2 算法流程3 部分关键代码 4 效果展示5 最后 0 前言 🔥 优质竞赛项目系列,今天要分享的是 🚩 毕业设计 图像识别 深度学习 身份证识别…...
StarRocks数据导入
1、相关环境 Flink作为当前流行的流式计算框架,在对接StarRocks时,若直接使用JDBC的方式"流式"写入数据,对StarRocks是不友好的,StarRocks作为一款MVCC的数据库,其导入的核心思想还是"攒微批降频率&qu…...
JavaSE | 初识Java(一) | JDK \ JRE \ JVM
Java初识 Java 是一门半编译型、半解释型语言。先通过 javac 编译程序把源文件进行编译,编译后生成的 .class 文件是由字节 码组成的平台无关、面向 JVM 的文件。最后启动 java 虚拟机 来运行 .class 文件,此时 JVM 会将字节码转换成平台能够理…...
6轮面试阿里Android开发offer,薪资却从21k降到17k,在逗我?
一小伙工作快3年了,拿到了阿里云Android开发岗位P6的offer,算HR面一起,加起来有6轮面试了,将近3个月的时间,1轮同级 1轮Android用人部门leader 1轮Android 组leader 1轮项目CTO 1轮HR 1轮HRBP。 一路上各种事件分…...
基于混合蛙跳优化的BP神经网络(分类应用) - 附代码
基于混合蛙跳优化的BP神经网络(分类应用) - 附代码 文章目录 基于混合蛙跳优化的BP神经网络(分类应用) - 附代码1.鸢尾花iris数据介绍2.数据集整理3.混合蛙跳优化BP神经网络3.1 BP神经网络参数设置3.2 混合蛙跳算法应用 4.测试结果…...
[架构之路-230]:计算机硬件与体系结构 - 可靠性、可用性、稳定性;MTTF、MTTR、MTBF
目录 一、软件质量属性 二、可靠性、可用性、稳定性区别 2.1 比较 2.2 公式比较 2.3 "正常工作时间"和"正常运行时间" 2.4 比较案例 2.5 可用性好但可靠性较差的示例 三、MTTF、MTTR、MTBF 3.1 图示 3.2 定义 (1)MTTF&am…...
php 做的应用网站/中国突然宣布大消息
昨天花了点时间整合了一下头像插件 东拼西凑的成果 先来看下效果1.先使用ajaxfileupload插件做异步上传。这个地方我本来想做个上传进度的效果,但技术有限失败了。上传按钮我还做了一个文件大小的限制,但是由于浏览器兼容性的问题,不完美在IE…...
商机网网站源码/推广员网站
echo off echo echo windows环境下Oracle数据库的自动备份脚本 echo 说明:启动备份时,需要配置以下变量 echo 1、BACKUP_DIR 指定要备份到哪个目录 echo 2、ORACLE_USERNAME 指定备份所用的Oracle用户名 echo 3、ORACLE_PASS…...
wordpress建站系统/网站排名优化培训电话
XSD(XML Schema Definition)学习笔记 XSD(XML Schema Definition) XML模式定义 XSD元素 元素解释element定义元素。attribute定义一个属性。sequence要求子元素必须按顺序出现。每个子元素可出现 0 到任意次数。complexType定义复杂类型。restriction定义对 simp…...
adobe mu做可视化网站/万网官网
从最新版本的linux系统开始,默认的是 Mariadb而不是mysql!这里依旧以mysql为例进行展示 1、先检查系统是否装有mysql rpm -qa | grep mysql 这里返回空值,说明没有安装 这里执行安装命令是无效的,因为ce yum install mysql …...
discuz怎么做网站地图/南京seo优化培训
今天装一个php程序,提示mysql_connect() 不支持 。baidu、google一个都不少的搜索,更改php.ini,拷贝dll.都试了,参考了如下方法等:1、没有正确安装Mysql数据库,在系统服务中Mysql相关的服务没有启动 (请…...
域名注册网站便宜/优化大师人工服务电话
数据库查询 查询出某个字段所有数据转为一维数组 Db::table(pic_link)->pluck(id); tinymce文本编辑器图片上传回显 D:\phpstudy_pro\WWW\Qianyi\OneYuan\oneyuanapi\plugin\admin\public\component\pear\module\tinymce\tinymce.js 第27行开始if (res[this.response.st…...