Epoll实现服务器高并发
最近在做一个关于高并发服务器相关的项目需要用到异步/非阻塞IO通信,实现高TCP并发。
以下用epoll技术实现一个简单的TCP高并发服务器,验证无业务处理的情况下,epoll处理并发连接的数的效果。
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/epoll.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<pthread.h>
#include<string.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<errno.h>
#include <sys/resource.h>
#define MAXBUF 1024
#define MAX_EVENTS 40000
/*
setnoblocking ---设置句柄为非阻塞方式
* */
int setnoblocking(int sockfd)
{
if(fcntl(sockfd,F_SETFL,fcntl(sockfd,F_GETFL,0)|O_NONBLOCK)==-1)
{
return -1;
}
return 0;
}
int main(int argc,char **argv)
{
int listenfd,clientfd;
struct sockaddr_in serv_addr,cli_addr; //服务器网络地址结构体
struct sockaddr_in remote_addr; //客户端网络地址结构体
unsigned int myport;
int sin_size;
int curfds; //记录当前的fd数据,即并发连接数
struct epoll_event ev; //epoll事件结构体
struct epoll_event events[MAX_EVENTS]; //事件监听队列
struct rlimit rt;
pthread_t thread;
pthread_attr_t attr;
sin_size=sizeof(struct sockaddr);
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=htonl(INADDR_ANY); //运行任何IP地址连接
serv_addr.sin_port=htons(9000);
//创建服务器套接字
if((listenfd=socket(AF_INET,SOCK_STREAM,0))<0){
perror("create socket error");
exit(EXIT_FAILURE);
}
/*设置socket属性,端口可以重用*/
int opt=SO_REUSEADDR;
setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
/* 设置每个进程允许打开的最大文件数 */
rt.rlim_max = rt.rlim_cur = MAX_EVENTS;
if (setrlimit(RLIMIT_NOFILE, &rt) == -1) {
perror("setrlimit");
exit(1);
}
//将套接字设置为非阻塞模式
setnoblocking(listenfd);
if((bind(listenfd,(struct sockaddr *)&serv_addr,sin_size))<0){
perror("bind failed!");
exit(EXIT_FAILURE);
}
//监听长度已经是内核允许最大值,512。
listen(listenfd,512);
//创建一个epoll句柄,把监听socket加入到epoll集合里
int epoll_fd;
if((epoll_fd=epoll_create(MAX_EVENTS))==-1){
perror("epoll_create failed!");
exit(EXIT_FAILURE);
}
ev.events=EPOLLIN | EPOLLET;
ev.data.fd=listenfd;
// 注意,这里的参数EPOLLIN | EPOLLET并没有设置对写socket的监听,
// 如果有写操作的话,这个时候epoll是不会返回事件的,如果要对写操作
// 也监听的话,应该是EPOLLIN | EPOLLOUT | EPOLLET
//向epoll注册listenfd监听事件
if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,listenfd,&ev)==-1)
{
perror("epll_ctl:server_sockfd register failed");
exit(EXIT_FAILURE);
}
curfds=1;
int nfds; //epoll监听事件发生的个数
int nread=0;
char recvbuf[1024];
int len;
//循环接受客户端请求
while(1)
{
//等待事件的发生
nfds=epoll_wait(epoll_fd,events,MAX_EVENTS,-1);
if(nfds==-1){
perror("epoll_wait");
exit(EXIT_FAILURE);
}
/*处理所有事件*/
int n;
for(n=0;n<nfds;n++){
// 如果是主socket的事件的话,则表示有新连接进入了,进行新连接的处理。
if(events[n].data.fd==listenfd && (events[n].events & EPOLLIN)){
clientfd=accept(listenfd,(struct sockaddr *)&cli_addr,&sin_size);
if(clientfd<0){
perror("accpet error");
continue;
}
nread++;
printf("accept success!\n");
printf("nconnect:%d\n",nread);
setnoblocking(clientfd); //将新连接置于阻塞模式
ev.events=EPOLLIN | EPOLLET; //将新的连接也加入epoll队列
ev.data.fd=clientfd;
if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,clientfd,&ev)==-1){
perror("epll_ctl:server_sockfd register failed");
exit(EXIT_FAILURE);
}
curfds++;
printf("curfds:%d\n",curfds);
}
else if(events[n].events & EPOLLIN){//如果是已连接的用户,并且收到数据,进行读入
clientfd=events[n].data.fd;
len=recv(clientfd,recvbuf,MAXBUF,0);
if(len>0){
send(clientfd,"hello",5,0);
do{
len=recv(clientfd,recvbuf,MAXBUF,0);
}while(len>0);
}
else if(len==0){ //发出了EPOLLIN事件却没有读取的,表示断线
epoll_ctl(epoll_fd,EPOLL_CTL_DEL,clientfd,&events[n]);
close(clientfd);
events[n].data.fd=-1;
}
}
}
}
close(listenfd);
return 0;
}
其他相关有价值的参考文档:
实现了一个比nginx速度更快的HTTP服务器
http://www.cnblogs.com/clowwindy/archive/2011/09/23/a_http_server_faster_than_nginx.html
常见多线程与并发服务器设计方案举例
http://blog.csdn.net/pi9nc/article/details/23246641