网络IO模型
简介
IO操作分为同步IO和异步IO,二者的区别在于,执行IO操作时,用户进程是否拥有控制权。
IO操作中,设备之间的差别,导致网络通信需要等待。一般分为:
1.输入操作:等待数据到达套接字缓冲区。
2.输出操作:等待套接字缓冲区有足够的空间容纳数据。
3.服务器接受客户连接请求,(java中是accept),监听端口。
4.客户端发起连接请求:等待服务器回送的ack回应syn。
7.1四种网络IO模型
阻塞IO模型
Linux默认的socket都是阻塞性的,也就是在IO操作彻底完成之前,计算机处于内核态,之后才返回用户空间。而非阻塞IO在被调用后仅仅返回用户一个状态值,不需要用户等待iO完成。
内核收到请求,内核准备数据,并把数据从内核中拷贝到用户内存中,然后返回执行结果,用户进程才解除阻塞。
阻塞性接口指的是,调用时不返回结果,而是在系统调用结束或者超时才返回结果。阻塞IO模型的特点就是,在数据准备和数据拷贝两个阶段,用户进程都被阻塞。
使用多线程,可以让每个连接拥有独立的线程,一个连接阻塞不会影响其他连接。
如果有很多客户阿端请求服务,推荐使用多线程减小开销,如果执行体是CPU执行时间长的,例如大规模/长时间数据运算/文件访问,那可以用更加安全的多进程。
服务器一个socket可以接受多次,就是利用了每次接受建立一个连接,每个连接都是独立的线程。每次accept都是在服务器socket fd请求队列取出第一个连接信息,创建与这个fd同类的新socket句柄,这个句柄就是这个端口read和recv函数的参数。
线程池
为了肩上创建和销毁线程频率而出现的。即维持一定数量的线程,并且空闲的线程可以重新承担新的任务。
连接池
尽量利用已有的连接。
在请求规模远远大于池的上限时,就会失去作用,因此需要根据相应规模调整池的大小。
非阻塞IO模型
socket默认是阻塞的,但可以设置成非阻塞。当用户发起读请求,如果内核没有准备好数据,就会返回一个错误值,用户的read没有阻塞。此时用户知道数据还没好,可以继续发read请求。当内核数据准备好了,同时又收到了用户进程的read系统调用,就会开始把数据复制到用户内存,返回正确值。最低级的办法是客户端循环调用recv接口,但这会大幅占用CPU,我们应该用更高级的多路复用模式。
多路复用IO模型
多路复用IO也叫做事件驱动IO。通过一个函数不断轮询指定结构内的所有socket,当某个socket数据到达了,就通知用户进程。
**用户进程在调用select函数期间仍然是阻塞的。**同时,内核监视所有select负责的socket,任何一个socket数据准备好,select函数都会返回,由相应的用户进程去内存中取数据。
在连接数少的时候,多路复用模型性能比zuseIO更差,因为阻塞IO只用到recvfrom一个系统调用,多路复用用到select和recvfrom两个。select/epoll的优势不在于单个处理连接更快,而在于处理更多的连接。
多路复用IO模型的每一个socket都设置成非阻塞,但是用户进程还是会被阻塞,只不过从socekt的IO阻塞变成了select函数阻塞。因此select和非阻塞IO效果类似。
int select(int nfds,fd_set* readfs,fd_set* writefds,
fd_set* exceptfds,timaval* timeout);
fd_set是一个用位标记句柄的队列,例如如果要标记第16个句柄,就把第16位bit置为1。
三个set参数同时作为输入参数和输出参数。
作为输入参数,这些集合应该标记所有需要检测的,对应可读事件,可写事件,错误事件的句柄,readfds永远包含:用来检测客户端connect的母句柄。
作为输出参数,三个集合包含了所有捕捉到的,事件的句柄值,程序通过检测对应位,确定哪些句柄发生了时间。
例如,select函数发现了一个可读事件,服务器需要执行recv系统调用为应用进程服务,同时根据发来的数据准备好要发送到用户空间内存的数据,然后把这个句柄加入到writefds中去。在下一次select检测时把数据写给用户。
这个模型的特点是:每次探测一组事件,每次时间都会触发一个响应,根据响应执行相应操作。这就是事件驱动模型。
select函数只需要单独一个进程,就可以监听多个客户端连接的状态,节约了CPU资源。
select模型在句柄值大的时候,每次轮询花费时间长,但是便于在平台间移植;更为高效的epoll模型可移植性差。
事件探测和事件响应在同一个线程中执行,在事件响应时间很长时,会影响整体性能,会大大降低事件监测的及时性。
linux自2.6开始,引入了异步响应IO模型。
异步IO模型
用户进程发起read请求后,立刻处理其他事务,不再循环检查内核返回状态。
内核收到异步read请求,立刻返回,不造成用户进程阻塞。内核默默地把数据准备完成,然后写入用户空间,一切完成后,再给用户进程发送一个信号,返回read信息。
调用阻塞IO会在调用函数后阻塞,直到调用完成;非调用阻塞IO会在**准备数据的时候返回,准备完再返回操作的值。**同步IO的定义就是再IO操作时会阻塞用户进程的IO操作。
因此,之前的三种IO模型都是同步IO,只不过阻塞性IO是在数据准备时就阻塞了(调用阻塞);非阻塞性IO在数据准备好之后,写入用户内存时阻塞(写时阻塞)。异步IO完全不阻塞用户进程,只是在所有内核操作完成之后,向用户进程发送一个信号,通知他可以到指定内存拿数据,用户进程在此期间都在处理别的事务。
非阻塞IO和异步IO的区别
非阻塞IO还是要求用户去不断查询内核工作状态(调用recv系统调用),而且在把数据写入内存时仍然会阻塞用户进程,而异步IO的数据复制工作完全由内核完成,完成之后通过信号汇报自己的工作情况,就好像内核完全代理了这项工作。用户不需要主动拷贝数据,也不要检查IO状态。
7.2 select
int select(int maxfdp,fd_set* readfs,fd_set* writefds,
fd_set* exceptfds,timaval* timeout);
之前就讲到了,select的三个集合都是句柄,现在补充一下,集合中存放的是文件句柄(后面是fds)。因为Unix下一切皆文件,所以socket也是文件,socket句柄就是一个文件描述符。
Timeval是一个表达时间的结构;
maxfdp表示所有文件描述符的范围,等于文件描述符最大值+1;
集合参数为NULL,表示不关心文件的读变化/写变化/出错事件。
timeout用于限制返回时间,若超时select返回0。
置为-1或NULL表示无限,相当于阻塞,直到监视事件发生;
置为0表示立刻执行,当前文件描述符有变化返回正值,否则返回0;
正数就是指定时间内一直监视,有事件发生提前返回。
用select优化服务器
服务器代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<unistd.h>
#include<arpa/inet.h>
#define MAXLINE 4096
#define DEFAULT_PORT 6666
int MyRecv(int iSock,char * pchBuf,size_t tCount){
size_t tBytesRead = 0;
int iThisRead;
while(tBytesRead < tCount){
do{
iThisRead = read(iSock,pchBuf,tCount - tBytesRead);
}while((iThisRead < 0) && (errno == EINTR));
//表示遇到了中断
if(iThisRead < 0){
return iThisRead;
}else if(iThisRead == 0){
return tBytesRead;
}
tBytesRead += iThisRead;//更新已读的数据量
pchBuf += iThisRead;//移动接收缓冲的偏移地址
}
}
int main(int argc,char** argv){
int serverfd,acceptfd;
struct sockaddr_in clients_addr;
struct sockaddr_in servaddr;
unsigned int sin_size,myport = 6666,lisnum = 10;
if((serverfd = socket(AF_INET,SOCK_STREAM,0)) == -1){
printf("create socket error: %s(error:%d\n)",strerror(errno),errno);
//perror("socket:");
return -1;
}
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(DEFAULT_PORT);
bzero(&(servaddr.sin_zero),0);
//bind里的socket类型是sockaddr,不是sockaddr——in
if(bind(serverfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) == -1){
printf("bind socket error: %s(error:%d\n)",strerror(errno),errno);
return -2;
}
if(listen(serverfd,lisnum) == -1){
printf("listen error: %s(error:%d\n)",strerror(errno),errno);
return -1;
}
//这里socket类型也是sockaddr
printf("======waiting for connection======\n");
fd_set client_fdset;
int maxsock;
struct timeval tv;
int client_sockfd[5];//select的弊端,监控集合大小固定
bzero((void*)client_sockfd,sizeof(client_sockfd));
int conn_amount = 0;
maxsock = serverfd;
char buf[1024];
int ret = 0;
while(true){
FD_ZERO(&client_fdset);
FD_SET(serverfd,&client_fdset);
tv.tv_sec = 30;
tv.tv_usec = 0;
for(int i=0;i<5;i++){
if(client_sockfd[i] != 0){
FD_SET(client_sockfd[i],&client_fdset);
}
}
printf("all socketfd are put in the select read set\n");
//用三个集合构造select函数
ret = select(maxsock+1,&client_fdset,NULL,NULL,&tv);
if(ret < 0){
printf("select error\n");
break;
}else if(ret == 0){
printf("time out");
continue;//again
}
for(int i=0;i<5;i++){
if(FD_ISSET(client_sockfd[i],&client_fdset)){
printf("start recv from client[%d]:\n",i);
ret = recv(client_sockfd[i],buf,1024,0);
if(ret <= 0){
printf("client[%d] closed",i);
close(client_sockfd[i]);
FD_CLR(client_sockfd[i],&client_fdset);
client_sockfd[i] = 0;
}else{
printf("recv from socket[%d],msg:%s\n",i,buf);
}
}
}
//check if there is a new connection
if(FD_ISSET(serverfd,&client_fdset)){
struct sockaddr_in client_addr;//从服务端看来是一个传入socket
size_t size = sizeof(struct sockaddr_in);
//从连接角度来看是一个普通socket
int sock_client = accept(serverfd,(struct sockaddr*)&client_addr,(unsigned int*)(&size));
//第三个参数:地址信息结构体大小
if(sock_client < 0){
perror("accept error:");
continue;
}
if(conn_amount < 5){
client_sockfd[conn_amount++] = sock_client;
bzero(buf,1024);
strcpy(buf,"welcome to my server\n");
send(sock_client,buf,1024,0);
printf("new connection client[%d] %s:%d\n",
conn_amount,inet_ntoa(clients_addr.sin_addr),ntohs(client_addr.sin_port));
bzero(buf,1024);
ret = recv(sock_client,buf,1024,0);
if(ret < 0){
printf("recv error");
close(serverfd);
return -1;
}//出错直接关闭服务器
printf("recv:%s",buf);
if(sock_client > maxsock){
maxsock = sock_client;
}else{
printf("set is full");
break;//先检查现有链接,断开不需要的,再检查新链接
}
}
}
}
for(int i=0;i<5;i++){
if(client_sockfd[i] !=0){
close(client_sockfd[i]);
}
}
close(serverfd);
return 0;
}
客户端代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<unistd.h>
#include<arpa/inet.h>
#define MAXLINE 4096 // the length of a row
int MySend(int iSock,char* putBuf,size_t tLen){
int iThisSend;
unsigned int iSended = 0;
if(tLen == 0){
return 0;
}
while(iSended < tLen){
do{
iThisSend = send(iSock,putBuf,tLen - iSended,0);
}while((iThisSend < 0) && (errno == EINTR));
//字节序是倒过来的,所以从后往前,isended作为偏移地址
if(iThisSend < 0){
return iSended;
}
iSended += iThisSend;
putBuf += iThisSend;
}
return tLen;
}
#define DEFAULT_PORT 6666
int main(int argc,char* argv[]){
int connfd=0,cLen=0;
struct sockaddr_in client;
if(argc != 2){
printf("usage: ./client <ipaddress>\n");
return 0;
}
client.sin_family = AF_INET;
client.sin_port = htons(DEFAULT_PORT);
client.sin_addr.s_addr = inet_addr(argv[1]);
if((connfd = socket(AF_INET,SOCK_STREAM,0)) < 0){
printf("create socket error: %s(error:%d\n)",strerror(errno),errno);
return -1;
}
// if(inet_pton(AF_INET,argv[1],&servaddr.sin_addr) <=0){
// printf("inet_pton error: %s(error:%d\n)",strerror(errno),errno);
// return -2;
// }
if(connect(connfd,(struct sockaddr*)&client,sizeof(client)) < 0){
printf("connect socket error: %s(error:%d\n)",strerror(errno),errno);
return -3;
}
char buffer[1024];
bzero(buffer,sizeof(buffer));
recv(connfd,buffer,1024,0);
printf("recv:%s\n",buffer);
bzero(buffer,sizeof(buffer));
strcpy(buffer,"this is client\n");
send(connfd,buffer,1024,0);
while(true){
bzero(buffer,sizeof(buffer));
scanf("%s",buffer);
int len = strlen(buffer);
buffer[len] = '\0';
send(connfd,buffer,1024,0);
printf("msg sended:%s",buffer);
}
close(connfd);
return 0;
}
使用select不会提升对某一个连接的效率,但是会提高总体效率。
poll
函数原型
其中的pollfd结构如下,每一个pollfd封装了一个文件描述符。
非常明显,这和select一样是一个事件驱动型的检测方式,将三种行为封装在了pollfd的event中。
event是要监听的事件掩码,由用户设置。
revent是发生事件的掩码,由内核在调用返回时设置。
poll提高服务器性能
服务器代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/wait.h>
#include<poll.h>
#define IPADDRESS "127.0.0.1"
#define PORT 6666
#define MAXLINE 4096
#define LISTENW 5
#define OPEN_MAX 1000
#define INFTIM -1//表示延迟无限,poll函数阻塞一直到指定事件发生
int bind_and_listen(){
int serverfd;
struct sockaddr_in my_addr;
unsigned int sin_size;
if((serverfd = socket(AF_INET,SOCK_STREAM,0)) == -1){
perror("socket:");
return -1;
}
printf("socket ok\n");
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(PORT);
my_addr.sin_addr.s_addr = INADDR_ANY;
bzero((&my_addr.sin_addr),0);
if(bind(serverfd,(struct sockaddr*)&my_addr,sizeof(struct sockaddr)) == -1){
perror("bind:");
return -2;
}
printf("bind ok\n");
if(listen(serverfd,LISTENW) == -1){
perror("listen:");
return -3;
}
printf("listen ok\n");
return serverfd;
}
void do_poll(int listenfd){
int connfd,sockfd;
struct sockaddr_in cliaddr;
socklen_t cliaddrlen;
struct pollfd clientfds[OPEN_MAX];
int maxi,i,nready;
clientfds[0].fd = listenfd;
clientfds[0].events = POLLIN;
for(int i=1;i<OPEN_MAX;i++)
clientfds[i].fd = -1;
maxi = 0;
while(true){
nready = poll(clientfds,maxi+1,INFTIM);
//poll返回结构体中revents不为0的文件描述符个数,也就是发生这个事件的fd个数
if(nready == -1){
perror("poll error");
exit(1);
}
if(clientfds[0].revents & POLLIN){
cliaddrlen = sizeof(cliaddr);
if((connfd = accept(listenfd,(struct sockaddr*)&cliaddr,&cliaddrlen)) == -1){
if(errno == EINTR)
continue;
else{
perror("accept error");
exit(1);
}
}
fprintf(stdout,"accept a new client,%s:%d\n",inet_ntoa(cliaddr.sin_addr),cliaddr.sin_port);
for(i=1;i<OPEN_MAX;i++){
if(clientfds[i].fd < 0){
clientfds[i].fd = connfd;
//printf("put%d client at index of :%d\n",cliaddr.sin_port,i);
break;
}
}
if(i == OPEN_MAX){
fprintf(stderr,"too many client\n");
exit(1);
}
clientfds[i].events = POLLIN;
maxi = (i > maxi ? i:maxi);
if(--nready <= 0)
continue;
//如果还有服务端请求,继续处理,直到nready变成负的
}
//终于给我找到了
//上面的都是监听端口的处理,下面才是服务端口的处理
//轮询,发送每个端口的消息
char buf[MAXLINE];
memset(buf,0,MAXLINE);
int readlen = 0;
for(int i=1;i<maxi;i++){
if(clientfds[i].fd <0){
continue;
}
if(clientfds[i].revents & POLLIN){
readlen = read(clientfds[i].fd,buf,MAXLINE);
printf("msg len is %d",readlen);
if(readlen == 0){
close(clientfds[i].fd);
clientfds[i].fd = -1;
continue;
}
write(STDOUT_FILENO,buf,readlen);
write(clientfds[i].fd,buf,readlen);
}
}
}
}
int main(int argc,char** argv){
int listenfd = bind_and_listen();
if(listenfd < 0){
return 0;
}
do_poll(listenfd);
return 0;
}
客户端代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/wait.h>
#include<poll.h>
#define MAXLINE 4096
#define DEFAULT_PORT 6666
#define max(a,b) (a>b)?a:b
static void handle_connection(int sockfd){
char sendline[MAXLINE],recvline[MAXLINE];
int maxfdp,stdineof;
struct pollfd pfds[2];
int n;
pfds[0].fd = sockfd;
pfds[0].events = POLLIN;
pfds[1].fd = STDIN_FILENO;//接受键盘输入流
pfds[1].events = POLLIN;
while(true){
poll(pfds,2,-1);//0,1下标都已经有socket了
if(pfds[0].revents & POLLIN){
n = read(sockfd,recvline,MAXLINE);
if(n == 0){
fprintf(stderr,"client:server is already closed\n");
close(sockfd);
}
printf("server msg:%s,len:%d\n",recvline,n);
write(STDOUT_FILENO,recvline,n);
}
//从服务器端口接收数据,写入到屏幕上,这把屏幕和服务器看成两个文件
if(pfds[1].revents & POLLIN){
n = read(STDIN_FILENO,sendline,MAXLINE);
if(n == 0){
shutdown(sockfd,SHUT_WR);
continue;
}
printf("input msg:%s,len:%d\n",sendline,n);
write(sockfd,sendline,n);
}
//从输入接收数据,发给服务端
}
}
int main(int argc,char* argv[]){
int connfd = 0;
int cLen = 0;
struct sockaddr_in client;
if(argc < 2){
printf("usage: ./client <ipaddress>\n");
return 0;
}
client.sin_family = AF_INET;
client.sin_port = htons(DEFAULT_PORT);
client.sin_addr.s_addr = inet_addr(argv[1]);
connfd = socket(AF_INET,SOCK_STREAM,0);
if(connfd < 0){
printf("create socket error: %s(error:%d\n)",strerror(errno),errno);
return -1;
}
if(connect(connfd,(struct sockaddr*)&client,sizeof(client)) < 0){
printf("connect socket error: %s(error:%d\n)",strerror(errno),errno);
return -3;
}
handle_connection(connfd);
return 0;
}
运行时有一个问题,第一个连接的客户端是失效的,后面的正常。
epoll
epoll使用一个文件描述符管理多个文件描述符,将与用户关系的,文件描述符的事件存储到内核事件表中。用户一段时间内的事件处理完成,就把事件表提交到内核,这样内核与用户空间的数据只复制一次。
函数原型
epoll提高服务器处理能力
服务器代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/wait.h>
#include<sys/epoll.h>
#define IPADDRESS "127.0.0.1"
#define PORT 6666
#define MAXSIZE 1024
#define LISTENW 5
#define FDSIZE 1000
#define EPOLLEVENTS 100
int socket_bind(const char* ip,int port);
void do_epoll(int listenfd);
void handle_events(int epollfd,struct epoll_event* events,int num,
int listenfd,char* buf);
void handle_accept(int epollfd,int listenfd);
void do_read(int epollfd,int fd,char *buf);
void do_write(int epollfd,int fd,char *buf);
void add_event(int epollfd,int fd,int state);
void modify_event(int epollfd,int fd,int state);
void delete_event(int epollfd,int fd, int state);
int main(int argc,char *argv[]){
int listenfd;
listenfd = socket_bind(IPADDRESS,PORT);
listen(listenfd,LISTENW);
do_epoll(listenfd);
return 0;
}
int socket_bind(const char* ip,int port){
int listenfd;
struct sockaddr_in servaddr;
listenfd = socket(AF_INET,SOCK_STREAM,0);
if(listenfd == -1){
perror("socket error:");
exit(1);
}
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(port);
if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) == -1){
perror("bind error");
exit(1);
}
printf("bind ok\n");
return listenfd;
}
void do_epoll(int listenfd){
int epollfd;
struct epoll_event events[EPOLLEVENTS];
//epoll_event 的格式:事件events,数据data
//events可以包含EPOLLIN,EPOLLOUT,EPOLLONESHOT等等
int ret;
char buf[MAXSIZE];
memset(buf,0,MAXSIZE);//第三个参数是以字节为单位,一个char是一个字节
epollfd = epoll_create(FDSIZE);
add_event(epollfd,listenfd,EPOLLIN);
//事件表中加入 文件描述符事件
printf("============waiting for the events==============\n");
while(true){
ret = epoll_wait(epollfd,events,EPOLLEVENTS,-1);
//类似select调用,events就是事件表,后一个大写的表示事件表最大数目
handle_events(epollfd,events,ret,listenfd,buf);
}
close(epollfd);
}
void handle_events(int epollfd,struct epoll_event *events,
int num,int listenfd,char* buf){
int i;
int fd;
for(i=0;i<num;i++){
fd = events[i].data.fd;
if((fd == listenfd) && (events[i].events & EPOLLIN)){
//我日日日日日日日日日日日日日日日日日日日日日日日日日日日日
//双等号写成单等号了
printf("handle accept\n");
handle_accept(epollfd,listenfd);
}else if(events[i].events & EPOLLIN){
printf("handle read\n");
do_read(epollfd,fd,buf);
}else if(events[i].events & EPOLLOUT){
printf("handle write\n");
do_write(epollfd,fd,buf);
}
}
}
void handle_accept(int epollfd,int listenfd){
int clifd;
struct sockaddr_in cliaddr;
socklen_t cliaddrlen;
clifd = accept(listenfd,(struct sockaddr*)&cliaddr,&cliaddrlen);
if(clifd == -1){
perror("accept error:");
}else{
printf("accept a new client,%s:%d",inet_ntoa(cliaddr.sin_addr),cliaddr.sin_port);
add_event(epollfd,clifd,EPOLLIN);
}
}
void do_read(int epollfd,int fd,char* buf){
int nread = 0;
nread = read(fd,buf,MAXSIZE);
if(nread == -1){
perror("read error:");
close(fd);
delete_event(epollfd,fd,EPOLLIN);
}else if(nread == 0){
fprintf(stderr,"client close.\n");
close(fd);
delete_event(epollfd,fd,EPOLLIN);
}else{
printf("get msg is:%s",buf);
modify_event(epollfd,fd,EPOLLOUT);
}
}
void do_write(int epollfd,int fd,char* buf){
int nwrite = 0;
nwrite = write(fd,buf,sizeof(buf));
if(nwrite == -1){
perror("write error:");
close(fd);
delete_event(epollfd,fd,EPOLLOUT);
}else{
printf("write msg:%s\n",buf);
modify_event(epollfd,fd,EPOLLIN);
}
memset(buf,0,MAXSIZE);
}
void add_event(int epollfd,int fd,int state){
struct epoll_event ev;
ev.events = state;
ev.data.fd = fd;
epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&ev);
printf("add a new event\n");
}
void delete_event(int epollfd,int fd,int state){
struct epoll_event ev;
ev.events = state;
ev.data.fd = fd;
epoll_ctl(epollfd,EPOLL_CTL_DEL,fd,&ev);
}
void modify_event(int epollfd,int fd,int state){
struct epoll_event ev;
ev.events = state;
ev.data.fd = fd;
epoll_ctl(epollfd,EPOLL_CTL_MOD,fd,&ev);
}
客户端代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/wait.h>
#include<sys/epoll.h>
#include<time.h>
#define IPADDRESS "127.0.0.1"
#define PORT 6666
#define MAXSIZE 1024
#define LISTENW 5
#define FDSIZE 1000
#define EPOLLEVENTS 20
void handle_connection(int socketfd);
void handle_events(int epollfd,struct epoll_event* events,int num,
int sockfd,char* buf);
void do_read(int epollfd,int fd,int sockfd,char *buf);
void do_write(int epollfd,int fd,int sockfd,char *buf);
void add_event(int epollfd,int fd,int state);
void modify_event(int epollfd,int fd,int state);
void delete_event(int epollfd,int fd, int state);
int count = 0;
int main(int argc,char* argv[]){
int socketfd;
struct sockaddr_in servaddr;
socketfd = socket(AF_INET,SOCK_STREAM,0);
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(PORT);
servaddr.sin_addr.s_addr = inet_addr(argv[1]);
connect(socketfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
handle_connection(socketfd);
close(socketfd);
return 0;
}
void handle_connection(int sockfd){
int epollfd;
struct epoll_event events[EPOLLEVENTS];
char buf[MAXSIZE];
int ret;
epollfd = epoll_create(FDSIZE);
add_event(epollfd,STDIN_FILENO,EPOLLIN);
printf("============waiting for the events==============\n");
while(true){
ret = epoll_wait(epollfd,events,EPOLLEVENTS,-1);
//类似select调用,events就是事件表,后一个大写的表示事件表最大数目
handle_events(epollfd,events,ret,sockfd,buf);
}
close(epollfd);
}
void handle_events(int epollfd,struct epoll_event *events,
int num,int sockfd,char* buf){
int i;
int fd;
for(i=0;i<num;i++){
fd = events[i].data.fd;
if(events[i].events & EPOLLIN){
do_read(epollfd,fd,sockfd,buf);
}else if(events[i].events & EPOLLOUT){
do_write(epollfd,fd,sockfd,buf);
}
}
}
void do_read(int epollfd,int fd,int sockfd,char* buf){
int nread = 0;
nread = read(fd,buf,MAXSIZE);
if(nread == -1){
perror("read error:");
close(fd);
}else if(nread == 0){
fprintf(stderr,"client close.\n");
close(fd);
}else{
printf("get msg:%s\n",buf);
if(fd == STDIN_FILENO){
add_event(epollfd,sockfd,EPOLLOUT);
}else{
delete_event(epollfd,sockfd,EPOLLIN);
add_event(epollfd,STDOUT_FILENO,EPOLLOUT);
}
}
}
void do_write(int epollfd,int fd,int sockfd,char* buf){
int nwrite = 0;
char temp[100];
buf[strlen(buf)-1] = '\0';
snprintf(temp,sizeof(temp),"%s_%02d\n",buf,count++);
nwrite = write(fd,temp,sizeof(temp));
if(nwrite == -1){
perror("write error:");
close(fd);
}else{
printf("write msg:%s\n",buf);
if(fd == STDOUT_FILENO){
delete_event(epollfd,fd,EPOLLOUT);
}else{
modify_event(epollfd,fd,EPOLLIN);
}
}
memset(buf,0,MAXSIZE);
}
void add_event(int epollfd,int fd,int state){
struct epoll_event ev;
ev.events = state;
ev.data.fd = fd;
epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&ev);
printf("add a new event\n");
}
void delete_event(int epollfd,int fd,int state){
struct epoll_event ev;
ev.events = state;
ev.data.fd = fd;
epoll_ctl(epollfd,EPOLL_CTL_DEL,fd,&ev);
}
void modify_event(int epollfd,int fd,int state){
struct epoll_event ev;
ev.events = state;
ev.data.fd = fd;
epoll_ctl(epollfd,EPOLL_CTL_MOD,fd,&ev);
}
三者的区别