《后台开发 核心技术与应用实践》 第七章,网络IO模型总结

IO有两种操作:同步IO和异步IO
同步IO: 必须等待IO操作完成后,控制权,才返回给用户进程。
异步IO:无需等待IO操作完成,就将,控制权,返回给用户进程。
四种网络IO模型
	1 阻塞IO模型
	2 非阻塞IO模型
	3 多路IO复用模型
	4 异步IO模型
					阻塞和非阻塞,描述用户线程调用内核IO操作,的方式
阻塞:是指IO操作需要,彻底完成后,才返回到用户空间
非阻塞:是指IO操作被调用后,立即返回给用户一个状态值,不需要等到IO操作彻底完成。

	read操作发生,经历两个过程:等待数据准备,将数据从内核拷贝到进程

网络IO: 	1 一开始还没有到达(数据不完整),等待
	 	2 将数据从系统内核拷贝到用户内存中

阻塞IO特点:在IO执行的两个阶段(等待数据,拷贝数据)都被阻塞了。

除非特殊指定,几乎所有的IO接口(包括socket)都是阻塞型。(怎么讲)
	调用send()的同时,线程处于阻塞状态,在此期间,线程将无法执行任何运算或者响应任何网络请求
	一个简单的改进方案:在服务器端,使用多线程,这样任意一个连接阻塞,不会影响其他的连接。具体用多线程,多进程,没有一个特定的模式。
	
为什么一个socket的可以accept多次?
实际上,socket设计者可能特意为多客户的情况留下伏笔,让accept()能返回一个新的socket
	int accept(int fd,struct sockaddr *addr,socklen_t *addrlen);

fd 是从socket(),bind(),listen()沿用下来的socket句柄值。
	执行bind()listen()后,OS已经开始在指定的端口,监听所有的连接请求,
							 有请求,则将连接请求加入请求队列。
							 调用accept()接口从socket fd的请求队列中抽取第一个连接信息,
							 创建一个与fd同类的新的socket返回句柄,
							 这个新的socket句柄是后续read()recv()的输入参数。
							 如果请求队列没有请求,则accept()将进入阻塞状态直到请求进入队列

上述,多线程,似乎完美解决了多个客户提供服务的要求,其实并不尽然。
【如果同时,响应成百上千的连接请求,多线程多进程都会严重占据系统资源,线程本身更容易进入假死状态】

更多人,会考虑,线程池 或 连接池
	线程池:旨在降低创建和销毁线程的频率,使其维持一定合理数量的线程,并让空闲的线程重新承担新的执行任务。
	连接池:是,维持连接的缓存池,尽量重用已有的连接,降低创建和关闭连接的频率。

线程池,连接池,也只能缓解,所谓“池”始终有其上线,使用"池”必须考虑其面临的响应规模,并根据响应规模调整“池”的大小。
非阻塞IO模型
	数据没有准备好的情况下,用户进程不断地主动询问,内核数据是否准备好。

非阻塞IO模型 和 阻塞IO模型 的区别
	非阻塞IO模型 被调用之后,立即返回
	fcntl(fd,F_SETFL,O_NONBLOCK);//将fd,设为非阻塞状态
非阻塞状态下,recv()接口被调用之后,立即返回
					返回值大于0,数据接收完毕,返回值为接收到的字节数
					返回0,表示连接已经正常断开
					返回-1,且errno等于EAGAIN,表示recv还没执行完成
					返回-1,且errno不等于EAGAIN,表示recv遇到系统错误
	
上述模型,绝对的不推荐,因为循环调用recv()将大幅度占用cpu使用率。
recv()更多的是,起到检测“操作是否完成”的作用,操作系统提供了更有效的方法。
更高效的检测“操作是否完成”的接口函数:
								select()多路复用模式,可以一次检测多个连接是否活跃
								
多路IO复用模型(事件驱动IO)
基本原理:有个函数(如select)会不断地轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。

