Linux網絡編程II

1.TCP通訊併發。使用多線程或多進程。
1. 一個父進程,多個子進程
2. 父進程負責等待並接受客戶端的鏈接
3. 子進程:完成通訊,接收一個客戶端鏈接就建立一個子進程用於通訊
ps: 回收子進程使用SIGCHILD信號,使用sigaction()信號捕捉函數,使用waitpid()非阻塞回收子進程資源
 
2.TCP狀態轉換。
 
3.半關閉
  • 當 TCP 連接中 A 向 B 發送 FIN 請求關閉,另外一端 B 迴應 ACK 以後(A 端進入 FIN_WAIT_2狀態),並無當即發送 FIN 給 A,A 方處於半鏈接狀態(半開關),此時 A 能夠接收 B 發送的數據,可是 A 已經不能再向 B 發送數據。
#include <sys/socket.h>
int shutdown(int sockfd, int how);
    - sockfd: 須要關閉的socket的描述符
    - how: 容許shutdown操做選擇如下幾種方式:
            - SHUT_RD(0): 關閉sockfd上的讀功能,該套接字再也不接收數據,任何在套接字接收緩衝區的數據都將被丟棄;
            - SHUT_WR(1): 關閉sockfd的寫功能,進程不能對此套接字發出寫操做;
            - SHUT_RDWR(2): 關閉讀寫功能,至關於調用shut down兩次,首先是SHUT_RD,而後是SHUT_WR.
 
1. 使用close(),若是多個進程共享一個套接字,close每調用一次,計數減1,直到計數爲0,也就是全部進程都調用了close,套接字被釋放。
2. 使用shutdown(),在多進程中若是一個進程調用了shutdown(sfd, SHUT_RDWR)後,其餘進程將沒法通訊。但若是一個進程close(sfd)將不會影響其餘進程。

 

4.端口複用。
  • 場景:服務器先關閉,會進入FIN_WAIT_2狀態,客戶端關閉,服務器會進入TIME_WAIT狀態,此時服務器的端口還未被釋放,不能被從新使用。
  • 程序忽然退出而系統沒有釋放端口
// 設置套接字的屬性,包括端口複用
#include <sts/types.h>
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
    - sockfd: 要操做的文件描述符
    - level: 級別 - SOL_SOCKET(端口複用)
    - optname: 選項的名稱
            - SO_REUSEADDR
            - SO_REUSEPORT
    - optval: 端口複用的值(整型)
            - 1: 能夠複用
            - 0: 不能夠複用
    - optlen: optval參數的大小,sizeof(optval)

 

5.IO多路複用(多路轉接)
  • IO:對緩衝區的操做
  • IO多路複用使程序能同時監聽多個文件描述符,可以提升程序的性能,Linux下實現多路複用的系統調用主要有select、poll、epoll。
 
6.IO模型
  • BIO模型(Blocking)
  • NIO模型(Nonblocking)
  • IO多路轉接技術
    • select、poll
    • epoll
 
7.IO多路轉接技術:select
  • 首先構造一個關於文件描述符的列表,將要監聽的文件描述符添加到該列表中。
  • 調用一個系統函數(select),監聽該列表中的文件描述符,直到這些描述符中一個或多個進行了IO操做時,該函數才返回。
    • 該函數是阻塞的
    • 函數對文件描述符的檢測操做是由內核完成的
  • 在返回時,該函數會告訴進程有多少(哪些)描述符要進行IO操做。
  • 缺點:
    • 每次調用select,都須要把fd集合從用戶區拷貝到內核區,這個開銷在fd比較多的時候會很大
    • 每次調用select都須要在內核遍歷傳遞進來的全部fd(O(n)時間複雜度),這個開銷也很大
    • select支持的文件描述符數量少,默認只有1024(fd_set底層是二進制位形式表示,fd集合用128個字節表示,全部默認表示的是1024位表明1024個文件描述符)
    • fds集合不能重用,每次須要重置(須要有一個fds集合專門記錄須要檢測的文件描述符,另外每次須要一個臨時的fds記錄從內核返回來的結果)
