linux下socket采用epoll編程demo

epoll工作流程

首先,需要調(diào)用epoll_create創(chuàng)建epoll;
此后我們就可以進行socket/bind/listen;
然后調(diào)用epoll_ctl進行注冊;
接下來,就可以通過一個while(1)循環(huán)調(diào)用epoll_wait來等待事件的發(fā)生;
然后循環(huán)查看接收到的事件并進行處理;
1)如果事件是sever的socketfd我們就要進行accept,并且把接收到client的socketfd加入到要監(jiān)聽的事件中;
2)如果在監(jiān)聽過程中,需要修改操作方式(讀/寫),可以調(diào)用epoll_ctl來重新修改;
3)如果監(jiān)聽到某一個客戶端關(guān)閉,那么我就需要再次調(diào)用epoll_ctl把它從epoll監(jiān)聽事件中刪除。


image.png

epoll的結(jié)構(gòu)體

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數(shù)據(jù)結(jié)構(gòu)
    struct epoll_event ev,events[20];

    epfd = epoll_create(256);

    //創(chuàng)建socket,并初始化事件ev
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("socket error!\n");
        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!\n");
        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!\n");
        return -1;
    }
    printf("bind\n");

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

    //開始循環(huán)
    while (1) {
        //等待事件發(fā)生,返回請求數(shù)目
        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\n");

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

                //設(shè)置文件標識符,設(shè)置操作屬性:寫操作
                ev.data.fd = clntfd;
                ev.events = EPOLLOUT | EPOLLET;
                //向創(chuàng)建的的epoll進行注冊寫操作
                epoll_ctl(epfd, EPOLL_CTL_ADD, clntfd, &ev);
            } else if (events[i].events & EPOLLOUT) {
                printf("EPOLLOUT\n");

                if ((newfd = events[i].data.fd) < 0)
                    continue;
                bzero(buffer,sizeof(buffer));
                strcpy(buffer,"welcome to myserver!\n");
                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\n");

                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!\n");
                    } else {
                        perror("readbuffer error!\n");
                    }
                } else if (n == 0) {//表示客戶端已經(jīng)關(guān)閉
                    close(newfd);
                    events[i].data.fd = -1;
                    printf("n為0\n");
                }
                if (buffer[0] != '0')
                    printf("have read: %s\n", buffer);
            }
        }
    }
    close(sockfd);
    return 0;
}


引自:https://www.bbsmax.com/A/l1dymR3Gde/

優(yōu)化



#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,第一個參數(shù)指明協(xié)議族(IPv4或IPv6等)
    if (listen_fd < 0) {                                //第二個參數(shù)指明套接字類型,字節(jié)流套接字(SOCK_STREAM)和數(shù)據(jù)報套接字(SOCK_DGRAM)
        perror("socket error!\n");
        return -1;
    }


    int epfd = epoll_create(256);  //參數(shù)會被忽略,但是要大于0, 
                                   //若成功返回一個大于 0 的值,表示 epoll 實例;若返回 -1 表示出錯

    //針對監(jiān)聽的sockfd,創(chuàng)建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!\n");
        return -1;
    }

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


    //定義epoll數(shù)據(jù)結(jié)構(gòu)
    struct epoll_event events[MAX_EVENTS];  //可以使用vector,參見muduo源碼中的使用

    while (1) {
        //等待事件發(fā)生,返回請求數(shù)目
        int nfds = epoll_wait(epfd, events, MAX_EVENTS, 500);   //maxevents: 返回的events的最大個數(shù),如果最大個數(shù)大于實際觸發(fā)的個數(shù),則下次epoll_wait的時候仍然可以返回
                                                                //int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
                                                                //成功返回的是一個大于 0 的數(shù),表示事件的個數(shù);返回 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\n",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);                    //設(shè)置非阻塞模式

    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);                   //本地端口號轉(zhuǎn)化為網(wǎng)絡(luò)端口號 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!\n");
        return -1;
    }

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

    return 0;
}

void onRecvNewConnect(int epfd, int clientfd) {
    ::fcntl(sockfd, F_SETFL, O_NONBLOCK);                //設(shè)置非阻塞模式
    //設(shè)置文件標識符,設(shè)置操作屬性:寫操作
    struct epoll_event ev_client;
    ev_client.data.fd = clintfd;
    ev_client.events = EPOLLOUT | EPOLLET;
    //向創(chuàng)建的的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!\n");
    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!\n");
            return -1;
        } else {
            perror("readbuffer error!\n");
        }
    } else if (n == 0) {      //表示客戶端已經(jīng)關(guān)閉
        close(sockfd);
        printf("n為0\n");
        return -1;
    }
    if (buffer[0] != '0')
        printf("have read: %s\n", buffer);
    return 0;
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容