2013年2月18日 星期一

unix network programming書摘 -- Part 1

只有記錄我認為容易忽略的地方,文章內不會有TCP/IP的詳細過程,可以參考richard stevens的另外一本大作TCP/IP Illustrate,另外手邊有的unix network programming一書是2ed

Chapter 1
伺服器端
 1: #include "unp.h"
 2: #include <time.h>
 3: 
 4: int
 5: main(int argc, char **argv)
 6: {
 7:  int     listenfd, connfd;
 8:  struct sockaddr_in servaddr;
 9:  char    buff[MAXLINE];
10:  time_t    ticks;
11: 
12:  listenfd = Socket(AF_INET, SOCK_STREAM, 0);
13: 
14:  bzero(&servaddr, sizeof(servaddr));
15:  servaddr.sin_family      = AF_INET;
16:  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
17:  servaddr.sin_port        = htons(13); /* daytime server */
18: 
19:  Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
20: 
21:  Listen(listenfd, LISTENQ);
22: 
23:  for ( ; ; ) {
24:   connfd = Accept(listenfd, (SA *) NULL, NULL);
25: 
26:         ticks = time(NULL);
27:         snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
28:         Write(connfd, buff, strlen(buff));
29: 
30:   Close(connfd);
31:  }
32: }

主要流程填寫sockaddr_in結構,socket()=>bind()=>listen()=>accept()=>read()/write()=>close()

客戶端

 1: #include "unp.h"
 2: 
 3: int
 4: main(int argc, char **argv)
 5: {
 6:  int     sockfd, n;
 7:  char    recvline[MAXLINE + 1];
 8:  struct sockaddr_in servaddr;
 9: 
10:  if (argc != 2)
11:   err_quit("usage: a.out <IPaddress>");
12: 
13:  if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
14:   err_sys("socket error");
15: 
16:  bzero(&servaddr, sizeof(servaddr));
17:  servaddr.sin_family = AF_INET;
18:  servaddr.sin_port   = htons(13); /* daytime server */
19:  if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0)
20:   err_quit("inet_pton error for %s", argv[1]);
21: 
22:  if (connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) < 0)
23:   err_sys("connect error");
24: 
25:  while ( (n = read(sockfd, recvline, MAXLINE)) > 0) {
26:   recvline[n] = 0; /* null terminate */
27:   if (fputs(recvline, stdout) == EOF)
28:    err_sys("fputs error");
29:  }
30:  if (n < 0)
31:   err_sys("read error");
32: 
33:  exit(0);
34: }
流程為填寫sockaddr_in結構,socket()=>connect()=>read()/write()=>close()

Chapter 2
重要的為TCP以及UDP的特色,以及richard stevens書中那張state diagram,中間牽涉到如何透3-way handshake建立連線,以及何時會斷線,為何TIME_WAIT狀態要等2MSL


  • 個人認為重要的觀念是TCP/UDP為全雙工,也就是系統如何處理這樣的概念,從這裏面衍生出了不少問題
  • TCP使用socket pair的概念來辨識連線,也就是Server IP+Server Port+Client IP+Client Port,因為UDP沒有連線概念的約束(也可以自行實作,但是TCP天生幫programmer處理這些)
  • 同時開始考慮,一台機器可能不只一個網路介面,或者說不只一張網路卡,一個service(如FTP)可以同時在所有卡啟動,只要指定socket結構內為INADDR_ANY即可,如果要綁定數張,就只能一個一個來
  • 另外書中提到kernel緩衝區的可以用SO_SNDBUF/SO_RCVBUF(setsockopt())來調整,TCP因為有window size的關係,照理不應該有buffer用盡的問題,但是UDP卻會,又加上UDP是unreliable,buffer一旦滿了就只會drop,連通知都不會有

Chapter 3
  • 因為unix/linux設計socket不只可以用在ethernet還可以用在domain socket上面(一種IPC),所以網路上用的是sockaddr_in結構但是在connect()/bind()/accpet()...參數用的卻是sockaddr結構(一種所謂generic socket結構),所以在呼叫這些api往往形態要轉型
  • bind()、connect()、sendto()是將資料由user space傳送到kernel space
  • accept()、recvfromgetsockname()、getpeername()則是由kernel space回傳資料到user space
  • byte order的問題,網路上使用的是big-edian,但是intel cpu是little-edian,所以有這問題,通常有四個函數來處理htons()、htonl()、ntohs()、ntohl(),h表示hostn表示networks表示16bits、l表示32bits
  • 新的由IP字串取得address的function為intet_pton()以及反向函數inet_ntop()其中p表示presentation
  • 作者時做了好一些輔助函數,很有參考價值