这个模型 和 阻塞IO模型,其实没有太大的不同,事实上更差一些。
	多路IO复用模型 需要两个系统调用:select recvfrom
	阻塞IO模型	 需要一个系统调用:recvfrom

但是,用 select 的优势,在于,它可以同时处理多个连接 !!!
所以,处理的连接数不是很高,使用select/epoll 不一定 比使用多线程的阻塞IO,性能好,可能延迟更大。

select/epoll的优势,不在对单个连接处理的更快,而在于,能处理更多连接

在多路IO复用模型中,每一个socket,一般都设置为非阻塞,但是,整个用户进程其实是一直被阻塞的
只不过,进程是被select 所阻塞的,而不是被socket IO 阻塞。使用select()的效果和非阻塞IO类似。

客户端的一个connect(),将在服务器激发一个“可读事件”,select()也能检测来自客户端的connect()行为。

只用select(),在于三个参数:readfds,writefds,exceptfds

在这里插入图片描述

多路IO复用模型,select()的特征,在于每一个执行周期都会试探一次或一组事件,
一个特定的事件会触发某个特定的响应,这里可以将这种模型归为 “事件驱动模型”。

存在的问题:
	select()不是实现“事件驱动”的最好选择。消耗大量的时间去轮询各个句柄。
	FD_ZERO(int fd,fd_set* fds);//将set清零
	FD_SET(int fd,fd_set* fds);//将fd加入set
	FD_ISSET(int fd,fd_set* fds);//如果fd在set中则真
	FD_CLR(int fd,fd_set* fds);//将fd从set中清除

int select(int maxfdp,fd_set *readfds,fd_set *writedfs,fd_set *errorfds,struct timeval *timeout);	

	更高效的接口:Linux提供epoll
				BSD提供kqueue
				Solaris提供/dev/poll

不同的操作系统特供 的epoll接口有很大的差异化,所以,使用类似于epoll实现跨平台服务的能力,比较困难。

幸运的是,有很多高效的 事件驱动库,可以屏蔽 事件响应执行体过于庞大的问题
		常见的事件驱动库:libevent库
					   libev库等
		这些库,会根据操作系统的特点选择最合适的,事件探测接口,并加入响应的技术,支持异步响应。
		Linux内核从2.6版本开始,也开始支持异步响应的IO操作:
													aio_read
													aio_write

异步IO模型
阻塞IO,非阻塞IO,IO复用,都属于同步IO
原因:同步IO在进行IO操作时,都会阻塞进程。
异步IO,当进程发起IO操作之后,直接返回,直到内核发送一个信号,告诉进程IO已完成,在这个整个过程中,进程完全没有被阻塞。

在这里插入图片描述

异步IO,好像,用户进程将整个IO操作交给他人(内核)完成,然后内核做完后,发信号通知。
在此期间,用户进程,不需要去检查IO操作的状态,也不需要主动拷贝数据。

开始上代码, select

初学者:习惯 connect(),accept(),recv()或者recvfrom()这样的阻塞程序
使用,select()就可以完成非阻塞方式工作的程序

int select(int maxfdp,fd_set *readfds,fd_set *writedfs,fd_set *errorfds,struct timeval *timeout);

fd_set:一个集合,存放着文件描述符 (file descriptor),即文件句柄
timeval是一个常用的结构体,用来代表时间值,有两个成员,一个秒数,另一个是毫秒数。

