《UNIX網絡編程卷1》--筆記

1. 簡介

  • POSIX:Portable Operating System Interface of UNIX,便可移植操做系統接口
  • ISO:International Organization for Standardization,即國際標準化組織
  • IEC:International Electrotechnical Commission,即國際電工委員會
  • Inc:the Institute for Electrical and Electronics Engineers,即電氣與電子工程師協會
  • ANSIC C :American National Standards Institute,美國國家標準協會(ANSI)及國際標準化組織(ISO)推出的關於C語言的標準

2. 傳輸層:TCP、UDP、SCTP

2.5 流控制傳輸協議(SCTP)

  • SCTP中使用」關聯」一詞取代」鏈接」是爲了不這樣的內涵:鏈接只涉及兩個IP地址之間的通訊,一個」關聯」指代兩個系統之間的一次通訊,它可能由於SCTP支持多宿而涉及不知兩個地址
  • SCTP是面向消息的

2.8 TIME_WAIT狀態

  • Time_wait狀態存在乎義
    1, 可靠實現TCP全雙工鏈接的終止:避免最後一個ack丟失,提供重傳機制
    2, 容許重複分節在網絡中消逝:防止端口被重用,引起錯誤

。。。。。。。(先把理論放一下)node

3. 套接字編程簡介

3.2.1 IPv4套接字的地址結構

  • 發現個人linux上struct sockaddr_in是這樣定義的:
struct sockaddr_in
  {
    __SOCKADDR_COMMON (sin_);
    in_port_t sin_port;         /* Port number. */
    struct in_addr sin_addr;        /* Internet address. */

    /* Pad to size of `struct sockaddr'. */
    unsigned char sin_zero[sizeof (struct sockaddr) -
               __SOCKADDR_COMMON_SIZE -
               sizeof (in_port_t) -
               sizeof (struct in_addr)];
  };

確實符合POSIX標準,不過沒有增長額外字段linux

  • 使用的地址的時候須要當心,注意in_addr是一個結構,而in_addr_t是一般是一個無符號32位整數
  • sin_zero字段不曾使用,最好置0(綁定一個非通配的IPv4的時候必須爲0)
  • 套接字地址結構僅在給定主機上使用,不在主機之間傳遞

3.3.2 通用套接字的地址結構

  • 由於套接字地址結構老是以引用(指針)方式來傳遞,而套接字函數必須兼容任何協議簇的套接字地址結構,,固然有了ANSI C 以後能夠用void *解決,不過這是1983年的事,1982年的解決辦法是定義一個通用的地址結構:
struct osockaddr
  {
    //uint8_t sa_len;書上還有這一句話
    unsigned short int sa_family;
    unsigned char sa_data[14];
  };
  • 這就要求對指向任何 特定於協議的套接字地址結構的指針進行強制轉化,例如:
struct sockaddr_in serv;
/* fill in serv{} */
bind(sockd, (struct sockadd *) &serv, sizeof(serv));

3.2.3 IPv6套接字地址結構

  • 個人機器中是下面這樣定義的,注意順序通過編排(從小到大),見Checklist 6.1,使得128的sockaddr_in6也按64位對齊(若是自己64位對齊)
/* IPv6 address */
struct in6_addr
  {
    union
      {
    uint8_t __u6_addr8[16];
#ifdef __USE_MISC
    uint16_t __u6_addr16[8];
    uint32_t __u6_addr32[4];
#endif
      } __in6_u;
#define s6_addr __in6_u.__u6_addr8
#ifdef __USE_MISC
# define s6_addr16 __in6_u.__u6_addr16
# define s6_addr32 __in6_u.__u6_addr32
#endif
  };
/* Ditto, for IPv6. */
struct sockaddr_in6
  {
    __SOCKADDR_COMMON (sin6_);
    in_port_t sin6_port;    /* Transport layer port # */
    uint32_t sin6_flowinfo; /* IPv6 flow information */
    struct in6_addr sin6_addr;  /* IPv6 address */
    uint32_t sin6_scope_id; /* IPv6 scope-id */
  };

3.2.5 套接字地址結構比較

這裏寫圖片描述

3.3 值-結果參數

  • 從進程到內核傳遞傳遞套接字地址結構的函數有三個:bing,connect,sendto
  • 從內核到進程的有:accept,recvfrom,getsockname,getpeername
struct sockaddr-un cli;  /* Unix domain */
socklen_t len;
len= sizeof(cli);
/ len is a value */
getpeername( unixfd,(SA *) &cli, &len);
/* len may have changed */

注:當函數被調用時,結構大小是一個值(此值告訴內核該結構的大小,使內核在寫此結構時不至於越界),當函數返回時,結構大小又是一個結果(它告訴進程內核在此結構中確切存儲了多少信息),這種參數類型叫值結果參數
### 3.4字節排序函數
+ 大端:高字節放起始地址,如0x0102,內存中放的是0x0102
+ 小端:高字節放高地址,低字節放低地址如0x0102,內存中放的是0x0201
+ 網際協議使用大端字節序傳送多字節整數git

#include <netinet/in h>
uint16_t htons (uint16t host16bitvalue): //h:host
uint32_t htonl (uint32t host32bitvalue); //n:network
uint16_t ntohs (uint16t net16bitvalue);  //s:short
uint32_t ntohl (uint32t net32bitvalue);  //l:long

3.5 字節操縱函數

  • str開頭:處理C字符串(即以\0結尾)
  • b開頭:起源與4.2BSD,這裏給出源自Berkeley的函數:
#ifdef __USE_MISC
/* Copy N bytes of SRC to DEST (like memmove, but args reversed). */
extern void bcopy (const void *__src, void *__dest, size_t __n)
     __THROW __nonnull ((1, 2));

/* Set N bytes of S to 0. */
extern void bzero (void *__s, size_t __n) __THROW __nonnull ((1));

/* Compare N bytes of S1 and S2 (same as memcmp). */
extern int bcmp (const void *__s1, const void *__s2, size_t __n)
     __THROW __attribute_pure__ __nonnull ((1, 2));

之後不要用memset了,bzero()好記多了github

  • mem開頭的,來看一下ANSI C給出的函數
__BEGIN_NAMESPACE_STD
/* Set N bytes of S to C.  */
extern void *memset (void *__s, int __c, size_t __n) __THROW __nonnull ((1));

/* Compare N bytes of S1 and S2.  */
extern int memcmp (const void *__s1, const void *__s2, size_t __n)
     __THROW __attribute_pure__ __nonnull ((1, 2));
__BEGIN_NAMESPACE_STD
/* Copy N bytes of SRC to DEST.  */
extern void *memcpy (void *__restrict __dest, const void *__restrict __src,
             size_t __n) __THROW __nonnull ((1, 2));
/* Copy N bytes of SRC to DEST, guaranteeing
   correct behavior for overlapping strings.  */
extern void *memmove (void *__dest, const void *__src, size_t __n)
     __THROW __nonnull ((1, 2));
__END_NAMESPACE_STD

/* Copy no more than N bytes of SRC to DEST, stopping when C is found.
   Return the position in DEST one byte past where C was copied,
   or NULL if C was not found in the first N bytes of SRC.  */
#if defined __USE_MISC || defined __USE_XOPEN
extern void *memccpy (void *__restrict __dest, const void *__restrict __src,
              int __c, size_t __n)
     __THROW __nonnull ((1, 2));
#endif /* Misc || X/Open.  */
  • 源字節串與目標字節串重疊時:bcopy能正確處理,memcpy不能
  • 助記:
    • desc = src
    • ANSI C的memxxx函數都須要一個長度參數且都在最後
    • memcmp:ptr1>ptr2返回大於0

3.6 inet_aton、inet_addr、inet_ntoa函數

  • int inet_aton(const char *strptr, struct in_addr *addrptr);
    • 將strptr指向的C字符串轉換成32位網絡字節序二進制值,經過addrptr存儲,若addrptr爲空,不存儲有效值
  • in_addr_t inet_addr(const char *strptr);
    • 功能同上,出錯返回INADDR_NONE常量(一般全1,即廣播地址)或-1
  • char *inet_ntoa(struct in_addr inaddr);
    • 網絡字節序二進制值轉換成C字符串

