Linux(程序设计):52---epoll处理套接字在LT、ET不同模式下的数据

 一、epoll的LT模式、ET模式介绍

二、编码实现

  • 下面我们通过epoll来分别实现对LT和ET不同模式下对数据是如何处理的
  • 如果我们的服务端监听套接字设置为EPOLLET模式工作,那么当服务端接收到客户端的请求(读写数据等)之后,可能会将这个就绪事件不立即进行处理,而是保留放到下一次epoll_wait进行处理。下面程序我们将服务端的监听套接字设置为EPOLLET模式的,所以当服务端接收到客户端的请求(例如:数据的接受)时,必须在本次epoll_wait返回之后进行处理,不能将本次的就绪事件放置到下一次epoll_wait去处理
  • 另外,在普通模式下工作的时候(下面的work_with_LT函数),我们服务端接收客户端的数据可能不会一次性读取完,可能还有数据保存在缓冲区中未读取。然后在ET工作模式下下面的(work_with_ET函数),我们接收到客户端发送过来数据的时候,由于本次事件处理完之后,下一次就不会再次触发了,所以我们使用while(1)循环读取客户端套接字的缓冲区数据,直到读取完之后才退出本次EPOLLIN事件处理
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#define BUFFSIZE 1024
#define LISTEN_NUM 10
#define MAX_EPOLLEVENT_NUM  1024

int setnonblock(int fd);
void addEpollFd(int epoll_fd,int add_fd,int enable_et);
void work_with_LT(int epoll_fd,int epoll_ret_value,int ser_fd,struct epoll_event *read_events);
void work_with_ET(int epoll_fd,int epoll_ret_value,int ser_fd,struct epoll_event *read_events);

int main(int argc,char *argv[])
{
    char *ip=argv[1];
    int port=atoi(argv[2]);

    int serverAlive;
    int serFd,epollFd;
    int epollRetValue,waitTimeValue;
    struct epoll_event readyEvent[MAX_EPOLLEVENT_NUM];
    struct sockaddr_in serAddr;

    if((serFd=socket(AF_INET,SOCK_STREAM,0))==-1){
        perror("socket");
        exit(EXIT_FAILURE);
    }

    bzero(&serAddr,sizeof(serAddr));
    serAddr.sin_family=AF_INET;
    serAddr.sin_port=htons(port);
    if(inet_pton(AF_INET,ip,&serAddr.sin_addr.s_addr)==-1){
        perror("inet_pton");
        exit(EXIT_FAILURE);
    }

    if(bind(serFd,(struct sockaddr*)&serAddr,sizeof(serAddr))==-1){
        perror("inet_pton");
        exit(EXIT_FAILURE);
    }    

    if(listen(serFd,LISTEN_NUM)==-1){
        perror("listen");
        exit(EXIT_FAILURE);
    }
    serverAlive=1;

    if((epollFd=epoll_create(5))==-1){
        perror("epoll_create");
        exit(EXIT_FAILURE);
    }

    addEpollFd(epollFd,serFd,1);
    
    while(serverAlive)
    {
        waitTimeValue=3000;
        printf("epoll_wait...\n");
        switch(epollRetValue=epoll_wait(epollFd,readyEvent,MAX_EPOLLEVENT_NUM,waitTimeValue))
        {
            case -1:
                perror("epoll_wait");
                serverAlive=0;
                break;
            case 0:
                printf("epoll_wait timeout\n");
                break;
            default:
                //work_with_ET(epollFd,epollRetValue,serFd,readyEvent);
                work_with_LT(epollFd,epollRetValue,serFd,readyEvent);
                break;
        }
    }
    
    exit(EXIT_SUCCESS);
}

int setnonblock(int fd)
{
    int oldFlags,newFlags;
    if((oldFlags=fcntl(fd,F_GETFL))==-1){
        perror("fcntl ");
        exit(EXIT_FAILURE);
    }
    newFlags = oldFlags|O_NONBLOCK;
    if(fcntl(fd,F_SETFL,newFlags)==-1){
        perror("fcntl ");
        exit(EXIT_FAILURE);
    }
    return oldFlags;
}