#include <sys/time.h>  
#include <stdio.h>  
#include <sys/types.h>  
#include <sys/stat.h>  
#include <fcntl.h>  
#include <assert.h>  
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <sys/select.h>
int main()
{
	int keyboard;  
	int ret,i;  
	char c;  
	fd_set readfd;  
	struct timeval timeout;
	keyboard = open("/dev/tty",O_RDONLY | O_NONBLOCK); 
	//O_RDONLY是只读方式,O_NONBLOCK指非阻塞方式
	assert(keyboard>0);  
	//assert是计算表达式expression,如果其值为假,向stderr打印一条错误信息
	while(1){
		timeout.tv_sec=1; //超时时间设为1s
		timeout.tv_usec=0; 
		FD_ZERO(&readfd); //把可读的fd集合全部清空 
		FD_SET(keyboard,&readfd);//再把打开终端的描述符加入到可读描述符集合中
		ret=select(keyboard+1,&readfd,NULL,NULL,&timeout); 
		//调用select函数查看是或否有可读的fd
		if(FD_ISSET(keyboard,&readfd)) {
		//如果终端的描述符在可读描述符集合中
			i=read(keyboard,&c,1);  //开始读取数据
			if('\n'==c)  
				continue;  
			printf("The input is %c\n",c);  
			if ('q'==c)  
				break;  
		}
	}
    return 0;
}
上述过程的,超时时间似乎没有派上用场,是因为,没有判断select的返回值导致的。
观察select超时的表现
#include <sys/time.h>  
#include <stdio.h>  
#include <sys/types.h>  
#include <sys/stat.h>  
#include <fcntl.h>  
#include <assert.h>  
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <sys/select.h>
int main(){
	int keyboard; 
	int ret,i; 
	char c;
	fd_set readfd;
	struct timeval timeout;
	keyboard = open("/dev/tty",O_RDONLY | O_NONBLOCK);
	assert(keyboard>0);
	while(1) {
		timeout.tv_sec=5;
		timeout.tv_usec=0;
		FD_ZERO(&readfd);
		FD_SET(keyboard,&readfd);
		ret=select(keyboard+1,&readfd,NULL,NULL,&timeout);
		if (ret == -1)
			perror("select error");
		else if (ret){
			if(FD_ISSET(keyboard,&readfd)){
				i=read(keyboard,&c,1);
				if('\n'==c)
					continue;
				printf("hehethe input is %c\n",c);
				if ('q'==c)
					break;
			}  
               }else if (ret == 0)
                   printf("time out\n");
	}
	return 0;
}  
使用select函数提高服务器的处理能力
// server.cpp
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>
#define DEFAULT_PORT 6666
int main( int argc, char ** argv){
    int serverfd,acceptfd; /* 监听socket: serverfd,数据传输socket: acceptfd */
    struct sockaddr_in my_addr; /* 本机地址信息 */
    struct sockaddr_in their_addr; /* 客户地址信息 */
    unsigned int sin_size, myport=6666, lisnum=10;
    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(DEFAULT_PORT);
    my_addr.sin_addr.s_addr = INADDR_ANY;
    bzero(&(my_addr.sin_zero), 0);
    if (bind(serverfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr )) == -1) {
        perror("bind" );
        return -2;
    }
    printf("bind ok \n");
    if (listen(serverfd, lisnum) == -1) {
        perror("listen" );
        return -3;
    }
    printf("listen ok \n");
	
	fd_set client_fdset;	/*监控文件描述符集合*/
	int maxsock;            /*监控文件描述符中最大的文件号*/
	struct timeval tv;		/*超时返回时间*/
	int client_sockfd[5];   /*存放活动的sockfd*/
	bzero((void*)client_sockfd,sizeof(client_sockfd));
	int conn_amount = 0;    /*用来记录描述符数量*/
	maxsock = serverfd;
	char buffer[1024];
	int ret=0;
	while(1){
		/*初始化文件描述符号到集合*/
		FD_ZERO(&client_fdset);
		/*加入服务器描述符*/
	FD_SET(serverfd,&client_fdset);
	/*设置超时时间*/
	tv.tv_sec = 30; /*30秒*/
	tv.tv_usec = 0;
	/*把活动的句柄加入到文件描述符中*/
	for(int i = 0; i < 5; ++i){
/*程序中Listen中参数设为5,故i必须小于5*/
	    if(client_sockfd[i] != 0){
	        FD_SET(client_sockfd[i], &client_fdset);
	    }
     }
	/*printf("put sockfd in fdset!\n");*/
	/*select函数*/
	ret = select(maxsock+1, &client_fdset, NULL, NULL, &tv);
	if(ret < 0){
	    perror("select error!\n");
	    break;
	}
	else if(ret == 0){
	    printf("timeout!\n");
	    continue;
	}
	/*轮询各个文件描述符*/
	for(int i = 0; i < conn_amount; ++i){
	/*FD_ISSET检查client_sockfd是否可读写,>0可读写*/
	    if(FD_ISSET(client_sockfd[i], &client_fdset)){
	        printf("start recv from client[%d]:\n",i);
	        ret = recv(client_sockfd[i], buffer, 1024, 0);
	        if(ret <= 0){
		    printf("client[%d] close\n", i);
		    close(client_sockfd[i]);
		    FD_CLR(client_sockfd[i], &client_fdset);
		    client_sockfd[i] = 0;
	        }
	        else{
		 printf("recv from client[%d] :%s\n", i, buffer);
	        }
	    }
	}
	/*检查是否有新的连接,如果收,接收连接,加入到client_sockfd中*/
	if(FD_ISSET(serverfd, &client_fdset)){
	    /*接受连接*/
	    struct sockaddr_in client_addr;
        size_t size = sizeof(struct sockaddr_in);
int sock_client = accept(serverfd, (struct sockaddr*)(&client_addr), (unsigned int*)(&size));
if(sock_client < 0){
	perror("accept error!\n");
	continue;
}
/*把连接加入到文件描述符集合中*/
if(conn_amount < 5){
	client_sockfd[conn_amount++] = sock_client;
	bzero(buffer,1024);
	strcpy(buffer, "this is server! welcome!\n");
	send(sock_client, buffer, 1024, 0);
	printf("new connection client[%d] %s:%d\n", conn_amount, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
	bzero(buffer,sizeof(buffer));
	ret = recv(sock_client, buffer, 1024, 0);
	if(ret < 0){
	    perror("recv error!\n");
	    close(serverfd);
	    return -1;
	}
	printf("recv : %s\n",buffer);
	if(sock_client > maxsock){
		maxsock = sock_client;
	}
	else{
	    printf("max connections!!!quit!!\n");
	    break;
	}
   }
  }
}
for(int i = 0; i < 5; ++i){
	if(client_sockfd[i] != 0){
	    close(client_sockfd[i]);
	}
}
close(serverfd);
return 0;	
}
//client.cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#define DEFAULT_PORT 6666
int main( int argc, char * argv[]){
    int connfd = 0;
    int cLen = 0;
    struct sockaddr_in client;
    if(argc < 2){
        printf(" Uasge: clientent [server IP address]\n");
        return -1;
    }	
    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){
	    perror("socket" );
        return -1;
    }
    if(connect(connfd, (struct sockaddr*)&client, sizeof(client)) < 0){
 	    perror("connect" );
        return -1;
    }
	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(1){
		bzero(buffer,sizeof(buffer));
		scanf("%s",buffer);
		int p = strlen(buffer);
		buffer[p] = '\0';
		send(connfd, buffer, 1024, 0);
		printf("i have send buffer\n");
	}
	close(connfd);
	return 0;
}

