study notes: high performance linux server programming

1:linux網絡API分爲:socker地址API,socker基礎API,網絡信息APIreact

  1,socker地址API:包含IP地址和端口(ip, port)。表示TCP通訊的一端。linux

  2,socker基礎API:建立/命名/監聽socker,接收/發起連接,讀寫數據,獲取地址信息,檢測帶外標記和讀取/設置socker選項。sys/socket.h數據庫

  3,網絡信息API:主機名和IP地址的轉換,服務名和端口號的轉換。netdb.h編程

2:socket和API的函數 和 相關知識。緩存

  1,函數。安全

1 IP地址轉換函數
  <arpa/inet.h>
  in_addr_t inet_addr( const char* strptr );  // 格式轉換:十進制字符串IP -> 網絡字節序(大端)IP
  int inet_aton( const char* cp, struct in_addr* inp );  // 格式轉換:如上,但將數據保存到參數inp中
  char* inet_ntoa( struct in_addr in );  //  格式轉換:網絡字節序(大端)IP -> 十進制字符串IP
  int inet_pton( int af, const char* src, void* dst ); // 格式轉換:字符串IP地址 -> 網絡字節序(大端)整數IP
  const char* inet_ntop( int af, const void* src, char* dst, socklen_t cnt );  格式轉換:網絡字節序(大端)整數IP -> 字符串IP地址
  // inet_ntoa函數不可重入。
  // inet_ntop返回目標儲存單元的地址。
2 建立socket
  <sys/types.h> <sys/socket.h>
  int socket( int domain, int type, int protocol );
  // 1 參數domain:使用的底層協議族。
  // 2 參數type:指定服務類型。SOCK_STREAM(TCP) SOCK_UGRAM(UDP)
  // 3 參數protocol:指定具體協議。通常默認爲0.(前面參數已經肯定了協議)
  // 4 返回:socket文件描述符。失敗-1,設置errno
3 命名socket
  <sys/types.h> <sys/socket.h>
  int bind( int sockfd, const struct sockaddr* my_addr, socklen_t addrlen );
  // 1 參數addrlen:socket地址長度。
  // 2 返回:成功0,失敗-1,設置errno
4 監聽socket
  <sys/socket.h>
  int listen( int sockfd, int backlog );
  // 1 參數sockfd:指定被監聽socket
  // 2 參數backlog:監聽隊列最大長度。典型值:5
5 接收連接。
  <sys/types.h> <sys/socket.h>
  int accept( int sockfd, struct sockaddr* addr, socklen_t* addrlen );
  // 1 參數sockfd:執行過listen的socket。
  // 2 參數addr:被接受連接的遠程socket地址。
  // 3 參數addrlen:地址長度。
  // 4 返回:成功新的連接socket,失敗-1設置errno
  // 5 accept只從監聽隊列取出鏈接,並不檢測網絡狀態。
6 發起連接
  <sys/types.h> <sys/socket.h>
  int connect( int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen );
  // 1 參數sockfd:由socket系統調用返回一個socket
  // 2 參數serv_addr:服務器監聽的socket地址。
  // 3 返回:成功0,失敗-1設置errno
7 關閉鏈接
  <unistd.h>
  int close( int fd );
  // 1 不會當即關閉,只是將引用減一。只有引用爲0時纔會真正關閉。
  <sys/socket.h>
  int shutdown( int sockfd, int howto );
  // 1 當即關閉鏈接。
  // 2 參數howto:決定函數的行爲。SHUT_RD/WR/RDWR:能夠分別控制讀寫關閉。
  // 3 返回:成功0,失敗-1設errno
8 TCP數據讀寫。
  <sys/types.h> <sys/socket.h>
  ssize_t recv( int sockfd, void* buf, size_t len, int flags );  // 返回:實際讀取到的數據長度。失敗-1errno
  ssize_t send( int sockfd, const void* buf, size_t len, int flags );  // 返回:實際寫入的數據長度。失敗-1reeno
  // 1 參數buf,len:緩衝區的地址和大小。
  // 2 參數flags:數據收發額外控制。一般設0.