#include <sys/times.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);    // sizeof(fd_set) = 128 byte = 1024 bit
    - nfds: 委託內核檢測的最大文件描述符+1
    - readfds:  要檢測的文件描述符的讀的集合,委託內核檢測哪些文件描述符的讀屬性,
                - 對應的是對方發送過來的數據,
                - 爲讀是被動接收數據,檢測的就是讀緩衝區
                - 是一個傳入傳出參數
    - writefds: 要檢測的文件描述符的寫的集合,委託內核檢測哪些文件描述符的寫屬性
                - 委託內核檢測寫緩衝區是否還能夠寫數據
    - exceptefds: 檢測發生異常的文件描述符的集合
    - timeout: 設置的超時時間, NULL 永久阻塞,直到檢測到了文件描述符有變化;tv_sec = 0 tv_usec = 0 不阻塞;tv_sec > 0 tv_usec > 0 阻塞對應時間
    - 返回值:失敗返回-1;成功返回>0(n)的數,表示檢測的集合中有n個文件描述符發生了變化
struct timeval {
    long tv_sec;
    long tv_usec;
}
 
void FD_CLR(int fd, fd_set *set);
    - 做用:將參數文件描述符fd對應的標誌位設置爲0
 
void FD_ISSET(int fd, fd_set *set);
    - 做用:判斷fd對應的標誌位是0仍是1,
    - 返回值:fd對應的標誌位的值
 
void FD_SET(int fd, fd_set *set);
    - 做用:將參數文件描述符fd對應的標誌位設置爲1
 
void FD_ZERO(fd_set *set);
    - 做用:fd_set一共有1024位,初始化全部位爲0
 
 
8.IO多路轉接技術:poll
  • 改進了select的第3和第4點缺點,用結構體pollfd取代了select的fd_set數據結構,pollfd能夠保存須要檢測事件以及內核返回的結果。
  • 缺點:沒有改進select的第1和第2點缺點
    • 每次調用也須要把fd集合從用戶區拷貝到內核區,這個開銷在fd比較多的時候會很大
    • 每次調用須要在內核遍歷傳遞進來的全部fd(O(n)時間複雜度),這個開銷在fd比較多的時候也很大
    • poll調用的返回的內核結果只告知了有幾個文件描述符發生了改變,並無告知是具體哪幾個,依然須要程序遍歷找出(O(n))
#include <poll.h>
struct pollfd {
    int fd;                // 委託內核檢測的文件描述符
    short events;          // 委託內核檢測文件描述符的什麼事件 POLLIN 讀  POLLOUT 寫
    short revents;         // 內核返回的文件描述符發生的事件
};
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
    - fds: 須要檢測的文件描述符的集合
    - nfds: 委託內核檢測的最大文件描述符+1
    - timeout: 阻塞時長, NULL 永久阻塞,-1 阻塞,當檢測到須要檢測的文件描述符發生變化時解除阻塞;0 不阻塞;>0 阻塞時長
    - 返回值:失敗返回-1;成功返回>0(n)的數,表示檢測的集合中有n個文件描述符發生了變化
 

  

9.IO多路轉接技術:epoll
  • 監控多個文件描述符,檢測其中是否有能夠進行IO操做的( monitoring multiple file descriptors to see if I/O is possible on any of them.)
  • 底層實現是紅黑樹和雙鏈表,紅黑樹記錄須要檢測的文件描述符,遍歷複雜度O(logn);雙鏈表記錄改變了的文件描述符,返回給程序後避免了再次遍歷查找具體改變的文件描述符
#include <sys/epoll.h>
int epoll_create(int size);
    - 做用:建立一個新的epoll實例。在內核中建立一個數據,包括須要檢測的文件描述符(RBT),和就緒列表存放檢測到數據發生改變的文件描述符信息(雙鏈表)
    - size: >0, 無心義;之前底層hashmap實現時須要
    - 返回值:成功返回文件描述符,操做epoll實例;失敗返回-1並設置errno
 
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    - epfd: epoll實例對應的文件描述符
    - op: 要進行什麼操做,EPOLL_CTL_ADD 添加;EPOLL_CTL_MOD 修改;EPOLL_CTL_DEL 刪除
    - fd: 要檢測的文件描述符
    - event: 檢測文件描述符什麼事件,常見的epoll檢測事件:EPOLLIN、EPOLLOUT、EPOLLERR
 
