(2)服务器从网络接收到日期时间请求字符串后,根据字符串格式生成对应的日期时间值返回给客户端。
二、事件I/O
在前面用select函数有效的解决了多个I/O端口的复用问题,但是select函数存在两个缺陷:一是进程所能同时打开的文件描述符个数受FD_SETSIZE大小的限制,二是每个select函数返回可用的文件描述符集合后,应用都必须对所有已注册的文件描述符进行遍历对比,以确定哪个描述符上发生了事件,从而对其进行读写操作,于是随着文件描述符的增加,系统的性能线性下降,从Linux内核2.6开始,提供了一种新的I/O模型,称为事件I/O(epoll)。epoll有效解决了select存在的问题,称为Linux平台上逐渐流行的编程模式,epoll通过三个系统调用完成了高效的I/O模型的实现:
(1)epoll_create 初始化epoll上下文环境
(2)epoll_ctl 向epoll上下文添加或者除去需要系统监视的文件描述符
(3)epoll_wait 等待文件描述符上发生事件
(一)、创建epoll上下文环境epoll_create
#include
int epoll_create(int size)
epoll_create()函数调用成功,则初始化一个epoll实例,并返回一个和此实例相关联的文件描述符,该文件描述符实际并不指向任何真实的文件,仅作为句柄用于后续使用此epoll实例,size参数表示应用预计需要内核监视的文件描述符个数,注意该参数并不表示最大监视文件描述符个数,而只是一个告诉内核的提示数目,内核可以根据此size参数分配合适的内部数据结构,因此该数据越准确,就越能获得更好的性能。当发生错误时,epoll_create返回-1,并且设置错误代码errno为以下几个值:
(1)EINVAL 参数size不是一个整数
(2)ENFILE 系统已经分配了最大限度的文件描述符个数了
(3)ENOMEM 无足够内存完成此操作
示例:
int epfd;
epfd=epoll_create(100)
if(epfd<0)
{
perror("epoll_creat");
}
注意:在使用完毕此文件描述符后,应该通过close()关闭
(二)、epoll设置epoll_ctl
#include
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
示例:
//注册一个文件描述符并要求epoll根据传入的事件类型参数进行监视
struct epoll_event event;
int ret;
event.data.fd=fd;//将来epoll会返回此fd给应用
event.events=EPOLLIN|EPOLLOUT;//监视可读和可写事件
ret=epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&event);
if(ret)
{
perror("epoll_ctl");
}
//如果需要修改文件描述符所关联的事件,可以按如下方式进行:
struct epoll_event event;
int ret;
event.data.fd=fd;//将来epoll会返回此fd给应用
event.events=EPOLLIN;//监视可读事件
ret=epoll_ctl(epfd,EPOLL_CTL_MOD,fd,&event);
if(ret)
{
perror("epoll_ctl");
}
//如果需要从epoll上下文中去除一个文件描述符,则可以按如下方式进行:
struct epoll_event event;
int ret;
ret=epoll_ctl(epfd,EPOLL_CTL_DEL,fd,&event);
if(ret)
{
perror("epoll_ctl");
}
#include
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
返回值:准备好的文件描述符个数表示成功,-1表示错误,errno记录错误号#define MAX_EVENTS 64 //设置一次epoll_wait最多能返回的事件
struct epoll_event *events;
int nr_events,epfd;
events=(struct epoll_event*)malloc(sizeof(struct epoll_event)*MAX_EVENTS);
if(!events)
{
perror("malloc");
return 1;
}
nr_events=epoll_wait(epfd,events,MAX_EVENTS,-1);
if(nr_events<0)
{
perror("epoll_wait");
free(events);
return 1;
}
//下面开始对epoll返回的事件进行遍历,处理
for(int i=0;i
//服务器
//用法: ./server ip port
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MaxFd 10240 //最大连接数
#define backlog 1000 //监听队列大小
#define MAX_EVENTS 64 //设置一次epoll_wait最多能返回的事件
#define MaxSize 1024 //缓冲区大小
#define Maxfile 100 //进程最多打开文件数
int do_task(int connfd) //客服端请求处理程序
{
int nread;
char buf[MaxSize];
nread=read(connfd, buf, sizeof(buf));
if (nread==0)
{
printf("client close the connection\n");
close(connfd);
return -1;
}
if (nread<0)
{
perror("read error");
close(connfd);
return -1;
}
buf[nread]=0;
printf("send '%s' ok\n",buf);
write(connfd, buf, nread);
return 0;
}
int main(int argc,char **argv)
{
char quit[10];
bool opt=true;
int handle,curfd,acceptCount;
struct sockaddr_in server,client;
int listenfd,connfd,port;
socklen_t socklen=sizeof(struct sockaddr_in);
struct epoll_event ev;
struct epoll_event events[MaxFd+10]; //事件集
struct rlimit rt;
//设置每个进程允许打开的最大文件数
rt.rlim_max=rt.rlim_cur=Maxfile;
if (setrlimit(RLIMIT_NOFILE, &rt)==-1)
{
perror("setrlimit error");
return -1;
}
listenfd=socket(AF_INET, SOCK_STREAM, 0);
if (listenfd==-1)
{
perror("socket error\n");
return -1;
}
bzero(&server, sizeof(server));
server.sin_family=AF_INET;
inet_aton(argv[1], &server.sin_addr);
port=atoi(argv[2]);
server.sin_port=htons(port);
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bind(listenfd, (struct sockaddr*)&server,socklen);
if (listen(listenfd, backlog)==-1)
{
perror("listen error");
return -1;
}
//创建 epoll 句柄,把监听 socket 加入到 epoll 集合里
handle=epoll_create(MaxFd);
ev.events=EPOLLIN | EPOLLET; //沿触发模式或水平触发模式。
ev.data.fd = listenfd; //监听的套接字
if (epoll_ctl(handle, EPOLL_CTL_ADD, listenfd, &ev) < 0)
{
fprintf(stderr, "epoll set listenfd error: fd=%d\n", listenfd);
return -1;
}
//添加输入 文件描述符
ev.events=EPOLLIN | EPOLLET;
ev.data.fd = STDOUT_FILENO;
epoll_ctl(handle, EPOLL_CTL_ADD, STDOUT_FILENO, &ev);
acceptCount=0;
printf("epollserver startup,port %d, max connection is %d, backlog is %d\n", port,MaxFd,backlog);
while (1)
{
//等待有事件发生
curfd=epoll_wait(handle,events,MAX_EVENTS,-1); //阻塞式调用
if (curfd==-1)
{
perror("epoll_wait");
continue;
}
for (int i=0; iMaxFd)
{
fprintf(stderr, "too many connection, more than %d\n", MaxFd);
close(connfd);
continue;
}
printf("accept from %s:%d, tot %d accepted\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port),acceptCount);
if (fcntl(connfd, F_SETFL,fcntl(connfd, F_GETFD,0)| O_NONBLOCK)==-1)
{
perror("set nonblocking error");
}
//添加连接套接字到事件集中
ev.events=EPOLLIN | EPOLLET;;
ev.data.fd = connfd;
if (epoll_ctl(handle, EPOLL_CTL_ADD, connfd, &ev) < 0)
{
fprintf(stderr, "epoll set connfd error: fd=%d\n", listenfd);
return -1;
}
continue;
}
else if(events[i].data.fd==STDOUT_FILENO) //可输入
{
scanf("%s",quit);
if (strcasecmp(quit, "quit")==0)
{
printf("server close...\n");
close(listenfd);
return 0;
}
continue;
}
if (do_task(connfd)<0) //执行客服端请求
{
epoll_ctl(handle, EPOLL_CTL_DEL, events[i].data.fd,&ev); //删除该连接套接字
acceptCount--;
}
}
}
return 0;
}
/*
TCP服务器
用法:./server port
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define BUFSIZE 1024
#define MAXCONN 200
static void bail(const char *on_what)
{
fputs(strerror(errno),stderr);
fputs(": ",stderr);
fputs(on_what, stderr);
fputc('\n',stderr);
exit(1);
}
struct sockfd_opt //处理每个socket描述符的结构体
{
int fd; //描述符
int (*do_task)(struct sockfd_opt *p_so); //回调函数
};
vectorHashHead[MAXCONN]; //链表元素
int epfd;
struct epoll_event *events;
//设置为非阻塞模式
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)<0)
bail("fcntl");
}
//生成hash值
int intHash(int key)
{
key+=~(key<<15);
key^=(key>>10);
key+=(key<<3);
key^=(key>>6);
key+=~(key<<11);
key^=(key>>16);
return key;
}
//向客户端发回日期时间
int send_reply(struct sockfd_opt *p_so)
{
char reqBuf[BUFSIZE]; //接收缓存
char dtfmt[BUFSIZE];//日期-时间结果字符串
time_t td; //当前时间和日期
struct tm tm;
long z;
unsigned int hash;
if ((z=read(p_so->fd, reqBuf, sizeof(reqBuf)))<=0)
{
//此fd代表的客户端关闭了连接,因此该fd将自动从epfd中删除,于是我们仅需将其从散列表中删除
hash=intHash(p_so->fd)& MAXCONN;
vector::iterator it;
HashHead[hash].erase(find(HashHead[hash].begin(), HashHead[hash].end(), p_so)); //删除
//关闭当前套接字描述符
close(p_so->fd);
free(p_so);
//若读操作返回-1且不是RST分段
if (z<0 && (errno|=ECONNRESET))
bail("read()");
}
else
{
reqBuf[z]=0;
time(&td);
tm=*localtime(&td);
strftime(dtfmt, sizeof(dtfmt), reqBuf, &tm);
//向客户端发回结果
z=write(p_so->fd, dtfmt, strlen(dtfmt));
if (z<0)
bail("write()");
}
return 0;
}
//接收TCP连接
int creat_conn(struct sockfd_opt *p_so)
{
unsigned int hash;
struct sockaddr_in client; //客户端ip地址
int conn_fd;
socklen_t sin_size;
sin_size=sizeof(client);
struct epoll_event ev;
if ((conn_fd=accept(p_so->fd, (struct sockaddr*)&client, &sin_size))==-1)
{
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(client.sin_addr),ntohs(client.sin_port));
int ret;
if ((p_so=(struct sockfd_opt*)malloc(sizeof(sockfd_opt)))==NULL)
{
perror("malloc");
return -1;
}
p_so->fd=conn_fd;
p_so->do_task=send_reply;
hash=intHash(conn_fd)&MAXCONN;
HashHead[hash].push_back(p_so);
// printf("fd2:%d hash2:%d size2:%d\n",conn_fd,hash,HashHead[hash].size());
//向epoll上下文注册此conn_fd
ev.data.fd=conn_fd;
ev.events=EPOLLIN;
//ev.events=EPOLLIN|EPOLLET
//添加此fd
ret=epoll_ctl(epfd,EPOLL_CTL_ADD,conn_fd,&ev);
if (ret)
bail("epoll_ctl");
return 0;
}
//初始化监听套接字选项
int init(int fd)
{
sockfd_opt *p_so;
struct epoll_event ev;
unsigned int hash;
int ret;
if ((p_so=(struct sockfd_opt*)malloc(sizeof(sockfd_opt)))==NULL)
{
perror("malloc");
return -1;
}
//设置监听套接字选项的回调函数
p_so->do_task=creat_conn;
p_so->fd=fd;
//将监听套接字选项加入到链表尾
hash=intHash(fd)&MAXCONN;
HashHead[hash].push_back(p_so);
// printf("fd1:%d hash1:%d size1:%d\n",fd,hash,HashHead[hash].size());
//向epoll上下文注册此fd
ev.data.fd=fd;
ev.events=EPOLLIN;
//ev.events=EPOLLIN|EPOLLET
//添加此fd
ret=epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev);
if (ret)
bail("epoll_ctl");
return 0;
}
int main(int argc,char *argv[])
{
int listen_fd; //用于监听的套接字描述符
struct sockaddr_in server;
int port;
socklen_t optlen;
epfd=epoll_create(MAXCONN); //epoll集合
int nev;//epoll_wait返回的文件描述符个数
vector::iterator it;//迭代器
struct sockfd_opt *p_so;
unsigned int hash;
port=atoi(argv[1]);
if((listen_fd=socket(PF_INET, SOCK_STREAM, 0))==-1)
bail("socket()");
setnonblocking(listen_fd);
//设置套接字选项
int opt;
optlen=sizeof(opt);
int ret=setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, optlen);
if (ret)
bail("setsockopt()");
//服务器监听地址准备
memset(&server, 0, sizeof(server));
server.sin_family=PF_INET;
server.sin_addr.s_addr=htonl(INADDR_ANY);
server.sin_port=htons(port);
//绑定服务器到监听套接字
if((bind(listen_fd, (struct sockaddr*)&server, sizeof(server)))==-1)
bail("bind()");
//开始监听
if(listen(listen_fd, 5)==-1)
bail("listen()");
if (init(listen_fd))
bail("init()");
events=(struct epoll_event*)malloc(sizeof(struct epoll_event)*MAXCONN);
printf("server is waiting for acceptance of new client\n");
for (; ; )
{
//等待注册的事件发生
nev=epoll_wait(epfd,events,MAXCONN,-1);
if (nev<0)
{
free(events);
bail("epoll_wait");
}
for (int i=0; ifd==events[i].data.fd)
{
(*it)->do_task(*it);
break; //跳出来,迭代器可能会失效(当删除一个套接字描述符后)
}
++it;
}
}
}
return 0;
}
//TCP客户端
/*
用法:./client hostname port
说明:本程序使用TCP连接和TCP服务器通信,当连接建立后,向服务器发送如下格式字符串
格式字符串示例:
(1) %D
(2) %A %D %H:%M:%S
(3) %A
(4) %H:%M:%S
(5)...
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define BUFSIZE 1024
#define backlog 128 //等待队列大小
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 sockfd;//客户端套接字
char buf[BUFSIZE];
struct sockaddr_in server_addr;
int portnumber;
long nbytes;
long z;
char reqBuf[BUFSIZE];
if (argc!=3)
{
printf("输入格式错误\n");
exit(1);
}
if ((portnumber=atoi(argv[2]))<0)
{
fprintf(stderr,"Usage:%s hostname portnumber\a\n",argv[0]);
exit(1);
}
//创建客户端套接字
if ((sockfd=socket(PF_INET,SOCK_STREAM,0))==-1)
{
fprintf(stderr,"Socket error:%s\a\n",strerror(errno));
exit(1);
}
//创建服务器地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(portnumber);
if (!inet_aton(argv[1], &server_addr.sin_addr))
{
bail("bad address");
}
//连接服务器
if (connect(sockfd, (struct sockaddr*)(&server_addr),sizeof(server_addr))==-1)
{
fprintf(stderr,"connect error:%s\a\n",strerror(errno));
exit(1);
}
printf("connected to server %s\n",inet_ntoa(server_addr.sin_addr));
//客户端主循环输入 “quit”退出
for (; ; )
{
//提示输入日期请求格式字符串
fputs("\nEnter fotmat string(^D or 'quit' to exit):",stdout);
if (!fgets(reqBuf,sizeof(reqBuf),stdin))
{
printf("\n");
break;
}
//为日期时间请求字符串添加NULL字符作为结尾,另外同时去掉末尾的换行符
z=strlen(reqBuf);
if (z>0 && reqBuf[--z]=='\n')
reqBuf[z]=0;
if (z==0)//客户端仅键入Enter
continue;
//输入‘quit’退出
if(!strcasecmp(reqBuf,"QUIT"))//忽略大小写比较
{
printf("press any key to end client.\n");
getchar();
break;
}
//发送日期时间请求字符串到服务器,注意请求信息中去掉了NULL字符
z=write(sockfd, reqBuf, sizeof(reqBuf));
printf("client has sent '%s' to the sever\n",reqBuf);
if (z<0)
bail("write()");
//从客户端套接字中读取服务器发回的应答
if ((nbytes=read(sockfd,buf,sizeof(buf)))==-1)
{
fprintf(stderr,"read error:%s\n",strerror(errno));
exit(1);
}
//若服务器由于某种原因关闭了连接,则客户端需要处理此事件
if(nbytes==0)
{
printf("server hs closed the socket.\n");
printf("press any key to exit...\n");
getchar();
break;
}
buf[nbytes]='\0';
//输出日期时间结果
printf("result from %s port %u:\n\t'%s'\n",inet_ntoa(server_addr.sin_addr),(unsigned)ntohs(server_addr.sin_port),buf);
}
close(sockfd);
return 0;
}