void addEpollFd(int epoll_fd,int add_fd,int enable_et)
{
    struct epoll_event event;
    bzero(&event,sizeof(event));
    event.events=EPOLLIN;
    event.data.fd=add_fd;

    if(enable_et){
        event.events 
|= EPOLLET;
    }
    
    if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,add_fd,&event)==-1){
        perror("epoll_ctl");
        exit(EXIT_FAILURE);
    }
    setnonblock(add_fd);
}

void work_with_LT(int epoll_fd,int epoll_ret_value,int ser_fd,struct epoll_event *read_events)
{
    int i,acceptFd,commRetValue;
    char commBuff[BUFFSIZE];
    char cliAddrBuf[24];
    char serBuff[]=
"I am server\n";
    socklen_t cliAddrLen;
    struct sockaddr_in cliAddr;
    struct epoll_event cliEvents;
    
    bzero(&cliAddr,sizeof(cliAddr));
    cliAddrLen=sizeof(cliAddr);
    
    for(i=0;i<epoll_ret_value;++i)
    {
        if((read_events[i].data.fd==ser_fd)&&(read_events[i].events&EPOLLIN)){
            if((acceptFd=accept(ser_fd,(struct sockaddr*)&cliAddr,&cliAddrLen))==-1){
                if(errno==EINTR){
                    printf("accept:catch signal...\n");
                    continue;
                }
                perror("accept");
                exit(EXIT_FAILURE);
            }else{
                if(inet_ntop(AF_INET,&cliAddr.sin_addr.s_addr,cliAddrBuf,sizeof(cliAddrBuf))==NULL){
                    printf("Get Connect: an unrecognized client address\n");
                }else{
                    printf("Get Connect: %s:%d\n",cliAddrBuf,ntohl(cliAddr.sin_port));
                }
                addEpollFd(epoll_fd,acceptFd, 0);
            }
        }
        else if(read_events[i].events&EPOLLIN){
            bzero(commBuff,sizeof(commBuff));
            switch(commRetValue=recv(read_events[i].data.fd,commBuff,sizeof(commBuff),0))
            {
            case 0:
                printf("client close\n");
                if(epoll_ctl(epoll_fd,EPOLL_CTL_DEL,read_events[i].data.fd,NULL)==-1){
                        perror("epoll_ctl");
                        exit(EXIT_FAILURE);
                }
                close(read_events[i].data.fd);
                break;
            case -1:
                perror("recv");
                close(read_events[i].data.fd);
                close(ser_fd);
                exit(EXIT_FAILURE);
            default:
                printf("Get data of client:%s\n",commBuff);
                
                if(bcmp(commBuff,"quit",4)==0){
                    if(epoll_ctl(epoll_fd,EPOLL_CTL_DEL,read_events[i].data.fd,NULL)==-1){
                        perror("epoll_ctl");
                        exit(EXIT_FAILURE);
                    }
                    close(read_events[i].data.fd);
                }
                
                cliEvents.events=EPOLLOUT;
                cliEvents.data.fd=read_events[i].data.fd;
                
                if(epoll_ctl(epoll_fd,EPOLL_CTL_MOD,read_events[i].data.fd,&cliEvents)==-1){
                    perror("epoll_ctl");
                    exit(EXIT_FAILURE);
                }
                break;
            }
        }
        else if(read_events[i].events&EPOLLOUT){
            bzero(commBuff,sizeof(commBuff));
            bcopy(serBuff,commBuff,sizeof(serBuff));
        
            switch(commRetValue=send(read_events[i].data.fd,commBuff,sizeof(commBuff),0))
            {
            case -1:
                perror("recv");
                close(read_events[i].data.fd);
                close(ser_fd);
                exit(EXIT_FAILURE);
            default:
                if(bcmp(commBuff,"quit",4)==0){
                    if(epoll_ctl(epoll_fd,EPOLL_CTL_DEL,read_events[i].data.fd,NULL)==-1){
                        perror("epoll_ctl");
                        exit(EXIT_FAILURE);
                    }
                    close(read_events[i].data.fd);
                }
                cliEvents.events=EPOLLIN;
                cliEvents.data.fd=read_events[i].data.fd;
                
                if(epoll_ctl(epoll_fd,EPOLL_CTL_MOD,read_events[i].data.fd,&cliEvents)==-1){
                    perror("epoll_ctl");
                    exit(EXIT_FAILURE);
                }
                break;
            }
        }
    }
}

