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;
- }