服務器端幾種模型:
1、阻塞式模型(blocking IO)
我們第一次接觸到的網絡編程都是從 listen()、accpet()、send()、recv() 等接口開始的。使用這些接口可以很方便的構建C/S的模型。這裡大部分的 socket 接口都是阻塞型的。所謂阻塞型接口是指系統調用(一般是 IO 接口)不返回調用結果並讓當前線程一直阻塞,只有當該系統調用獲得結果或者超時出錯時才返回。
如下面一個簡單的Server端實現:
#include <Winsock2.h><cstdio><iostream><> comment(lib,"ws2_32.lib") (WSAStartup(MAKEWORD(,) , &wsaData ) != - Server_Port 10286 handle_client(( buff[, result = recv(newfd,buff,,(result <= ret = send(newfd,buff,result,(ret>= socket(AF_INET, SOCK_STREAM, addr_server.sin_addr.S_un.S_addr ===(bind(listener,( sockaddr *)&addr_server,(addr_server)) < - (listen(listener, )< - runing = clientlen = ((client_sock = accept(listener, ( sockaddr *) &addr_client, &clientlen)) < main( c, **
這裡的socket的接口是阻塞的(blocking),在線程被阻塞期間,線程將無法執行任何運算或響應任何的網絡請求,這給多客戶機、多業務邏輯的網絡編程帶來了挑戰。
2、多線程的服務器模型(Multi-Thread)
應對多客戶機的網絡應用,最簡單的解決方式是在服務器端使用多線程(或多進程)。多線程(或多進程)的目的是讓每個連接都擁有獨立的線程(或進程),這樣任何一個連接的阻塞都不會影響其他的連接。
多線程Server端的實現:
#include <Winsock2.h><cstdio><iostream><> comment(lib,"ws2_32.lib") (WSAStartup(MAKEWORD(,) , &wsaData ) != - Server_Port 10286 *newfd = ( *( buff[, result = recv(*newfd,buff,,(result <= ret = send(*newfd,buff,result,(ret>* sock_clients[]; = socket(AF_INET, SOCK_STREAM, addr_server.sin_addr.S_un.S_addr ===(bind(listener,( sockaddr *)&addr_server,(addr_server)) < - (listen(listener, )< - fd_count = runing = clientlen = ((client_sock = accept(listener, ( sockaddr *) &addr_client, &clientlen)) < (fd_count<=(CreateThread(NULL,,handle_client,&sock_clients[fd_count],,NULL)== -++ main( c, **
上述多線程的服務器模型可以解決一些連接量不大的多客戶端連接請求,但是如果要同時響應成千上萬路的連接請求,則無論多線程還是多進程都會嚴重占據系統資源,降低系統對外界響應效率。
在多線程的基礎上,可以考慮使用“線程池”或“連接池”,“線程池”旨在減少創建和銷毀線程的頻率,其維持一定合理數量的線程,並讓空閒的線程重新承擔新的執行任務。“連接池”維持連接的緩存池,盡量重用已有的連接、減少創建和關閉連接的頻率。這兩種技術都可以很好的降低系統開銷,都被廣泛應用很多大型系統。
3、非阻塞式模型(Non-blocking IO)
非阻塞的接口相比於阻塞型接口的顯著差異在於,在被調用之後立即返回。
阻塞型IO和非阻塞型IO的區別,如下圖:
4、多路復用IO
#include <Winsock2.h><cstdio><cstdlib><cassert><iostream><> comment(lib,"ws2_32.lib") (WSAStartup(MAKEWORD(,) , &wsaData ) != - Server_Port 10286 MAX_LINE 16384 FD_SETSIZE 1024 fd_state * alloc_fd_state( fd_state *state = ( fd_state *)malloc(( (!->buffer_used = state->n_written = state->writing =->write_upto = ->buffer, free_fd_state( fd_state * set_socket_nonblocking( mode = result = ioctlsocket(fd, FIONBIO, & (result != - do_read( fd, fd_state * buf[ (,= recv(fd, buf, (buf), (result <= (i=; i < result; ++ (state->buffer_used < (state->->buffer[state->buffer_used++] =->writing = ->write_upto = state->,state->buffer+state->n_written,state->write_upto-state-> (result == (result < (result == - && WSAGetLastError()== (errno == - do_write( fd, fd_state * (state->n_written < state-> result = send(fd, state->buffer + state->->write_upto - state->n_written, (result < (result == - && WSAGetLastError()== (errno == -!= ,state->buffer+ state->->n_written += (state->n_written == state->->n_written = state->write_upto = state->buffer_used = ->writing = fd_state *== = (i = ; i < FD_SETSIZE; ++== socket(AF_INET, SOCK_STREAM, one = *)&one, (bind(listener, ( sockaddr*)&sin, (sin)) < (listen(listener, )<&&& (=&&&& (i=; i < FD_SETSIZE; ++ (i >=& (state[i]->& ((maxfd+, &readset, &writeset, &exset, NULL) < (FD_ISSET(listener, & slen = fd = accept(listener, ( sockaddr*)&ss, & (fd < (fd >= (i=; i < maxfd+; ++ r = (i == (FD_ISSET(i, &= (r == && FD_ISSET(i, &== main( c, **
這裡Select監聽的socket都是Non-blocking的,所以在do_read() do_write()中對返回為EAGAIN/WSAEWOULDBLOCK都做了處理。
從代碼中可以看出使用Select返回後,仍然需要輪訓再檢測每個socket的狀態(讀、寫),這樣的輪訓檢測在大量連接下也是效率不高的。因為當需要探測的句柄值較大時,select () 接口本身需要消耗大量時間去輪詢各個句柄。
很多操作系統提供了更為高效的接口,如 linux 提供 了 epoll,BSD 提供了 kqueue,Solaris 提供了 /dev/poll …。如果需要實現更高效的服務器程序,類似 epoll 這樣的接口更被推薦。遺憾的是不同的操作系統特供的 epoll 接口有很大差異,所以使用類似於 epoll 的接口實現具有較好跨平台能力的服務器會比較困難。
5、使用事件驅動庫libevent的服務器模型
Libevent 是一種高性能事件循環/事件驅動庫。
為了實際處理每個請求,libevent 庫提供一種事件機制,它作為底層網絡後端的包裝器。事件系統讓為連接添加處理函數變得非常簡便,同時降低了底層IO復雜性。這是 libevent 系統的核心。
創建 libevent 服務器的基本方法是,注冊當發生某一操作(比如接受來自客戶端的連接)時應該執行的函數,然後調用主事件循環 event_dispatch()。執行過程的控制現在由 libevent 系統處理。注冊事件和將調用的函數之後,事件系統開始自治;在應用程序運行時,可以在事件隊列中添加(注冊)或 刪除(取消注冊)事件。事件注冊非常方便,可以通過它添加新事件以處理新打開的連接,從而構建靈活的網絡處理系統。
使用Libevent實現的一個回顯服務器如下:
#include <event2/.h><assert.h><.h><stdlib.h><stdio.h><errno.h> MAX_LINE 16384 do_read(evutil_socket_t fd, events, * do_write(evutil_socket_t fd, events, * * * fd_state * alloc_fd_state( event_base * fd_state *state = ( fd_state *)malloc(( (!->read_event = event_new(, fd, EV_READ| (!state->->write_event = event_new( (!state->->->buffer,->buffer_used = state->n_written = state->write_upto = free_fd_state( fd_state *->-> do_read(evutil_socket_t fd, events, * fd_state *state = ( fd_state * buf[->(,= recv(fd, buf, (buf), (result <= (i=; i < result; ++ (state->buffer_used < (state->->buffer[state->buffer_used++] =,state->buffer+state->n_written,state->write_upto-state->->->->write_upto = state-> (result == (result < (result == - && WSAGetLastError()== (errno == do_write(evutil_socket_t fd, events, * fd_state *state = ( fd_state * (state->n_written < state-> result = send(fd, state->buffer + state->->write_upto - state->n_written, (result < (result == - && WSAGetLastError()== (errno ==!= ,state->buffer+ state->->n_written += (state->n_written == state->->n_written = state->write_upto = state->buffer_used = ->buffer, do_accept(evutil_socket_t listener, , * event_base * = ( event_base * slen = fd = accept(listener, ( sockaddr*)&ss, & (fd > fd_state *= alloc_fd_state(->-> event_base * * = (!=== = htons(= socket(AF_INET, SOCK_STREAM, one = *)&one, (bind(listener, ( sockaddr*)&addr_server, (addr_server)) < (listen(listener, )<= event_new(, listener, EV_READ|EV_PERSIST, do_accept, (*)(WSAStartup(MAKEWORD(,) , &wsaData ) != - main( c, **
參考:
http://www.ibm.com/developerworks/cn/aix/library/au-libev/
https://www.ibm.com/developerworks/cn/linux/l-async/