void work_with_ET(int epoll_fd,int epoll_ret_value,int ser_fd,struct epoll_event *read_events)
{
    int i,acceptFd,commRetValue;
    char commBuff[BUFFSIZE];
    char cliAddrBuf[24];
    char serBuff[]=
"I am server\n";
    socklen_t cliAddrLen;
    struct sockaddr_in cliAddr;
    struct epoll_event cliEvents;
    
    bzero(&cliAddr,sizeof(cliAddr));
    cliAddrLen=sizeof(cliAddr);
    
    for(i=0;i<epoll_ret_value;++i)
    {
        if((read_events[i].data.fd==ser_fd)&&(read_events[i].events&EPOLLIN)){
            if((acceptFd=accept(ser_fd,(struct sockaddr*)&cliAddr,&cliAddrLen))==-1){
                if(errno==EINTR){
                    printf("accept:catch signal...\n");
                    continue;
                }
                perror("accept");
                exit(EXIT_FAILURE);
            }else{
                if(inet_ntop(AF_INET,&cliAddr.sin_addr.s_addr,cliAddrBuf,sizeof(cliAddrBuf))==NULL){
                    printf("Get Connect: an unrecognized client address\n");
                }else{
                    printf("Get Connect: %s:%d\n",cliAddrBuf,ntohl(cliAddr.sin_port));
                }
                addEpollFd(epoll_fd,acceptFd, 0);
            }
        }
        else if(read_events[i].events&EPOLLIN){
            while(1)
            {
                bzero(commBuff,sizeof(commBuff));
                commRetValue=recv(read_events[i].data.fd,commBuff,sizeof(commBuff),0);
                if(commRetValue==0){
                    printf("client close\n");
                    if(epoll_ctl(epoll_fd,EPOLL_CTL_DEL,read_events[i].data.fd,NULL)==-1){
                        perror("epoll_ctl");
                        exit(EXIT_FAILURE);
                    }
                    close(read_events[i].data.fd);
                    break;
                }
                else if(commRetValue==-1){
                    if((errno==EAGAIN)||(errno==EWOULDBLOCK)){
                        printf("End of reading...\n");
                        cliEvents.events=EPOLLOUT;
                        cliEvents.data.fd=read_events[i].data.fd;  
                        if(epoll_ctl(epoll_fd,EPOLL_CTL_MOD,read_events[i].data.fd,&cliEvents)==-1){
                            perror("epoll_ctl");
                            exit(EXIT_FAILURE);
                        }
                        break;
                    }
                    perror("recv");
                    close(read_events[i].data.fd);
                    close(ser_fd);
                    exit(EXIT_FAILURE);
                }
                else{
                    printf("Get data of client:%s\n",commBuff);
                
                    if(bcmp(commBuff,"quit",4)==0){
                        if(epoll_ctl(epoll_fd,EPOLL_CTL_DEL,read_events[i].data.fd,NULL)==-1){
                            perror("epoll_ctl");
                            exit(EXIT_FAILURE);
                        }
                        close(read_events[i].data.fd);
                    }
                }
            }
        }
        else if(read_events[i].events&EPOLLOUT){
            bzero(commBuff,sizeof(commBuff));
            bcopy(serBuff,commBuff,sizeof(serBuff));
        
            switch(commRetValue=send(read_events[i].data.fd,commBuff,sizeof(commBuff),0))
            {
            case -1:
                perror("recv");
                close(read_events[i].data.fd);
                close(ser_fd);
                exit(EXIT_FAILURE);
            default:
                if(bcmp(commBuff,"quit",4)==0){
                    if(epoll_ctl(epoll_fd,EPOLL_CTL_DEL,read_events[i].data.fd,NULL)==-1){
                        perror("epoll_ctl");
                        exit(EXIT_FAILURE);
                    }
                    close(read_events[i].data.fd);
                }
                cliEvents.events=EPOLLIN;
                cliEvents.data.fd=read_events[i].data.fd;
                
                if(epoll_ctl(epoll_fd,EPOLL_CTL_MOD,read_events[i].data.fd,&cliEvents)==-1){
                    perror("epoll_ctl");
                    exit(EXIT_FAILURE);
                }
                break;
            }
        }
    }
}

