PS: 更全面的總結能夠從相關的文檔中獲取,爲了敘述方便這裏特指linux環境下linux
對應block,若是一個socket設置爲nonblock,那麼其相關的操做將變爲非阻塞的。這裏所說的非阻塞,並非說異步回調什麼的,例如,調用recv()
函數:服務器
#include <sys/types.h> #include <sys/socket.h> ssize_t recv(int sockfd, void *buf, size_t len, int flags); read = recv(sock, buf, len, 0);
若是是默認的block情形,這個函數將一直等待直到獲取到數據,或者報錯。在高併發中,這顯然是悲劇的。
若是設置爲noblock,一樣的調用將直接返回。
下邊詳細描述一下的recv的情形:併發
鏈接失敗
block:當即返回,返回值-1,同時設置errno := ENOTCONN
nonblock: 同上dom
緩衝區中有數據:
block: 當即返回,將緩衝區的數據寫入buf,最多寫入len字節,返回值爲寫入的字節數
nonblock: 同上異步
緩衝區無數據:
block:將阻塞等待緩衝區有數據
nonblock:當即返回,返回值-1,同時設置errno := EAGAINsocket
相似的,對於send()
, connect()
, bind()
, accept()
,均有相似同樣的區別函數
有以下方式設置nonblock高併發
新建 socket 時設置
在傳入 socket type 時,同時置SOCK_NONBLOCK
位爲1spa
sock = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
使用fcntl()
設置線程
int flag = fcntl(sock, F_GETFL); fcntl(sock, F_SETFL, flag | O_NONBLOCK);
使用even2設置
#inlcude <event2/util.h> int evutil_make_socket_nonblocking(evutil_socket_t sock);
一個socket在系統中的表示以下
{<protocol>, <src addr>, <src port>, <dest addr>, <dest port>}
若是指定src addr
爲0.0.0.0
,將再也不表示某一個具體的地址,而是表示本地的全部的可用地址。
reuse有三個級別:
non-reuse
: src addr
和src port
不能衝突(同一個protocol
下), 0.0.0.0
和其餘IP視爲衝突
reuse-addr
: src addr
和src port
不能衝突(同一個protocol
下), 0.0.0.0
和其餘IP視爲不衝突
reuse-port
: src addr
和src port
能夠衝突
下邊仍然舉例說明reuse
的特性
系統有兩個網口,分別是192.168.0.101
和10.0.0.101
。
情形1:
sock1綁定了192.168.0.101:8080
,sock2嘗試綁定10.0.0.101:8080
non-reuse
- 能夠綁定成功,雖然端口同樣,可是addr不一樣reuse
- 同上
情形2
sock1綁定了0.0.0.0:8080
, sock2嘗試綁定192.168.0.101:8080
non-reuse
- 不能綁定成功,系統認爲0.0.0.0
包含了全部的本地ip,發生衝突reuse
- 能夠綁定成功,系統認爲0.0.0.0
和192.168.0.101
不是同樣的地址
情形3
sock1綁定了192.168.0.101:8080
,sock2嘗試綁定0.0.0.0:8080
non-reuse
- 不能綁定成功,系統認爲0.0.0.0
包含了全部的本地ip,發生衝突reuse
- 能夠綁定成功,系統認爲0.0.0.0
和192.168.0.101
不是同樣的地址
情形4
sock1綁定了0.0.0.0:8080
,sock2嘗試綁定0.0.0.0:8080
non-reuse
- 不能綁定成功,系統認爲0.0.0.0
包含了全部的本地ip,發生衝突reuse-addr
- 不能綁定成功,系統認爲0.0.0.0
包含了全部的本地ip,發生衝突reuse-port
- 能夠綁定成功
使用setsockopt()
必須設置全部相關的sock。
設置reuse-addr:
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int));
設置reuse-port:
setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &(int){1}, sizeof(int));
使用event2設置
#inlcude <event2/util.h> int evutil_make_listen_socket_reuseable(evutil_socket_t sock);
#include <sys/socket.h> int socket(int domain, int type, int protocol);
domain
通常設置爲:
AF_UNIX
- 本地socket
AF_INET
- ipv4
AF_INET6
- ipv6
type
通常設置爲:
SOCK_STREAM
- TCP
SOCK_DGRAM
- UDP
#include <sys/socket.h> int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
對於不一樣協議,addr
的類型不一樣,長度也不一樣,這裏須要把不一樣的類型強轉爲struct sockaddr *
,在強轉中,addr
的類型信息丟失,因此須要在addrlen
中指定原有類型的長度。
#include <sys/socket.h> int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
addr
相似connect()
,這個函數經常使用語服務器端,可是實際上客戶端也是可使用的(然並卵通常沒啥意義)
#include <sys/types.h> #include <sys/socket.h> ssize_t recv(int sockfd, void *buf, size_t len, int flags); ssize_t send(int sockfd, const void *buf, size_t len, int flags);
讀寫數據涉及的問題較多,第一是失敗時候返回-1而不是0,若是是0表示socket關閉。第二就是讀寫不必定100%完成,計劃讀寫512字節,可是讀到256字節的時候發生了中斷或者沒有數據/空閒緩衝區都是是可能的,返回值表示實際讀入和寫出的字節數。
#include <sys/socket.h> int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); int listen(int sockfd, int backlog); int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
和主動發起鏈接不一樣,被動接收鏈接分爲三個階段,bind()
用來設置本地端口,listen()
表示socket開始接收到來的鏈接,而不會創建鏈接,要真正創建鏈接,使用accept()
#include <unistd.h> int close(int fd);
關閉便可,沒啥說的
舊版libevent中,通常只能操做一個全局的event_base,而在新版libevent中,event_base交由用戶來管理,用戶能夠建立刪除event_base,也能夠把event註冊到不一樣的event_base上。
#include <event2/event.h> struct event_base *event_base_new(void);
#include <event2/event.h> void event_base_free(struct event_base *eb);
event的生命週期與相關的函數關係密切
用戶本身建立的event是uninitialized
的,須要使用event_assign()
進行初始化,或者直接使用event_new()
從無到有建立一個新的初始化了的event。在初始化時,完成了回調函數的綁定。
event的初始狀態是non-pending,表示這個event不會被觸發。
struct event *event_new(struct event_base *base, evutil_socket_t fd, short events, event_callback_fn callback, void *callback_arg);
新建event須要給定event_base, evutil_socket_t
與系統相兼容,在linux下實際就是int,與socket()返回的類型一致
#ifdef WIN32 #define evutil_socket_t intptr_t #else #define evutil_socket_t int #endif
events是一組flag,用於表示要監視的事件類型,還會影響event的一些行爲,包括:
EV_TIMEOUT
- 監視超時的事件
須要說明的是,在調用event_new()
時,這個flag是不用設置的,若是event發生超時,則必然會觸發,不管設置與否
EV_READ
- 監視可讀的事件
EV_WRITE
- 監視可寫的事件
EV_SIGNAL
- 監視信號量
EV_PERSIST
- 永久生效,不然觸發一次後就失效了
EV_ET
- 設置邊緣觸發(edge-triggered)
callback和callback_arg是回調操做所需的,再也不詳述
新建的event是non-pending狀態的
int event_assign(struct event *ev, struct event_base *base, evutil_socket_t fd, short events, event_callback_fn callback, void *callback_arg);
這個不會申請內存,其餘同event_new()
void event_free(struct event *ev);
int event_initialized(const struct event *ev);
int event_add(struct event *ev, const struct timeval *timeout);
其中timeout能夠指定超時時間,超時和EV_TIMEOUT配合使用。若是timeout若是爲NULL,則表示永不超時,struct timeval的結構爲:
struct timeval { time_t tv_sec; /* seconds */ suseconds_t tv_usec; /* microseconds */ };
額外說句,操做當前時間對應的timeval能夠用
#include <sys/time.h> int gettimeofday(struct timeval *tv, struct timezone *tz); int settimeofday(const struct timeval *tv, const struct timezone *tz);
int event_del(struct event *ev);
int event_pending(const struct event *ev, short events, struct timeval *tv);
須要注意的是,不須要查詢event是否爲active狀態,由於在active時,線程正在執行回調函數,其餘函數須要等到回調執行完畢,而此時已經退出了active狀態
void event_active(struct event *ev, int res, short/* deprecated */);
res
是要手動指派的flag