Linux環境:C編程之網絡通信進階操作

參考鏈接:IO多路複用之epoll總結
參考鏈接:套接字的阻塞與非阻塞

套接字選項

套接字選項詳細規定了套接字的屬性,影響socket的各項操作。我們可以通過setsockopt函 數和getsockopt函數愉快的設定和獲取socket的屬性。
首先來看一下setsockopt函數:

setsockopt函數
  • 作用:設定socket的屬性,注意不是修改,setsockopt操作應該在bind之前
  • 原型:int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
  • 參數:
    • sockfd:要設置的socket的文件描述符
    • level:指定了套接字選項所適用的協議,比如IPPROTO_TCP、IPPROTO_IP 和 IPPROTO_IPV6分別代表了tcp、ip和ipv6協議,通常情況下爲SOL_SOCKET,表示作用於套接字API層。
    • optname:需設置的socket選項
    • optval:指向緩衝區的指針,用來指定要設置的值,根據選項不同指向整數或者結構體
    • opilen:表明緩衝區的長度、
  • 返回值:成功返回0,失敗返回-1
getsockopt函數
  • 作用:獲取socket的屬性
  • 原型:int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
  • 參數:
    • sockfd:要獲取選項的socket的文件描述符
    • level:指定了套接字選項所適用的協議,比如IPPROTO_TCP、IPPROTO_IP 和 IPPROTO_IPV6分別代表了tcp、ip和ipv6協議,通常情況下爲SOL_SOCKET,表示作用於套接字API層。
    • optname:需獲取的socket選項
    • optval:傳出參數,指向緩衝區的指針,用來存儲要獲取的選項,根據選項不同指向整數或者結構體
    • opilen:傳出參數,返回緩衝區的長度、
  • 返回值:成功返回0,失敗返回-1
套接字API層常用的socket屬性
名稱 選項 數值類型
SO_BROADCAST 允許發送廣播數據 int
SO_DEBUG 允許調試 int
SO_DONTROUTE 不查找路由 int
SO_ERROR 獲得套接字錯誤 int
SO_KEEPALIVE 保持連接 int
SO_LINGER 延遲關閉連接 struct linger
SO_OOBINLINE 帶外數據放入正常數據流 int
SO_RCVBUF 接收緩衝區大小 int
SO_SNDBUF 發送緩衝區大小 int
SO_RCVLOWAT 接收緩衝區下限 int
SO_SNDLOWAT 發送緩衝區下限 int
SO_RCVTIMEO 接收超時 struct timeval
SO_SNDTIMEO 發送超時 struct timeval
SO_REUSEADDR 允許重用本地地址和端口 int
SO_TYPE 獲得套接字類型 int
SO_BSDCOMPAT 與 BSD 系統兼容 int

epoll 多路複用模型

epoll多路複用模型是linux中特有的,相比之前學過的select,參見linux環境:C編程文件操作,epoll更加靈活,性能也更出色,支持水平觸發和邊緣觸發兩種觸發模式。

接口函數

epoll模型由三個函數組成:
int epoll_create(int size);//創建一個epoll實例
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);//添加監控的套接字
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);//返回I/O就緒的套接字
函數原理:

  • epoll_creat函數創建epoll實例,返回對應的文件描述符。size參數指定文件的初始大小,即要監控的套接字的個數,沒有太大意義,因爲epoll實例可以根據需要修改添加監控對象。
  • epoll_ctl事件註冊函數,它不同與select()是在監聽事件時告訴內核要監聽什麼類型的事件,而是在這裏先註冊要監聽的事件類型。
    • 第一個參數是epoll_create()的返回值,
    • 第二個參數表示動作,用三個宏來表示:
      EPOLL_CTL_ADD:註冊新的fd到epfd中;
      EPOLL_CTL_MOD:修改已經註冊的fd的監聽事件;
      EPOLL_CTL_DEL:從epfd中刪除一個fd;
    • 第三個參數是需要監聽的fd
    • 第四個參數是告訴內核需要監聽什麼事,
      • struct epoll_event結構如下
    //聯合體,事件觸發時返回的信息,一般填對應的文件描述符,來表示該事件由誰觸發
    	typedef union epoll_data {
    	void *ptr;
    	int fd;
    	__uint32_t u32;
    	__uint64_t u64;
    	} epoll_data_t;
    
    	struct epoll_event {
    	__uint32_t events; //觸發事件
    	epoll_data_t data; 
    	};
    
    events可以是以下幾個宏的集合:
    EPOLLIN :表示對應的文件描述符可以讀(包括對端SOCKET正常關閉);
    EPOLLOUT:表示對應的文件描述符可以寫;
    EPOLLPRI:表示對應的文件描述符有緊急的數據可讀(這裏應該表示有帶外數據到來);
    EPOLLERR:表示對應的文件描述符發生錯誤;
    EPOLLHUP:表示對應的文件描述符被掛斷;
    EPOLLET: 將EPOLL設爲邊緣觸發(Edge Triggered)模式,這是相對於水平觸發(Level Triggered)來說的。
    EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之後,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL隊列裏
  • epoll_wait函數返回就緒的套接字列表,第一個參數是epoll實例,第二個參數是返回的結構體數組,每個元素對應一個套接字的信息,第三個列表是該結構體數組的容量, 值不能大於epoll實例中就緒列表的最大長度,第四個參數是監視計時器(毫秒,0會立即返回,-1將不確定,也有說法說是永久阻塞)。該函數返回需要處理的事件數目,如返回0表示已超時。