Chapter 4
重點之一,流程圖,書中逐一解說各個function用途


  • socketaddr_in要轉型的原因如前一章,另外要注意,因為可以合乎各種family,所以在connect()/bind()/accept()除了轉型之外,還需要傳入結構大小的指標
  • 這裡衍生出一個重要的觀念,TCP server一定要bind()嗎?答案是不一定,可以由系統指派,之後在用getsockname()取回sockaddr得知,而client的sockaddr資訊則可以用getsockpeername()取回
  • fork()解決一小部分 socket為了全雙工所面臨的問題,因為程式不能因為系統呼叫而block住


chapter 5
  • 書中提到了系統的問題,也就是signal會干擾正常流程,以及如何避免child process變成zombie的問題,使用waitpid()來解決,wait()因為kernel不queue signal可能還是會產生zombie
  • SIGPIPE有可能是因為網路上遇到RST訊號,這屬於網路問題,但是unix反應到process上變成signal,必須妥善處理才能避免連線的問題

chapter 6
  • 解說了blocking/nonblocking IO跟IO multiplexing、還有signal IO、asynchronous IO
  • select()函數示範了IO multiplexing,這個函數可以簡單的處理網路的IO以及一般的輸入,但是中間呼叫其他system call還是有可能會block整個process,所以要小心
  • select() -- read ready
    • 正常狀況,資料高於低水位,可以透過setsockopt()的SO_RCVLOWAT調整低水位
    • 對方已經關閉連線,會回傳EOF
    • 在完成connected的queue中有可用的socket
    • 有sock error存在,read()回傳-1,利用getsockopt()取得SO_ERROR資料
  • select() -- write ready
    • TCP在socket已經連線的時候可以寫入,或者UDP/TCP寫入資料多於低水位的時候,使用setsockopt()的SO_SNDLOWAT調整
    • 連線端已經關閉寫入,會產生SIGPIPE
    • 網路有sock error存在,write()回傳是-1,利用getsockopt()取得SO_ERROR資料
  • shutdown()函數以及setsockopt()的SO_LINGER可以調整關閉socket時候的動作,必須考慮到網路的封包。
  • close()只是關閉write/read fd並沒有關閉網路連線(或者TCP沒有送出FIN)
Chapter 7
介紹常見的socket options,分成幾個層次,一般socket options、IPv4 options、IPv6 options、ICMP options、TCP options。一般socket options指的是大多與protocol獨立無關,由kernel透過與protocol無關的code設定
我摘要了幾個前面常常碰見的options,有些options大多集中在書本前面,且偏向一般socket options

  • SO_BROADCAST : 廣播
  • SO_ERROR : 網路出錯
  • SO_KEEPALIVE : 兩個小時透過封包確定TCP連線還存活著
  • SO_LINGER : FIN訊號的處理
  • SO_RCVBUF/SO_SNDBUF : 設定TCP/UDP的buffer,TCP不用特意調整,他有window size機制
  • SO_RCVLOWAT/SO_SNDLOWAT : 資料的低水位控制,有關效率以及select()
  • SO_REUSEPORT
  • SO_RESUEADDR : 只要IP不同,特別不用等待2MSL就可以重新bind()
  • TCP_KEEPALIVE : 要開啟這個選項就要同時開啟SO_KEEPALIVE
  • TCP_NODELAY : nagle演算法實作


Chapter 8
UDP介紹,如同TCP一樣,有張流程圖,不過不是一定的

  • 不是一定的理由是,用戶端可以呼叫bind()跟connect(),當然兩者也可以都忽略
  • 書中探討到了UDP失去了TCP特色的問題,比方UDP無法得知連線IP/Name,UDP沒有流量控制,UDP沒有重傳機制
  • UDP因為不做3-way handshake,所以無法確定client/server是透過哪個IP或者介面卡傳輸,兩邊都可以從自己主機上的任一IP或者介面卡回應另一方
  • UDP可以透過IP比對,或者DNS比對(多個IP對應到單一domain name),來驗證資料來源
  • UDP可以透過前一點機制與Timeout設定來確定封包有沒有送達
  • UDP可以透過SO_RCVBUF改善流量問題
  • UDP的好處之一,可以透過一個client同時對多個server提出要求,但是會引發一個問題是ICMP的訊息將是非同步產生
  • 其他可以參考我之前寫的socket FAQ

沒有留言:

張貼留言