poll

select()函数一样,poll()也可以用于执行多路复用IO

#include<poll.h>
int poll(struct pollfd* fds,unsigned int nfds,int timeout);

struct pollfd {
	int fd;//文件描述符
	short events;//等待的事件
	short revents;//实际发生了的事件
};

每个pollfd,指定一个被监事的文件描述符,可以传递多个结构体,指示poll()监视多个文件描述符。
每个结构体的events域,是监视该文件描述符的事件掩码,由用户来设置这个域的属性
revents域,是文件描述符的操作结果事件掩码,内核在调用返回时,设置这个域,
		   并且events域,中请求的任何事件都有可能在revents域中返回。

在这里插入图片描述

POLLIN|POLLPRI 等价于 select()的读事件
POLLOUT|POLLWRBAND 等价于 select()的写事件
POLLIN 等价于 POLLRDNORM|POLLRDBAND
POLLOUT 等价于 POLLWRNORM

errno的值
在这里插入图片描述

poll()提高服务器处理能力
//server.cpp
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>
#include <poll.h>
#define IPADDRESS   "127.0.0.1"
#define PORT        6666
#define MAXLINE     1024
#define LISTENQ     5
#define OPEN_MAX    1000
#define INFTIM      -1

/*创建套接字,进行绑定和监听*/
int bind_and_listen(){
	int serverfd; /* 监听socket: 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_zero), 0);
    if (bind(serverfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr )) == -1) {
        perror("bind" );
        return -2;
    }
    printf("bind ok \n");
    if (listen(serverfd, LISTENQ) == -1) {
        perror("listen" );
        return -3;
    }
    printf("listen ok \n");
	return serverfd;
}

/*IO多路复用poll*/
void do_poll(int listenfd){
    int  connfd,sockfd;
    struct sockaddr_in cliaddr;
    socklen_t cliaddrlen;
    struct pollfd clientfds[OPEN_MAX];
    int maxi;
    int i;
    int nready;
    /*添加监听描述符*/
    clientfds[0].fd = listenfd;
    clientfds[0].events = POLLIN;
    /*初始化客户连接描述符*/
    for (i = 1;i < OPEN_MAX;i++)
        clientfds[i].fd = -1;
    maxi = 0;
    /*循环处理*/
    while(1){
        /*获取可用描述符的个数*/
        nready = poll(clientfds,maxi+1,INFTIM);
        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;
            break;
        }
     }
    if (i == OPEN_MAX){
        fprintf(stderr,"too many clients.\n");
        exit(1);
     }
     /*将新的描述符添加到读描述符集合中*/
    clientfds[i].events = POLLIN;
     /*记录客户连接套接字的个数*/
    maxi = (i > maxi ? i : maxi);
    if (--nready <= 0)
          continue;
    }
    /*处理多个连接上客户端发来的包*/
	char buf[MAXLINE];
	memset(buf,0,MAXLINE);
	int readlen=0;
	for (i = 1;i <= maxi;i++){
	    if (clientfds[i].fd < 0)
		continue;
	    /*测试客户描述符是否准备好*/
	    if (clientfds[i].revents & POLLIN){
	        /*接收客户端发送的信息*/
	        readlen = read(clientfds[i].fd,buf,MAXLINE);
	        if (readlen == 0){
	            close(clientfds[i].fd);
	            clientfds[i].fd = -1;
		  continue;
	        }
	        /*printf("read msg is: ");*/
		write(STDOUT_FILENO,buf,readlen);
		/*向客户端发送buf*/
		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;
}

//client.cpp
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>
#include <poll.h>
#define MAXLINE     1024
#define DEFAULT_PORT   6666
#define max(a,b) (a > b) ? a : b
static void handle_connection(int sockfd);
int main(int argc,char *argv[]){
	int connfd = 0;
    int cLen = 0;
    struct sockaddr_in client;
    if(argc < 2){
        printf(" Uasge: clientent [server IP address]\n");
        return -1;
    }	
    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){
		perror("socket" );
        return -1;
    }
    if(connect(connfd, (struct sockaddr*)&client, sizeof(client)) < 0){
 		perror("connect" );
        return -1;
    }
    /*处理连接描述符*/
    handle_connection(connfd);
    return 0;
}
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(1){
        poll(pfds,2,-1);
        if (pfds[0].revents & POLLIN){
            n = read(sockfd,recvline,MAXLINE);
            if (n == 0){
                    fprintf(stderr,"client: server is closed.\n");
                    close(sockfd);
            }
            write(STDOUT_FILENO,recvline,n);
        }
        /*测试标准输入是否准备好*/
        if (pfds[1].revents & POLLIN) {
            n = read(STDIN_FILENO,sendline,MAXLINE);
            if (n  == 0) {
                shutdown(sockfd,SHUT_WR);
				continue;
            }
            write(sockfd,sendline,n);
        }
    }
}

