伺服器程式設計——I/O複用(select、poll、epoll)

readyao發表於2016-02-24








select例子:

伺服器上用select來監聽connfd是否就緒,如果是正常資料的話,則正常處理。如果是異常資料的話,則異常處理。

伺服器端程式碼:

/*************************************************************************
	> File Name: practice_91.c
	> Author: 
	> Mail: 
	> Created Time: 2016年02月24日 星期三 15時38分01秒
 ************************************************************************/
/*
能夠同時接收普通資料和帶外資料
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/time.h>


int main(int argc, char *argv[])
{
    if(argc <= 2){
        printf("%s ip_address port_number\n", argv[0]);
        return -1;
    }

    const char * ip = argv[1];
    int port = atoi(argv[2]);

    int ret = 0;

    struct sockaddr_in address;
    bzero(&address, sizeof(address));

    address.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &address.sin_addr);
    address.sin_port = htons(port);
    
    //socket
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if(listenfd < 0){
        printf("socket error\n");
        return -1;
    }

    //bind
    ret = bind(listenfd, (struct sockaddr *)&address, sizeof(address));
    if(ret != 0){
        printf("bind error\n");
        return -1;
    }

    //listen
    ret = listen(listenfd, 5);
    if(ret != 0){
        printf("listen error\n");
        return -1;
    }

    //客戶端地址
    struct sockaddr_in client_addr;
    socklen_t client_addrlen = sizeof(client_addr);
    
    //accept
    int connfd = accept(listenfd, (struct sockaddr *)&client_addr, &client_addrlen);
    if(connfd < 0){
        printf("accept error\n");
        close(listenfd);
        return -1;
    }

    char buf[1024];
    fd_set read_fds;
    fd_set exception_fds;
    FD_ZERO(&read_fds);
    FD_ZERO(&exception_fds);
    
    while(1){
        memset(buf, 0, sizeof(buf));
        FD_SET(connfd, &read_fds);
        FD_SET(connfd, &exception_fds);
    
        ret = select(connfd+1, &read_fds, NULL, &exception_fds, NULL);//會一直阻塞,直到檔案描述符就緒;每次事件發生之後,都要再重新設定檔案描述符
        if(ret < 0){
            printf("select error\n");
            break;
        }

        if(FD_ISSET(connfd, &read_fds)){//正常可讀事件
            ret = recv(connfd, buf, sizeof(buf)-1, 0);
            if(ret <= 0){//ret == 0表示對方關閉了連線
                break;
            }
            printf("get %d bytes of normal data: %s\n", ret, buf);
        }
        else if(FD_ISSET(connfd, &exception_fds)){//對於異常事件
            ret = recv(connfd, buf, sizeof(buf)-1, MSG_OOB);
            if(ret <= 0){//ret == 0表示對方關閉了連線
                break;
            }
            printf("get %d bytes of oob data: %s\n", ret, buf);
        }
    }
    
    close(connfd);
    close(listenfd);

    return 0;
}

客戶端程式碼:

/*************************************************************************
	> File Name: 53backlog.c
	> Author: 
	> Mail: 
	> Created Time: 2016年01月16日 星期六 11時35分15秒
 ************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <string.h>
#include <assert.h>
#include <stdbool.h>//包含bool型別,C語言是沒有bool型別的,C++才有
#include <unistd.h>
#include <errno.h>

static bool stop = false;

static void handle_term(int sig)
{
    stop = true;
}

int main(int argc, char *argv[])
{
    signal(SIGTERM, handle_term);
    if(argc <= 2){
        printf("usage: %s ip_address port_number backlog\n", argv[0]);
        return 1;
    }

    const char * ip = argv[1];
    int port = atoi(argv[2]);
    int backlog = 5;//atoi(argv[3]);

    int sock = socket(AF_INET, SOCK_STREAM, 0);
    assert(sock >= 0);

    //建立ipv4的地址
    struct sockaddr_in address;
    bzero(&address, sizeof(address));
    address.sin_family = AF_INET;
    address.sin_port = htons(port);
    inet_pton(AF_INET, ip, (void *)&address.sin_addr);

    int ret = connect(sock, (struct sockaddr *)&address, sizeof(address));
    if(ret < 0)
        printf("connect failed.\n");
    else{
        const char * oob_data = "abc";
        const char * normal_data = "123";

        printf("send the normal_data: %s\n", normal_data);
        send(sock, normal_data, strlen(normal_data), 0);

        sleep(1);//傳送延遲1秒,如果沒有延遲的話,太快有時候會導致收不到oob_data
        printf("send the oob_data: %s\n", oob_data);
        send(sock, oob_data, strlen(oob_data), MSG_OOB);

        sleep(1);
        printf("send the normal_data: %s\n", normal_data);
        send(sock, normal_data, strlen(normal_data), 0);
    }
    
    printf("send over...,按任意鍵退出\n");
    getchar();
    close(sock);

    return 0;
}

在終端下輸入:./practice_server_91 192.168.0.127 12345       //啟動了伺服器

在另外一個終端下輸入:./56senddata 192.168.0.127 12345   //啟動了客戶端

伺服器終端輸出為:

get 3 bytes of normal data: 123
get 2 bytes of normal data: ab
get 1 bytes of oob data: c
get 3 bytes of normal data: 123


客戶端終端輸出為:

send the normal_data: 123
send the oob_data: abc
send the normal_data: 123
send over...,按任意鍵退出


POLL例項

伺服器和客戶端用poll機制實現,伺服器實現對客戶端的響應的回顯。伺服器將客戶端的輸入直接回復。

伺服器用poll機制實現對監聽套接字和若干連線套接字進行I/O複用。

客戶端用poll機制實現對連線套接字和標準輸入檔案描述符進行I/O複用。

伺服器原始碼:

/*************************************************************************
	> File Name: practice_91.c
	> Author: 
	> Mail: 
	> Created Time: 2016年02月24日 星期三 15時38分01秒
 ************************************************************************/