9 UDP數據讀寫。
  <sys/types.h> <sys/socket.h>
  ssize_t recvfrom( int sockfd, void* buf, size_t len, int flags, struct sockaddr* src_addr, socklen_t *addrlen );
  ssize_t sendto( int sockfd, const void* buf, size_t len, int flags, const struct sockaddr* dest_addr, socklen_t *addrlen );
  // 1 參數src/dest_addr:肯定發送/接收端地址。(因UDP沒有鏈接概念)
  // 2 這兩個函數一樣能夠應用於TCP讀寫數據。
10 通用數據讀寫函數。
  <sys/socket.h>
  ssize_t recvmsg( int sockfd, struct msghdr* msg, int flags );
  ssize_t sendmsg( int sockfd, struct msghdr* msg, int flags );
  struct msghdr{
    void* msg_name;             // socket 地址
    socklen_t msg_namelen;    // 地址長度
    struct iovec* msg_iov;      // 分散的內存塊
    int msg_iovlen;             // 分散內存塊的數量
    void* msg_control;      // 輔助數據地址
    socklen_t msg_controllen;   // 輔助數據大小
    int msg_flags;              // 保存函數flag參數
  }
11 肯定下一個數據是否時帶外數據。
  <sys/socket.h>
  int sockatmark( int sockfd );  // 返回:帶外1,不然0
12 獲取地址信息。
  <sys/socket.h>
  int getsockname( int sockfd, struct sockaddr* address, socklen_t* address_len );  // 本端
  int getpeername( int sockfd, struct sockaddr* address, socklen_t* address_len );  // 遠端
  // 1 返回:成功0,失敗-1errno
13 獲取/設置socket選項
  <sys/socket.h>
  int getsockopt( int sockfd, int level, int option_name, void* option_value, socklen_t* restrict option_len );
  int setsockopt( int sockfd, int level, int option_name, const void* option_value, socklen_t* restrict option_len );
  // 1 參數level:具體協議選項。
  // 2 返回:成功0,失敗-1errno。

// 此後爲網絡信息API
14 獲取主機完整信息。
  <netdb.h>
  struct hostent* gethostbyname( const char* name );  // 經過主機名稱獲取
  struct hostent* gethostbyaddr( const void* addr, size_t len, int type );  //經過IP地址獲取
  // 1 參數type:IP地址的類型。
  // 2 返回:主機信息。
15 獲取服務的完整信息。
  <netdb.h> 
  struct servent* getservbyname( const char* name, const char* proto );  // 經過名字獲取
  struct servent* getservbyport( int port, const char* proto );  // 經過端口號獲取
  // 1 參數proto:肯定服務類型:UDP/TCP
16 獲取主機和服務的綜合函數。
  int getaddrinfo( const char* hostname, const char* service, const struct addrinfo* hints, struct addrinfo** result );
  int getnameinfo( const struct sockaddr* sockaddr, socklen_t addrlen, char* host, socklen_t hostlen, char* serv, socklen_t servlen, int flags );

 

   2,88-94在完成第一版IM時,須要進行測試服務器

3:高級IO(高編已由函數省略)網絡

  1,函數多線程

1 兩個文件描述符之間直接傳遞數據
  ssize_t sendfile( int out_fd, int in_fd, off_t* offset, size_t count );
  // 1 參數count:傳輸字節數
  // 2 返回:成功返回傳輸字節數,失敗-1errno
  // 3 in_fd:必須時真實文件,out_fd:必須是socket
2 兩個文件描述符之間移動數據。零copy操做
  ssize_t splice( int fd_in, loff_t* off_in, int fd_out, loff_t* off_out, size_t len, unsigned int flags );  // 具體須要再瞭解和使用。
3 兩個管道文件描述符之間複製數據。零copy操做
  ssize_t tee( int fd_in, int fd_out, size_t len, unsigned int flags );

 

4:linux服務程序規範。併發

  1,函數。

1 和日誌進程通訊。
  <syslog.h>
  void syslog( int priority, const char* message );
  // 1 參數priority:設置值與日誌級別按位與。
2 設置日誌進程的輸出方式。
  void openlog( const char* ident, int logopt, int facility );
  // 1 參數ident:添加到日誌消息的日期和時間以後。
  // 2 參數facility:用來修改syslog函數中的默認值。
3 設置日誌進程的掩碼。(設置屏蔽信息)
  int setlogmask( int maskpri );
4 關閉日誌功能。
  void closelog();