3.7 inet_pton和inet_ntop函數

  • int inet_pton(int family, const char *strptr, void *addrptr);
    • family:AF_INET或AF_INET6
    • 成功返回1
  • const char inet_ntop(int family, const char *strptr, char strptr, size_t len);
    • 指定size_t防止溢出
      • #define INET_ADDRSTRLEN 16
      • #define INET6_ADDRSTRLEN 46
    • 總結
      地址轉換
/* include inet_pton */
int
inet_pton(int family, const char *strptr, void *addrptr)
{
    if (family == AF_INET) {
        struct in_addr  in_val;

        if (inet_aton(strptr, &in_val)) {    //調用了上一節講的函數,返回的是C字符串形式的IP地址
            memcpy(addrptr, &in_val, sizeof(struct in_addr));
            return (1);
        }
        return(0);
    }
    errno = EAFNOSUPPORT;
    return (-1);
}
/* end inet_pton */

3.8 scok_ntop和相關函數

  • 看部分自定義源碼
/* include sock_ntop */
char *sock_ntop(const struct sockaddr *sa, socklen_t salen)
{
    char        portstr[8];
    static char str[128];       /* Unix domain is largest */

    switch (sa->sa_family) {
    case AF_INET: {
        struct sockaddr_in  *sin = (struct sockaddr_in *) sa;
        //地址轉換:成功則返回c字符串形式的IP地址,str指定轉換格式
        if (inet_ntop(AF_INET, &sin->sin_addr, str, sizeof(str)) == NULL)
            return(NULL);
        //字節排序:網絡轉換爲主機的字節序
        if (ntohs(sin->sin_port) != 0) {
            snprintf(portstr, sizeof(portstr), ":%d", ntohs(sin->sin_port));
            strcat(str, portstr);
        }
        return(str);
    }
    //。。。。。。

3.9 字節流套接字

  • 不用一般問文件IO是由於內核中用於套接字的緩衝區可能已經到達了極限,會形成實際比請求的少
  • 以readn()爲例,看自定義的源碼,感受沒有特別要注意的地方
ssize_t                     /* Read "n" bytes from a descriptor. */
readn(int fd, void *vptr, size_t n)
{
    size_t  nleft;
    ssize_t nread;
    char    *ptr;

    ptr = vptr;
    nleft = n;
    while (nleft > 0) {

        //若是讀取失敗
        if ( (nread = read(fd, ptr, nleft)) < 0) {
            if (errno == EINTR) /* 查找EINTR錯誤,表示系統被一個捕獲信號中斷 */
                nread = 0;      /* and call read() again */
            else
                return(-1);
        //若是讀成功了 
        } else if (nread == 0)
            break;              /* EOF */
        //計算漏讀的字節數,再讀文件
        nleft -= nread;
        ptr   += nread;
    }
    return(n - nleft);      /* return >= 0 */
}
/* end readn */
  • readline函數由於效率低,書上給了一個高效的寫法(代碼太長,就不粘貼了)代碼在這

4. 基本TCP套接字編程

4.2 socket函數

  • int socket(int family, int type, int protocol)
    • family:指明協議簇
      • AF_INET :IPv4協議
      • AF_INET6 :IPv6協議
      • AF_LOCAL : Unix域協議
      • AF_ROUTE :路由套接字
      • AF_KEY : 密鑰套接字
    • type:套接字類型
    • protocol:傳輸協議
    • 返回:套接字描述符
  • 注意:有的傳輸協議只支持特定的套接字類型,如TCP只支持SOCK_STREAM,能夠填0由type和family組合默認選擇

4.3 connect函數

  • int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);
    • sockfd:socket()函數返回的套接字描述符
    • servaddr:指向套接字地址結構的指針
  • 若是是TCP套接字會激發三次握手過程,僅在鏈接創建成功或出錯時才返回,其中出錯返回可能有如下幾種狀況
    • TCP客戶沒有收到SYN分節的響應,則返回BTIMEDOUT錯誤
    • 對客戶的SYN的響應是RST(例如服務器進程也許沒在運行),這是硬錯誤會立刻返回
    • 客戶發出的SYN在中間的某個路由器上引起了一個「destination unreachable」(目的地不可達) ICMP錯誤(軟錯誤)–>24s後再發,共75s未回做爲EHOSTUNREACH或ENETUNREACH錯誤返回給進程
      • 按照本地系統的轉發表,根本沒有到達遠程系統的路徑
      • connect調用根本不等待就返回

4.3 bind()函數

  • int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
    • myaddr:由於有const修飾,因此若是使用默認值內核分配的端口是沒法返回的,不過能夠用getsockname()來返回協議地址

4.4 listen()函數

把一個未鏈接的套接字轉換成一個被動套接字,即從CLOSE轉換到LISTEN狀態算法

  • int listen(int sockfd, int backlog);
    • backlog:指定內核應爲相應套接字排隊的最大鏈接數
      • 未完成鏈接隊列:處於SYN_RCVD狀態
      • 已完成鏈接隊列:處於ESTABLISHED狀態
    • 能夠經過容許環境變量LISTENQ覆寫由調用者指定的值
    • 若是設置設置成未鏈接隊列最大長度易遭受SYN氾濫攻擊

4.5 accept()函數

  • int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
    • 用於從已完成隊列頭返回下一個已完成鏈接
    • cliaddr:返回客戶地址
    • addrlen:值-結果參數
    • 返回值:內核自動生成的一個全新描述符,也叫已鏈接套接字描述符,它的第一個參數叫監聽套接字描述符

4.6 fork()函數和exec()函數

  • pid_t fork(); :這個函數再《UNIX環境高級編程》有更詳細的敘述
  • execX(...):見書本90頁
    這裏寫圖片描述

4.9 close()函數

  • int close(sockfd);:能夠用來關閉套接字,並終止TCP鏈接
  • 確實想終止鏈接能夠用 shutdown()函數

4.10 getsockname()和getpeername()函數

  • int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen);:返回本地IP和端口號
    • sockfd:必須時已鏈接套接字描述符(socket()函數的返回值)
  • int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);
    • 獲取客戶身份的惟一途徑

5. TCP 客戶/服務器程序實例

  • 1.編譯代碼: gcc tcpserv01.c -o serv -L. ../libunp.a
    gcc tcpcli01.c -o cli -L. ../libunp.a
  • 2.運行代碼:./serv &
    ./cli
  • 3.查看運行狀況:netstat -ant
  • 4.產看進程間關係:ps -ef –forest | sed -n ‘/tcp/p’
  • 5.客戶端進程輸入:ctrl+D,就是EOF
  • 6.查看:netstat -ant | grep 9877 ,獲得下面的輸出結果(-代替空格)編程

    tcp—0—-0 0.0.0.0:9877———-0.0.0.0:*————-LISTEN
    tcp—-0—-0 127.0.0.1:39960—-127.0.0.1:9877—-TIME_WAIT api

  • 7.查看進程:ps -ef –forest -o pid,ppid,tty,stat,args,wchan | sed -n ‘/tcp/p’數組

110361 109527 pts/18   S     \_ ./tcpserv01 XDG_VTNR=7  inet_csk_accept
110366 110361 pts/18   Z     |   \_ [tcpserv01] <defunc -
110517 109527 pts/18   S+    \_ sed -n /tcp/p XDG_VTNR= pipe_wait

能夠看到僵死進程緩存

5.8 POSIX信號處理

  • 1.signal:有時也叫軟件中斷(software interrupt),每一個信號都有與之關聯的處置,也叫行爲
    • 1.除SIGKILL和SIGSTOP兩個信號不能被忽略外,其餘信號均可以設置成SIG_IGN來忽略
    • 2.設SIG_DFL來啓用默認設置
  • 2.signal函數原型:void (*signael (int signo, void (*fun)(int)))(int);
    • 1.爲了簡化添加宏定義:#typedef void Sigfunc(int);
    • 2.簡化後:Sigfunc *signal(int signo, Sigfunc *func);

