Bootstrap

学习伊圣雨老师的 epoll 编程:select是水平触发模式,回声客户端代码,epoll 服务器端,验证默认的水平触发模式,采用边缘触发模式

(1)书里提出了疑问,epoll 函数的工作方式,区分为水平触发与边缘触发 :

在这里插入图片描述

(2) 11 1 5-5 伊圣雨老师的 epoll 教学范例:回声客户端代码与错误处理函数 error_handling () :

在这里插入图片描述

++ 源代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE   100

void error_handling(char * str)//此函具有换行功能
{   // int fputs(const char *str,FILE *stream);
    fputs(str,stderr); // fputs()写入字符串到文件中
    fputc('\n',stderr);//     但不会主动添加换行符。
    exit(1); // int fputc(int char,FILE *stream);
}

int main(int argc,char * argv[])
{   // 回声客户端,三个参数,argc = 3
    int  sock, str_len;    char message[BUF_SIZE];
    struct sockaddr_in  serv_adr;
    
    if(argc != 3) { printf("参数不是3个\n");exit(1); }

    sock = socket(PF_INET,SOCK_STREAM,0);
    if(sock == -1)
        error_handling("创建套接字socket() 失败\n");

    memset(&serv_adr,0,sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;

    // serv_adr.sin_addr.s_addr = inet_addr(argv[1]);
    // inet_addr() 的语义不明,不好
    // 处理文本地址,只需使用 inet_pton() 与 inet_ntop() 即可。
    // int inet_pton(int af, const char *src, void *dst);
    inet_pton(AF_INET, argv[1], &serv_adr.sin_addr.s_addr);

    serv_adr.sin_port = htons(atoi(argv[2]));

    if(connect(sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1)
        error_handling("connect() 失败");
    else
        puts("客户端套接字连接至服务器成功\n");

    while(1) // 函 fputs() 不会自动添加换行符
    {   // int fputs(const char *str,FILE *stream);
        fputs("Input message(Q to quit):", stdout);
        fgets(message, BUF_SIZE, stdin);
        // char *fgets(char *str, int n, FILE *stream);
        // 函 fgets 会保留换行符在字符串中。可手动去除

        if( !strcmp(message,"Q\n") || !strcmp(message,"q\n") )
            break;

        write(sock, message, strlen(message));
        str_len = read(sock, message, BUF_SIZE - 1);
        message[str_len] = 0; // read()不会主动添加空字符。
        printf("服务器端回响过来的信息:%s",message);
    }

    close(sock);
    return 0;

}

(3) 11 2 5-5 伊圣雨老师的 epoll 教学范例:回声服务器端代码:使用 epoll ,大缓存,水平触发:

在这里插入图片描述

++

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>

#define BUF_SIZE   100
#define EPOLL_SIZE  50

void error_handling(char * str)//此函具有换行功能
{   // int fputs(const char *str,FILE *stream);
    fputs(str,stderr);
    fputc('\n',stderr);// int fputc(int char,FILE *stream);
    exit(1);
}


int main(int argc,char * argv[])
{   // 最基本版本的 epoll() 实现的回声服务器端,argc = 2
    int serv_sock, clnt_sock, str_len, i, epfd, event_cnt;
    struct sockaddr_in serv_adr,clnt_adr;
    socklen_t adr_sz;    char buf[BUF_SIZE]; // 100
    struct epoll_event event, * ep_events;

    if(argc != 2) { printf("参数不是2个\n");exit(1); }

    serv_sock = socket(PF_INET,SOCK_STREAM,0);
    
    memset(&serv_adr,0,sizeof(serv_adr));
    serv_adr.sin_family = AF_INET; // 协议
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);//IP地址
    serv_adr.sin_port = htons(atoi(argv[1])); //端口号
    if(bind(serv_sock,(struct sockaddr *)&serv_adr, sizeof(serv_adr))==-1)
        error_handling("bind() error"); 
        // 不必再处理字符串换行问题
    if(listen(serv_sock,5)==-1)    // 绑定,开启监听
        error_handling("listen() error");

    epfd = epoll_create(EPOLL_SIZE); // EPOLL_SIZE = 50
    event.events  = EPOLLIN;
    event.data.fd = serv_sock;
    epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event);

    ep_events = malloc(sizeof(struct epoll_event) * EPOLL_SIZE);
    while (1) // 此循环在正常情况下是不会退出的。
    {   event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);       
        if(-1 == event_cnt) { // 防止少写 ==
            puts("epoll_wait() 出错"); // 结束循环,进程退出
            break; //puts() 会自动换行
        }
        for(i = 0 ; i < event_cnt ; i++)
        {   if(ep_events[i].data.fd == serv_sock)//监听套接字
            {   adr_sz = sizeof(clnt_adr);
                clnt_sock = accept(serv_sock,(struct sockaddr *)&clnt_adr,&adr_sz);

                event.events  = EPOLLIN;
                event.data.fd = clnt_sock;
                epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event); 
                printf("创建了通信套接字 id: %d\n", clnt_sock);
            } else {   // read() 是不会为接收的字符串添加空字符 '\0' 的
                str_len = read(ep_events[i].data.fd, buf, BUF_SIZE);
                if(0 == str_len) // FIN 报文
                {   epoll_ctl(epfd, EPOLL_CTL_DEL,ep_events[i].data.fd,NULL);
                    close(ep_events[i].data.fd);
                    printf("关闭了通信套接字 id: %d\n",ep_events[i].data.fd);
                }
                else   write(ep_events[i].data.fd, buf, str_len);
            }
        } // for(...)  
    } // while(...)
    
    close(serv_sock); // 如此,监听套接字 
    close(epfd); //serv_sock 会被关闭两次。
    return 0;
    
}