5 使進程稱爲後臺進程:
  <unistd.h>
  int daemon( int nochdir, int noclose );
  // 1 參數nochdir:傳0,則工做目錄爲根目錄/。不然不變。
  // 2 參數noclose:傳0,則被重定向爲/dev/null。
  // 3 返回:成功0,失敗-1errno。

 

  2,基本規範。

    1)通常之後臺進程形式運行。後臺進程又稱爲守護進程。

    2)一般有一套日誌系統。至少能輸出日誌到文件。

    3)通常以某個專門的非root身份運行。

    4)一般時可配置的。

    5)啓動時,會生成一個PID文件並存入/var/run目錄中。

    6)一般須要考慮系統資源和限制。

  3,日誌進程:rsyslogd。

    1)可以接收 用戶/內核 的日誌。

  4,ps命令可查看進程、進程組和會話之間的關係。

5:高性能服務器框架。

  1,函數。

  2,零散細節。

    1)客戶鏈接請求時隨即到達的異步事件,服務器須要使用某種IO模型來監聽。

    2)服務器能夠解構成三個主要模塊:IO處理單元,邏輯單元,儲存單元。

    3)服務器基本模塊的功能描述。

模塊 單個服務器程序 服務器機羣
IO處理單元 處理客戶鏈接,讀寫網絡數據 做爲接入服務器,實現負載均衡
邏輯單元 業務進程或線程 邏輯服務器
網絡儲存單元 本地數據庫、文件或緩存。 數據庫服務器
請求隊列 各單元之間的通訊方式。 各服務器之間的永久TCP鏈接

    4)IO服用函數自己時阻塞的,它們能提升程序效率的緣由:它們具備同時監聽多個IO事件的能力。

    5)同步IO嚮應用程序通知的是IO就緒事件,異步IO嚮應用程序通知的是IO完成事件。

    6)服務器程序一般須要處理三類事件:IO事件,信號和定時事件。

    7)若是程序是計算密集型, 併發沒有任何優點。但若是是IO密集型,併發就會有意義。由於IO速度比CPU緩慢。

    8)併發模式中,同步指程序徹底按照代碼序列順序執行(用於處理客戶邏輯),異步指程序執行須要由系統事件來驅動(用於處理IO)。

    9)主線程向工做線程發送scoket最簡單的方式:往管道中寫數據。

    10)池是一組資源的集合。這組資源在服務器啓動之初就被徹底建立好並初始化。常見的有:內存池,線程池,進程池和鏈接池。

    11)高性能服務器應該避免沒必要要的數據複製。(避免數據複製,共享內存是最塊的方式,但不必定是最好的。

    12)併發程序必須考慮上下文切換的開銷。

    13)儘可能減小鎖的開銷。

  3,P2P模式:peer to peer。

    1)P2P模式時,主機之間很難相互發現。

  4,reactoer模式。

    1)它要求主線程(IO處理單元)只負責監聽文件描述符是否有事件發生,有的話就當即將該事件通知工做線程(邏輯單元)。

    2)特色:主線程只肯定有請求,工做線程針對具體事件類型針對處理。

  5,proactor模式:它將全部IO操做都交給主線程和內核來處理。工做線程僅僅負責業務邏輯。。

  6,半同步/半反應堆模式:主線程用來監聽IO,併發送請求到請求隊列。而後工做線程競爭搶奪處理權限。

    1)主線程和工做線程共享請求隊列。須要鎖來保證讀寫,消耗CPU

    2)每一個工做線程同一時間只能處理一個客戶請求。

  7,leader/follewer模式:leader監聽scoket。當有新請求時,leader去處理請求,並從follower中推選新leader。

    1)須要包含幾個組件:句柄集,線程集,事件處理器,具體事件處理器。

6:IO複用

  1,函數。

1 epoll系列:linux特有的IO複用函數。
  <sys/epoll.h>
  int epoll_create( int size ); // 建立一個描述符(標識事件表)
  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 ); // 等待事件。
2

 

  2,零散細節。

    1)IO複用雖然能同時監聽多個文件描述符,但它自己是阻塞的。

    2)epoll系列有兩種模式:LT和ET。ET是相對高效的模式。

    3)ET模式的文件描述符都應該是非阻塞的。(若是是阻塞的,可能會一直阻塞下去)

    4)EPOLLONESHOT事件:使事件只能觸發一次。若須要再次觸發,須要重置。能夠防止重複讀寫。

  3,select,poll,epoll的區別

