Tcp服务器
一请求一线程
首先说明问题: 已请求一线程能承载的请求数量极少,posix标准线程8M,请求数量多时极其占用内存
简单实现 实现一请求一线程很简单:
#define BUFFER_SIZE 1024
void *client_routine(void *arg)
{
int clientfd = *(int *) arg;
while(1)
{
char buffer[BUFFER_SIZE] = {0};
int len = recv(clientfd, buffer, BUFFER_SIZE, 0);
if(len <0 )
{
close(clientfd);
break;
}
else if(len ==0 )
{
close(clientfd);
break;
}
else{
printf("Recv: %s, %d btye(s) from %d\n", buffer, len, clientfd);
}
}
}
int main(int argc, char *argv[])
{
if(argc <2)
return -1;
int port = atoi(argv[1]);
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr;
memset(&addr, 0, sizeof(struct sockaddr_in));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
if(bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) <0)
{
perror("bind\n");
return 2;
}
if(listen(sockfd, 5) <0 )
{
perror("listen\n");
return 3;
}
while(1)
{
struct sockaddr_in client_addr;
memset(&client_addr, 0, sizeof(struct sockaddr_in));
socklen_t client_len = sizeof(client_addr);
int clientfd = accept(sockfd, (struct sockaddr*)&client_addr, &client_len);
pthread_t thread_id;
pthread_create(&thread_id, NULL, client_routine, &clientfd);
}
return 0;
}
main函数中首先在指定端口处打开一个迎宾套接字sockfd用于对到来的请求分配线程(新建客户端套接字)来处理
int clientfd = accept(sockfd, (struct sockaddr*)&client_addr, &client_len); 就是迎宾套接字不断检测是否有新请求到来,如果到来就返回clientfd 后面新建立的线程将clientfd作为参数传递给线程执行函数即可: pthread_create(&thread_id, NULL, client_routine, &clientfd);
Epoll实现Tcp服务端
使用epoll来管理多个io到来的请求然后依次处理这些请求
首先理清逻辑
还是通过迎宾套接字来捕获请求,只是迎宾套接字应该首先加入epoll中 外层循环需要对epoll中有输入的套接字(io)遍历,如果有输入(有请求),就处理,这里需要判断套接字的类型:如果是迎宾套接字说明有新的请求,需要通过accpet来创建clientfd并交给epoll管理,如果不是就处理请求,处理完毕后从epoll中删除套接字(io),所以这个逻辑下可以知道,到来的请求是在下一轮中才处理的,并不是一到来就立即处理
C实现
#define EPOLL_SIZE 1024
#define BUFFER_SIZE 1024
int main(int argc, char *argv[])
{
if(argc <2)
return -1;
int port = atoi(argv[1]);
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr;
memset(&addr, 0, sizeof(struct sockaddr_in));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
if(bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) <0)
{
perror("bind\n");
return 2;
}
if(listen(sockfd, 5) <0 )
{
perror("listen\n");
return 3;
}
int epfd = epoll_create(1);
struct epoll_event events[EPOLL_SIZE] = {0};
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = sockfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);
while(1)
{
int nready = epoll_wait(epfd, events, EPOLL_SIZE, 5);
//-1 events里没东西就不去
if(nready == -1) continue;
int i=0;
for(i = 0;i <nready;++i)
{
if (events[i].data.fd == sockfd)
{ //迎宾的sock那就新产生一个clientfd然后交给epoll
struct sockaddr_in client_addr;
memset(&client_addr, 0, sizeof(struct sockaddr_in));
socklen_t client_len = sizeof(client_addr);
int clientfd = accept(sockfd, (struct sockaddr*)&client_addr, &client_len);
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = clientfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev);
}
else
{
int clientfd = events[i].data.fd;
char buffer[BUFFER_SIZE] = {0};
int len = recv(clientfd, buffer, BUFFER_SIZE, 0);
if(len <0 )
{
close(clientfd);
ev.events = EPOLLIN;
ev.data.fd = clientfd;
epoll_ctl(epfd, EPOLL_CTL_DEL, clientfd, &ev);
}
else if(len ==0 )
{
close(clientfd);
ev.events = EPOLLIN;
ev.data.fd = clientfd;
epoll_ctl(epfd, EPOLL_CTL_DEL, clientfd, &ev);
}
else{
printf("Recv: %s, %d btye(s) from %d\n", buffer, len, clientfd);
}
}
}
}
return 0;
}
关键的函数是epoll_ctl,epoll_create,epoll_wait
epoll_create的参数是一个int size这个参数在新版本linux内核中无意义,原来也只是告诉内核epoll大致的大小 epoll_ctl配合EPOLL_CTL_xxx的宏来对epoll进行操作(添加删除修改) epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout) 用来等待事件发生,其他参数比较简单,只说timeout参数:在没有检测到事件发生时最多等待的时间(单位为毫秒)
ET、LT工作模式 水平触发模式:
ev.events = EPOLLIN;
边缘触发模式:
ev.events = EPOLLIN | EPOLLET;
设置好触发模式后可以把event加入epoll中:
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = sockfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);