++ 11 3 5-5 伊圣雨老师的 epoll 教学范例:图 2 的回声服务器的测试效果 :

在这里插入图片描述

(4) 11 4 5-5 伊圣雨老师的 epoll 教学范例:回声服务器端代码:使用 epoll ,小缓存,验证默认的水平触发模式:

在这里插入图片描述

++

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>

#define BUF_SIZE     3 
#define EPOLL_SIZE  50

void error_handling(char * str)//此函具有换行功能
{   // int fputs(const char *str,FILE *stream);
    fputs(str,stderr);
    fputc('\n',stderr);// int fputc(int char,FILE *stream);
    exit(1);
}


int main(int argc,char * argv[])
{   //验证 epoll 的默认的水平触发模式的回声服务器端,argc = 2
    int serv_sock, clnt_sock, str_len, i, epfd, event_cnt;
    struct sockaddr_in serv_adr,clnt_adr;
    socklen_t adr_sz;    char buf[BUF_SIZE]; // 100
    struct epoll_event event, * ep_events;

    if(argc != 2) { printf("参数不是2个\n");exit(1); }

    serv_sock = socket(PF_INET,SOCK_STREAM,0);
    
    memset(&serv_adr,0,sizeof(serv_adr));
    serv_adr.sin_family = AF_INET; // 协议
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);//IP地址
    serv_adr.sin_port = htons(atoi(argv[1])); //端口号
    if(bind(serv_sock,(struct sockaddr *)&serv_adr, sizeof(serv_adr))==-1)
        error_handling("bind() error"); 
        // 不必再处理字符串换行问题
    if(listen(serv_sock,5)==-1)    // 绑定,开启监听
        error_handling("listen() error");

    epfd = epoll_create(EPOLL_SIZE); // EPOLL_SIZE = 50
    event.events  = EPOLLIN;
    event.data.fd = serv_sock;
    epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event);

    ep_events = malloc(sizeof(struct epoll_event) * EPOLL_SIZE);
    while (1) // 此循环在正常情况下是不会退出的。
    {   event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);       
        if(-1 == event_cnt) { // 防止少写 ==
            puts("epoll_wait() 出错"); // 结束循环,进程退出
            break; //puts() 会自动换行
        } //增加 puts() 行统计 epoll_wait() 的返回次数,其余部分不变。
        puts("从 epoll_wait() 返回了");
        for(i = 0 ; i < event_cnt ; i++)
        {   if(ep_events[i].data.fd == serv_sock)//监听套接字
            {   adr_sz = sizeof(clnt_adr);
                clnt_sock = accept(serv_sock,(struct sockaddr *)&clnt_adr,&adr_sz);

                event.events  = EPOLLIN;
                event.data.fd = clnt_sock;
                epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event); 
                printf("创建了通信套接字 id: %d\n", clnt_sock);
            } else {   // read() 是不会为接收的字符串添加空字符 '\0' 的
                str_len = read(ep_events[i].data.fd, buf, BUF_SIZE);
                if(0 == str_len) // FIN 报文
                {   epoll_ctl(epfd, EPOLL_CTL_DEL,ep_events[i].data.fd,NULL);
                    close(ep_events[i].data.fd);
                    printf("关闭了通信套接字 id: %d\n",ep_events[i].data.fd);
                } else   write(ep_events[i].data.fd, buf, str_len);
            }
        } // for(...)  
    } // while(...)
    
    close(serv_sock); // 如此,监听套接字 
    close(epfd); //serv_sock 会被关闭两次。
    return 0;
    
}