编写一个echo server程序,功能是客户向服务器发送消息,服务器接收输出并原样返回客户端,客户端接收消息后输出终端。
server.cpp中,把套接字的创建,绑定和监听,都写在一个函数中,方便使用。

先把服务器的描述符,加入到描述符集合中,需要注意的是,select所用的描述符集合,是一个fd_set的结构体中,而poll的描述符,却是一个,以pollfd为元素的数组中代码如下:
//添加监听描述符
clientfds[0].fd = listenfd;
clientfds[0].events = POLLIN;
epoll,是Linux2.6内核中提出的,是之前select和poll的增强版本
epoll 使用一个文件描述符管理多个文件描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,
      这样在用户空间和内核空间之间的数据拷贝只需要一次。

#include<sys/epoll.h>
//epoll操作需要的3个接口
int epoll_create(int size);
	创建一个epoll的句柄,size,用来告诉内核要监听的数目,
	这个参数不同于select()中的第一个参数,是最大监听的fd+1的值
	当创建好epoll句柄后,它会占用一个fd值,
	在LInux下如果查看/proc/进程的id/fd/,是能够看到这个fd值的,所以在使用完epoll后,必须调用	
	close()关闭,否则会导致fd的耗尽。

int epoll_ctl(int epfd,int op,int fd,struct epoll_event* event);
	epoll的事件注册函数,它不同于select()在监听事件时,告诉内核要监听什么类型的事件,而是先注册要监听的事件类型。
	第一个参数是,epoll_create()的返回值
	第二个参数是,表示动作,用3个宏表示:
								EPOLL_CTL_ADD 注册新的fd到epfd中
								EPOLL_CTL_MOD 修改已经注册的fd的监听事件
								EPOLL_CTL_DEL 从epfd中删除一个fd
	第三个参数是,需要监听的fd,
	第四个参数是,告诉内核需要监听什么事,
	struct epoll_event{
		__uint32_t events;	//Epoll events
		epoll_data_t data;	//User data variable
	};
	events可以是,以下几个宏的集合:
							  EPOLLIN 表示对应的文件描述符可以读
							  EPOLLOUT 表示对应的文件描述符可写
							  EPOLLPRI 表示对应的文件描述符有紧急的数据可读(有带外数据到来)
							  EPOLLERR 表示对应的文件描述符发生错误
							  EPOLLHUP 表示对应的文件描述符被挂断
							  EPOLLET  将epoll设为边缘触发(Edge Triggered)模式
							  			这是相对于水平触发(level Triggered)而言的
							  EPOLLONESHOT 只监听一次事件,当监听这次事件后,
							  			   如果还需要继续监听这个socket的话,
							               需要再次把这个socket加入到epoll队列中
							               
