UNIX網絡編程:I/O復用技術(select、poll、epoll)

http://blog.csdn.net/dandelion_gong/article/details/51673085

Unix下可用的I/O模型一共有五種:阻塞I/O 、非阻塞I/O 、I/O復用 、信號驅動I/O 、異步I/O。此處我們主要介紹第三種I/O符復用。?
I/O復用的功能:如果一個或多個I/O條件滿足(輸入已準備好讀,或者描述字可以承接更多輸出)時,我們就被通知到。這就是有select、poll、epoll實現。

I/O復用應用場合:?
1、當客戶處理多個描述字時(一般是交互式輸入和網絡套接口),必須使用I/O復用。在這前一段中已做描述。?
2、一個客戶同時處理多個套接口是可能的,但很少出現。?
3、如果一個TCP服務器機要處理監聽套接口,有要處理已連接套接口,一般也要用到I/O復用。?
4、如果一個服務器機要處理TCP,有要處理UDP,一般也要使用I/O復用。?
5、如果一個服務器要處理多個服務或者多個協議,一般要使用I/O復用。

I/O復用原理圖:?
這里寫圖片描述

select:?
使用Select就可以完成非阻塞(所謂非阻塞方式non-block,就是進程或線程執行此函數時不必非要等待事件的發生,一旦執行肯定返回,以返回值的不同來反映函數的執行情況,如果事件發生則與阻塞方式相 同,若事件沒有發生則返回一個代碼來告知事件未發生,而進程或線程繼續執行,所以效率較高)方式工作的程序,它能夠監視我們需要監視的文件描述符的變化情 況——讀寫或是異常。

所要用到的結構體:?
struct timeval{?
long tv_sec; //等待的秒數?
long tv_usec; //等待的微秒數?
}

select()函數:?
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);?
作用:用來檢測描述符中是否有準備好讀、寫、或異常的描述符?
參數1(nfds):被測試的描述字個數;它的值為要被測試的最大描述符個 數+1,而描述字0,1,2,……..,nfds-1;?
參數2—4 (readfds)、(writefds)、(exceptfds):這三個參數指定我們要讓內核測試讀、寫、異常條件所需的描述字。當我們在調用該函數,指定好我們所要檢測的描述字集后,如果檢測三種情況下任何一中情況準備好,則將相應的狀態變為可用狀態。如果到達函數返回時沒有可讀可寫則返回失敗。如果我們不關心其中哪個狀態,可將其設為NULL。?
參數5(timeout):指定等待時間,有三種情況:?
(1)、永遠等待下去(參數timeout設置為空指針):僅在有一個描述字準備好I/O時才返回。?
(2)、等待固定時間(指定timeval中的秒數和微秒數):在不超過timeval結構體中所指定的秒數和微秒數內檢測到有一個描述字準備好I/O時返回?
(3)、根本不等待(timeval中秒數和微秒數均設置為0):檢查描述字后立即返回。

select工作原理:?
select就是巧妙的利用等待隊列機制讓用戶進程適當在沒有資源可讀/寫時睡眠,有資源可讀/寫時喚醒。下面我們看看select睡眠的詳細過程。

select會循環遍歷它所監測的fd_set(一組文件描述符(fd)的集合)內的所有文件描述符對應的驅動程序的poll函數。驅動程序提供的poll函數首先會將調用select的用戶進程插入到該設備驅動對應資源的等待隊列(如讀/寫等待隊列),然后返回一個bitmask告訴select當前資源哪些可用。當select循環遍歷完所有fd_set內指定的文件描述符對應的poll函數后,如果沒有一個資源可用(即沒有一個文件可供操作),則select讓該進程睡眠,一直等到有資源可用為止,進程被喚醒(或者timeout)繼續往下執行。

select調用過程:?
這里寫圖片描述

頭文件:下面poll、epoll的頭文件與該文件相同

#include<unistd.h>
#include<stdio.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/select.h>
#include<poll.h>
#include<sys/epoll.h>
#include<sys/types.h>#define IPADDR  "192.168.3.169"
#define PORT    8787
#define MAXLINE 1024
#define LISTENQ 5//select
#define SIZE 10
//poll
#define OPEN_SIZE 10
//epoll
#define FDSIZE 100
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

服務器端:

#include"../unp.h"
#include<malloc.h>typedef struct server_context_st   //服務器描述表
{int cli_cnt;                   //客戶端連接個數int clifds[SIZE];              //描述字集合fd_set allfds;                 //設置所有的描述字int maxfd;                     //最大描述字個數
}server_context_st;static server_context_st *s_srv_ctx = NULL;         int server_init()                          //服務器初始化函數
{s_srv_ctx = (server_context_st*)malloc(sizeof(server_context_st));          //申請一個服務器描述表if(s_srv_ctx == NULL)return -1;memset(s_srv_ctx, 0, sizeof(server_context_st));                            //將該描述表清0for(int i=0; i<SIZE; ++i)                                                   //將該表中的每一位設為-1{s_srv_ctx->clifds[i] = -1;}return 0;
}
void server_uninit()                      //服務器去初始化函數
{if(s_srv_ctx)                         //如果服務器描述表不為0,即該表申請成功存在{free(s_srv_ctx);                  //釋放該表的內存s_srv_ctx = NULL;                 //將指針值為NULL。}
}int create_server_proc(const char *ip, short port)     //創建服務器進程
{int fd;                                          fd = socket(AF_INET, SOCK_STREAM, 0);              //建立一個套接字,記錄返回的描述字,if(fd == -1)                                       //檢測是否創建成功{perror("socket");return -1;}//初始化服務器信息結構體struct sockaddr_in addrSer;addrSer.sin_family = AF_INET;                         //指定用到的協議族addrSer.sin_port = htons(port);                       //指定服務器端口號addrSer.sin_addr.s_addr = inet_addr(ip);              //指定服務器ip地址socklen_t addrlen = sizeof(struct sockaddr);int res = bind(fd, (struct sockaddr*)&addrSer, addrlen);     //將創建的描述字與剛才所設置的服務器信息綁定if(res == -1)                                                //檢測是否綁定成功嗯{perror("bind");return -1;}listen(fd, LISTENQ);       //監聽是否有客戶端請求連接,如果有則將該套接字設為可用return fd;
}int accept_client_proc(int srvfd)             //結束客戶端連接請求
{struct sockaddr_in addrCli;socklen_t addrlen = sizeof(struct sockaddr);int clifd;
ACCEPT:clifd = accept(srvfd, (struct sockaddr*)&addrCli, &addrlen);      //結束客戶端的連接請求if(clifd == -1)                    //判斷是否連接成功{goto ACCEPT;                   //如果沒有連接成功,則跳轉至ACCEPT處繼續連接}printf("accept a new client: %s:%d\n",inet_ntoa(addrCli.sin_addr),addrCli.sin_port);int i;for(i=0; i<SIZE; ++i)                     //循環遍歷描述字{if(s_srv_ctx->clifds[i] == -1)        //如果描述字為-1,表明只連接了i個客戶端(0 —— i-1){s_srv_ctx->clifds[i] = clifd;     //則將連接描述字賦給服務器描述表中第i個描述字s_srv_ctx->cli_cnt++;             //已連接的客戶端數量加一break;}}if(i == SIZE)                             //如果i等于SIZE,說明描述字集合已滿{printf("Server Over Load.\n");        return -1;}
}void handle_client_msg(int fd, char *buf)     //處理接收到的客戶端信息函數
{printf("recv buf is:> %s\n",buf);         //服務器將接收到的來自客戶端的信息打印出來send(fd, buf, strlen(buf)+1, 0);          //向客戶端發送信息
}void recv_client_msg(fd_set *readfds)        //接收客戶端信息
{int clifd; char buffer[256];int n;for(int i=0; i<s_srv_ctx->cli_cnt; ++i)        //輪尋查找(從描述字集合的第一個描述字開始到最后一個已連接的客戶端){clifd = s_srv_ctx->clifds[i];              if(clifd < 0)                              //如果套接字小于0,則失敗continue;if(FD_ISSET(clifd, readfds))               //將該描述字設置為可讀{n = recv(clifd, buffer, 256, 0);        //接收來自客戶端的信息if(n <= 0)                              //如果返回址小于等于0則接收失敗,說明客戶端已斷開連接{FD_CLR(clifd, &s_srv_ctx->allfds);  //將描述字清0close(clifd);                       //關閉這個描述字s_srv_ctx->clifds[i] = -1;          //將第i個描述字設為-1(i為已連接的客戶端個數-1)s_srv_ctx->cli_cnt--;               //將以連接的客戶端個數減一continue;}handle_client_msg(clifd, buffer);       //調用處理客戶端信息函數,來處理即接收到的信息}}
}int handle_client_proc(int srvfd)                //客戶端處理程序
{int clifd = -1;                              //將客戶端描述字設置為-1int retval = 0;fd_set *readfds = &s_srv_ctx->allfds;        //讓可讀指針指向描述字struct timeval tv;                           while(1){FD_ZERO(readfds);                        //清除可讀描述字指針FD_SET(srvfd, readfds);                  //用srvfd設置readfdss_srv_ctx->maxfd = srvfd;                //設置最大描述符個數tv.tv_sec = 30;                          //設置時間結構體中的秒與微秒tv.tv_usec = 0;int i;for(i=0; i<s_srv_ctx->cli_cnt; ++i)          //遍歷已經連接的客戶端{clifd = s_srv_ctx->clifds[i];           FD_SET(clifd, readfds);                  //將他們的描述字設置為可讀s_srv_ctx->maxfd = (clifd > s_srv_ctx->maxfd ? clifd : s_srv_ctx->maxfd);  //選取兩個中較大的一個作為最大描述字個數}retval = select(s_srv_ctx->maxfd+1, readfds, NULL, NULL, &tv);      //檢測是否有準備好的I/O接口if(retval == -1)              //如果沒有則檢測失敗{perror("select");return -1;}if(retval == 0)                    //如果返回址為0則檢測超時{printf("server time out.\n");continue;}//acceptif(FD_ISSET(srvfd, readfds))  //如果該位是作為描述字的{accept_client_proc(srvfd);   //接收客戶端的連接}else                            //如果是作為可讀指針的{recv_client_msg(readfds);     //則接收客戶端信息}}
}int main(int argc, char *argv[])
{int sockSer;if(server_init() < 0)                            //檢測服務器描述表是否初始化失敗perror("server_init");sockSer = create_server_proc(IPADDR, PORT);      //創建一個服務器進程if(sockSer < 0)                                  //檢測服務器服務是否創建失敗{perror("create_server_porc");goto err;}handle_client_proc(sockSer);                     //處理客戶端return 0;
err:server_uninit();                                //去初始化服務器描述表return -1;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189

客戶端:

#include"../unp.h"void handle_connection(int sockfd)              //連接處理函數
{fd_set readfds;            int maxfd = sockfd;                         //描述字最大個數struct timeval tv;while(1){FD_ZERO(&readfds);                     //將可讀描述字空間清0FD_SET(sockfd, &readfds);              //用套接字設置可讀描述字空間maxfd = sockfd;                        //設置最大描述字個數tv.tv_sec = 5;                         //設置時間結構體中的秒數tv.tv_usec = 0;                        //設置時間結構體中的微秒數int res = select(maxfd+1, &readfds, NULL, NULL, &tv);   //等待可讀的描述字if(res == -1)                       //如果函數返回時沒有可讀I/O準備好{perror("select");               //則檢測失敗return;}if(res == 0)                        //如果返回值為0{printf("Client time out.\n");   //則表明檢測超時continue;}int n;char recvbuf[256];if(FD_ISSET(sockfd, &readfds))             {n = recv(sockfd,recvbuf, 256, 0);      //接收來自服務器的信息if(n <= 0)                             //如果返回的讀取長度小于0,則表明服務器已經關閉{printf("Server is closed.\n");       close(sockfd);                     //關閉套接字描述符FD_CLR(sockfd, &readfds);          //清除描述字可讀標識為return;}printf("client recv slef msg:> %s\n",recvbuf);    //打印客戶端接收到的信息sleep(3);send(sockfd, recvbuf, strlen(recvbuf)+1, 0);      //發送信息}}
}int main()
{int sockCli;sockCli = socket(AF_INET, SOCK_STREAM, 0);     //創建一個套接字struct sockaddr_in addrSer;                   addrSer.sin_family = AF_INET;                  //設置通信所用到的協議族addrSer.sin_port = htons(PORT);                //指定通信端口號addrSer.sin_addr.s_addr = inet_addr(IPADDR);   //指定ip地址socklen_t addrlen = sizeof(struct sockaddr);int res = connect(sockCli, (struct sockaddr*)&addrSer, addrlen);     //連接服務器已客戶端if(res < 0)                                                       //連接失敗perror("connect");printf("Client connect Server Ok.\n");send(sockCli,"hello Server.",strlen("hello Server.")+1, 0);         //給服務器發送數據,此處為了方便用固定值handle_connection(sockCli);                         //處理連接return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63

運行結果:

select幾點不足:?
(1)每次調用select,都需要把fd集合從用戶態拷貝到內核態,這個開銷在fd很多時會很大?
(2)同時每次調用select都需要在內核遍歷傳遞進來的所有fd,這個開銷在fd很多時也很大?
(3)select支持的文件描述符數量太小了,默認是1024

poll:?
poll的機制與select類似,與select在本質上沒有多大差別,管理多個描述符也是進行輪詢,根據描述符的狀態進行處理,但是poll沒有最大文件描述符數量的限制。poll和select同樣存在一個缺點就是,包含大量文件描述符的數組被整體復制于用戶態和內核的地址空間之間,而不論這些文件描述符是否就緒,它的開銷隨著文件描述符數量的增加而線性增大。?
使用poll()和select()不一樣,你不需要顯式地請求異常情況報告。?
  POLLIN | POLLPRI等價于select()的讀事件,POLLOUT |POLLWRBAND等價于select()的寫事件。POLLIN等價于POLLRDNORM |POLLRDBAND,而POLLOUT則等價于POLLWRNORM。例如,要同時監視一個文件描述符是否可讀和可寫,我們可以設置 events為POLLIN |POLLOUT。在poll返回時,我們可以檢查revents中的標志,對應于文件描述符請求的events結構體。如果POLLIN事件被設置,則文件描述符可以被讀取而不阻塞。如果POLLOUT被設置,則文件描述符可以寫入而不導致阻塞。這些標志并不是互斥的:它們可能被同時設置,表示這個文件描述符的讀取和寫入操作都會正常返回而不阻塞。?
  timeout參數指定等待的毫秒數,無論I/O是否準備好,poll都會返回。timeout指定為負數值表示無限超時,使poll()一直掛起直到一個指定事件發生;timeout為0指示poll調用立即返回并列出準備好I/O的文件描述符,但并不等待其它的事件。這種情況下,poll()就像它的名字那樣,一旦選舉出來,立即返回。

pollfd結構體:?
struct pollfd{?
int fd; //文件描述符?
short events; //等待的事件?
short revents; //實際發生了的事件?
}

poll函數:?
int poll(struct pollfd *fds, nfds_t nfds, int timeout);?
作用:返回準備好的描述字的個數。?
參數1:struct pollfd結構體用來指定一個被監聽的文件描述符?
參數2:nfds_t類型的參數,用于標記數組fds中的結構體元素的總數量;?
參數3:是poll函數調用阻塞的時間,單位:毫秒;

用poll實現客戶端與服務器之間的通信:(頭文件與select相同)?
客戶端程序:

#include "../unp.h"void handle_connection(int sockfd)        //連接處理函數
{pollfd fds[2];fds[0].fd = sockfd;                   //讓第一個描述字設為創建套接字的描述符fds[0].events = POLLIN;               //把第一個描述字設置為普通或優先級帶數據可讀fds[1].fd = STDIN_FILENO;             //把第二個檢驗描述字設置為標準文件輸入fds[1].events = POLLIN;               //把第二個描述字事件標志設置為普通或優先級帶數據可讀int n;char buf[256];for(; ;){                            poll(fds, 2, -1);                      //無限等待,因為POSIX標準里并沒有INFTIM,所以用-1          if(fds[0].revents & POLLIN){           //如果fd[0]中事件已經發生并且為普通或優先級帶數據可讀,表明客戶端可讀n = recv(sockfd, buf, 256, 0);     //則接收來自服務器哦的數據if(n <= 0){                        //如果接收到的數據長度小于0,則表明服務器已經關閉printf("Server is Closed.\n");close(sockfd);}write(STDOUT_FILENO, buf, n);      //標準輸出,打印出接收大的來自服務器的信息}if(fds[1].revents & POLLIN){           //如果fd[0]中事件已經發生并且為標準輸入,表明客戶端可寫n = read(STDIN_FILENO, buf, 256);  //讀取鍵盤輸入的內容if(n == 0){                        //如果沒有地到輸入的內容,則繼續continue;}write(sockfd, buf, n);             //將從鍵盤獲得的數據寫入套接字描述符中}}
}int main()
{int sockCli;sockCli = socket(AF_INET, SOCK_STREAM, 0);     //創建一個套接字struct sockaddr_in addrSer;addrSer.sin_family = AF_INET;                  //規定通信協議族addrSer.sin_port = htons(PORT);                //設置所用端口號addrSer.sin_addr.s_addr = inet_addr(IPADDR);   //設置服務器ipconnect(sockCli, (struct sockaddr*)&addrSer, sizeof(struct sockaddr));       //創建連接handle_connection(sockCli);        //調用處理客戶端連接函數return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

服務器端程序:

#include "../unp.h"
#include <stdlib.h>int sock_bind(const char *ip, short port)            //綁定處理函數
{int fd;fd = socket(AF_INET, SOCK_STREAM, 0);            //創建一個套接字struct sockaddr_in addrSer;addrSer.sin_family = AF_INET;                    //設置所用到的協議族addrSer.sin_port = htons(port);                  //規定所用到的端口號addrSer.sin_addr.s_addr = inet_addr(ip);         //設置ip號socklen_t addrlen = sizeof(struct sockaddr);    bind(fd, (struct sockaddr*)&addrSer, addrlen);    //綁定套接字描述符和地址結構體信息return fd;
}void handle_connection(struct pollfd *connfds, int num)     //連接處理函數
{int n;char buf[256];for(int i = 1; i <= num; ++i){                        //輪尋if(connfds[i].fd == -1){                          //如果fd額日-1,則繼續執行continue;}if(connfds[i].revents & POLLIN){                  //如果該描述字有事件發生并且時普通或優先級數據可讀n = recv(connfds[i].fd, buf, 256, 0);         //則接收來自客戶端的數據if(n <= 0){                                   //如果接收到的數據常速小于0,說明客戶端退出close(connfds[i].fd);                     //關閉連接描述符connfds[i].fd = -1;                       continue;}printf("recv msg:>%s\n", buf);                //打印接收到的來自客戶端的信息send(connfds[i].fd, buf, n, 0);               //發送服務器的信息}}
}void do_poll(int sockSer)
{pollfd clientfds[OPEN_SIZE];                  //定義一個存放描述字的數組clientfds[0].fd = sockSer;                    //讓fd[0]的描述字設置為創建的套接字描述符clientfds[0].events = POLLIN;                 //將其事件設置為普通或優先級帶數據for(int i = 1; i < OPEN_SIZE; ++i){          //將數組內所有的描述字設置為-1clientfds[i].fd = -1;}int maxi = 0;int nready;struct sockaddr_in addrCli;              socklen_t addrlen = sizeof(struct sockaddr);int i;   for(; ;){nready = poll(clientfds, maxi+1, -1);       //等待準備好的描述字if(nready == -1){                           //如果返回值為-1則表明查找失敗perror("poll");exit(1);}if(clientfds[0].revents & POLLIN){          //如果clientfd[0]有事件發生,并且為POLLIN,則接收連接int sockConn = accept(sockSer, (struct sockaddr*)&addrCli, &addrlen);  //接收客戶端連接請求if(sockConn == -1){         //檢測返回值,判斷是否連接成功perror("accept");continue;}//打印連接信息printf("accept a new client:%s:%d\n", inet_ntoa(addrCli.sin_addr), addrCli.sin_port);for(i = 1; i < OPEN_SIZE; ++i){       //找到還沒有標志連接的描述字,將連接返回的描述符給他if(clientfds[i].fd < 0){clientfds[i].fd = sockConn;break;}}if(i == OPEN_SIZE){                   //如果數組內所有描述字都標志連接,則說明連接的客戶端數量已夠printf("Server Over Load.\n");continue;}clientfds[i].events = POLLIN;         //將該描述字的事件設為POLLINmaxi = (i > maxi ? i : maxi);         //如果此時連接數已經超過描述字的最大個數,則更改最大值,否則不變if(--nready <= 0){continue;}}handle_connection(clientfds, maxi);   //調用連接處理函數}
}int main()
{int sockSer;sockSer = sock_bind(IPADDR, PORT);       //服務器信息的綁定listen(sockSer, LISTENQ);                //監聽等待隊列有沒有客戶端申請連接do_poll(sockSer);                        //do_epoll函數,來處理信息傳遞return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100

epoll:?
epoll對文件描述符的操作有兩種模式:LT(level trigger)和ET(edge trigger)。LT模式是默認模式,LT模式與ET模式的區別如下:?
  LT模式:當epoll_wait檢測到描述符事件發生并將此事件通知應用程序,應用程序可以不立即處理該事件。下次調用epoll_wait時,會再次響應應用程序并通知此事件。?
  ET模式:當epoll_wait檢測到描述符事件發生并將此事件通知應用程序,應用程序必須立即處理該事件。如果不處理,下次調用epoll_wait時,不會再次響應應用程序并通知此事件。 

首先,通過epoll_create(int maxfds)來創建一個epoll的句柄,其中maxfds為你epoll所支持的最大句柄數。這個函數會返回一個新的epoll句柄,之后的所有操作將通過這個句柄來進行操作。在用完之后,記得用close()來關閉這個創建出來的epoll句柄。

然后,在你的網絡主循環里面,每一幀的調用epoll_wait(int epfd, epoll_event* events, int max events, int timeout)來查詢所有的網絡接口,看哪一個可以讀,哪一個可以寫了。基本的語法為:nfds = epoll_wait(kdpfd, events, maxevents, -1);?
其中kdpfd為用epoll_create創建之后的句柄,events是一個epoll_event*的指針,當epoll_wait這個函數操作成功之后, events里面將儲存所有的讀寫事件。max_events是當前需要監聽的所有socket句柄數。最后一個timeout是 epoll_wait的超時,為0的時候表示馬上返回,為-1的時候表示一直等下去,直到有事件范圍,為任意正整數的時候表示等這么長的時間,如果一直沒有事件,則返回。一般如果網絡主循環是單獨的線程的話,可以用-1來等,這樣可以保證一些效率,如果是和主邏輯在同一個線程的話,則可以用0來保證主循環的效率。

events可以是以下幾個宏的集合:?
EPOLLIN :表示對應的文件描述符可以讀(包括對端SOCKET正常關閉);?
EPOLLOUT:表示對應的文件描述符可以寫;?
EPOLLPRI:表示對應的文件描述符有緊急的數據可讀(這里應該表示有帶外數據到來);?
EPOLLERR:表示對應的文件描述符發生錯誤;?
EPOLLHUP:表示對應的文件描述符被掛斷;?
EPOLLET: 將EPOLL設為邊緣觸發(Edge Triggered)模式,這是相對于水平觸發(Level Triggered)來說的。?
EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之后,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL隊列里。

用epoll代替select/poll實現代碼:?
服務器端:

#include "../unp.h"
#include "utili.h"
#include <stdlib.h>int sock_bind(const char *ip, short port)          //綁定函數
{int fd;fd = socket(AF_INET, SOCK_STREAM, 0);          //創建一個套接字struct sockaddr_in addrSer;addrSer.sin_family = AF_INET;                  //設定所用的協議族addrSer.sin_port  = htons(port);               //設定所用到的端口號addrSer.sin_addr.s_addr = inet_addr(ip);       //設定服務器ipsocklen_t addrlen = sizeof(struct sockaddr);return fd;
}void handle_accept(int epollfd, int listenfd)      //結束連接函數
{struct sockaddr_in addrCli;int sockConn;socklen_t addrlen = sizeof(struct sockaddr);  sockConn = accept(listenfd, (struct sockaddr*) &addrCli, &addrlen);   //服務器接受客戶端的連接請求if(sockConn == -1){           //判斷是否接受成功perror("accept");}else{printf("accept a new client:%s:%d\n", inet_ntoa(addrCli.sin_addr), addrCli.sin_port);add_event(epollfd, sockConn, EPOLLIN);     //增加事件}
}void do_read(int epollfd, int fd, char *buf)          //讀數據函數
{int nread = read(fd, buf, 256);                   //讀數據if(nread <= 0){                                   //如果獨到的數據長度小于0,則表明服務器關閉printf("Server is Closed.\n");close(fd);delete_event(epollfd, fd, EPOLLIN);           //刪除剛才添加的事件}printf("recv msg:>%s\n", buf);                    //如果接受成功,則打印出所接受到的內容modify_event(epollfd, fd, EPOLLOUT);              //設置事件列表為輸出
}void do_write(int epollfd, int fd, char *buf)         //寫數據函數
{int nwrite = write(fd, buf, strlen(buf)+1);       //向緩存區中寫入數據if(nwrite <= 0){                                  //如果寫入數據長度小于0,說明客戶端關閉printf("client is closed.\n");close(fd);delete_event(epollfd, fd, EPOLLOUT);          //刪除所添加的事件}else{modify_event(epollfd, fd, EPOLLIN);           //如果寫入成功則設置事件列表為輸入}
}void handle_events(int epollfd, epoll_event *events, int num, int listenfd, char *buf)   //事件處理函數
{int fd;for(int i = 0; i < num; ++i){        //將所有描述字遍歷一遍fd = events[i].data.fd;          if((fd == listenfd) && (events[i].events & EPOLLIN)){    //判斷如果描述字處于監聽狀態,并且有事件準備好并且為EPOLLINhandle_accept(epollfd, listenfd);              //則調用接受連接函數}else if(events[i].events & EPOLLIN){              //如果不處于監聽狀態,并且有事件準備好且為EPOLLINdo_read(epollfd, fd, buf);                     //則調用讀處理函數}else if(events[i].events & EPOLLOUT){             //如果不處于監聽狀態,并且有事件準備好且為EPOLLOUTdo_write(epollfd, fd, buf);                    //則調用寫處理函數}}
}void do_epoll(int listenfd)         
{int epollfd;epoll_event events[1024];                     //事件列表epollfd = epoll_create(FDSIZE);               //創建一個epoll句柄add_event(epollfd, listenfd, EPOLLIN);        //將EPOLLIN添加到事件列表中int res;char buf[256];for(; ;){res = epoll_wait(epollfd, events, 1024, -1);     //等待事件列表中的事件準備好if(res == -1){                                   //判斷是否有事件準備好perror("epoll_wait");exit(1);}handle_events(epollfd, events, res, listenfd, buf);    //調用事件處理函數}close(epollfd);          //關閉描述字
}int main()
{int listenfd;listenfd = sock_bind(IPADDR, PORT);   //調用綁定函數listen(listenfd, LISTENQ);         //監聽是否有客戶端請求連接do_epoll(listenfd);                //調用do_epoll函數return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99

客戶端:

#include "../unp.h"
#include "utili.h"void do_read(int epollfd, int fd, int sockfd, char *buf)   //讀處理函數
{int nread;nread = read(fd, buf, 256);       //讀數據if(nread == -1){                  //如果讀到的數據長度為-1,則顯示讀錯誤信息perror("read");close(fd);                    //關閉描述字}else if(nread == 0){             //如果讀數據返回值為0,則表明服務器關閉printf("Server is close.\n");close(fd);exit(1);}else{if(fd == STDIN_FILENO){        //如果描述字為標準輸入add_event(epollfd, sockfd, EPOLLOUT);   //則在事件列表中增加EPOLLOUT}else{       delete_event(epollfd, fd, EPOLLIN);     //否則刪除事件列表中的事件EPOLLIN}}printf("recv msg:>%s\n", buf);         //打印接收到的信息modify_event(epollfd, fd, EPOLLFD)     //設置事件為EPOLLIN
}void do_write(int epollfd, int fd, int sockfd, char *buf){    //寫處理函數int nwrite;nwrite = write(fd, buf, strlen(buf)+1, 0);      //向緩存區中寫入數據if(nwrite == -1){             //判斷是否寫入失敗perror("write");close(fd);}
}void handle_events(int epollfd, epoll_event *events, int num, int sockfd, char *buf)     //事件處理函數
{int fd;for(int i = 0; i < num; ++i){        //將描述字遍歷一遍fd = events[i].dsts.fd;if(events[i].events & EPOLLIN){      //如果有事件準備好并且為EPOLLIN,do_read(epollfd, fd, sockfd, buf);  //則調用讀操作函數}else if(events[i].events & EPOLLOUT){   //額uguo有事件準備好并且為EPOLLOUTdo_write(epollfd, fd, sockfd, buf);   //則調用寫操作函數}}
}void handle_connection(int sockfd)        //連接處理函數
{char buf[256];int epollfd;epoll_event events[1024];           //事件列表epollfd = epoll_create(FDSIZE);     //創建一個epoll句柄ad_event(epollfd, STDIN_FILENO, EPOLLIN);  //增加事件EPOLLINint res;for(; ;){res = epoll_wait(epollfd, events, 1024, -1);       //等待事件列表中存在的事件準備好handle_events(epollfd, events, res, sockfd, buf);  //調用事件處理函數}close(epollfd);      //關閉描述字
}int main()
{int sockCli;sockCli = socket(AF_INET, SOCK_STREAM, 0);      //創建套接字struct sockaddr_in addrSer;addrSer.sin_family = AF_INET;                   //設置協議族addrSer.sin_port = htons(PORT);                 //設置端口號addrSer.sin_addr.s_addr = inet_addr(IPADDR);    //設置ip號connect(sockCli, (struct sockaddr *)&addrSer, sizeof(struct sockaddr));      //連接服務器與客戶端handle_connection(sockCli);         //調用連接處理函數,進行連接后的相關操作return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78

總結:?
(1)select,poll實現需要自己不斷輪詢所有fd集合,直到設備就緒,期間可能要睡眠和喚醒多次交替。而epoll其實也需要調用epoll_wait不斷輪詢就緒鏈表,期間也可能多次睡眠和喚醒交替,但是它是設備就緒時,調用回調函數,把就緒fd放入就緒鏈表中,并喚醒在epoll_wait中進入睡眠的進程。雖然都要睡眠和交替,但是select和poll在“醒著”的時候要遍歷整個fd集合,而epoll在“醒著”的時候只要判斷一下就緒鏈表是否為空就行了,這節省了大量的CPU時間。這就是回調機制帶來的性能提升。?
(2)select,poll每次調用都要把fd集合從用戶態往內核態拷貝一次,并且要把current往設備等待隊列中掛一次,而epoll只要一次拷貝,而且把current往等待隊列上掛也只掛一次(在epoll_wait的開始,注意這里的等待隊列并不是設備等待隊列,只是一個epoll內部定義的等待隊列)。這也能節省不少的開銷。


本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/384195.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/384195.shtml
英文地址,請注明出處:http://en.pswp.cn/news/384195.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

解決iex -S mix報錯

執行iex -S mix命令的時候會遇到如下錯誤&#xff1a; 執行 mix deps.get 然后就可以運行 iex -S mix了 其中&#xff0c;有可能會出現 按照其網站下載相應文件&#xff0c;復制到項目根目錄下&#xff0c;然后執行命令&#xff08;mix local.rebar rebar ./rebar&#xff09;即…

貪心算法——選擇不相交區間問題

題目描述&#xff1a;設有n個活動的集合&#xff0c;其中每個活動都要求使用同一個資源&#xff0c;而在同一時間內只有一個活動能夠使用這一資源&#xff0c;每個活動i都有一個要求使用該資源的起始時間si和一個結束時間fi(si<fi)&#xff0c;如果選擇了活動i&#xff0c;則…

Anker—工作學習筆記

http://www.cnblogs.com/Anker/archive/2013/08/17/3263780.html 1、基本知識 epoll是在2.6內核中提出的&#xff0c;是之前的select和poll的增強版本。相對于select和poll來說&#xff0c;epoll更加靈活&#xff0c;沒有描述符限制。epoll使用一個文件描述符管理多個描述符&am…

Supervisor監控

可參考&#xff1a;https://www.cnblogs.com/wang_yb/archive/2016/06/08/5564459.html &#xff1a;https://segmentfault.com/a/1190000007379204 轉載于:https://www.cnblogs.com/lr1402585172/p/11551488.html

深度搜索剪枝——數的劃分

【題目描述】將整數n分成k份&#xff0c;且每份不能為空&#xff0c;問有多少種分法&#xff1f; 【輸入格式】兩個整數n,m(6<n<200,2<m<6) 【輸出格式】輸出不同的分法數 【樣例輸入】7 3 【樣例輸出】4 對于這種搜索題&#xff0c;關鍵就在于剪枝&#xff1a;確定…

Linux網絡編程——tcp并發服務器(I/O復用之select

http://blog.csdn.net/lianghe_work/article/details/46519633 與多線程、多進程相比&#xff0c;I/O復用最大的優勢是系統開銷小&#xff0c;系統不需要建立新的進程或者線程&#xff0c;也不必維護這些線程和進程。 代碼示例&#xff1a; [csharp] view plaincopy #include &…

ets

:ets.new(table_name, pattern) 第一個參數是表名&#xff0c;第二個參數是表的設置選項。 :set  一個key&#xff0c;一個數據&#xff0c;無序 :ordered_set  一個key&#xff0c;一個數據&#xff0c;有序&#xff1b; 1 1.0 :bag  一個key&#xff0c;多個數據&…

貪心算法-區間選點問題-種樹

【題目描述】一條街道的一邊有幾座房子。因為環保原因居民想要在路邊種些樹&#xff0c;路邊的地區被分割成n塊&#xff0c;并被編號為1~n。每塊大小為一個單位尺寸且最多可總一棵樹。每個居民想在門前種些樹并制定了三個數b,e,t&#xff0c;這三個數代表居民想在b和e之間最少種…

ets注意事項

當表類型為 :set 時&#xff0c;使用 :ets.first 和 :ets.last 會獲取到同一個 key。將表類型換為 :oedered_set 就可以避免這種情況 轉載于:https://www.cnblogs.com/lr1402585172/p/11599219.html

CodeForces - 1141CPolycarp Restores Permutation搜索+剪枝

Polycarp Restores Permutation 【題意分析】題意大概是給定一個串&#xff0c;包含從1到n所有的數字。但是給定的是相鄰數字的差&#xff0c;需要復原這個串。 大概分析以后發現給定的是一個差分數組&#xff0c;所以只需要枚舉第一個元素就可以確定所有元素的值。 問題是如何…

CodeForces - 1141ESuperhero Battle簡單模擬

Superhero Battle 這道題卡了我一個多小時&#xff0c;最后也沒有做出來&#xff0c;成功稱為吊車尾。。。 思路什么的都沒有問題&#xff0c;主要是&#xff0c;爆long long了&#xff0c;這個太可怕了&#xff0c;就因為一個中間變量忘記開longlong導致一直一直wa&#xff0c…

Linux下的I/O復用與epoll詳解

http://www.cnblogs.com/lojunren/p/3856290.html 前言 I/O多路復用有很多種實現。在linux上&#xff0c;2.4內核前主要是select和poll&#xff0c;自Linux 2.6內核正式引入epoll以來&#xff0c;epoll已經成為了目前實現高性能網絡服務器的必備技術。盡管他們的使用方法不盡相…

校門外的樹——樹狀數組+區間修改

校門外的樹 【題目分析】題目描述的是一種區間修改&#xff0c;看起來好像要用線段樹。但是對于這種區間內部沒有差別并且查詢的是區間內的類別的問題&#xff0c;是可以轉化為樹狀數組進行的。畢竟樹狀數組更加簡單。 我們的關注點應該放在區間的端點處&#xff0c;然后通過統…

數據結構--順序棧和鏈式棧

http://www.cnblogs.com/jingliming/p/4602458.html 棧是一種限定只在表尾進行插入或刪除操作,棧也是線性表表頭稱為棧的底部,表尾稱為棧的頂部,表為空稱為空棧&#xff0c;棧又稱為后進先出的線性表,棧也有兩種表示:順序棧與鏈式棧順序棧是利用一組地址連續的存儲單元&#xf…

CodeForces - 1144F搜索+簡單圖論

【題目鏈接】Graph Without Long Directed Paths 【題目分析】題目想要講一個無向圖變成一個最長路徑不超過1的有向圖。假如某個邊是從u到v的&#xff0c;那么所有和v相連的都必須是指向v的&#xff0c;所有和u相連的都必須是從u開始的。相當于涂色&#xff0c;相連的節點應該涂…

數據結構--雙鏈表的創建和操作

http://www.cnblogs.com/jingliming/p/4602144.html#0-tsina-1-42616-397232819ff9a47a7b7e80a40613cfe1 一、雙向鏈表的定義 雙向鏈表也叫雙鏈表&#xff0c;是鏈表的一種&#xff0c;它的每個數據結點中都有兩個指針&#xff0c;分別指向直接后繼和直接前驅。所以&#xff0c…

CodeForces - 1152B二進制+思維

【題目鏈接】Neko Performs Cat Furrier Transform 【題目分析】要求將一個數字變成2n-1,通過嘗試我們發現如果將最低位的全零位和對應的全一數字&#xff08;例如11000對應的就是111&#xff09;異或那么數字就會變成想要的結果&#xff08;11111&#xff09; 但是如果前面還有…

C語言文件操作之fgets()

http://blog.csdn.net/daiyutage/article/details/8540932 來說一說fgets(..)函數。 原型 char * fgets(char * s, int n,FILE *stream); 參數&#xff1a; s: 字符型指針&#xff0c;指向存儲讀入數據的緩沖區的地址。 n: 從流中讀入n-1個字符 stream &#xff1a; 指向讀取…

指針與零的比較以及浮點型與零的比較

指針和零的比較 int *p null;if(p ! null){p 20; } 整形和零的比較 int i 0; if(0i) {... } 浮點型和零的比較 判斷一個浮點數是不是零 #define EXP 0.0000000000001 float f 0.00001; if((f > -EXP)&&(f < EXP)) {... } 擴展后 判斷一個浮點數是不…

CodeForces 1138B暴力+剪枝

【題目鏈接】Circus 【題目分析】理解題意以后發現并沒有什么思路&#xff0c;沒有什么算法能用&#xff0c;這個時候就應該想到計算機解題的本質——暴力求解。相應的就要想到剪枝的條件&#xff0c;肯定不能盲目的暴力求解。 總共有四種人&#xff1a;00,01,10,11&#xff0c…