/*
能夠同時接收普通資料和帶外資料
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/time.h>
#include <poll.h>
#include <errno.h>

#define OPEN_MAX 1024

int do_poll(int listenfd)
{
    int connfd, sockfd;
    struct sockaddr_in cliaddr;
    socklen_t cliaddrlen = sizeof(cliaddr);

    struct pollfd clientfds[1024];
    int maxi;
    char buf[1024];
    int i, num;
    int nready;

    //新增監聽檔案描述符
    clientfds[0].fd = listenfd;
    clientfds[0].events = POLLIN;
    //初始化客戶連線檔案描述符
    for(i = 1; i < OPEN_MAX; ++i){
        clientfds[i].fd = -1;
    }

    maxi = 0;

    for(; ;){
        //獲取就緒的檔案描述符的個數
        nready = poll(clientfds, maxi+1, -1);
        if(nready == -1){
            printf("poll error\n");
            return -1;
        }
        //測試監聽檔案描述符是否就緒好
        if(clientfds[0].revents & POLLIN){
            //接受新的連線
            connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddrlen);
            if(connfd == -1){
                if(errno == EINTR)
                    continue;
                else{
                    printf("accept error\n");
                    return -1;
                }
            }

            printf("接受新的客戶連線:%s:%d\n", inet_ntoa(cliaddr.sin_addr), cliaddr.sin_port);
            //將新的連線新增到陣列中
            for(i = 1; i < OPEN_MAX; ++i){
                if(clientfds[i].fd < 0){
                    clientfds[i].fd = connfd;
                    break;
                }
            }

            if(i == OPEN_MAX){
                printf("too many clients\n");
                return -1;
            }

            clientfds[i].events = POLLIN;
            //記錄客戶連線套接字的個數
            maxi = (i > maxi ? i : maxi);

            if(--nready <= 0)
                continue;
        }

        //不是監聽套接字就緒
        //處理客戶連線,處理連線套接字
        //handle_connectin(clientfds, maxi);
        memset(buf, 0, 1024);

        for(i = 1; i <= maxi; ++i){
            if(clientfds[i].fd < 0)
                continue;
            //測試連線套接字是否就緒可讀
            if(clientfds[i].revents & POLLIN){
                //有訊息則讀取到buf中
                num = read(clientfds[i].fd, buf, 1024);
            
                if(num == 0){
                    close(clientfds[i].fd);
                    clientfds[i].fd = -1;
                    continue;
                } 
                printf("server read the data is : %s", buf);
                write(clientfds[i].fd, buf, num);
            }
        }
    }
    
}

int main(int argc, char *argv[])
{
    if(argc <= 2){
        printf("%s ip_address port_number\n", argv[0]);
        return -1;
    }

    const char * ip = argv[1];
    int port = atoi(argv[2]);

    int ret = 0;

    struct sockaddr_in address;
    bzero(&address, sizeof(address));

    address.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &address.sin_addr);
    address.sin_port = htons(port);
    
    //socket
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if(listenfd < 0){
        printf("socket error\n");
        return -1;
    }

    //bind
    ret = bind(listenfd, (struct sockaddr *)&address, sizeof(address));
    if(ret != 0){
        printf("bind error\n");
        return -1;
    }

    //listen
    ret = listen(listenfd, 5);
    if(ret != 0){
        printf("listen error\n");
        return -1;
    }

    do_poll(listenfd);

    close(listenfd);

    return 0;
}

客戶端原始碼:

/*************************************************************************
	> File Name: poll_client.c
	> Author: 
	> Mail: 
	> Created Time: 2016年02月26日 星期五 20時25分24秒
 ************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <poll.h>
#include <time.h>
#include <unistd.h>
#include <arpa/inet.h>


int handle_connection(int sockfd)
{
    char sendbuf[1024], recvbuf[1024];
    struct pollfd pfds[2];
    int num;
    //新增連線套接字
    pfds[0].fd = sockfd;
    pfds[0].events = POLLIN;
    //新增標準輸入檔案描述符
    pfds[1].fd = STDIN_FILENO;
    pfds[1].events = POLLIN;

    for(; ;){
        poll(pfds, 2, -1);
        //連線檔案描述符準備好
        if(pfds[0].revents & POLLIN){
            num = read(sockfd, recvbuf, 1024);
            if(num == 0){
                printf("client: server closed.\n");
                close(sockfd);
            }
            //將收到的內容輸出到標準輸出
            write(STDOUT_FILENO, recvbuf, num);
        }
        //測試標準輸入是否就緒
        if(pfds[1].revents & POLLIN){
            num = read(STDIN_FILENO, sendbuf, 1024);
            if(num == 0){
                continue;
            }
            write(sockfd, sendbuf, num);
        }
    }
    return 0;
}


int main(int argc, char *argv[])
{
    if(argc <= 2){
        printf("input the: %s ipaddr port", argv[0]);
        return -1;
    }
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in servaddr;
    bzero(&servaddr, sizeof(servaddr));

    const char * ip = argv[1];
    int port = atoi(argv[2]);

    servaddr.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &servaddr.sin_addr);
    servaddr.sin_port = htons(port);

    connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
    
    //處理連線套接字
    handle_connection(sockfd);

    return 0;
}

在終端1下輸入:./poll_server 192.168.0.140 12345      //啟動了伺服器

在終端2下輸入:./poll_client 192.168.0.140 12345   //啟動了客戶端1

client1 hello world
client1 hello world
client1 linux
client1 linux

在終端3下輸入:./poll_client 192.168.0.140 12345   //啟動了客戶端2

client2 hello world
client2 hello world
client ^H
client 
client 2
client 2

伺服器終端輸出為:


接受新的客戶連線:192.168.0.140:59023
server read the data is : client1 hello world
server read the data is : client1 linux
接受新的客戶連線:192.168.0.140:59279
server read the data is : client2 hello world
server read the data is : client 
server read the data is : client 2




EPOLL例項

伺服器通過選擇不同的模式(LT,ET)來對連線套接字進行監聽。用ET模式對監聽套接字進行監聽。將收到的資料輸出到標準輸出上顯示。
客戶端僅僅是從標準輸入獲得資料併傳送到連線套接字上。

伺服器原始碼:

/*************************************************************************
	> File Name: epoll_server.c
	> Author: 
	> Mail: 
	> Created Time: 2016年02月27日 星期六 16時17分58秒
 ************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <stdbool.h>
#include <fcntl.h>
#include <errno.h>
#define MAX_EVENT_NUMBER 1024
#define BUFFER_SIZE 10

//設定檔案描述符為非阻塞
int setnonblocking(int fd)
{
    int old_option = fcntl(fd, F_GETFL);
    int new_option = old_option | O_NONBLOCK;
    fcntl(fd, F_SETFL, new_option);
    return old_option;
}

//將檔案描述符fd新增到核心事件表中
void addfd(int epollfd, int fd, bool enable_et)
{
    struct epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN;
    if(enable_et)
        event.events |= EPOLLET;

    epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
    setnonblocking(fd);
}

//LT模式的工作流程
void lt(struct epoll_event * events, int number, int epollfd, int listenfd)
{
    char buf[BUFFER_SIZE];
    int i;
    for(i = 0; i < number; ++i){
        int sockfd = events[i].data.fd;
        if(sockfd == listenfd){//如果描述符是監聽套接字
            struct sockaddr_in clientaddr;
            socklen_t clientaddr_len;
            int connfd = accept(sockfd, (struct sockaddr*)&clientaddr, &clientaddr_len);
            addfd(epollfd, connfd, false);//對connfd禁用et模式
        }
        else if(events[i].events & EPOLLIN){//如果不是監聽套接字,則是連線套接字,如果可讀則讀取資料,LT模式
            printf("event trigger once\n");
            memset(buf, 0, BUFFER_SIZE);
            int ret = recv(sockfd, buf, BUFFER_SIZE-1, 0);
            if(ret <= 0){
                close(sockfd);
                continue;
            }
            printf("get %d bytes data: %s\n", ret, buf);
        }
        else{
            printf("其它情況....\n");
        }
    }
}

//ET模式的工作流程
void et(struct epoll_event * events, int number, int epollfd, int listenfd)
{
    char buf[BUFFER_SIZE];
    int i;
    for(i = 0; i < number; ++i){
        int sockfd = events[i].data.fd;
        if(sockfd == listenfd){//如果描述符是監聽套接字
            struct sockaddr_in clientaddr;
            socklen_t clientaddr_len;
            int connfd = accept(sockfd, (struct sockaddr*)&clientaddr, &clientaddr_len);
            addfd(epollfd, connfd, true);//對connfd開啟et模式
        }
        else if(events[i].events & EPOLLIN){//如果不是監聽套接字,則是連線套接字,如果可讀則讀取資料,ET模式,所以要重複的讀完
            printf("event trigger once");
            while(1){
                memset(buf, 0, BUFFER_SIZE);
                int ret = recv(sockfd, buf, BUFFER_SIZE-1, 0);
                if(ret < 0){
                    if((errno==EAGAIN) || (errno==EWOULDBLOCK)){//此時表示讀取完畢
                        printf("read later, 此時讀取完畢\n");
                        break;
                    }
                    close(sockfd);
                    break;   
                }
                else if(ret == 0){
                    close(sockfd);
                }
                else{
                    printf("\nget %d bytes data: %s", ret, buf);
                }
            }
        }
        else{
            printf("其它情況....\n");
        }
    }
}



int main(int argc, char *argv[])
{
    if(argc <= 3){
        printf("input: %s ip_addr port_num et or lt", argv[0]);
        return -1;
    }

    const char * ip = argv[1];
    int port = atoi(argv[2]);

    int ret = 0;
    struct sockaddr_in servaddr;
    bzero(&servaddr, sizeof(servaddr));

    servaddr.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &servaddr.sin_addr);
    servaddr.sin_port = htons(port);

    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if(listenfd < 0){
        printf("socket error\n");
        return -1;
    }

    ret = bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
    if(ret < 0){
        printf("bind error\n");
        close(listenfd);
        return -1;
    }

    ret = listen(listenfd, 5);
    if(ret < 0){
        printf("listen error\n");
        close(listenfd);
        return -1;
    }

    struct epoll_event events[MAX_EVENT_NUMBER];
    int epollfd = epoll_create(5);
    if(epollfd < 0){
        printf("epoll_create error\n");
        close(listenfd);
    }

    addfd(epollfd, listenfd, true);//true表示開啟ET模式

    while(1){
        int ret = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);
        if(ret < 0){
            printf("epoll_wait error\n");
            break;
        }
        if(strncmp(argv[3], "lt", 2) == 0)
            lt(events, ret, epollfd, listenfd);
        else if(strncmp(argv[3], "et", 2) == 0)
            et(events, ret, epollfd, listenfd);
    }
    close(listenfd);

    return 0;
}

客戶端原始碼:

/*************************************************************************
	> File Name: 53backlog.c
	> Author: 
	> Mail: 
	> Created Time: 2016年01月16日 星期六 11時35分15秒
 ************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <string.h>
#include <assert.h>
#include <stdbool.h>//包含bool型別,C語言是沒有bool型別的,C++才有
#include <unistd.h>
#include <errno.h>

static bool stop = false;

static void handle_term(int sig)
{
    stop = true;
}

int main(int argc, char *argv[])
{
    signal(SIGTERM, handle_term);
    if(argc <= 2){
        printf("usage: %s ip_address port_number backlog\n", argv[0]);
        return 1;
    }

    const char * ip = argv[1];
    int port = atoi(argv[2]);
    int backlog = 5;//atoi(argv[3]);

    int sock = socket(AF_INET, SOCK_STREAM, 0);
    assert(sock >= 0);

    //建立ipv4的地址
    struct sockaddr_in address;
    bzero(&address, sizeof(address));
    address.sin_family = AF_INET;
    address.sin_port = htons(port);
    inet_pton(AF_INET, ip, (void *)&address.sin_addr);

    int ret = connect(sock, (struct sockaddr *)&address, sizeof(address));
    if(ret < 0)
        printf("connect failed.\n");
    else{
        char buf[1024];
        int num = 0;
        
        while(1){
            num = read(STDIN_FILENO, buf, 1024);
            if(num <= 0){
                break;
            }
            write(sock, buf, num);
        }
    }
    
    printf("send over...,按任意鍵退出\n");
    getchar();
    close(sock);

    return 0;
}

以LT模式啟動伺服器:

在一個終端輸入啟動伺服器:./epoll_server 192.168.56.101 12345 lt   //以LT模式啟動該伺服器
在另一個終端輸入啟動客戶端: ./epoll_client 192.168.56.101 12345      //啟動客戶端
可以啟動多個客戶端連線到該伺服器上。

客戶端輸入:
linux
linuxeverreadwrite
haha

伺服器輸出:
event trigger once
get 6 bytes data: linux

event trigger once
get 9 bytes data: linuxever
event trigger once
get 9 bytes data: readwrite
event trigger once
get 1 bytes data: 

event trigger once
get 5 bytes data: haha

可以發現第二次的資料比較多,一次不能讀完,然後觸發了該讀事件3次。

以ET模式啟動伺服器:

在一個終端輸入啟動伺服器:./epoll_server 192.168.56.101 12345 et  //以ET模式啟動該伺服器
在另一個終端輸入啟動客戶端: ./epoll_client 192.168.56.101 12345      //啟動客戶端
可以啟動多個客戶端連線到該伺服器上。

客戶端輸入:

linux
linuxeverreadwrite
haha

伺服器輸出:

event trigger once
get 6 bytes data: linux
read later, 此時讀取完畢

event trigger once
get 9 bytes data: linuxever
get 9 bytes data: readwrite
get 1 bytes data: 
read later, 此時讀取完畢

event trigger once
get 5 bytes data: haha
read later, 此時讀取完畢


EPOLL使用EPOLLONESHOT事件:

伺服器中對每個連線套接字可讀就緒的時候建立新的工作執行緒去處理資料。如果在該執行緒讀取完畢,處理資料過程中,如果該套接字上又有資料輸入了,如果沒有註冊epolloneshot事件則還會建立新的執行緒去處理資料,那麼就會有多個執行緒同時處理一個連線套接字,這樣會出現錯誤的。所以在連線套接字上註冊了epolloneshot事件,那麼只能有一個執行緒在處理該套接字。當該執行緒處理完資料之後,重新註冊該事件,再次檢測到epollin事件的時候再建立一個執行緒去處理資料。

伺服器原始碼:

/*************************************************************************
	> File Name: epoll_server.c
	> Author: 
	> Mail: 
	> Created Time: 2016年02月27日 星期六 16時17分58秒
 ************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <stdbool.h>
#include <fcntl.h>
#include <errno.h>
#include <pthread.h>

#define MAX_EVENT_NUMBER 1024
#define BUFFER_SIZE 1024

struct fds
{
    int epollfd;
    int sockfd;
};

//設定檔案描述符為非阻塞
int setnonblocking(int fd)
{
    int old_option = fcntl(fd, F_GETFL);
    int new_option = old_option | O_NONBLOCK;
    fcntl(fd, F_SETFL, new_option);
    return old_option;
}

//將檔案描述符fd新增到核心事件表中
void addfd(int epollfd, int fd, bool oneshot)
{
    struct epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN | EPOLLET;
    if(oneshot)
        event.events |= EPOLLONESHOT;

    epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
    setnonblocking(fd);
}

void reset_oneshot(int epollfd, int fd)
{
    struct epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN | EPOLLET | EPOLLONESHOT;
    epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &event);
}

//處理連線套接字上的資料
void* worker(void* arg)
{
    int sockfd = ((struct fds*)arg)->sockfd;
    int epollfd = ((struct fds*)arg)->epollfd;

    printf("####start new thread to receive data on fd: %d\n", sockfd);

    char buf[BUFFER_SIZE];
    memset(buf, 0, BUFFER_SIZE);

    int ret;
    while(1){
        ret = recv(sockfd, buf, BUFFER_SIZE-1, 0);
        if(ret == 0){
            close(sockfd);
            printf("對端關閉了連線\n");
            break;
        }
        else if(ret < 0){
            if(errno == EAGAIN){
                reset_oneshot(epollfd, sockfd);
               //printf("read later\n");
                break;
            }
        }
        else{
            printf("get %d bytes data: %s\n", ret, buf);
            sleep(4);//模擬此時是正在處理資料 
        }
        memset(buf, 0, BUFFER_SIZE);
    }
    printf("####end new thread receiving data on fd: %d\n", sockfd);
}


int main(int argc, char *argv[])
{
    int i;

    if(argc <= 2){
        printf("input: %s ip_addr port_num\n", argv[0]);
        return -1;
    }

    const char * ip = argv[1];
    int port = atoi(argv[2]);

    int ret = 0;
    struct sockaddr_in servaddr;
    bzero(&servaddr, sizeof(servaddr));

    servaddr.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &servaddr.sin_addr);
    servaddr.sin_port = htons(port);

    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if(listenfd < 0){
        printf("socket error\n");
        return -1;
    }

    ret = bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
    if(ret < 0){
        printf("bind error\n");
        close(listenfd);
        return -1;
    }

    ret = listen(listenfd, 5);
    if(ret < 0){
        printf("listen error\n");
        close(listenfd);
        return -1;
    }

    struct epoll_event events[MAX_EVENT_NUMBER];
    int epollfd = epoll_create(5);
    if(epollfd < 0){
        printf("epoll_create error\n");
        close(listenfd);
    }

    addfd(epollfd, listenfd, false);

    while(1){
        int ret = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);
        if(ret < 0){
            printf("epoll_wait error\n");
            break;
        }
        for(i = 0; i < ret; ++i){
            int sockfd = events[i].data.fd;
            if(sockfd == listenfd){
                struct sockaddr_in client_address;
                socklen_t client_addresslen = sizeof(client_address);
                int connfd = accept(sockfd, (struct sockaddr*)&client_address, &client_addresslen);
                //對連線套接字註冊oneshot事件
                addfd(epollfd, connfd, true);
            }
            else if(events[i].events & EPOLLIN){
                pthread_t thread;
                struct fds fds_for_new_worker;
                fds_for_new_worker.sockfd = sockfd;
                fds_for_new_worker.epollfd = epollfd;
                //啟動新的程式為連線socket服務
                pthread_create(&thread, NULL, worker, (void*)&fds_for_new_worker);
            }
            else{
                printf("發生了其它情況\n");
            }
        }
    }
    close(listenfd);

    return 0;
}


客戶端原始碼同上一樣

客戶端輸入:

linux
lin
lin
lin
linsadfasdf(輸入之後等待超過4秒)
linuxever  //此時會啟動新的工作執行緒
liin

伺服器輸出:

####start new thread to receive data on fd: 5
get 6 bytes data: linux

get 4 bytes data: lin

get 4 bytes data: lin

get 4 bytes data: lin

get 12 bytes data: linsadfasdf

####end new thread receiving data on fd: 5
####start new thread to receive data on fd: 5
get 10 bytes data: linuxever

get 5 bytes data: liin

####end new thread receiving data on fd: 5


參考文章:

http://www.cnblogs.com/Anker/p/3265058.html

http://www.cnblogs.com/Anker/archive/2013/08/14/3258674.html

http://www.cnblogs.com/Anker/archive/2013/08/15/3261006.html

http://www.cnblogs.com/Anker/archive/2013/08/17/3263780.html

高併發網路程式設計之epoll詳解

epoll機制:epoll_create、epoll_ctl、epoll_wait、close

Linux Epoll介紹和程式例項

linux epoll機制

相關文章