int epoll_wait(int epfd,struct epoll_event* events,int maxevents,int timeout); 
	等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合,
	maxevents,告诉内核这个events有多大,且maxevents的值不能大于创建epoll_create()时的size,
	timeout是超时时间,ms为单位,0会立即返回,-1将不确定或永久阻塞
	该函数,返回需要处理的事件数目
epoll()提高服务器处理能力
//server.cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <sys/types.h>

#define IPADDRESS   "127.0.0.1"
#define PORT        6666
#define MAXSIZE     1024
#define LISTENQ     5
#define FDSIZE      1000
#define EPOLLEVENTS 100

/*函数声明*/
/*创建套接字并进行绑定*/
int socket_bind(const char* ip,int port);
/*IO多路复用epoll*/
void do_epoll(int listenfd);
/*事件处理函数*/
void handle_events(int epollfd,struct epoll_event *events,int num,int listenfd,char *buf);
/*处理接收到的连接*/
void handle_accpet(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,LISTENQ);
    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;
    inet_pton(AF_INET,ip,&servaddr.sin_addr);
    servaddr.sin_port = htons(port);
    if (bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) == -1){
        perror("bind error: ");
        exit(1);
    }
    return listenfd;
}

void do_epoll(int listenfd){
    int epollfd;
    struct epoll_event events[EPOLLEVENTS];
    int ret;
    char buf[MAXSIZE];
    memset(buf,0,MAXSIZE);
    /*创建一个描述符*/
    epollfd = epoll_create(FDSIZE);
    /*添加监听描述符事件*/
    add_event(epollfd,listenfd,EPOLLIN);
    while(1){
        /*获取已经准备好的描述符事件*/
        ret = epoll_wait(epollfd,events,EPOLLEVENTS,-1);
        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))
            handle_accpet(epollfd,listenfd);
        else if (events[i].events & EPOLLIN)
            do_read(epollfd,fd,buf);
        else if (events[i].events & EPOLLOUT)
            do_write(epollfd,fd,buf);
    }
}

