linux下epoll服务器

#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include<netdb.h>
#include<sys/types.h>
#include<sys/epoll.h>
#include<fcntl.h>
#include<time.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<assert.h>

#include"list.h"

#define BUFSIZE		512
#define MAXCONN		200
#define	MAX_EVENTS	MAXCONN

struct sockfd_opt		//socket对象结构体
{
	int fd;
	int (*do_task)(struct sockfd_opt *p_so);//回调函数指针
	struct hlist_head hlist;		//内嵌链表节点
};

typedef struct sockfd_opt SOCKOPT;		//为socket对象结构体定义新名字

int send_replay(struct sockfd_opt*);		//发送应答函数
int create_conn(struct sockfd_opt*);		//创建连接函数
int init(int);				//初始化函数
int intHash(int);
void setnonblockng(int);

struct hlist_head fd_hash[MAXCONN];		//sockfd_opt的散列表
int epfd;
int num;		//fd_hash中的fd总数
struct epoll_event *events;

static void bail(const char *on_what)		//错误报告函数
{
	fputs(strerror(errno),stderr);
	fputs(": ",stderr);
	fputs(on_what,stderr);
	fputc('\n',stderr);
	exit(1);
}

int main(int argc,char argv[])			//主函数为带命令行参数的函数,用于输入段端口信息
{
	int listen_fd;				//监听套接字
	struct sockaddr_in srvaddr;	//监听套接字地址
	int port;					//服务器监听端口
	int nev;					//epoll_wait返回的文件描述符个数
	unsigned int hash;
	struct hlist_node *n;
	struct sockfd_opt *p_so;

	if(argc!=2)				//若命令行参数个数不等于2,输出端口信息错误
	{
		fprintf(stderr,"Usage: %s port\a\n",argv[0]);
		exit(1);
	}

	if((port=atoi(argv[1]))<0)		//若命令行的的二个参数经过字符到整型的转换小于0,输出端口信息错误
	{
		fprint(stderr,"Usage: %s port\a\n",argv[0]);
		exit(1);
	}

	epfd=epoll_create(MAXCONN);		//创建epoll上下文环境

	if((listen_fd=socket(PF_INET,SOCK_STREAM,0))==-1)//创建监听套接字
	{
		fprintf(stderr,"Socket error: %s\a\n",strerror(errno);
		exit(1);
	}
	
	setnonblocking(listen_Fd);		//设置为费阻塞模式

	//设置服务器套接字地址
	memset(&srvaddr,0,sizeof(srvaddr);
	srvaddr.sin_family=PF_INET;
	srvaddr.sin_addr.s_addr=htonl(INADDR_ANY);
	srvaddr.sin_port=htons(port);

	if((bind(listen_fd,(struct sockaddr*)(&srvaddr),sizeof(srvaddr)))==-1)//绑定监听套接字
	{
		fprintf(stderr,"Bind error: %s\a\n",strerror(errno);
		exit(1);
	}
	
	if(listen(listen_fd,5)==-1)	//开始监听
		bail("listen()");

	if(init(listen_fd))		//初始化监听套接字
		bail("init");
	
	events=malloc(sizeof(epoll_event)*MAX_EVENTS));		//这里直接申请最大事件数量乘以epoll_event结构体大小的空间,并将受地址返回个赋值给之前声明的结构体指针
	if(!events)
		bail("malloc");

	printf("Server is waiting for acceptance of new client\n");
	for(;;)		//循环接受客户端
	{
		//等待注册事件的发生,
		nev=epoll_wait(epfd,events,MAX_EVENTS,-1);
		//该函数的原型为 int epoll_wait(int epfd,struct epoll_event *event,int maxevents,int timeout)
		//调用epoll_wait()后,进程将等待时间的发生,直到timeout参数设定的超时值到时为止
		//返回值为准备好的文件描述符个数,第一个参数指明在之前创建成功的epfd文件描述符上进行操作,
		//当epoll_wait成功返回后,返回值为发生了所监视事件的文件描述符个数,并且第二个参数,
		//一个指向 struct epoll_event的指针将会指向被epoll上下文返回的事件,
		//本次epool_wait调用最多可已返回的时间个数由第三个参数确定,因此可已通过遍历的方式逐个处理发生的时间的那些文件描述符
		//注:若timeout参数为0,则函数立即返回,及时没有任何事件发生,若timeout参数为-1,则epoll_wait不返回,直到发生事件为止
		
		if(nev<0)			//epoll_wait错误信息
		{
			free(events);
			bail("epoll_wait");
		}

		for(int i=0;i<nev;i++)
		{
			hash=intHash(events[i].data.fd)&MAXCONN;
			hlist_for_each_entry(p_so,n,&fd_hash[hash],hlist)
			{
				if(p_so->fd==events[i].data.fd)
					p_so->dotask(p_so);
			}
		}
	}

	return 0;
}

int send_reply(struct sockfd_opt *p_so)		//发送回应函数
{
	char reqBuf[BUFSIZE];		//接受缓存
	char dtfmt[BUFSIZE];		//日期-时间结果字符串
	time_t td;					//当前时间和日期
	struct tm tv;
	int z;

	//当前fd向服务器发送了请求
	if((z=read(p_so->fd,reqBuf,sizeof(reqBuf)))<=0)	//若read()返回0即无数据可读则
	{
		//此fd代表的客户端关闭了连接,因此该fd将自动从epfd中删除,于是我们仅需将其从散列表中删除
		close(p_so->fd);		//关闭此套接字
		hlist_del(&p_so->hlist);			//套接字选项链表中删除当前选项p_so
		free(p_so);				 //释放指向该套接字对象的指针

		if(z<0&&errno!=ECONNRESET)
		bail("read()");
	}
	else			//其他情况即read()返回值>0
	{
		reqBuf[z]=0;
		time(&td);		//取当前时间赋值给td
		tv=*localtime(&td);    //转化为本地时间

		strftime(dtfmt,sizeof dtfmt,reqBuf,&tv);		//根据区域信息格式化本地时间和日期

		z=write(p_so->fd,dtfmt,strlen(dtfmt));			//发送时间数据			
		
		if(z<0)
			bail("write()");			//错误信息
	}

	return 0;
}

int create_conn(struct sockfd_opt *p_so)		//创建连接函数,参数是一个指向套接字文件描述符结构体的指针
{
	struct sockaddr_in cliaddr;				//客户端internet地址
	int conn_fd;					//客户端连接套接字
	socklen_t sin_size;				//客户端地址长度
	unsigned int hash;
	struct epoll_event ev;
	int ret;

	sin_size=sizeof(struct sockaddr_in);	//长度赋值

	if((conn_fd=accept(p_so->fd,(struct sockaddr*)(&cliaddr),&sin_size))==-1 )		//调用accept函数接受连接返回一个新的套接字赋给conn_fd
	{
		fprintf(stderr,"Accept error: %s\a\n",strerror(errno));
		exit(1);
	}

	setnonblocking(conn_fd);
	fprintf(stdout,"Server got connection from %s: %d\n",inet_ntoa(cliaddr.s in_addr),ntohs(cliaddr.sin_port));

	if((p_so=(P_SKOPT)malloc(sizeof(SKOPT)))==NULL)	//创建储存该套接字对象的空间
	{
		perror("malloc");
		return -1;
	}

	p_so->fd=conn_fd;	//对该套接字对象的链表节点赋值
	p_so->do_task=send_reply;	//函数指针赋值

	hash=intHash(conn_fd) & MAXCONN;		//使用散列函数散列并与最大连接数相与,保证哈希值小于MAXCONN
	list_add_head(&p_so->hlist,&fd_hash[hash]);	//加入链表节点到链表
	num++;

	ev.data.fd=conn_fd;		//将来epoll会返回此fd给应用
	ev.events=EPOLLIN;		//监视可读事件
	ret=epoll_ctl(epfd,EPOLL_CTL_ADD,conn_fd,&ev);
	//该函数的原型为 int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event)
	//返回值为0表示成功,第一个参数epfd指明在之前创建成功的epfd文件描述符上进行操作,第二个参数op规定了将对第三个参数文件描述符fd的操作方式,
	//event参数进一步向epoll操作提供必要的数据。
	//这里表示对epfd上下文进行添加文件描述符conn_fd操作,
	if(ret)
	{
		perror("epoll_ctl");
		return -1;
	}
	return 0;
}


int init(int fd)	//参数为套接字
{
	struct sockfd_opt *p_so;		//处理每个socket描述符结构体指针
	struct epoll_event ev;			//
	int ret;
	unsigned int hash;

	assert(hlist_empty(&fd_hash[0]));
	num=0;				//设定哈希表中元素个数为零
	if((p_so=(P_SKOPT)malloc(sizeof(SOCKOPT)))==NULL) //创建socket结构体的空间
	{
		perror("malloc");
		return -1;
	}

	p_so->do_task=create_conn;			//设定监听套接字的回调函数为create_conn函数
	p_so->fd=sk;					//将套接字参数赋值给结构体中的套接字成员

	hash=intHash(fd) & MAXCONN;
	hlist_add_head(&p_so->hlist,&fd_hash[hash]);	//将监听套接字描述符加入到哈希表
	//向epoll上下文注册此fd
	ev.data.fd=fd;
	ev.events=EPOLLIN;

	//添加此fd
	ret=epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev);
	if(ret)
		bail("epoll_ctl");
	num++;

	return 0;
}

//计算fd的散列键值
int intHash(int key)
{
	key+=-(key<<15);
	key^=(key>>10);
	key+=(key<<3);
	key^=(key>>6);
	key+=~(key<<11);
	key^=(key>>16);

	return key;
}

void setnonblocking(int sock)		//将套接字设置为非阻塞函数
{
	int opts;						

	opts=fcntl(sock,F_GETFL);		//取套接字的原有工作模式
	if(opts<0)
		bail("fcntl");
	opts=opts|O_NONBLOCK;			//原有工作模式与非阻塞模式进行或运算,在原有工作模式的基础上加上非阻塞工作模式
	if(fcntl(sock,F_SETFL,opts))	//设置新的套接字工作模式
		bail("fcntl");
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值