《Linux高性能服務器編程》閱讀筆記

  • bind成功時返回0,失敗時返回-1並設置errno。其中,兩種常見的errno是EACCES和EADDRINUSE,他們的含義分別是:
    • EACCES:被綁定的地址是受保護的地址,僅有超級用戶能夠訪問
    • EADDRINUSE:被綁定的地址正在使用中。
  • listenbacklog參數表示:處於徹底鏈接狀態的socket的上限。
  • accept只是從監聽隊列中取出鏈接,而無論鏈接處於何種狀態。(ESSTABLISHED或者CLOSE_WAIT
  • connect失敗時返回-1並設置errno。其中,最多見的兩種errno是ECONNREFUSEDETIMEDOUT
    • ECONNREFUSED:目標端口不存在,鏈接被拒絕。
    • ETIMEDOUT:鏈接超時。
  • close並不是當即關閉一個鏈接,而是將fd的引用數減1。算法

    重點來了:

  • 對於監聽Socket來講,在listen調用前設置這些經常使用的Socket選項,那麼Accept返回的鏈接Socket將繼承這些選項:SO_KEEPALIVESO_LINGERSO_RCVBUFSO_REVLOWATSO_SNDBUFSO_SNDLOWATTCP_NODELAY
  • 對於客戶端Socket來講,上述的選項應該在調用Connect函數以前設置,由於Connect調用成功返回以後,TCP三次握手已經完成。
  • 下面我將詳細介紹這些套接字選項:
    • SO_REUSEADDR:服務器程序能夠經過設置Socket選項來強制使用陷入TIME_WAIT狀態的鏈接佔用的Socket Address
    • SO_REUSPORT:容許在一個端口上啓動一個服務的多個實例,只要每一個實例綁定不一樣的本地IP地址便可。
    • SO_RCVBUFSO_SNDBUF:表示TCP接受緩衝區與發送緩衝區的大小。TCP接受緩衝區的大小最好設置爲MSS(1460)的偶數倍.
    • SO_RCVLOWATSO_SNDLOWAT:表示接受緩衝區與發送緩衝區的低水平位標記。默認狀況下,均爲1。
    • SO_LINGER:能夠經過該選項控制Close系統調用的不一樣的行爲。默認狀況下,當咱們使用Close系統調用來關閉一個Socket時,Slose當即返回,TCP模塊負責將該Socket發送緩衝區中的數據發送給對方。
    • SO_KEPPALIVE:發送週期性保活報文維持鏈接。
    • TCP_NODELAY:禁止Nagle算法,即在鏈接上會出現不少的小的數據塊,在局域網環境下能夠開啓。
  • 零拷貝函數:
#include<sys/sendfile.h>
ssize_t sendfile(int out_fd, int in_fd, off_t* offset, size_t count);
  • 零拷貝函數:
#include<fcntl.h>
ssize_t splice(int in_fd, loff_t* off_in, int out_fd, loff_t* off_out, size_t len, unsigned int flags);

//使用splice實現零拷貝反射服務器
int pipefd[2];
int ret = pipe(pipefd);
ret = splice(connectfd, NULL, pipefd[1], NULL, 65536, SPLICE_F_MORE | SPLICE_F_MOVE);
ret = splice(pipefd[0], NULL, connectfd, NULL, 65536, SPLICE_F_MORE | SPLICE_F_MOVE);
close(connectfd);
  • 針對非阻塞IO執行的系統調用老是當即返回,而無論事情是否已經發生。若是事情沒有當即發生,這些系統調用就當即返回-1,就和出錯返回同樣,此時咱們必須根據返回時設置的errno來判斷具體發生了什麼狀況。對Accept,Send,Recv而言,事件發生未發生時errno一般被設置爲EAGAIN(意味着「再來一次」)或者EWOULDBLOCK(意味着「指望阻塞」);對於Connect而言,errno則被設置爲EINPEOCESS(意味着「在處理中」)。
  • IO複用函數自己是阻塞的,他們能夠提升程序效率的緣由在於他們具備同事監聽多個IO事件的能力。
  • 服務器程序一般須要處理三類事件:IO事件定時器事件信號事件
  • 兩種事件處理模式:ReactorProator
    • Reactor
      • 主線程往Epoll內核事件表中註冊可讀事件
      • 主線程等待可讀事件發生
      • 可讀事件發生時,主線程將可讀事件放入請求隊列
      • 主線程將睡眠在請求隊列中的工做線程喚醒,處理可讀事件。處理完畢以後,向Epoll註冊可寫事件
      • 主線程等待可寫事件發生
      • 可寫事件發生時,主線程將可寫事件放入請求隊列
      • 如此往復循環
    • Proator
      • 主線程向內核註冊讀完成事件,完成時,經過信號通知應用程序
      • 主線程繼續處理其餘邏輯
      • 主線程收到讀完成事件後,將讀到的數據封裝成一個事件對象送入工做線程。工做線程處理完畢以後,向內核註冊讀完成事件,並告訴內核用戶緩衝區的位置
      • 主線程繼續處理其餘邏輯
      • 主線程收到讀完成事件,作善後處理
      • 如此往復循環
  • 三個IO複用機制
    • select:每次事件發生以後,以前註冊事件都會被修改,須要用FD_ISSET進行判斷,而後從新註冊。
    • poll:同select,使用輪詢機制
    • epollepoll對文件描述符的操做有兩種模式:LT(電平觸發)和ET(邊沿觸發)。LT模式是默認的工做模式。當設置EPOLLET時,epoll會以ET模式來操做文件描述符,ET是高效工做模式。
      • ET:當epoll_wait檢測到其上有事件發生並將這次的事件通知給應用程序後,應用程序必須馬上處理該事件,由於後續的epoll_wait不會再次嚮應用程序通知這一事件。下降了epoll事件被觸發的次數,所以效率高於LT。
      • LT:epoll_wait檢測到有事件發生並將這次的事件通知應用程序後,應用程序能夠不當即處理該事件。這樣,當應用程序下一次調用epoll_wait的時候,epoll_wait還會再次嚮應用通知該事件,直到該事件被處理。
      • EPOLLONESHOT:對於註冊EPOLLONESHOT事件的文件描述符,操做系統最多觸發其上註冊的一個事件(可讀,可寫或者異常),而且只觸發一次,除非咱們使用epoll_ctl函數重置該文件描述符上註冊的EPOLLONESHOT事件。
        ```編程

        include<sys/select.h>

        int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);服務器

FD_ZERO(fd_set* fdset);
FD_SET(int fd, fd_set* fdset);
FD_CLR(int fd,fd_set* fdset);
int FD_ISSET(int fd,fd_set* fdset);網絡

struct timeval
{
long tv_sec; //秒數
long tv_usec; //微秒數
}socket

 

include<poll.h>

int poll(struct pollfd* fds, nfds_t nfds, int timeout);函數

struct pollfd
{
int fd;
short event; //註冊的事件
short revent; //實際發生的事件,內核填充,記得每次判斷後歸零
}ui

POLLIN:數據可讀
POLLOUT:數據可寫
POLLREHUP:TCP鏈接被對方關閉,或者對方關閉了寫操做
POLLERR:錯誤
POLLHUP:掛起
POLLNVAL:文件描述符沒有打開操作系統

 

include<sys/epoll.h>

int epoll_create(int size);
int epoll_ctl(int epollfd, int op, int fd, struct epoll_event* event);
int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);線程

op參數的類型:
EPOLL_CTL_ADD:向事件註冊表中註冊fd事件
EPOLL_CTL_MOD:修改fd上註冊的事件
EPOLL_CTL_DEL:刪除fd上註冊的事件ssr

struct epoll_event
{
_uint32_t events;
epoll_data_t data;
};

union epoll_data
{
void* ptr;
int fd; //指定事件所從屬的描述符
uint32_t u32;
uint64_t u64;
} epoll_data_t;

- 服務器端處理信號的方式:

void sig_handler(int sig)
{
int save_errno = errno;
int msg = sig;
send(pipefd[1], (char*)&msg, 1, 0);
errno = save_errno;
}

void addsig(int sig)
{
struct sigaction sa;
memset(&sa, '\0', sizeof(sa));
sa.sa_handleer = sig_handler;
sa.sa_flags |= SA_RESTART;
sigfillset(&sa.sa_mask);
assrt(sigaction(sig, &sa, NULL) != -1);
}
```

  • 與網絡編程相關的信號:
    • SIGHUP:掛起進程的控制終端時,SIGHUP信號會被觸發。
    • SIGPIPE:往一個關閉的管道或者Socket中寫入數據。默認狀態是結束進程。
    • SIGURG:帶外數據到達。
    • SIGCHLD:子進程狀態發生變化,使用wait進行處理
    • SIGTERM:終止進程。kill命令默認發送該信號。
    • SIGINT:鍵盤輸入以中斷進程。
相關文章
相關標籤/搜索