在上一篇里,我介紹了我為什么寫了個Maestro Webserver以及介紹了我寫的http message parser.下面我就介紹一下,我是如何應用socket和epoll的.
在socket編程中會碰到到底是使用阻塞還是非阻塞的方式;由于想提升效率我選擇了非阻塞方式,又由于linux中和非阻塞方式配合的比較好的是epoll,所以我就用了epoll.
那么,用什么方式來組織和管理epoll和socket呢?
我是這么考慮的,由于任何從客戶端發來的資訊都是要在建立了Socket連接的基礎上才會被處理,那么,epoll和socket就應該用來管理和處理socket連接.所以,應該創建一個socket連接的結構.在我的程式中這個結構叫httpconn_t,
typedef struct { int sockfd; int epfd; long stamp; PGconn *pgconn; rbtree_t *cache; rbtree_t *timers; rbtree_t *authdb; httpcfg_t *cfg; } httpconn_t;
先不用看其他部分,大家目前階段只關注sockfd和epfd這兩個代表socket file descriptor和epoll file descriptor的結構變數就好了,其他的變數是用來后續實作資料庫連接,快取,http keep-alive實作,用戶認證等相關的功能的.
epoll在linux底層應該是通過紅黑樹實作的,但對應user space而言,我們只需要把它看作是可以輪詢的epoll file descriptor陣列就可以了.所以,我在我的程式中用來do while loop來使用它,請看這個函式,
1 do { 2 int nevents = epoll_wait(epfd, events, MAXEVENTS, EPOLL_TIMEOUT); 3 if (nevents == -1) { 4 if (errno == EINTR) continue; 5 perror("epoll_wait()"); 6 } 7 8 if ((mstime() - loop_time) >= EPOLL_TIMEOUT) { 9 /* expire the timers */ 10 thpool_add_task(taskpool, httpconn_expire, timers); 11 /* expire the cache */ 12 thpool_add_task(taskpool, httpcache_expire, cache); 13 loop_time = mstime(); 14 } 15 16 /* loop through events */ 17 int i = 0; 18 do { 19 httpconn_t *conn = (httpconn_t *)events[i].data.ptr; 20 /* error case */ 21 if ((events[i].events & EPOLLERR) || (events[i].events & EPOLLHUP)) { 22 if (errno == EAGAIN || errno == EINTR) 23 nsleep(10); 24 else { 25 D_PRINT("[EPOLL] errno = %d, ", errno); 26 perror("[ERR|HUP]"); 27 break; 28 } 29 } 30 /* get input */ 31 if (events[i].events & EPOLLIN) { 32 if (conn->sockfd == srvfd) 33 epsock_connect(srvfd, epfd, pgconn, cache, timers, authdb, cfg); 34 else { 35 /* client socket; read client data and process it */ 36 thpool_add_task(taskpool, httpconn_task, conn); 37 } 38 } 39 i++; 40 } while (i < nevents);
第17行到第40行是管理組織epoll及socket的關鍵代碼.第31行表示,如果遇到和EPOLLIN相關的事件時,則說明有從客戶端傳來的資訊通過socket連接conn傳了進來.
后面的代碼則說明,如果傳入的連接conn的sockfd是和提前創建的服務器socket file descriptor srvfd相等的話,則創建新的socket連接.否則,則把這個連接作為引數加入到執行緒池中,讓執行緒池的任務函式httpconn_task處理.httpconn_task負責產生http response回傳給客戶端(比如,瀏覽器).
再來看看上一段代碼中的epsock_connect這個函式,它負責建立新的socket連接,
1 void epsock_connect(const int srvfd, 2 const int epfd, 3 PGconn *pgconn, 4 rbtree_t *cache, 5 rbtree_t *timers, 6 rbtree_t *authdb, 7 httpcfg_t *cfg) 8 { 9 struct sockaddr cliaddr; 10 socklen_t len_cliaddr = sizeof(struct sockaddr); 11 12 /* server socket; accept connections */ 13 for (;;) { 14 int clifd = accept(srvfd, &cliaddr, &len_cliaddr); 15 16 if (clifd == -1) { 17 if (errno == EINTR) continue; 18 if (errno == EAGAIN || errno == EWOULDBLOCK) { 19 /* we processed all of the connections */ 20 break; 21 } 22 perror("accept()"); 23 close(clifd); 24 break; 25 } 26 27 char *cli_ip = inet_ntoa(((struct sockaddr_in *)&cliaddr)->sin_addr); 28 D_PRINT("[CONN] client %s connected on socket %d\n", cli_ip, clifd); 29 30 _set_nonblocking(clifd); 31 32 httpconn_t *cliconn = httpconn_new(clifd, epfd, 33 pgconn, cache, timers, authdb, 34 cfg); 35 /* install the new timer */ 36 pthread_mutex_lock(&timers->mutex); 37 rbtree_insert(timers, cliconn); 38 pthread_mutex_unlock(&timers->mutex); 39 40 if (httpconn_epoll(cliconn, EPOLL_CTL_ADD) == -1) return; 41 } 42 }
其中,第32行httpconn_new()負責創建新的socket連接,第30行的作用是把連接設定成非阻塞方式.
具體的代碼還是需要讀者去我的github專案Maestro看,我寫的程式做的封裝不是很深,相信大家都能看懂的.
值得一提的是,在輪詢的程序中,作為整體待處理的是httpconn_t的這個結構,在后續的處理程序中也是需要基于這個結構整體設計和處理,socket和epoll的file descriptor要捆綁在一起考慮,否則http keep-alive的實作將很困難,至少我當時是無從下手的.
我會在第三篇內容里介紹 執行緒池 在Webserver中的應用...
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/285682.html
標籤:其他
上一篇:快來選擇你的編程語言!
