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