觸發模式

LT模式:工作邏輯和select一致,只要緩衝區中存在沒有讀寫的數據就一直觸發。
ET模式:僅當緩衝區狀態發生變化的時候才獲得通知。這裏的狀態的變化並不包括緩衝區中還有未處理的數據,也就是說,如果要採用ET模式,需要一直read/write直到出錯爲止,否則未讀寫的數據就會丟失。

套接字的阻塞和非阻塞模式

套接字的默認狀態是阻塞狀態,即在進行bind,read,write,send,recv,connect,accept等操作時,如果沒有輸入,則會阻塞進程。

設置非阻塞套接字

可以通過設置文件描述符的標誌位來把套接字設置爲非阻塞模式。
用到以下函數:
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struct flock *lock);
fcntl函數可以獲取或者設置文件的標誌位,fd是文件描述符,cmd是命令,如果有第三個參數則是命令的參數。
通過以下代碼可以設置非阻塞套接字

	//獲取當前文件的文件描述標誌位
	int status=fcntl(fd,F_GETFL);
	//將該標誌位或上非阻塞標誌
    status=status|O_NONBLOCK;
    //重新設置文件的標誌位
    fcntl(fd,F_SETFL,status);
阻塞和非阻塞的區別

套接字調用可分爲四種

  • 輸入操作,包括read,readv,recv,recvfrom,recvmsg。

    阻塞:

    • TCP:如果接收緩衝區沒有數據讀,則阻塞,直到數據到達。

    • UDP:如果接收緩衝區沒有數據讀,則阻塞,直到UDP數據報到達。

    非阻塞:

    • 如果輸入操作不能被滿足(對於TCP套接字即至少有一個字節的數據可讀,對於UDP套接字即有一個完整的數據報可讀),相應調用將立即返回EWOULDBLOCK。
  • 輸出操作,包括write,writev,send,sendto,sendmsg
    阻塞:

    • TCP:如果發送緩衝區沒有空間,則阻塞。有一些空間時,則返回不足計數

    非阻塞:

    • TCP:如果發送緩衝區沒有空間,會立即返回一個EWOULDBLOCK錯誤。如
      果有一些空間,返回值將是內核能夠複製到該緩衝區中的字節數。
    • UDP:沒有發送緩衝區,不會因與TCP套接字一樣的原因而阻塞,不過有可能會因其他原因而阻塞。
  • 接收外來連接,即用於accept函數

    • 阻塞:阻塞在accept,直到有新的連接
    • 非阻塞:沒有新的連接來時,accept調用將立即返回一個EWOULDBLOCK
  • 發起外出連接,即用於TCP的connect函數。

    • 阻塞:connect函數一直要等到客戶收到對於自己的SYN的ACK爲止才返回。所以TCP的每一個connect總會阻塞其調用的進程至少一個到服務器的RTT時間。

    • 非阻塞:調用connect,並連接不能立即建立,那麼連接的建立能照樣發起,不過會返回一個EINPROGRESS錯誤。這個錯誤不同於上述三個情形,但客戶端和服務端在同一主機,這些連接會立即建立。所以也要預備connect成功返回的情況。

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