5.9 處理SIGCHLD信號

SIGCLD:在一個進程終止或者中止時,將SIGCHLD信號發送給其父進程;設置僵死進程是爲了維護子進程信息,不處理可能致使耗盡資源服務器

5.10 wait和waitpid函數

#include<sys/wait.h>
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid, int *statloc, int option);
  • 返回子進程的進程ID號
  • statloc:表明進程終止狀態的一個整數
  • option:附加選項,經常使用的是WNOHANG(告知沒有子進程時不終止,有還沒有終止的子進程在運行時不要阻塞)
  • (1)當fork子進程時,必須捕獲SIGCHLD信號;
  • (2)當捕獲信號時,必須處理被中斷的系統調用;
  • (3) SIGCHLD的信號處理函數必須正確編寫,應使用waitpid函數以避免留下僵死進程。

5.11 accept()返回鏈接前終止

這裏寫圖片描述
+ 服務器:已由TCP排隊,等待進程調用accept()卻受到RST,稍後執行accept()
+ 處理方法:
+ Berkeley:徹底在內核中處理已終止的鏈接,服務器進程根本看不到
+ 大多數SVR4:返回一個EPROTO(protocol error:協議錯誤)
+ POSIX:errno值必須是ECONNABORTED(software caused connection abort,軟件引發的鏈接終止),由於流子系統其餘一些致命協議相關事件時也會返回EPROTO

5.12 服務器進程終止

殺死服務器子進程(模擬服務器進程崩潰),server發送FIN,然而client阻塞在fgets(同時應對兩個文件描述符,套接字和用戶輸入),從未沒法返回ACK,此時鍵入一行文本,客戶端將發送到服務器,此時有兩種狀況:
+ 1.調用readline(),而後收到RET,因爲前面的FIN,readline()馬上返回0,收到未預期的EOF,提示出錯信息:server terminated prematurely,(服務器過早終止)
+ 先收到RET,調用readline()時返回一個ECONNRESET(connection reset by peer),對方復位鏈接錯誤

5.13 SIGPIPE信號

上節狀況下的客戶端不理會readline()的錯誤反而寫入更多的數據到服務器,即一個進程向某個已收到RST的套接字執行寫操做,內核會向進程發送一個SIGPIPE信號,默認行爲是終止進程。不管是捕獲仍是忽略該信號都將返回EPIPE錯誤。沒有特殊事情要作就設置成SIG_IGN(忽略)
+ 須要注意:若是使用多個套接字,該信號的提交無法告訴咱們是哪個套接字出錯了
- 因此要麼不理會
- 要麼從信號函數返回後再處理來自wirite的EPIPE

5.14 服務主機崩潰

  • 服務器崩潰後再向服務器發送數據,客戶TCP會持續重傳數據分節,試圖重服務器接收一個ACK,會有三種結果:
    • 1.對客戶TCP數據分節沒有響應:返回ETIMEDOUT
    • 2.ICMP不可達,返回 EHOSTUNREACH
    • 3.ICMP不可達,返回 ENETUNREACH

5.15 服務器崩潰後重啓

  • 崩潰的主機重啓後丟失崩潰前的全部鏈接信息,再次收到客戶TCP的信息返回RST,當收到服務器的RST時,客戶TCP正阻塞於readline(),的致使返回ECONNRESET錯誤

5.16 服務器主機關機

  • unix關機時,init進程先給全部進程發送 SIGTERM(sigterm,每每5~20s時間),而後給全部進程發送SIGKILL,後面發生的事情跟5.12節同樣

5.17 TCP程序例子小結

  • 本地端口由bind指定,bind指定的IP地址一般是通配IP地址。
  • 多宿主機上綁定通配IP地址後能夠經過getsockname肯定本地IP
  • 兩個外地址由accept調用返回給服務器(4.10節講了)

5.18 數據格式

  • 穿越套接字傳遞二進制結構絕對是不明智的
    • 能夠把全部的數據看成文本串來傳遞
    • 顯示定義全部數據的二進制格式,並以這樣的格式在客戶端與服務器之間傳遞數據。遠程過程調用(remote Procedure call,RPC)軟件包一般使用這種技術

6. I/O複用:select和poll函數

6.1 概述

  • I/O複用:內核一旦發現進程指定的一個或多個I/O條件就緒(也就是說輸入已準備好被讀取,或者描述符已能承接更多的輸出),就通知進程。

6.2 I/O模型

  • I/O模型
    • 阻塞式I/O:系統直到數據包到達且被複制到應用進程緩衝區或發生錯誤才返回
    • 非阻塞式I/O:應用進程持續輪詢(polling)內核,以查看操做是否就緒(對一個非阻塞描述符循環調用recvfrom)。
    • I/O複用(select 和 poll):阻塞在這兩個系統調用的某一個上,而不是阻塞在真正的I/O系統調用上,優點在於能夠等待多個描述符就緒
    • 信號驅動式I/O(SIGIO):當數據報準備好讀取時,內核爲進程產生一個SIGIO信號
    • 異步I/O(POSIX的aio_系列函數):告知內核啓動某個動做,並讓內核在整個操做完成後通知咱們。
      輸入操做:1.等待數據準備好,2.從內核向進程複製數據
      這裏寫圖片描述

6.3 select函數

  • int select(int maxfdp1,fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);
    • timeout:告知內核等待指定描述符中的任何一個就緒須要花多少時間
    • struct timeval {
      long tv_sec; /* seconds */
      long tv_usec; /* microseconds ,許多UNIX向上舍10ms整數倍,再加調度延遲,時間更不許確*/
      }

      • 表示永遠等待下去:置空指針,僅在有描述符準備好I/O時才返回
      • 等待一段固定的時間:由timeout指定
      • 根本不等待:定時器值置爲0,這稱爲輪詢(poll)
    • fd_set變量使用例子(maxfdp1設置爲6):注意時值-結果參數(返回之後須要從新對感興趣的位置1)
      • fd_set rset;
        FD_ZERO(&rset);
        FD_SET(1, &rset);
        FD_SET(4, &rset);
        FD_SET(5, &rset);
    • 計時器到時返回0,-1表示出錯
  • 描述符就緒的條件
    • 一個套接字準備好讀的狀況:
      • 1.接收緩衝區中字節數 >= 接收緩衝區低水位標記的當前大小(默認1,由SO_RCVLOWAT設置)
      • 2.讀半部關閉(接收了FIN)將不阻塞並返回0
      • 3.監聽套接字的已鏈接數不爲0,這時accept一般不阻塞
      • 4.其上有一個套接字錯誤待處理,返回-1,error設置成具體的錯誤條件,可經過SO_ERROR套接字選項調用getsockopt獲取並清除
    • 一個套接字準備好寫
      • 1.以鏈接套接字或UDP套接字發送緩衝區中的可用字節數 >= 發送緩衝區低水位標記的當前大小(默認2048,可用SO_SNDLOWAT)
      • 2.寫半部關閉的套接字,寫操做將產生一個SIGPIPE信號
      • 3.非阻塞式connect的套接字已創建鏈接,或者connect以失敗了結
      • 4.其上有一個套接字錯誤待處理,返回-1,error設置成具體的錯誤條件,可經過SO_ERROR套接字選項調用getsockopt獲取並清除
        這裏寫圖片描述
  • 混合使用stdio和select被認爲是很是容易犯錯誤的
    • readline緩衝區中可能有不完整的輸入行
    • 也可能有一個或多個完整的輸入行

6.6 shutdown 函數

