轉載-socket總結

1. 涉及的一些背景知識

1.1. nonblock socket

描述

對應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的情形:

  1. 連接失敗
    block:立即返回,返回值-1,同時設置errno := ENOTCONN
    nonblock: 同上
  2. 緩衝區中有數據:
    block: 立即返回,將緩衝區的數據寫入buf,最多寫入len字節,返回值爲寫入的字節數
    nonblock: 同上
  3. 緩衝區無數據:
    block:將阻塞等待緩衝區有數據
    nonblock:立即返回,返回值-1,同時設置errno := EAGAIN

類似的,對於send(), connect(), bind(), accept(),均有類似一樣的區別

設置

有如下方式設置nonblock

  1. 新建 socket 時設置
    在傳入 socket type 時,同時置SOCK_NONBLOCK位爲1

    sock = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
    
  2. 使用fcntl()設置

    int flag = fcntl(sock, F_GETFL);
    fcntl(sock, F_SETFL, flag | O_NONBLOCK); 
    
  3. 使用even2設置

    #inlcude <event2/util.h>
    
    int evutil_make_socket_nonblocking(evutil_socket_t sock);
    

1.2. reuseable socket

描述

一個socket在系統中的表示如下

{<protocol>, <src addr>, <src port>, <dest addr>, <dest port>}

如果指定src addr0.0.0.0,將不再表示某一個具體的地址,而是表示本地的所有的可用地址。

reuse有三個級別:

  1. non-reuse: src addrsrc port不能衝突(同一個protocol下), 0.0.0.0和其他IP視爲衝突
  2. reuse-addr: src addrsrc port不能衝突(同一個protocol下), 0.0.0.0和其他IP視爲不衝突
  3. reuse-port: src addrsrc port可以衝突

下邊仍然舉例說明reuse的特性
系統有兩個網口,分別是192.168.0.10110.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.0192.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.0192.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 - 可以綁定成功

設置reuse

  1. 使用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));
    
  2. 使用event2設置

    #inlcude <event2/util.h>
    
    int evutil_make_listen_socket_reuseable(evutil_socket_t sock);
    

2. 常用的系統API接口

新建一個socket

#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);

關閉即可,沒啥說的

3. 常用的event2的接口

舊版libevent中,一般只能操作一個全局的event_base,而在新版libevent中,event_base交由用戶來管理,用戶可以創建刪除event_base,也可以把event註冊到不同的event_base上。

新建一個 event_base

#include <event2/event.h>

struct event_base *event_base_new(void);

釋放一個event_base

#include <event2/event.h>

void event_base_free(struct event_base *eb);

event的生命週期

event的生命週期與相關的函數關係密切

event生命週期

用戶自己創建的event是uninitialized的,需要使用event_assign()進行初始化,或者直接使用event_new()從無到有創建一個新的初始化了的event。在初始化時,完成了回調函數的綁定。
event的初始狀態是non-pending,表示這個event不會被觸發。

新建(並初始化)一個 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狀態的

初始化一個event

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()

釋放一個event

void event_free(struct event *ev);

判斷event是否初始化/被釋放

int event_initialized(const struct event *ev);

將event置爲pending狀態

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);

將event置爲non-pending狀態

int event_del(struct event *ev);

檢查event是否爲pending狀態

int event_pending(const struct event *ev, short events, struct timeval *tv);

需要注意的是,不需要查詢event是否爲active狀態,因爲在active時,線程正在執行回調函數,其他函數需要等到回調執行完畢,而此時已經退出了active狀態

將event置爲active狀態

void event_active(struct event *ev, int res, short/* deprecated */);

ev);


## 檢查event是否爲pending狀態

int event_pending(const struct event *ev, short events, struct timeval *tv);


需要注意的是,不需要查詢event是否爲active狀態,因爲在active時,線程正在執行回調函數,其他函數需要等到回調執行完畢,而此時已經退出了active狀態

## 將event置爲active狀態



void event_active(struct event ev, int res, short/ deprecated */);


`res`是要手動指派的flag


想了解學習更多C++後臺服務器方面的知識,請關注:
微信公衆號:C++後臺服務器開發


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章