void handle_accpet(int epollfd,int listenfd){
    int clifd;
    struct sockaddr_in cliaddr;
    socklen_t  cliaddrlen;
    clifd = accept(listenfd,(struct sockaddr*)&cliaddr,&cliaddrlen);
    if (clifd == -1)
        perror("accpet error:");
    else{
        printf("accept a new client: %s:%d\n",inet_ntoa(cliaddr.sin_addr),cliaddr.sin_port);
        /*添加一个客户描述符和事件*/
        add_event(epollfd,clifd,EPOLLIN);
    }
}

void do_read(int epollfd,int fd,char *buf){
    int nread;
    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("read message is : %s",buf);
        /*修改描述符对应的事件,由读改为写*/
        modify_event(epollfd,fd,EPOLLOUT);
    }
}

void do_write(int epollfd,int fd,char *buf){
    int nwrite;
    nwrite = write(fd,buf,strlen(buf));
    if (nwrite == -1){
        perror("write error:");
        close(fd);
        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);
}

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);
}

client.cpp
//客户端也用epoll实现,控制STDIN_FILENO,STDOUT_FILENO,sockfd三个描述符
#include <netinet/in.h>
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <arpa/inet.h>

#define MAXSIZE     1024
#define IPADDRESS   "127.0.0.1"
#define SERV_PORT   6666
#define FDSIZE        1024
#define EPOLLEVENTS 20

void handle_connection(int sockfd);
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_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 delete_event(int epollfd,int fd,int state);
void modify_event(int epollfd,int fd,int state);
int count=0;
int main(int argc,char *argv[]){
    int                 sockfd;
    struct sockaddr_in  servaddr;
    sockfd = socket(AF_INET,SOCK_STREAM,0);
    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERV_PORT);
    inet_pton(AF_INET,IPADDRESS,&servaddr.sin_addr);
    connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
    /*处理连接*/
    handle_connection(sockfd);
    close(sockfd);
    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);
    while (1 ) {
        ret = epoll_wait(epollfd,events,EPOLLEVENTS,-1);
        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 fd;
    int i;
    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;
    nread = read(fd,buf,MAXSIZE);
    if (nread == -1){
        perror("read error:");
        close(fd);
    }
    else if (nread == 0){
        fprintf(stderr,"server close.\n");
        close(fd);
    }
    else{
        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;
    char temp[100];
	buf[strlen(buf)-1]='\0';
	snprintf(temp,sizeof(temp),"%s_%02d\n",buf,count++);
    nwrite = write(fd,temp,strlen(temp));   
    if (nwrite == -1){
        perror("write error:");
        close(fd);
    }
    else{
        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);
}

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);
}

select poll epoll的区别

select poll epoll都是多路IO复用的机制,多路IO复用,就通过一种机制,可以监视多个描述符,
一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。
select poll epoll本质上都是同步IO,因为它们都需要在读写事件就绪后,自己负责进行读写,即使阻塞的。
而异步IO,无须自己负责进行读写,异步IO的实现会负责把数据从内核拷贝到用户空间。

在这里插入图片描述
在这里插入图片描述

发布了30 篇原创文章 · 获赞 5 · 访问量 2195

猜你喜欢

转载自blog.csdn.net/weixin_44408476/article/details/104971655