后台开发:网络IO模型

本文介绍了四种网络IO模型:阻塞IO、非阻塞IO、多路复用IO(select/poll)和异步IO。阻塞IO在数据准备和写入时会阻塞用户进程,非阻塞IO则在准备数据时返回,多路复用IO通过轮询检测多个socket,异步IO完全不阻塞用户进程。select和poll函数用于多路复用IO,而epoll提供更高效的方式处理大量连接。服务器性能优化通常通过合理选择IO模型和使用连接池、线程池来实现。
摘要由CSDN通过智能技术生成


简介

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

三者的区别

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

补充

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星辰的野望

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值