int shutdown(int sockfd, int howto)

  • close()把描述符的引用計數減1,shutdown直接激發TCP的正常鏈接序列的終止
  • shutdown告訴對方我已近完成了數據的發送(對方仍然能夠發給我)
  • SHUT_RD:關閉鏈接的讀這一半
    • 能夠把第二個參數置爲SHUT_RD防止迴環複製
    • 關閉SO_USELOOPBACK套接字選項也能防止迴環
  • SHUT_WR:關閉鏈接的寫這一半,也叫半關閉
  • SHUT_RDWR:鏈接的讀半部和寫半部都關閉

6.8 TCP回射服務器程序

見代碼註釋:github連接

6.9 pselect函數

#include<sys/select.h>
#include<signal.h>
#include<time.h>
int pselect(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *execptset, const struct timespec *timeout,const sigset_t *sigmask);

相比較於select:
+ 1.使用timespec 而不用timeval

struct timespec {
    time_t tv_sec;  //秒
    long tv_nsec;   //注意:這個表示納秒
}
  • 2.增長了第六個參數:指向信號掩碼的指針
    • 容許先禁止遞交某些信號,再測試由禁止信號的信號處理函數設置全局變量,而後調用pselect,告訴新設置的信號掩碼

6.10 poll函數

功能與select相似,不過在處理流設備時能提供更多的信息

#include <poll.h>
int poll(struct pollfd *fdarray, unsigned long nfds, int timeout);
  • fdarray:pollfd結構體指針


    • struct pollfd {
      int fd ; //須要檢查的描述符,負數將被忽略
      short events; //測試條件
      short revents; //返回的描述符狀態
      }
  • nfds:結構體數組中元素的個數
  • timeout:返回前須要等待多長的時間
    • INFTIM:永遠等待,可能在

6.11 TCP回射服務器程序(再修版)

見代碼註釋:github連接


7. 套接字選項

7.2 getsockopt和setsockopt函數:僅用於套接字

#include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname, void *optname, void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
均返回:成功0,出錯-1
  • sockfd:一個打開的套接字描述符
  • level:(級別)指定系統中解釋選項的代碼或爲通用套接字代碼,或爲特定於某個協議的代碼(IPV四、IPV六、TCP、SCTP)
  • optval:指向某個變量的指針,set..經過它獲取新值,get..把已獲取的選項當前值存放到*optval
  • optlen:optval的長度
  • 套接字選項粗分爲兩大基本類型:
    • 標誌選項:啓用或禁止某個特性的二元選項
      • getsockopt:optval爲0表示禁止,不然表示啓用
      • setsockopt:optval爲0用來禁止
    • 值選項:取得並返回咱們能夠設置或檢查的特定值選項
      • 用戶進程與系統之間傳遞所指數據類型的值

7. 套接字選項

7.2 getsockopt和setsockopt函數:僅用於套接字

#include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname, void *optname, void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
均返回:成功0,出錯-1
  • sockfd:一個打開的套接字描述符
  • level:(級別)指定系統中解釋選項的代碼或爲通用套接字代碼,或爲特定於某個協議的代碼(IPV四、IPV六、TCP、SCTP)
  • optval:指向某個變量的指針,set..經過它獲取新值,get..把已獲取的選項當前值存放到*optval
  • optlen:optval的長度
  • 套接字選項粗分爲兩大基本類型:
    • 標誌選項:啓用或禁止某個特性的二元選項
      • getsockopt:optval爲0表示禁止,不然表示啓用
      • setsockopt:optval爲0用來禁止
    • 值選項:取得並返回咱們能夠設置或檢查的特定值選項
      • 用戶進程與系統之間傳遞所指數據類型的值

7.3 檢查選項是否受支持並獲取默認值

見註釋:代碼連接

7.4 套接字狀態

accept一直要到三次握手完成之後才返回給服務器已鏈接的套接字,想在三次握手完成時確保這些套接字選項中的某一個是給 已鏈接套接字 設置的,必須先設置 監聽套接字

7.5 通用套接字選項

7.5.1 SO_BROADCAST

  • 本選項開啓或禁止進程發送廣播(僅數據報套接字支持,且需在支持廣播消息的網絡上如以太網和令牌環網)
  • 能夠防止沒有設置成廣播時發送廣播數據:如UDP發送一個的目的地址是一個廣播地址,可是該選項沒設置,就會返回EACCES錯誤

7.5.2 SO_DEBUG

僅由TCP支持,選項開啓時內核將爲TCP在該套接字發送和接收全部分組保留詳細信息,可用trpt查看

7.5.3 SO_DONTROUTE

規定外出分組將繞過底層協議的正常路由機制,用來繞過路由表,以強制將分組從特定的接口發出

7.5.4 SO_ERROR(可獲取不能設置)

套接字發生錯誤時,將套接字的so_error變量設置爲爲Unix Exxx值中的一個,也叫待處理錯誤(pending error),能夠用下面兩種方式中的一種當即通知進程
+ 1.阻塞在select時,返回設置RW中一個或兩個條件
+ 2.信號驅動IO模型:產生SIGIO信號,進程經過訪問SO_ERROR獲取so_error的值getsockopt()返回的整個數值就是待處理錯誤,處理後復位爲0
+ 阻塞在read()且沒有數據且so_error非0,返回-1,error設置爲so_error的值,so_error設置爲0。
+ 阻塞在read()但有數據,則返回數據
+ 調用write時so_error非0,返回-1,error設置爲so_error的值,so_error設置爲0

7.5.5 SO_KEEPALIVE

設置保活選項後,2小時後(期間沒有數據)TCP自動發送保活探測分節(keep-alive probe),會致使三種狀況
+ 1.以指望ACK響應,進程得不到通知
+ 2.響應RST,表對端已崩潰並重啓,套接字的待處理錯誤設置爲ECONNRESET
+ 3.沒有任何響應,間隔75s再發8個探測分節,11m15s後放棄且帶錯李錯誤設置爲ETIMEOUT.若是收到ICMP錯誤就返回相應錯誤。
+ 這是一個清理通向不可達客戶的半開鏈接的好方法

7.5.6 SO_LINGER

本選項指定close()函數對面向鏈接的協議如何操做,默認當即返回,若是有數據殘留將嘗試把這些數據發送給對端
檢測TCP條件的各類方法
要求傳送給內核以下結構:

struct linger {
     int l_onoff;    /*0=off,l_linger被忽略,>nonzero=on*/
     int l_linger;   /*linger time*/
}
  • linger=0:丟棄緩衝區的任何數據,發送RST給對端,沒有四分節終止序列,可避免TCP的TIME_WAIT狀態。可能引起錯誤:在2MSL內建立另外一個化身,剛終止的鏈接上的舊的分節不被正確的傳遞到新的化身上
  • linger!=0,套接字關閉時內核拖延一段時間:進程將睡眠到全部數據已發送並被確認或延滯時間到
  • 套接字是非阻塞型的,延滯時間到以前數據沒發送完返回EWOULDBLOCK錯誤
  • close()成功返回只能說明發送的數據和FIN已有對端確認,但不表明進程已經讀取,因此改用shutdown好一點,固然也能用應用級ACK
    shutdown和SO_LINGER狀況總結

7.5.7 SO_OOBINLINE

  • 帶外數據將被留存在正常的輸入隊列中(即在線留存),此時接收函數的MSG_OOB標誌不能用來讀取帶外數據

7.5.8 SO_CVBUF和SO_SNDBUF

  • 套接字接收緩衝區中可用空間大小限定了TCP通告對端窗口的大小
  • 注意順序:窗口規模選項是在創建鏈接時用SYN分節獲得的,因此客戶需在connect以前,serv需在listen以前
  • 根據快恢復算法,緩衝區大小至少應是MSS值的四倍,最好是偶數倍

7.5.9 SO_RCVLOWAT和SO_SNDLOWAT(低水位標記)

  • 接收低水位標記:select返回可讀時接收緩衝區所需的數據量,T/UDP、SCTP默認爲1
  • 發送緩衝區:select()返回可寫時發送緩衝區所需的可用空間。tcp默認2048,UDP的發送緩衝區的可用字節數從不改變(不保留副本)

