天天看點

epoll詳解(三)-- ET模式執行個體

通過本文你會了解到:

1. 非阻塞預備知識點

2. 非阻塞server源碼

3. 運作測試(應用linux 的 nc 工具)

非阻塞預備知識點

O_NONBLOCK

- 應用于socket描述符時,另之後對此描述符的阻塞操作(如read/write等)變為非阻塞。

EAGAIN

- 表示沒有可用資料,稍後再試。産生此錯誤的前提條件是socket描述符為非阻塞時(即,設定為

O_NONBLOCK

标志)。在VxWorks和Windows上,EAGAIN的名字叫做EWOULDBLOCK。

在非阻塞socket程式設計時,為保證相容性問題,最好同時判斷

EAGAIN

EWOULDBLOCK

标志。

非阻塞server源碼

約定

格式為 的注釋對程式的主要流程進行解釋

格式為 // 的注釋對程式做必要的說明

#include <stdio.h> // for printf()
#include <stdlib.h> // for exit()
#include <unistd.h> // for read() and write()
#include <sys/epoll.h> // for epoll
#include <sys/socket.h> // for epoll
#include <netinet/in.h> //struct sockaddr_in
#include <string.h> // memset
#include <fcntl.h> // fcntl
#include <errno.h> // errno

#define EPOLL_QUEUE_LEN 32 //監聽的最大連接配接數
#define BUF_SIZE 1024

static int set_nonblocking(int fd)
{
    int flags;

    flags = fcntl(fd, F_GETFL, );
    if(flags == -) {
        perror("fcntl");
        return -;
    }

    flags |= O_NONBLOCK;

    if(fcntl(fd, F_SETFL, flags) == -) {
        perror("fcntl");
        return -;
    }

    return ;
}

static int listen_socket(int port)
{
    int fd;
    struct sockaddr_in sa;


    fd = socket(AF_INET, SOCK_STREAM, );
    if(fd == -) {
        perror("socket");
        return -;
    }

    memset(&sa, , sizeof(sa));
    sa.sin_family = AF_INET;
    sa.sin_port = htons(port);
    sa.sin_addr.s_addr = htonl(INADDR_ANY);
    if(bind(fd, (struct sockaddr *)&sa, sizeof(sa)) == -) {
        perror("bind");
        close(fd);
        return -;
    }

    if(set_nonblocking(fd) == -) {
        close(fd);
        return -;
    }

    if(listen(fd, ) == -) {
        perror("listen");
        close(fd);
        return -;
    }

    return fd;
}

int main(int argc, char **argv)
{
    struct epoll_event ev;
    struct epoll_event events[EPOLL_QUEUE_LEN];
    int epfd;
    int sfd;
    int nfds;

    if(argc != ) {
        printf("usage: %s [port]\n", argv[]);
        exit(EXIT_FAILURE);
    }

    /*建立 epoll 執行個體*/
    epfd = epoll_create(EPOLL_QUEUE_LEN);
    if(epfd == -) {
        perror("epoll_create");
        exit(EXIT_FAILURE);
    }

    /*對輸入port進行監聽*/
    printf("server listen form port: %d\n", atoi(argv[]));
    sfd = listen_socket(atoi(argv[]));
    if(sfd == -) {
        printf("listen_socket failed\n");
        exit(EXIT_FAILURE);
    }

    /*将server描述符添加到epoll中*/
    ev.data.fd = sfd;
    ev.events = EPOLLIN | EPOLLET;
    if(epoll_ctl(epfd, EPOLL_CTL_ADD, sfd, &ev) == -) {
        perror("epoll_ctl");
        exit(EXIT_FAILURE);
    }

    /*主循環*/
    while() {
        int i;

        /*等待epoll執行個體中描述符有I/O事件發生*/
        nfds = epoll_wait(epfd, events, EPOLL_QUEUE_LEN, -);
        for(i = ; i < nfds; i++) {
            if(events[i].events & (EPOLLERR | EPOLLHUP)) {
                //EPOLLERR - 出現錯誤
                //EPOLLHUP - 用戶端提前關閉連接配接(close by peer)
                continue;
            }

            if(!(events[i].events & EPOLLIN)) { //不是IN操作
                continue;
            }

            if(sfd == events[i].data.fd) { 
                /*有用戶端連入server*/
                struct sockaddr_in in_addr;
                socklen_t in_len;
                int infd;

                while() { //非阻塞操作accept需要循環檢測
                    infd = accept(sfd, (struct sockaddr *)&in_addr, &in_len);
                    if(infd == -) {
                        if(errno == EAGAIN || errno == EWOULDBLOCK) {
                            ///已接收到所有描述符
                            break;
                        } else {
                            perror("accept");
                            break;
                        }
                    }
                    if(set_nonblocking(infd) == -) {
                        break;
                    }

                    ev.data.fd = infd;
                    ev.events = EPOLLIN | EPOLLET;
                    if(epoll_ctl(epfd, EPOLL_CTL_ADD, infd, &ev) == -) {
                        perror("epoll_ctl");
                        exit(EXIT_FAILURE);
                    }
                    printf("incoming client [fd=%d]\n", infd);
                }
            } else {
                /*收到用戶端資料*/
                int done = ;

                while() {
                    ssize_t cnt;
                    char buf[BUF_SIZE];

                    memset(buf, , sizeof(buf));
                    cnt = read(events[i].data.fd, buf, sizeof(buf));
                    if(cnt == -) {
                        if(errno == EAGAIN) {
                            done = ;
                            break;
                        } else {
                            perror("read");
                        }
                    } else if (cnt == ) { //fd 被關閉
                        done = ;
                        break;
                    }

                    printf("receive data: %s\n", buf);
                }

                if(done == ) {
                    printf("close client [fd=%d]\n", events[i].data.fd);
                    close(events[i].data.fd);
                }
            } 
        }
    }

    close(sfd);

    return ;
}
           

運作測試

linux中nc是一個強大的網絡工具,本文隻用nc來建立一個socket用戶端,來測試epoll server,如果想深入了解nc可以參考linux

man nc

nc建立用戶端指令:

接入server:

nc ip port

- ip 為server的IP位址 prot為server的端口

當接入server後輸入資料并回車即可向server發送資料。

測試截圖:

epoll詳解(三)-- ET模式執行個體

繼續閱讀