select,epoll网络模型经常在面试中出现,epoll是对poll的优化,是linux下最优秀的网络模型
epoll优点:
# 相对select,没有最大并发数限制 /proc/sys/file-max
# 数据传递(用户空间跟内核空间)通过共享内存(mmap)方式
# epoll_wait 直接返回被触发的fd对应的一块buffer,不需要遍历所有的fd
一.Linux select模型
流程:
1. 声明数组fd_A,添加多个socket client fd
2. 监听端口
3. 将sock_fd 和 数组fd不为0描述符放入select将检查的fd_set中
4. 处理 fdsr可以接收数据的连接; 如是sock_fd,添加新连接至fd_A;
详见: http://blog.chinaunix.net/uid-25808509-id-2233262.html
// select_tcp_server.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define MYPORT 1234 // the port users will be connecting to #define BACKLOG 5 // how many pending connections queue will hold #define BUF_SIZE 200 int fd_A[BACKLOG]; // accepted connection fd int conn_amount; // current connection amount void showclient() { int i; printf("client amount: %d\n", conn_amount); for (i = 0; i < BACKLOG; i++) { printf("[%d]:%d ", i, fd_A[i]); } printf("\n\n"); } int main(void) { int sock_fd, new_fd; // listen on sock_fd, new connection on new_fd struct sockaddr_in server_addr; // server address information struct sockaddr_in client_addr; // connector's address information socklen_t sin_size; int yes = 1; char buf[BUF_SIZE]; int ret; int i; if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("socket"); exit(1); } if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) { perror("setsockopt"); exit(1); } server_addr.sin_family = AF_INET; // host byte order server_addr.sin_port = htons(MYPORT); // short, network byte order server_addr.sin_addr.s_addr = INADDR_ANY; // automatically fill with my IP memset(server_addr.sin_zero, '\0', sizeof(server_addr.sin_zero)); if (bind(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) { perror("bind"); exit(1); } if (listen(sock_fd, BACKLOG) == -1) { perror("listen"); exit(1); } printf("listen port %d\n", MYPORT); fd_set fdsr; int maxsock; struct timeval tv; conn_amount = 0; sin_size = sizeof(client_addr); maxsock = sock_fd; while (1) { // initialize file descriptor set FD_ZERO(&fdsr); FD_SET(sock_fd, &fdsr); // timeout setting tv.tv_sec = 30; tv.tv_usec = 0; // add active connection to fd set for (i = 0; i < BACKLOG; i++) { if (fd_A[i] != 0) { FD_SET(fd_A[i], &fdsr); } } ret = select(maxsock + 1, &fdsr, NULL, NULL, &tv); if (ret < 0) { perror("select"); break; } else if (ret == 0) { printf("timeout\n"); continue; } // check every fd in the set for (i = 0; i < conn_amount; i++) { if (FD_ISSET(fd_A[i], &fdsr)) { ret = recv(fd_A[i], buf, sizeof(buf), 0); if (ret <= 0) { // client close printf("client[%d] close\n", i); close(fd_A[i]); FD_CLR(fd_A[i], &fdsr); fd_A[i] = 0; } else { // receive data if (ret < BUF_SIZE) memset(&buf[ret], '\0', 1); printf("client[%d] send:%s\n", i, buf); } } } // check whether a new connection comes if (FD_ISSET(sock_fd, &fdsr)) { new_fd = accept(sock_fd, (struct sockaddr *)&client_addr, &sin_size); if (new_fd <= 0) { perror("accept"); continue; } // add to fd queue if (conn_amount < BACKLOG) { fd_A[conn_amount++] = new_fd; printf("new connection client[%d] %s:%d\n", conn_amount, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); if (new_fd > maxsock) maxsock = new_fd; } else { printf("max connections arrive, exit\n"); send(new_fd, "bye", 4, 0); close(new_fd); break; } } showclient(); } // close other connections for (i = 0; i < BACKLOG; i++) { if (fd_A[i] != 0) { close(fd_A[i]); } } exit(0); }
二. linux epoll模型
//epoll_tcp_server.c #include <stdio.h> #include <unistd.h> #include <netinet/in.h> #include <sys/socket.h> #include <sys/epoll.h> #include <arpa/inet.h> #include <fcntl.h> #include <errno.h> #include <time.h> #include <list> #include <stdlib.h> #include <string.h> #define BUF_SIZE 1024 #define SERV_PORT 1234 #define EPOLL_RUN_TIMEOUT -1 #define EPOLL_SIZE 10000 #define STR_WELCOME "welcome to seChat! You ID is:Client #%d" #define STR_MESSAGE "Client #%d>>%s" #define STR_NOONE_CONNECTED "Noone connected to server except you" #define CHK(eval) if(eval<0){ perror("eval"); exit(-1); } #define CHK2(res,eval) if((res = eval)<0){ perror("eval"); exit(-1); } using namespace std; list<int> clients_list; // 设置非阻塞 int SetNoblocking(int sockfd) { CHK(fcntl(sockfd,F_SETFL,fcntl(sockfd,F_GETFD,0)|O_NONBLOCK)); return 0; } // 处理消息 int HandleMsg(int client) { char buf[BUF_SIZE],message[BUF_SIZE]; bzero(buf,BUF_SIZE); bzero(message,BUF_SIZE); int len; CHK2(len,recv(client,buf,BUF_SIZE,0)); printf("Accept:%s\n",buf); // 客户端关闭 if(len==0) { CHK(close(client)); clients_list.remove(client); }else { // 向客户端发送消息 if(clients_list.size()==1) { CHK(send(client,STR_NOONE_CONNECTED,strlen(STR_NOONE_CONNECTED),0)); return len; } sprintf(message,STR_MESSAGE,client,buf); list<int>::iterator it; for(it = clients_list.begin(); it!=clients_list.end();it++) { if(*it!=client) { CHK(send(*it,message,BUF_SIZE,0)); } } } return len; } int main(int argc,char **argv) { int listener; struct sockaddr_in addr,their_addr; addr.sin_family = AF_INET; addr.sin_port = htons(SERV_PORT); addr.sin_addr.s_addr = inet_addr("127.0.0.1"); socklen_t socklen; socklen = sizeof(struct sockaddr_in); static struct epoll_event ev,events[EPOLL_SIZE]; ev.events = EPOLLIN|EPOLLET; // 读感兴趣,边沿触发 char message[BUF_SIZE]; int epfd; clock_t tStart; int client,res,epoll_events_count; CHK2(listener,socket(AF_INET,SOCK_STREAM,0)); SetNoblocking(listener); CHK(bind(listener,(struct sockaddr*)&addr,sizeof(addr))); CHK(listen(listener,1)); CHK2(epfd,epoll_create(EPOLL_SIZE)); ev.data.fd = listener; CHK(epoll_ctl(epfd,EPOLL_CTL_ADD,listener,&ev)); while(1) { CHK2(epoll_events_count,epoll_wait(epfd,events,EPOLL_SIZE,EPOLL_RUN_TIMEOUT)); tStart = clock(); int i; for(i=0;i<epoll_events_count;i++) { if(events[i].data.fd == listener) // new connection { CHK2(client,accept(listener,(struct sockaddr*)&their_addr,&socklen)); SetNoblocking(client); ev.data.fd = client; CHK(epoll_ctl(epfd,EPOLL_CTL_ADD,client,&ev)); // register clients_list.push_back(client); // add to list bzero(message,BUF_SIZE); res = sprintf(message,STR_WELCOME,client); CHK2(res,send(client,message,BUF_SIZE,0)); }else { CHK2(res,HandleMsg(events[i].data.fd)); } printf("Statistics: %d events handled at: %.2fs\n",epoll_events_count,(double)(clock()-tStart)/CLOCKS_PER_SEC); } } close(listener); close(epfd); return 0; }
TCP测试客户端,模拟1万个连接
// huge_tcp_client.c #define EPOLL_SIZE 10000 using namespace std; char message[BUF_SIZE]; list<int> list_of_clients; int res; clock_t tStart; int main(int argc,char **argv) { int sock; struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(SERV_PORT); addr.sin_addr.s_addr = inet_addr("127.0.0.1"); tStart = clock(); for(int i=0;i<EPOLL_SIZE;i++) { CHK2(sock,socket(AF_INET,SOCK_STREAM,0)); CHK(connect(sock,(struct sockaddr*)&addr,sizeof(addr))<0); list_of_clients.push_back(sock); bzero(&message,BUF_SIZE); CHK2(res,recv(sock,message,BUF_SIZE,0)); printf("Accept:%s\n",message); } list<int>::iterator it; for(it = list_of_clients.begin();it!=list_of_clients.end();it++) { close(*it); } printf("Test passed:.2fs\n",(double)(clock()-tStart)/CLOCKS_PER_SEC); printf("Total server connections:%d\n",EPOLL_SIZE); return 0; }