7.5.10 SO_RCVTIMEO和SO_SNDTIMEO

設置超時值,默認設置爲0,即禁止超時

7.5.11 SO_REUSEADDR和SO_REUSEPORT(重用地址端口)

  • 監聽服務器終止,子進程繼續處理鏈接,重啓監聽服務器時會出錯,而開啓了SO_REUSEADDR就不會。
  • SO_REUSEADDR容許同一個端口的多個服務器實例(只要不一樣的本地IP地址便可),通配地址捆綁通常放到最後
  • SO_REUSEADDR容許同一個端口捆綁同一個進程的不一樣套接字
  • SO_REUSEADDR容許UDP徹底重複捆綁(通常來講),用於多播

7.5.12 SO_TYPE

  • 本選項返回套接字類型,返回值是一個諸如SOCK_STREAM或SOCK_DGRAM之類的值,一般由啓動時繼承了套接字的進程使用

7.5.12 SO_USELOOPBACK

僅用於路由域(AF_ROUTE)套接字,默認打開。開啓時,相應套接字將接收在其上發送的任何數據報的一個副本。

7.6 IPv4套接字選項

級別爲IPPROTO_IP(get/setsockopt()的第二個參數)

7.6.1 IP_HDRINCL

  • 若是是給原始IP套接字設置的,必須本身構造首部,下列狀況例外:
    • 見Page168

7.6.2 IP_OPTIONS

容許在IPv4首部總設置IP選項

7.6.3 IP_RECVDSTADDR

  • 開啓致使所收到的UDP數據報的目的地址由recvmsg做爲輔助函數返回

7.6.4 IP_RECVIF

  • 開啓致使所收到的UDP數據報的接收接口索引由recvmsg函數做爲輔助函數返回

7.6.5 IP_TOS

  • 本套接字選項容許咱們爲TCP、UDP、SCTP設置IP首部中的服務類型字段。。。

7.6.6 IP_TTL

  • 用於設置或獲取系統用在從某個給定套接字發送的單播分組上的默認TTL值

7.7 ICMPv6套接字選項

  • 級別爲IPPROTO_ICMPV6
  • ICMP6_FILTER:用於獲取或設置一個icmp6_filter結構

7.8 IPv6套接字選項

  • 級別爲:IPPORTO_IPV6

7.8.1 IPV6_CHECKSUM

  • 指定用戶數據中校驗和所處位置的字節偏移

7.8.2 IPV6_DONTFRAG

  • 禁止爲UDP套接字或原始套接字自動插入分片首部,將外出分組中大小超過發送接口MTU的那些分組將被丟棄

7.8.3 IPV6_NEXTHOP

  • 將外出數據報的嚇一跳地址指定爲一個套接字地址結構

7.8.4 IPV6_PATHMTU

  • 返回路徑MTU發現功能肯定的當前MTU

7.8.5 IPV6_RECVDSTOPTS

  • 任何接收的IPv6地址都將由recvmsg做爲輔助函數返回,默認關閉

7.8.6 IPV6_RECVHOPLIMIT

  • 開啓後,任何接收的跳限字段都將由recvmsg做爲輔助函數返回

7.8.7 IPV6_RECVHOPOPTS

  • 開啓時,任何接收的IPv6跳選項做爲輔助函數返回

。。。不懂這節在說什麼

7.9 TCP套接字選項

級別:IPPROTO_TCP

7.9.1 TCP_MAXSEG

  • 該選項容許設置或獲取TCP鏈接的最大分節大小,返回值是TCP能夠發送給對端的最大數據量

7.9.2 TCP_NODELAY

  • 本選項將禁止TCP的Nagle算法(防止一個鏈接在任什麼時候刻有多個小分組待確認)
  • Nagle算法一般和ACK延滯算法(delayed ACK algorithm),該算法使得TCP在接收到數據後延滯一段時間(50-200ms)再確認
    +對於客戶以若干小片發送數據向服務器發送單個邏輯請求:首選方法是調用writev(),次一點是兩次數據放到緩衝區而後調用write(),最次方法是先設置TCP_NODELAY再調用兩次write()

7.10 SCTP套接字選項

級別:IPPROTO_SCTP

………………………………………(後面再看)

7.11 fcntl函數

  • fcntl()函數(file control)可執行各類描述符控制操做
    小結
#include <fcntl.h>
int fcntl(int fd, int cmd, .../* int arg */);
  • 每種描述符都有一組由F_GETFL命令獲取或由F_SETFL命令設置的文件標誌,影響套接字描述符的有兩個:O_NONBLOCK(非阻塞式IO),O_ASYNC(信號驅動式IO)
  • 正確設置非阻塞式IO的寫法:
int flag;
/* Set a socket as nonblocking */
if((flag=fcntl(fd, F_GETFL, 0)) < 0){    //必需要先獲取其餘文件標誌
    err_sys("F_GETFL, error");
}
flag |=O_NONBLOCK;                       //或運算,打開非阻塞標誌
if(fcntl(fd, F_SETFL, flags) <0 ){
    err_sys("F_SETFL error");
}
flag &=~O_NONBLOCK;                      //與運算,關閉非阻塞標誌
if(fcntl(fd, F_SETFL, flags) <0 ){
    err_sys("F_SETFL error");
}
  • F_SETOWN的參數是正值則指出接收信號的進程ID,是負數則絕對值指出信號的組ID
  • F_GETOWN與上面相似
  • 使用socket()函數建立的套接字沒有屬組。若是一個新的套接字是從一個監聽套接字建立而來,屬組將繼承過來。

8. 基本UDP套接字編程

recvfrom()和sendto()函數

#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buff, size_t nbytes, int flag, struct sockaddr *from, socklen_t *addrlen);
ssize_t sendto(int sockfd, const void *buff, size_t nbytes, int flag, const struct sockaddr *to, socklen_t addrlen);
  • buff:接收的內容,能夠爲0
  • falg:將在後面討論
  • from:發送方的地址

8.3 UDP回射服務程序:main函數

代碼地址

8.5 UDP回射客戶程序

代碼地址

8.9服務器進程未運行

  • 用tcpdump工具能夠看到返回一個端口不可達的ICMP錯誤,不過不會通知進程,也叫異步錯誤。有一個重要的決定:僅在進程已將其UDP套接字鏈接到偏偏一個對端後,這個異步 錯誤才返回給進程

8.10 UDP程序例子小結

8-13_從IP數據報獲取信息

8.11 UDP的connect函數

  • 這裏的connect()不一樣於TCP,只檢查時候存在當即可知的錯誤,記錄對端IP地址和端口號,而後返回給進程。鏈接後主要發生三點變化:

    • 1.不指定目的地址,即不用sendto(或第六個參數爲空指針),改用write()或send().
    • 2.沒必要使用recvfrom(),而改用read(),recv(),recvmsg(),這說明一個UDP套接字僅與一個IP地址做數據交換(多播、廣播地址)
    • 3.異步錯誤將返回給進程
    • 4.可屢次調用connect(),以斷開套接字或指定新的IP地址和端口號
      • 最便於移植的方法是清零一個地址結構後把它的地址簇成員設置爲AF_UNSPEC
    • 若是要給同一個目的地址發送多個數據報, 顯式 connect()好一點

    /etc/resolv.conf:保存服務器主機IP的文件

8.12 dg_cli函數(修訂版)

代碼是書上的
+ 當鏈接一個沒有運行的udp服務器的程序時,鏈接不會出錯,可是發送數據時會返回一個目的端口不可達的ICMP錯誤,被內核映射成ECONNREFUSED,UnixWare內核不會返回這種錯誤(page200)

8.13 UDP缺少流量控制

  • netstat -s:顯示丟失的數據包
  • UDP發送端淹沒接收端是垂手可得的事
  • FreeBSD5.1限制一個套接字接收緩存區默認大下262 144字節(實際233 016字節)
  • 修改緩存區代碼