先整体介绍程序

  • ①老规矩,先创建一个服务端套接字,然后绑定、监听等

  • ②我们创建一个epoll的句柄,然后将我们的服务端监听socket添加进epoll事件表,服务端监听socket为EPOLLIN与EPOLLET模式(设置为EPOLLET之后,我们的监听套接字在接收到服务端的请求(比如数据的发送,连接),那么服务端必须在一次epoll_wait返回中处理完这些请求,不能暂时不处理,留到下一次epoll_wait来处理)

  • addEpollFd函数使我们自己创建的函数,参数1位epoll的句柄,参数2位要添加进epoll事件表的套接字,参数3为添加的套接字是否设置为EPOLLET类型(ET)

  • 另外,我们需要将套接字都设置为非阻塞的。所以我们也自定义了一个将套接字设置为非阻塞的setnonblock函数,该函数返回套接字之前的套接字标志

  • ③之后,使用主线程中的大while循环,持续进行epoll_wait,等待epoll事件池中有哪些事件已经准备好开始操作了,如果epoll_wait返回正确就调用我们自定义的“work_with_ET”或“work_with_LT”函数,来实现在不同的模式下epoll是如何工作的

work_with_LT函数(epoll的LT处理模式)

  • 这个模式是epoll的默认工作方式,下面我们来详细介绍这个工作方式如何与客户进行交互。此函数的参数分别为:epoll的句柄,epoll_wait的成功返回值,服务端的socket,以及epoll_wait成功返回之后存放着就绪事件的struct epoll_event结构体
  • ①首先定义一些变量在下面使用(就不详细介绍了,用的时介绍),然后通过主for循环来判断epoll_wait返回的这些就绪事件当中分别是哪些事件

  • ②首先判断就绪事件的fd是否为我们服务端的socket,由于服务端的监听套接字是非阻塞的,并且工作模式为EPOLLIN,那么当有客户端连接时才会EPOLLIN模式才会触发,此时服务端才会开始监听,所以这个if能够准确判断是否为我们服务端的socket就绪。如果是我们服务端的socket就绪,那么:
    • 使用accept来接受客户端的请求(如果accept出错返回,但errno为EINTR,说明accept是被信号中断的,所以程序不退出,继续执行)
    • accept执行成功,打印客户端地址,并且将我们的客户端socket加入到我们的epoll事件表中,并且不设置为EPOLLET模式

  • ③然后再次判断就绪的套接字是否为可读状态(EPOLLIN),也就是客户端的socket给服务端发送数据之后,EPOLLIN状态就被激活了,此时使用recv来读取缓冲区中的数据,接收数据的返回值有如下3种情况,我们使用switch来判断:
    • 如果返回0:那么客户端关闭连接,我们将客户端套接字移除epoll事件表,然后关闭客户端套接字,之后退出此次switch,继续执行for循环
    • 如果返回-1:那么就是recv出错了,我们直接关闭服务端套接字与客户端套接字退出程序
    • 如果recv成功返回:那么打印接收到打印的数据,如果服务端输入的是“quit”,那么代表客户端要终止会话,那么就将客户端套接字移出epoll事件表,然后关闭客户端套接字。如果客户端输入的不是“quit”,那么就将客户端的套接字事件类型设置为EPOLLOUT,那么代表客户端的套接字可以进行数据输出,此时我们的服务端可以向客户端发送数据了(具体在下面一个else if中判断)

  • ④同理,这个else if判断这个套接字是否为可以进行写,也就是是否可以向客户端写入数据,一开始我们的客户端被设置为EPOLLIN类型,但是当我们的客户端发送数据到服务端之后,服务端接收到数据之后将其套接字事件类型设置为EPOLLOUT了(这个是我们程序自己设计的地方)。之后我们调用send向客户端发送一些数据(commBuff),send返回值有以下两种
    • 如果出错返回-1,出错之后关闭服务端与客户端,然后退出应用程序
    • 如果没有出错,判断一下服务端发送的内容是否为“quit”,如果为quit那么就代表服务端想与客户端会话断开,那么就用epoll_ctl将客户端的套接字移出epoll的事件表。如果不是“quit”,那么就将客户端的套接字再次设置为EPOLLIN,那么客户端就又可以向服务端发送数据了(这样就与上面那么else if形成了一个客户端与服务端可以进行数据读写交互的模式)

  • 到此,work_with_LT函数介绍完毕

