socket采用epoll编程demo

Posted yizhou35

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了socket采用epoll编程demo相关的知识,希望对你有一定的参考价值。

epoll工作流程

首先,需要调用epoll_create创建epoll;
此后我们就可以进行socket/bind/listen;
然后调用epoll_ctl进行注册;
接下来,就可以通过一个while(1)循环调用epoll_wait来等待事件的发生;
然后循环查看接收到的事件并进行处理;
1)如果事件是sever的socketfd我们就要进行accept,并且把接收到client的socketfd加入到要监听的事件中;
2)如果在监听过程中,需要修改操作方式(读/写),可以调用epoll_ctl来重新修改;
3)如果监听到某一个客户端关闭,那么我就需要再次调用epoll_ctl把它从epoll监听事件中删除。

 

技术图片

epoll的结构体
typedef union epoll_data {
     void        *ptr;
     int          fd;
     uint32_t     u32;
     uint64_t     u64;
 } epoll_data_t;
 struct epoll_event {
     uint32_t     events;      /* Epoll events */
     epoll_data_t data;        /* User data variable */
 };

#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <unistd.h>
#define SERV_PORT  8802
int main()
{
    int i,flag;
    int sockfd,clntfd,newfd;
    int epfd,nfds;
    ssize_t n;
    char buffer[1024];
    int s = sizeof(struct sockaddr);

    struct sockaddr_in serv_addr;
    struct sockaddr_in clnt_addr;
    //定义epoll数据结构
    struct epoll_event ev,events[20];

    epfd = epoll_create(256);

    //创建socket,并初始化事件ev
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("socket error!
");
        return -1;
    }
    ev.data.fd = sockfd;
    ev.events = EPOLLIN|EPOLLET;

    //注册epoll事件
    flag = epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);
    if (flag < 0) {
        perror("epoll_ctl error!
");
        return -1;
    }
    bzero(&serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(SERV_PORT);
    serv_addr.sin_addr.s_addr = htonl( INADDR_ANY );

    flag = bind(sockfd, (struct sockaddr*)&serv_addr, sizeof(struct sockaddr));
    if (flag < 0) {
        perror("bind error!
");
        return -1;
    }
    printf("bind
");

    flag = listen(sockfd, 20);
    if (flag < 0) {
        perror("listen error!
");
        return -1;
    }
    printf("listen
");

    //开始循环
    while (1) {
        //等待事件发生,返回请求数目
        nfds = epoll_wait(epfd, events, 20, 500);
        //一次处理请求
        for (i = 0; i < nfds; ++i) {
            if (events[i].data.fd == sockfd){
                clntfd = accept(sockfd, (struct sockaddr*)&clnt_addr,(unsigned int*)&s);
                if (clntfd < 0) {
                    perror("accept error");
                    continue;
                }
                printf("accept
");

                char *str = inet_ntoa(clnt_addr.sin_addr);
                printf("accepnt the client ip : %s
",str);

                //设置文件标识符,设置操作属性:写操作
                ev.data.fd = clntfd;
                ev.events = EPOLLOUT | EPOLLET;
                //向创建的的epoll进行注册写操作
                epoll_ctl(epfd, EPOLL_CTL_ADD, clntfd, &ev);
            } else if (events[i].events & EPOLLOUT) {
                printf("EPOLLOUT
");

                if ((newfd = events[i].data.fd) < 0)
                    continue;
                bzero(buffer,sizeof(buffer));
                strcpy(buffer,"welcome to myserver!
");
                flag = send(newfd, buffer, 1024, 0);
                if (flag < 0) {
                    perror("send error");
                    continue;
                }
                //修改操作为读操作
                ev.data.fd = clntfd;
                ev.events = EPOLLIN | EPOLLET;
                epoll_ctl(epfd, EPOLL_CTL_MOD, newfd, &ev);
            } else if (events[i].events & EPOLLIN) {
                printf("EPOLLIN
");

                bzero(buffer,sizeof(buffer));
                if ((newfd = events[i].data.fd) < 0)
                    continue;
                if ((n = read(newfd, buffer, 1024)) < 0) {
                    if (errno == ECONNRESET){
                        close(newfd);
                        events[i].data.fd = -1;
                        printf("errno ECONRESET!
");
                    } else {
                        perror("readbuffer error!
");
                    }
                } else if (n == 0) {//表示客户端已经关闭
                    close(newfd);
                    events[i].data.fd = -1;
                    printf("n为0
");
                }
                if (buffer[0] != ‘0‘)
                    printf("have read: %s
", buffer);
            }
        }
    }
    close(sockfd);
    return 0;
}

  

引自:https://www.bbsmax.com/A/l1dymR3Gde/
优化
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <unistd.h>

#define SERV_PORT   8802
#define MAX_EVENTS  20


int main()
{
    int listen_fd = socket(AF_INET, SOCK_STREAM, 0);    //若成功则返回非负描述符,若失败则返回-1,第一个参数指明协议族(IPv4或IPv6等)
    if (listen_fd < 0) {                                //第二个参数指明套接字类型,字节流套接字(SOCK_STREAM)和数据报套接字(SOCK_DGRAM)
        perror("socket error!
");
        return -1;
    }


    int epfd = epoll_create(256);  //参数会被忽略,但是要大于0, 
                                   //若成功返回一个大于 0 的值,表示 epoll 实例;若返回 -1 表示出错

    //针对监听的sockfd,创建epollevent
    struct epoll_event event;
    event.data.fd = listen_fd;
    event.events = EPOLLIN | EPOLLET;

    //注册epoll事件
    int flag = epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &event);  //int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    if (flag < 0) {                                                //成功返回0,出错返回-1
        perror("epoll_ctl error!
");
        return -1;
    }

    
    if (bindAndListenFd(listen_fd) < 0)
        return -1;


    //定义epoll数据结构
    struct epoll_event events[MAX_EVENTS];  //可以使用vector,参见muduo源码中的使用

    while (1) {
        //等待事件发生,返回请求数目
        int nfds = epoll_wait(epfd, events, MAX_EVENTS, 500);   //maxevents: 返回的events的最大个数,如果最大个数大于实际触发的个数,则下次epoll_wait的时候仍然可以返回
                                                                //int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
                                                                //成功返回的是一个大于 0 的数,表示事件的个数;返回 0 表示的是超时时间到;若出错返回 -1.

        for (int i = 0; i < nfds; ++i) {                  
            if (events[i].data.fd == listen_fd) {
                struct sockaddr_in client_addr;
                int client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, sizeof(client_addr));  //若成功则为非负描述符,若出错则返回-1
                if (client_fd < 0) {
                    perror("accept error");
                    continue;
                }

                char *str = inet_ntoa(client_addr.sin_addr);
                printf("accept the client ip : %s
",str);

                onRecvNewConnect(epfd, client_fd);

            } else if (events[i].events & EPOLLOUT) {
                int sockfd = events[i].data.fd;
                if (sockfd < 0)
                    continue;

                onWriteFd(epfd, sockfd);
                
            } else if (events[i].events & EPOLLIN) {
                int sockfd = events[i].data.fd;
                if (sockfd < 0)
                    continue;

                if (onReadFd(epfd, sockfd) < 0) {
                    events[i].data.fd = -1;
                }
            }
        }
    }
    close(sockfd);
    return 0;
}

int bindAndListenFd(int sockfd) {
    ::fcntl(sockfd, F_SETFL, O_NONBLOCK);                    //设置非阻塞模式

    struct sockaddr_in serv_addr;

    bzero(&serv_addr, sizeof(serv_addr));                    //void bzero(void *dest, size_t nbytes);
    serv_addr.sin_family = AF_INET;                          //类似void *memset(void *dest, int c, size_t len);
    serv_addr.sin_port = htons(SERV_PORT);                   //本地端口号转化为网络端口号 host to network short
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);           //INADDR_ANY代表本机所有的IP地址  host to network long

    int flag = bind(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));   //成功返回0,出错返回-1
    if (flag < 0) {
        perror("bind error!
");
        return -1;
    }

    flag = listen(sockfd, 20);                         //成功返回0,出错返回-1
    if (flag < 0) {
        perror("listen error!
");
        return -1;
    }

    return 0;
}

void onRecvNewConnect(int epfd, int clientfd) {
    ::fcntl(sockfd, F_SETFL, O_NONBLOCK);                //设置非阻塞模式
    //设置文件标识符,设置操作属性:写操作
    struct epoll_event ev_client;
    ev_client.data.fd = clintfd;
    ev_client.events = EPOLLOUT | EPOLLET;
    //向创建的的epoll进行注册写操作
    epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev_client);
}

void onWriteFd(int epfd, int sockfd) {
    char buffer[1024];
    bzero(buffer, sizeof(buffer));
    strcpy(buffer, "welcome to myserver!
");
    int flag = send(sockfd, buffer, 1024, 0);
    if (flag < 0) {
        perror("send error");
        return;
    }
    //修改操作为读操作
    struct epoll_event ev_client;
    ev_client.data.fd = sockfd;
    ev_client.events = EPOLLIN | EPOLLET;
    epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev_client);
}


int onReadFd(int epfd, int sockfd) {
    char buffer[1024];
    bzero(buffer, sizeof(buffer));
    int n = read(sockfd, buffer, 1024);
    if (n < 0) {
        if (errno == ECONNRESET) {
            close(sockfd);
            printf("errno ECONRESET!
");
            return -1;
        } else {
            perror("readbuffer error!
");
        }
    } else if (n == 0) {      //表示客户端已经关闭
        close(sockfd);
        printf("n为0
");
        return -1;
    }
    if (buffer[0] != ‘0‘)
        printf("have read: %s
", buffer);
    return 0;
}

以上是关于socket采用epoll编程demo的主要内容,如果未能解决你的问题,请参考以下文章

socket编程:多路复用I/O服务端客户端之epoll

计网 - Socket 编程:epoll 为什么用红黑树?

linux网络编程epoll内核实现代码分析

linux网络编程 - epoll内核实现代码分析

socket编程的select poll和epoll这两种机制,本质区别在哪里?

非阻塞socket调用connect, epoll和select检查连接情况示例