8.14 UDP的外出接口的肯定

  • connect()函數的一個反作用是可用來肯定特定目的地的外出接口(本地IP經過爲目的地址搜索路由表獲得)
  • 代碼見地址

8.15 使用select()的TCP和UDP回射服務器程序

8.16 小結

8.1
recvfrom返回2048個字節的數據,它不會返回大於一個數據報大小的數據

9. 基本SCTP套接字編程

9.1 概述

  • 是一個面向消息的協議,在端點之間提供多個流,併爲多宿提供傳輸級支持,

9.2 接口模型

  • 一到一形式(方便把現有TCP引應用移植到STCP中)
    • TCP套接字選項必須映射成SCTP選項
    • SCTP保存邊界消息
    • SCTP沒有半關閉,須要在數據流中告知對端
    • sendto或sendmesg指定的任何地址都被認爲是對目的地址的重寫,send()使用變普通
  • 一到多形式
    • 客戶關閉一個關聯時,服務器自動關閉
    • 在四路握手的三四分組中捎帶數據的惟一方式
    • 沒有創建的鏈接的IP使用sendto等致使對端主動打開嘗試
    • 用戶不能使用send()或write()
    • 任什麼時候候調用分組發送函數時所用的目的地址是在系統關聯創建階段選定的
    • 關聯事件可能被啓動

9.3 stcp_bindx()函數

#include <netinet/sctp.h>
int sctp_bindx(int sockfd, const struct sockaddr *addrs, int addrcnt, int flags);
  • SCTP容許捆綁主機一個特定的子集
  • sockfd:返回的套接字描述符
  • addrs:緊湊的地址列表指針
    int sctp_bindx(int sockfd, const struct sockaddr *addrs, int addrcnt, int flags);
  • addrcnt:操做的指針數
  • flags:
    • SCTP_BINDX_REM_ADDR :刪除
    • SCTP_BINDX_ADD_ADDR :增長
  • 全部地址結構和端口號必須相同

9.4 sctp_connectx()函數

#include <netinet/sctp.h>
int sctp_connectx(int sockfd, const struct sockaddr *addrs, int addrcnt);
Returns: 0 for success, –1 on error
  • 用於鏈接到一個多宿對端主機

9.5 sctp_getpaddrs()函數

#include <netinet/sctp.h>

int sctp_getpaddrs(int sockfd, sctp_assoc_t id, struct sockaddr **addrs);

Returns: the number of peer addresses stored in addrs, –1 on error
  • getpeername :僅僅返回主目的地址
  • sctp_getpaddrs:返回對端全部地址

9.6 sctp_freepaddrs()函數

#include <netinet/sctp.h>

void sctp_freepaddrs(struct sockaddr *addrs);
  • 上一個函數中addrs所指向 目的地址是動態分配的,須要用這個函數釋放

9.7 sctp_getladdrs()函數

#include <netinet/sctp.h>

int sctp_getladdrs(int sockfd, sctp_assoc_t id, struct sockaddr **addrs);

Returns: the number of local addresses stored in addrs, –1 on error
  • 用於回去某個關聯的本地地址

9.8 sctp_freeladdrs()函數

#include <netinet/sctp.h>

void sctp_freeladdrs(struct sockaddr *addrs);
  • 上一個函數中addrs所指向 目的地址是動態分配的,須要用這個函數釋放

9.9 sctp_sendmsg()函數

ssize_t sctp_sendmsg(int sockfd, const void *msg, size_t msgsz, const struct sockaddr *to, socklen_t tolen, uint32_t ppid, uint32_t flags, uint16_t stream, uint32_t timetolive, uint32_t context);

Returns: the number of bytes written, –1 on error
  • sockfd:socket()函數返回的套接字描述符
  • msg:長度爲msgsz字節的緩衝區,內容將發送到對端
  • ppid:指定將隨數據塊傳遞的淨荷協議標識符(the pay-load protocol identifier )
  • flag:傳遞給SCTP棧,用於標識SCTP選項
  • stream:SCTP流號
  • timetolive:生命週期,0表示無限長
  • context:指定可能有的用戶上下文

9.10 sctp_recvmsg()函數

ssize_t sctp_recvmsg(int sockfd, void *msg, size_t msgsz, struct sockaddr *from, socklen_t *fromlen, struct sctp_sndrcvinfo *sinfo, int *msg_flags);

Returns: the number of bytes read, –1 on error
  • 不只能獲取對端地址,也能獲取一般伴隨recvmsg()函數調用返回的msg_flags參數(e.g., MSG_NOTIFICATION, MSG_EOR, etc.)

9.11 sctp_opt_info()函數

int sctp_opt_info(int sockfd, sctp_assoc_t assoc_id, int opt void *arg, socklen_t *siz);

Returns: 0 for success, –1 on error
  • This inability to use the getsockopt function is because some of the SCTP socket options, for example, SCTP_STATUS, need an in-out variable to pass the association identification
  • assoc_id:給出可能存在的關聯標識
  • siz:存放參數大小

9.12 sctp_peeloff()函數

int sctp_peeloff(int sockfd, sctp_assoc_t id);

Returns: a new socket descriptor on success, –1 on error
  • 從一個一到多套接字中抽取一個關聯

9.13 shutdown()函數_

  • 用於一到一接口的SCTP端點(可是反應不一樣於TCP)
  • 發起關聯終止序列時,這兩個端點都得把已排隊的任何數據發送掉再關
  • SHUT_RD: The same semantics as for TCP discussed in Section 6.6;
  • SHUT_WR
    Disables further send operations and initiates the SCTP shutdown procedures, which will terminate the association
  • SHUT_RDWR
    Disables all read and write operations, and initiates the SCTP shutdown procedure. Any queued data that was in transit to the local endpoint will be acknowledged and then silently discarded.

9.14 通知

  • SCTP_EVENTS 套接字選項能夠預約8個事件
  • 當有通知時, recvmsg function or the sctp_recvmsg function 返回的msg_flags 將包含MSG_NOTIFICATION flag.
  • 通知採用TVL格式(tag-length-value)
  • 通知格式:
struct sctp_tlv {
  u_int16_t sn_type;
  u_int16_t sn_flags;
  u_int32_t sn_length;
};

/* notification event */
union sctp_notification {
  struct sctp_tlv sn_header;  //用於解釋類型值
  struct sctp_assoc_change sn_assoc_change;
  struct sctp_paddr_change sn_paddr_change;
  struct sctp_remote_error sn_remote_error;
  struct sctp_send_failed sn_send_failed;
  struct sctp_shutdown_event sn_shutdown_event;
  struct sctp_adaption_event sn_adaption_event;
  struct sctp_pdapi_event sn_pdapi_event;
};
  • 通知的類型舉例(sn_header. sn_type ),具體查閱page218
    • SCTP_ASSOC_CHANGE:關聯自己發生變更
    • SCTP_PEER_ADDR_CHANGE:對端的某個地址經歷了狀態變更
    • SCTP_REMOTE_ERROR:遠程端點可能給本地端點發送了一個操做性錯誤消息
    • SCTP_SEND_FAILED:沒法遞送到對端的消息經過本通知會送用戶
    • SCTP_SHUTDOWN_EVENT:對端發送一個SHUTDOWN 到本地端點
    • SCTP_ADAPTION_INDICATION:有些實現支持適應層指示參數,該參數在INIT和INIT-ACK中交換,用於通知對端將執行什麼類型的適應行爲
    • SCTP_PARTIAL_DELIVERY_EVENT:部分遞送應用程序接口用於經由套接字緩衝區向用戶傳遞大消息

9.15 小結

.。。。先放一下,留給個人時間很少了。

10.SCTP客戶/服務器程序例子

10.1 Introduction

這裏寫圖片描述

10.2 SCTP One-to-Many-Style Streaming Echo Server: main Function

  • lsmod | grep sctp:查看是否安裝了sctp
  • 而後安裝可用的安裝包,,,,
  • 忽然據說sctp章節先不看,,,i++

