#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");
}
linux下epoll服务器
最新推荐文章于 2022-10-19 17:53:32 发布