int epoll_wait(nt epfd, struct epoll_event *events, int maxevents, int timeout);
    - epfd: epoll實例對應的文件描述符
    - events: 傳出參數,保存了發生了變化的文件描述符的信息
    - maxevents: 第二參數結構體數組的大小
    - timeout: 阻塞時長, NULL 永久阻塞,-1 阻塞,當檢測到須要檢測的文件描述符發生變化時解除阻塞;0 不阻塞;>0 阻塞時長
    - 返回值:失敗返回-1;成功返回>0(n)的數,表示檢測的集合中有n個文件描述符發生了變化

  

10.epoll的工做模式
  • LT模式(水平觸發)
    • 缺省的工做模式,同時支持阻塞和非阻塞socket。
    • 內核告訴你一個文件描述符是否就緒,而後程序能夠對該就緒的fd進行IO操做。若是不進行任何操做,內核會繼續通知程序。
    • 過程:
      • 假設委託內核檢測讀事件 -> 檢測fd的讀緩衝區
      • 讀緩衝區有數據 -> epoll檢測到了會給用戶通知
        • a. 用戶不讀數據 -> 數據一直在緩衝區,epoll會一直通知
        • b. 用戶只讀了一部分數據 -> epoll會繼續通知
        • c. 緩衝區的數據讀完了 -> 不通知
  • ET模式(邊沿觸發)
    • 高速的工做模式,支持非阻塞socket。
    • 當描述符從從未就緒變爲就緒時,內核會通知程序,而後內核會假設程序已經知道文件描述符就緒了,將再也不爲那個文件描述發送更多的就需通知。直到緩衝區有新數據有新數據到達或由空變爲非空的時候,將會再次觸發內核通知程序。
    • ET模式從很大程度上減小了epoll時間被重複處罰的次數,效率比LT模式高。
    • ET模式下epoll工做必須使用非阻塞套接字接口,而且須要結合循環讀取數據的方式,以免因爲一個文件描述符的阻塞讀/寫操做把處理多個文件描述符的任務餓死。
    • 過程:
      • 假設委託內核檢測讀事件 -> 檢測fd的讀緩衝區
      • 讀緩衝區有數據 -> epoll檢測到了會給用戶通知
        • a. 用戶不讀數據 -> 數據一直在緩衝區,epoll下次檢測的時候不通知
        • b. 用戶只讀了一部分數據 -> epoll不通知
        • c. 緩衝區的數據讀完了 -> 不通知,直到緩衝區有新的數據寫入才通知
 
11.LT和ET模式下對讀寫操做是否就緒的判斷
  • 水平觸發
    • 讀操做:只要緩衝內容不爲空,LT模式返回讀就緒。
    • 寫操做:只要緩衝區還不滿,LT模式返回寫就緒。
  • 邊沿觸發
    • 讀操做:1)緩衝區由空變爲非空;2)緩衝區有新數據到達時;3)緩衝區有數據可讀,且文件描述符進行EPOLL_CTL_MOD修改EPOLLIN事件時。
    • 寫操做:1)緩衝區由非空變爲空;2)緩衝區有數據被髮走時,即緩衝區內容減小時;3)緩衝區有空間可寫,且文件描述符進行EPOLL_CTL_MOD修改EPOLLOUT事件時。
 
12. 使用epoll模型,水平(LT)觸發模式,當socket可寫時,會不停的觸發socket可寫的事件,如何處理?
  • 開始不把socket加入epoll,須要向socket寫數據的時候,直接調用write或者send發送數據。若是返回EAGAIN,把socket加入epoll,在epoll的驅動下寫數據,所有數據發送完畢後,再移出epoll。
相關文章
相關標籤/搜索