++ 11 5 5-5 伊圣雨老师的 epoll 教学范例:图 4 的回声服务器的默认水平触发模式的测试效果:

在这里插入图片描述

(5) 11 6 5-5 伊圣雨老师的 epoll 教学范例:回声服务器端代码:使用 epoll ,小缓存,通信套接字使用非阻塞的边缘触发模式:

在这里插入图片描述

++

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <fcntl.h> //增加这俩头文件
#include <errno.h>

#define BUF_SIZE     3 
#define EPOLL_SIZE  50

void error_handling(char * str)//此函具有换行功能
{   // int fputs(const char *str,FILE *stream);
    fputs(str,stderr);
    fputc('\n',stderr);// int fputc(int char,FILE *stream);
    exit(1);
}

void setnonblockingmode(int fd) //非阻塞
{   int flag = fcntl(fd, F_GETFL, 0);
    fcntl(fd, F_SETFL, flag | O_NONBLOCK); }

int main(int argc,char * argv[])
{   //验证 epoll 的边缘触发模式的回声服务器端,argc = 2
    int serv_sock, clnt_sock, str_len, i, epfd, event_cnt;
    struct sockaddr_in serv_adr,clnt_adr;
    socklen_t adr_sz;    char buf[BUF_SIZE]; // 100
    struct epoll_event event, * ep_events;

    if(argc != 2) { printf("参数不是2个\n");exit(1); }

    serv_sock = socket(PF_INET,SOCK_STREAM,0);
    setnonblockingmode(serv_sock);//设置监听套接字为非阻塞

    memset(&serv_adr,0,sizeof(serv_adr));
    serv_adr.sin_family = AF_INET; // 协议
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);//IP地址
    serv_adr.sin_port = htons(atoi(argv[1])); //端口号
    if(bind(serv_sock,(struct sockaddr *)&serv_adr, 
                               sizeof(serv_adr))==-1)
        error_handling("bind() error"); 
        // 不必再处理字符串换行问题
    if(listen(serv_sock,5)==-1)    // 绑定,开启监听
        error_handling("listen() error");

    epfd = epoll_create(EPOLL_SIZE); // EPOLL_SIZE = 50
    event.events  = EPOLLIN; // 监听套接字仍为水平触发模式
    event.data.fd = serv_sock;
    epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event);

    ep_events = malloc(sizeof(struct epoll_event) * EPOLL_SIZE);
    while (1) // 此循环在正常情况下是不会退出的。
    {   event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);       
        if(-1 == event_cnt) { // 防止少写 ==
            puts("epoll_wait() 出错"); // 结束循环,进程退出
            break;  //puts() 会自动换行 
        }       
        puts("从 epoll_wait() 返回了");//统计epoll_wait()的返回次数
        for(i = 0 ; i < event_cnt ; i++) //依次处理所有发生了事件的套接字
        {   if(ep_events[i].data.fd == serv_sock)//监听套接字
            {   adr_sz = sizeof(clnt_adr);
                clnt_sock = accept(serv_sock,(struct sockaddr *)&clnt_adr,&adr_sz);
                setnonblockingmode(clnt_sock);
                event.events  = EPOLLIN | EPOLLET; //通信套接字边缘触发
                event.data.fd = clnt_sock;
                epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event); 
                printf("创建了通信套接字 id: %d\n", clnt_sock);
            } else while(1) {   // 从通信套接字读取所有数据
                str_len = read(ep_events[i].data.fd, buf, BUF_SIZE);
                if(0 == str_len) // FIN 报文
                {   epoll_ctl(epfd, EPOLL_CTL_DEL,ep_events[i].data.fd,NULL);
                    close(ep_events[i].data.fd);
                    printf("关闭了通信套接字 id: %d\n",ep_events[i].data.fd);
                    break;  // 下面的是读完了接收缓存中的数据,返回-1,并设置errno
                } else if(str_len < 0){ if(EAGAIN == errno) break; } //跳出内循环
                else   write(ep_events[i].data.fd, buf, str_len);
            } //内层 while(...)
        } // for(...)  
    } // while(...)
    
    close(serv_sock); // 如此,监听套接字 
    close(epfd); //serv_sock 会被关闭两次。
    return 0;
    
}

++11 7 5-5 伊圣雨老师的 epoll 教学范例:图 6 的回声服务器的非阻塞边缘触发模式的测试效果:

在这里插入图片描述

(6)

谢谢

;