work_with_ET函数(epoll的ET处理模式)

  • ET模式不是我们epoll的默认工作方式,但是我们一开始我们将服务端的套接字设置为ET模式,那么我们可以使用这个函数来测试一下epoll的ET工作模式是什么样子的
  • 这个函数与work_with_LT函数之后判断数据接收方面不一样,其他方面都是一样的
  • ①首先,与work_with_LT一样,定义一些变量在下面使用,然后通过主for循环来判断epoll_wait返回的这些就绪事件当中分别是哪些事件

  • ②判断是否为服务端的监听套接字就绪了,与work_with_LT是一模一样的

  • ③接着,判断是否为有数据可接收的套接字就绪,那么服务端就来读取这些数据,读取数据与普通的LT模式有不一样的地方,因为ET模式必须在一次处理完,并且再下次进行epoll_wait的时候讲不再相应这个事件,所以下面我们使用while循环来一次性读取完socket缓冲区中的所有数据。while的循环过程如下:
    • 如果读取的返回值为0,说明客户端关闭了套接字,那么我们就将客户端的套接字移出epoll的事件表,然后关闭客户端的套接字,之后break退出while,进行下一次的for大循环检测下一个就绪事件
    • 如果recv返回-1,那么又分为两种情况
      • 如果错误码为EAGAIN或者EWOULDBLOCK,那么就说明此次读取操作数据已经全部读取完毕,下次epoll_wai将不再响应这个EPOLLIN事件,我们就服务端的套接字设置为EPOLLOUT模式,以便下次让服务端向客户端的套接字中写入数据
      • 如果错误码不是上面的值,说明我们的recv函数出错了,那么就关闭服务端与客户端的套接字,然后终止程序
    • 如果recv返回值是正值,说明读取到数据了,那么就打印数据的信息,如果服务端发送的内容是否为“quit”,如果为quit那么就代表服务端想与客户端会话断开,那么就用epoll_ctl将客户端的套接字移出epoll的事件表(备注:因为是循环读取数据,所以不能再每读一次数据就讲客户端的套接字设置为EPOLLOUT模式,我们在第一个if中对客户端的数据读取完毕之后再将客户端的套接字设置为EPOLLOUT模式然后break掉)

  • ④与work_with_LT函数的意义一样,都是向客户端回送消息

程序演示效果(演示LT模式的):

演示效果(演示ET模式的):

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

董哥的黑板报

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

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

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

打赏作者

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

抵扣说明:

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

余额充值