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的实现会负责把数据从内核拷贝到用户空间。