10.3 SCTP One-to-Many-Style Streaming Echo Client: main Function

10.4 SCTP Streaming Echo Client: str_cli Function

10.5 Exploring Head-of-Line Blocking

10.6 Controlling the Number of Streams

10.7 Controlling Termination

10.8 Summary

11. 名字與地址轉換

11.1 Introduction

11.2 Domain Name System (DNS)

  • 解析器使用UDP向本地名字服務器發出查詢,若是本地名字服務器不知道,再使用UDP在整個因特網中查詢其餘名字服務器(答案太長會自動切換到TCP)
  • /etc/hosts:靜態主機文件
  • FreeBSD 5.x、HP-UX 10..使用:/etc/nsswitch.conf
  • AIX使用:/etc/netsvc.conf
  • BIND 9.2.2 supplies its own version :/etc/irs.conf
  • Fortunately, these differences are normally hidden to the application programmer

11.3 gethostbyname()

#include <netdb.h>
//函數的侷限是隻能返回IPv4地址
struct hostent *gethostbyname (const char *hostname);

Returns: non-null pointer if OK,NULL on error with h_errno set
  • 返回指針結構(都以空字符結尾)
struct hostent {
   char  *h_name;       /* 主機的規範名字 */
   char **h_aliases;    /* 別名 */
   int    h_addrtype;   /* host address type: AF_INET */
   int    h_length;     /* length of address: 4 */
   char **h_addr_list;  /* ptr to array of ptrs with IPv4 addrs */
};
  • 下面這張圖對這些參數說得很清楚
    hostent結構
    代碼地址

11.4 gethostbyaddr()

#include <netdb.h>

struct hostent *gethostbyaddr (const char *addr, socklen_t len, int family);

Returns: non-null pointer if OK, NULL on error with h_errno set
  • 在in_addr.arpa域中查詢PTR記錄
  • addr實際是一個指向存放in_addr結構體指針

11.5 getservbyname() and getservbyport()

  • /etc/services:名字到端口的映射
#include <netdb.h>

struct servent *getservbyname (const char *servname, const char *protoname);

Returns: non-null pointer if OK, NULL on error
  • 返回的結構以下:
struct servent {
  char   *s_name;      /* official service name */
  char  **s_aliases;   /* alias list */
  int     s-port;      /* port number, network-byte order:網絡字節序 */
  char   *s_proto;     /* protocol to use */
};
  • 典型調用以下:
struct servent *sptr;

sptr = getservbyname("domain", "udp"); /* DNS using UDP */
sptr = getservbyname("ftp", "tcp");    /* FTP using TCP */
sptr = getservbyname("ftp", NULL);     /* FTP using TCP */
sptr = getservbyname("ftp", "udp");    /* this call will fail */
  • 根據給定端口號和可選協議查找相應服務
#include <netdb.h>

struct servent *getservbyport (int port, const char *protoname);

Returns: non-null pointer if OK, NULL on error
  • 典型調用以下:
struct servent *sptr;

sptr = getservbyport (htons (53), "udp"); /* DNS using UDP */
sptr = getservbyport (htons (21), "tcp"); /* FTP using TCP */
sptr = getservbyport (htons (21), NULL);  /* FTP using TCP */
sptr = getservbyport (htons (21), "udp"); /* this call will fail */

11.6 getaddrinfo()

#include <netdb.h>

int getaddrinfo (const char *hostname, const char *service, const struct addrinfo *hints, struct addrinfo **result) ;

Returns: 0 if OK, nonzero on error (see Figure 11.7)
  • hints:指望返回的信息
  • 返回的result結構體以下:
struct addrinfo {
   int          ai_flags;           /* AI_PASSIVE, AI_CANONNAME */
   int          ai_family;          /* AF_xxx */
   int          ai_socktype;        /* SOCK_xxx */
   int          ai_protocol;        /* 0 or IPPROTO_xxx for IPv4 and IPv6 */
   socklen_t    ai_addrlen;         /* length of ai_addr */
   char        *ai_canonname;       /* ptr to canonical name for host */
   struct sockaddr    *ai_addr;     /* ptr to socket address structure */
   struct addrinfo    *ai_next;     /* ptr to next structure in linked list */
};

。。。後面的太多先放一下

11.7 gai_strerror()

  • 對getaddrinfo返回的非0錯誤值返回一個錯誤信息串
#include <netdb.h>

const char *gai_strerror (int error);

Returns: pointer to string describing error message

11.8 freeaddrinfo()

  • 釋放getaddrinfo()申請的空間
#include <netdb.h>
void freeaddrinfo (struct addrinfo *ai);
  • 注意淺複製和深複製的問題

11.9 getaddrinfo(): IPv6

  • 在彙總該函數爲IPv4或IPv6的返回信息以前,須要注意下面幾點:
    • 1.getaddrinfo處理兩個不一樣的輸入,套接字地址結構類型和資源記錄類型
    • 在hints中指定AF_INET,就不會返回sockaddr_in6結構
    • POSIX says that specifying AF_UNSPEC will return addresses that can be used with any protocol family that can be used with the hostname and service name
    • if the AI_PASSIVE flag is specified without a hostname, then the IPv6 wildcard address (IN6ADDR_ANY_INIT or 0::0) should be returned as a sockaddr_in6 structure, along with the IPv4 …
    • hint structure’s ai_family member指定的地址簇協議以及在ai_flags成員中指定的AI_V4MAPPED and AI_ALL 決定了在DNS中查找的資源記錄類型
    • The hostname can also be either an IPv6 hex string or an IPv4 dotted-decimal string. The validity of this string depends on the address family specified by the caller
      11-8_getaddrinfo函數機器行爲和結果彙總.jpg

11.10 getaddrinfo(): Examples

11.11 host_serv()

#include "unp.h"
struct addrinfo *host_serv (const char *hostname, const char *service, int family, ints socktype);
Returns: pointer to addrinfo structure if OK, NULL on error

函數源碼

11.12 tcp_connect()

#include "unp.h"

int tcp_connect (const char *hostname, const char *service);

Returns: connected socket descriptor if OK, no return on error

函數源碼

用到該函數的一個例子

11.13 tcp_listen()

#include "unp.h"

int tcp_listen (const char *hostname, const char *service, socklen_t *addrlenp);

Returns: connected socket descriptor if OK, no return on error
  • 執行TCP服務器的一般步驟
  • 下面的程序容許用戶做爲程序命令行參數輸入一個IP地址或主機名
    • 鍵入:server,則默認IPv6
    • 鍵入:server 0.0.0.0,則默認IPv4
    • 鍵入:server 0::0,則默認IPv6
  • 程序源碼

11.14 udp_client ()

  • 建立未鏈接的UDP套接字
#include "unp.h"

int udp_client (const char *hostname, const char *service, struct sockaddr **saptr, socklen_t *lenp);

Returns: unconnected socket descriptor if OK, no return on error

函數源碼
+ 協議無關獲取時間客戶程序

11.15 udp_connect()

  • 建立已鏈接的UDP套接字
#include "unp.h"

int udp_connect (const char *hostname, const char *service);

Returns: connected socket descriptor if OK, no return on error

函數源碼

11.16 udp_server()

#include "unp.h"

int udp_server (const char *hostname, const char *service, socklen_t *lenptr);

Returns: unconnected socket descriptor if OK, no return on error

函數源碼
+ 協議無關獲取時間服務器程序

11.17 getnameinfo()

int getnameinfo (const struct sockaddr *sockaddr, socklen_t addrlen, char *host, socklen_t hostlen, char *serv, socklen_t servlen, int flags) ;

Returns: 0 if OK, nonzero on error (see Figure 11.7)
  • sockaddr:指向套接字地址的結構,一般由accept(),recvfrom()返回
  • hostlen=0,表調用者不想返回主機字符串
  • servlen=0, 表調用者不想返回服務字符串
  • flags:可改變getnameinfo的操做
    • NI_DGRAM:數據報服務
    • NI_NAMEREQD:不能解析名字則返回錯誤