系統調用 select poll epoll
事件集合 用戶經過三個參數分別傳入可讀,可寫和異常事件。內核經過這些參數在線修改就緒事件。每次調用都會重讀 統一處理全部事件類型。須要一個事件集。用戶經過pollfd.event傳入事件。內核經過修改pollfd.event反饋就緒事件。 內核經過事件表直接管理全部事件。無需反覆傳入事件。epoll_wait僅用來反饋就緒事件(不須要多餘判斷)
應用程序索引就緒文件描述符的時間複雜度 n n

1

最大支持文件描述符數 通常有最大值限制 65535 65535
工做模式 LT LT LT/ET
內核實現和工做效率 輪詢方式檢測就緒事件 輪詢 回調方式檢測就緒事件。

 

7:信號。

  1,函數

  2,零散細節。

    1)服務器程序必須處理(至少能忽略)一些常見信號。以免異常終止。

    2)信號是一種異步事件:信號處理函數和程序的主循環是兩條不一樣的執行路線。因此信號須要儘快完成。典型的解決方案:信號僅提供信號,具體處理儘可能方在主循環中。

8:時間堆:將全部定時器超時時間最小的一個定時器的超時值做爲心搏間隔。

9:高性能IO框架庫。

  1,常見框架:ACE,ASIO,Libevent。

  2,linux服務器程序必須處理三類事件:IO,信號和定時事件。但須要考慮三個問題

    1)統一事件源。須要統一管理三類事件。通常方法爲:利用IO複用系統調用來管理全部事件。

    2)可移植性。

    3)對併發編程的支持。

  3,IO框架庫以庫函數的形式,封裝了較爲底層的系統調用,給應用程序提供了一組更便於使用的接口。

  4,Libevent框架庫。

    1)跨平臺支持,擁有統一事件源,線程安全,基於Reactor模式。

    2)官方網站:libevent.org

  5,P240,須要執行代碼確認

10:多線程編程。

  1,線程實現方式:徹底在用戶空間實現,徹底由內核調度和雙層調度。

    1)徹底在用戶空間實現優勢:建立和調度線程無需內核的干預,速度很快。建立多各線程對系統性能沒有太大影響。但沒法在多CPU狀況下使用。

    2)內核調度:和用戶空間徹底相反。

    3)雙層調度:上兩種模式的混合體。

11:進程池和線程池。

  1,線程池中的線程數量應該和CPU數差很少。

  2,進程之間(有關聯進程)傳遞數據最簡單的方法是管道。

12:服務器調製,調試和測試。

  1,linux平臺的一個優秀特性是內核微調:能夠經過修改文件的方式來調正內核參數。

  2,調試方法:tcpdump抓包看數據,或者gdb。

  3,linux對應用程序能打開的最大文件描述符數量有兩個層次的限制:用戶級限制和系統級限制。

  4,gdb調試多進程的兩個簡單方法:

    1)找到PID,而後使用attach添加到gdb中。

    2)set follow-fork mode(parent or child)

  5,gdb調試多線程的方法。

    1)info threads:顯示當前可調試的全部線程。

    2)threadID:調試ID線程

    3)set scheduler-locking(off|on|step):off全部線程執行,on只有當前線程執行,step單步調試時,只有當前線程會執行。

  6,壓力測試:IO複用,多線程,多進程併發編程等方法(不懂- -)。

13:經常使用工具。

  1,tcpdump:經典的網絡抓包工具

  2,lsof:列出當前系統打開的文件描述符的工具。

  3,nc:用來快速構建網絡鏈接。

    1)服務器方式運行時:監聽某個端口並接收客戶端鏈接。

    2)客戶端方式運行時:能夠向服務器發起鏈接並收發數據。

    3)因此能夠用來調試客戶端和服務器。

  4,strace:測試服務器性能的工具。

    1)跟蹤程序運行過程當中執行的系統調用和接收到的信號,並將系統調用名、參數、返回值及信號名數處到標準輸出或指定文件。

  5,netstat:網絡信息統計工具。

    1)能夠打印本地網卡接口上的所有鏈接、路由表信息、網卡接口信息。

  6,ifstat:interface statistics。簡單的網絡流量檢測工具。

  7,mpstat:multi-processor statistics。能實時監測多處理器系統上的每一個CPU的使用狀況。

相關文章
相關標籤/搜索