11.18 Re-entrant (可重入函數)

  • 重入問題的條件:主控制流中和某個信號處理函數同時調用gethostbyname and gethostbyaddr,而進程只存在host變量的單個副本

注意如下幾點:
+ gethostbyname, gethostbyaddr, getservbyname, and get servbyport are not re-entrant 由於 all return a pointer to 同一個 static structure.
+ inet_pton and inet_ntop are always 可重入(re-entrant).
+ Historically(歷史緣由), inet_ntoa is not re-entrant, but some implementations(一些實現) that support threads provide a re-entrant version that uses thread-specific data(線程特定數據).
+ ……
+ 在信號處理函數中應儘可能不使用任何可重入函數

11.19 gethostbyname_r and gethostbyaddr_r()

  • There are two ways to make a nonre-entrant function such as gethostbyname re-entrant
      1. Instead of filling in and returning a static structure, the caller allocates the structure and the re-entrant function fills in the caller’s structure
      1. The re-entrant function calls malloc and dynamically allocates the memory
#include <netdb.h>

struct hostent *gethostbyname_r (const char *hostname, struct hostent *result, char *buf, int buflen, int *h_errnop) ;

struct hostent *gethostbyaddr_r (const char *addr, int len, int type, struct hostent *result, char *buf, int buflen, int *h_errnop) ;

Both return: non-null pointer if OK, NULL on error

11.20 Obsolete IPv6 Address Lookup()

11.21 Other Networking Information(其餘網絡相關信息)

這裏寫圖片描述

11.22 Summary

12. IPv4與IPv6的互操做性

IPv4客戶與IPv6客戶

  • IPv4客戶到IPv6服務器
    • IP地址將被映射成IPV6地址
    • 通訊都使用IPv4載送的數據報
  • IPv4監聽套接字只能接收來自IPv4客戶的外來鏈接

13. 守護進程和inetd超級服務器

守護進程:後臺啓動且不與任何控制終端關聯

13.2 syslogd守護進程

  • 讀配置文件,/etc/syslog.conf
  • A Unix domain socket is created and bound to the pathname /var/run/log (/dev/log on some systems).—>往syslogd綁定的路徑名發送消息就是發送日誌消息
  • A UDP socket is created and bound to port 514 (the syslog service).—->往127.0.0.1:514發送消息就是發送日誌消息
  • The pathname /dev/klog is opened. Any error messages from within the kernel(內核) appear as input on this device.(看起來像是從這個設備輸入的)

13.3 13.2 syslogd Function

#include <syslog.h>

void syslog(int priority, const char *message, ... );
  • priority:級別(level)和設施(facility:標識消息發送進程類型的facility)的組合
  • 增設了格式控制符 m%,將被替換成當前errno值對應的出錯消息
  • 舉例:syslog(LOG_INFO|LOG_LOCAL2, "rename(%s, %s): %m", file1, file2);
#include <syslog.h>
void openlog(const char *ident, int options, int facility);//首次調用syslog前調用
void closelog(void);//在進程不須要發送日誌消息時調用
  • ident:一般是程序名
  • facility:指定syslog()的默認facility

13.4 daemon_init()

  • 經過調用它,能把一個普通進程編程守護進程

13.5 inetd Daemon

  • inetd進程先使用daemon_init函數的技巧把本身變成守護進程,接着處理本身的配置文件/etc/inetd.conf

13.6 daemon_inetd Function

設置由inetd啓動的程序步驟:
+ 1.在/etc/services文件中服務名和端口
+ 2.添加程序路徑:/etc/inetd.conf,問題是找不到這個文件
+ 3.給inet發送sIGHUP信號
+ 4.日誌存放文件:/var/adm/messages(根據/etc/syslog.conf),這兩個文件也找不到

13.7 Summary

14. 高級IO函數

14.2 套接字超時

14. 3 recv()和send()

#include <sys/socket.h>

ssize_t recv(int sockfd, void *buff, size_t nbytes, int flags);

ssize_t send(int sockfd, const void *buff, size_t nbytes, int flags);

Both return: number of bytes read or written if OK, –1 on error
  • flag :
    • DONTROUTE:繞過路由表
    • MSG_DONTWAIT:把單個IO指定爲非阻塞
    • MSG_OOB:With send, this flag specifies that out-of-band data(帶外數據) is being sent
    • MSG_PEEK:This flag lets us look at the data that is available to be read, without having the system discard the data after the recv or recvfrom returns(讀,可是完後不丟棄)
    • MSG_WAITALL:能夠這樣用
      • #define readn(fd, ptr, n) recv(fd, ptr, n, MSG_WAITALL)

14.4 rreadv()和writev()函數

#include <sys/uio.h>

ssize_t readv(int filedes, const struct iovec *iov, int iovcnt);

ssize_t writev(int filedes, const struct iovec *iov, int iovcnt);

Both return: number of bytes read or written, –1 on error
  • filedes;可用於任何描述符
  • iov:指向iovec結構體的一個數組,allows up to 1,024, while HP-UX has a limit of 2,100.
struct iovec {
  void   *iov_base;   /* starting address of buffer */
  size_t  iov_len;    /* size of buffer */
};
  • writev()能夠避免Nagle算法

14.5 recvmsg()和sendmsg()函數

#include <sys/socket.h>

ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

ssize_t sendmsg(int sockfd, struct msghdr *msg, int flags);

Both return: number of bytes read or written if OK, –1 on error
  • msg:
struct msghdr {
  void         *msg_name;        /* protocol address,指向一個套接字的地址結構 */
  socklen_t     msg_namelen;     /* size of protocol address */
  struct iovec *msg_iov;         /* scatter/gather array,指定輸出緩衝區數組*/
  int           msg_iovlen;      /* # elements in msg_iov */
  void         *msg_control;     /* ancillary data (cmsghdr struct),指定可選輔助數據 */
  socklen_t     msg_controllen;  /* length of ancillary data */
  int           msg_flags;       /* flags returned by recvmsg() */
};

.

14.6 輔助數據(control information)

  • 以僞代碼的形式使用宏
struct msghdr     msg;
struct cmsghdr    *cmsgptr;
/* fill in msg structure */
/* call recvmsg() */
for (cmsgptr = CMSG_FIRSTHDR(&msg); cmsgptr != NULL;
     cmsgptr = CMSG_NXTHDR(&msg, cmsgptr)) {
    if (cmsgptr->cmsg_level == ... &&
        cmsgptr->cmsg_type == ... ) {
        u_char *ptr;
        ptr = CMSG_DATA(cmsgptr);
        /* process data pointed to by ptr */
    }
}

14.7 排隊的數據量

獲悉已排隊的數據量:
- 1,使用非阻塞IO
- 2,使用MSG_PEEK標誌
- 3,ioctl的第三個參數

14.8 套接字和標準IO

使用標準IO容易引發緩衝問題,能夠用setvbuf迫使變爲行緩衝

14.9 高級輪詢技術

  • 1./dev/poll
  • 2.kqueue接口
#include <sys/types.h>
#include <sys/event.h>
#include <sys/time.h>
int kqueue(void);
int kevent(int kq, const struct kevent *changelist, int nchanges, struct kevent *eventlist, int nevents, const struct timespec *timeout) ;
void EV_SET(struct kevent *kev, uintptr_t ident, short filter, u_short flags, u_int fflags, intptr_t data, void *udata);

儘管總的來講應該避免編寫不可移植的代碼,然而對於一個任務繁重的網絡應用程序來講,使用各類可能得方式爲他在特定的主機上進行優化也至關廣泛

14.10 T/TCP:事物目的TCP

  • 有些系統可能不支持T/TCP,可用MSG_EOF檢查
  • 客戶端須要開啓級別爲IPPROTO的套接字選項TCP_NOPUSH

14.11 小結

相關文章
相關標籤/搜索