真㊣的 porting
在工作上,常常需要把同樣的功能,porting 到不同的平台上去。有些 porting 很簡單,找出對應的 API 代換即可,但有些 porting,就真的很需要對各個平台,非常細緻的理解與熟習。
對應的 API 代換很簡單,例如 pthread_create() 換到 Win32 平台,可以用 _beginthreadex() 代替,頂多需要注意一下參數的不同即可。
有些 API 代換則比較複雜一些,牽涉到程式邏輯的不同。不過因為在功能面上是對等的,只是寫法不同,所以也不是什麼大問題。
例如,「列出目錄內容」這件事,在 UNIX 上要用 opendir/readdir/closedir 這組函式:
void list_dir(const char* dir)
{
DIR* d = opendir(dir);
dirent* e;
while ((e = readdir(d)) != NULL) {
printf("%s\n", e->d_name);
}
closedir(d);
}
但是到了 Windows 上,則要用 FindFirstFile/FindNextFile/FindClose 這組函式:
void list_dir(const char* dir)
{
// prepare wildcard path
char wild[MAX_PATH];
strcpy(wild, dir);
strcat(wild, "\\*");
WIN32_FIND_DATA wfd;
memset(&wfd, 0, sizeof(wfd));
HANDLE h = FindFirstFile(wild, &wfd);
if (h == INVALID_HANDLE_VALUE) {
throw "FindFirstFile() failed.";
}
do {
printf("%s\n", wfd.cFileName);
} while (FindNextFile(h, &wfd));
FindCloseFile(h);
}
兩組函式的寫法不太一樣,差別在於,第一筆資料在 UNIX 上,同其他筆資料,係由 readdir() 取得,但在 Windows 上,卻是第一筆資料由 FindFirstFile() 取得,其他筆資料由 FindNextFile() 取得。這點差異,導致了兩邊的程式寫法不太一樣。
不過如何,最近我碰到的這個例子,才真的叫難。
話說 socket programming 是由 BSD (UNIX) 發展出來的,之後才移植到各個平台,如 Windows 上的 WinSock。當一條 TCP 連線使用完畢時,我們可以呼叫 close() 將這個 socket 關閉,也可以用 shutdown() 取代 close()。
在 UNIX 裡,close() 會做 reference counting,若有多個 socket 對應到同一條 TCP connection,則只有當最後一個 socket 被 close() 時,這條 TCP connection 才真的會被結束掉。這通常發生於使用 fork() 來處理 incoming connection 時,母行程 accept() 得到 socket 後,會呼叫 fork() 複製出子行程來處理。此時,母行程與子行程各自擁有一個 socket,同時對應到這條 TCP connection。當母行程 fork() 完畢後,會馬上呼叫 close() 將 socket 關閉,因為母行程再也不需要用到這條 TCP connection。如果這個 close() 真的把 TCP connection 給結束掉,那子行程就沒戲唱了。所以,close() 會做 reference counting,只有在子行程也 close() 時,才會真的把 TCP connection 關閉。
在 Windows 裡,socket 不能用 close() 關閉,要用一個特別版本的 closesocket() 才行。closesocket() 與 UNIX 上的 close(),功能相同。
至此,porting 仍處在 API 對應的等級,很簡單。可是,問題就出在可以取代 close() 的 shutdown()。
兩者之間的差別在於,shutdown() 將不理會 socket 的 reference counting,馬上送出 FIN 將 TCP connection 結束。使用 shutdown() 的好處顯而易見,我們可以確定這個 TCP connection 立刻結束,可加強資源控管。
依據《UNIX Network Programming》這本 socket programming 的聖經級書本所述:
If we really want to send a
FINon a TCP connection, theshutdownfunction can be used instead ofclose.
我們可以用 shutdown() 完全取代 close():用了 close() 就不要用 shutdown(),用了 shutdown() 就不要用 close()。
可是,MSDN 卻說:
The
shutdownfunction does not close the socket. Any resource attached to the socket will not be freed untilclosesocketis invoked.
意思是說,在 WinSock 裡,用了 shutdown() 之後,還要用 closesocket(),才能「關乾淨」把資源全部釋放。
就是因為這一點細節的不同,若是忽略了,就會產生 leak。要不是不小心看到 MSDN 的這一句,我也會疏忽掉,產生極難追蹤的 bug。
唯有通曉這類精微的細節,才能做到「真㊣的 porting」。面對浩如瀚海的 Windows APIs,我也只能兢兢業業地,一步一腳印,細讀 MSDN 的每一字每一句,希望能夠捕捉到,這些會害死人不償命的微光掠影。



13 Comments
本文中講的三類工作:thread, file I/O, network 都可以用 boost 解決,這樣跨平台就沒問題了! boost 萬歲! XD
av,
我最近有空時,會練習自己實作 C++0x 的 std-lib (比較簡單的部份XD),然後就發現,其實 boost 與 C++0x 並沒有 100% 相容,很多細節不太一樣。看來屆時從 boost 進化到 C++0x,還有一段路要走啊。
jeffhung
請教jeffhung版大,
因小弟正在學習Win32 programming (使用VC6), 雖然有C/C++的基礎, 但卻仍然有不得其門而入的感覺, 請教版大可否分享學習Win32 programming的心得, 或是指引一個方向, 謝謝.
CHL,
先讀完 Charles Petzold 的《Programming Windows 95》,對 Windows Programming 的基本架構,有個認識之後,再選擇一套 GUI framework,尋其 bible 級入門書詳讀後,最後就是不斷地練習,再練習。
MSDN 要勤讀、詳讀,使用的所有函式,甚至包括 standard C 的函式如
strcpy等,統統都要一個一個去找出 MSDN 頁面,把裡面所有內容讀完。需要特殊功能,可以先找 CodeProject 這個網站,這是 Windows Programming 的大寶庫。
最後,再強調一次,MSDN 要細讀、詳讀。
祝好運。XD
jeffhung
謝謝版大的分享.
想再請問版大, 是先學Windos API (SDK)? 還是從MFC學起?
.net 我可能暫時不會考慮.
另外再請教版大, 請問除了MSDN外, 是否有推薦的補充教材? 例如book或是雜誌?
MSDN我會努力study的.
謝謝版大不吝指教.
CHL,
先學 Windows SDK 底子才夠,不過 Charles Petzold 的書看完,概念就差不多了。
進階的書或雜誌的話,我就沒什麼研究了,基本上 MSDN + Google + CodeProject 就已經很豐富,夠處理大部分會遇到的問題了。
jeffhung
Hi Jeffhung,
關於 Stevens 的那句話, 你可能理解的不夠正確. 前半段的重點似乎沒得到你的注意:
shutdown絕不能替代close/closesocket, 前者是在操作一個 socket, 後者是 dereference 一個 socket.例如, 一個被以
shutdown(sockfd, SHUT_WR)的 socket 依然是 valid socket (desciptor). 它會 ACK incoming packets, 也會把 data 放進 receive queue, application 也可以把 data 從 queue 讀進來.我注意了一下 UNP 上面的幾個相關範例. 在掃過的範例中, 的確大都在
shutdown後沒有close. 但它們都是短命的程式, 基本上在shutdown後 process 就結束掉了. 我認為在這些範例中, Stevens 是利用 process 的 life scope 來作 garbage collection, 並不是以shutdown取代close.客戶的Engineer如果都這樣子就好了!!
有個客戶要把Intel的WPS(wsccmd)從我們的板子(MIPS)porting到他們的板子(Intel IXP435)上
他的認知竟然是,只要把tool chain換成他們的就好,有問題的地方就拿掉 (直接用 /**/ 拿掉)
然後再跟我們抱怨有些功能不會動.
fr3@K,
根據《Unix Network Programming》,Section 7.5 的
SO_LINGERSocket Option 這一段,shutdown與close的使用,還有許多奧妙存在,看來值得再寫一篇。不過,這段裡面有提到一句:
從這段話來看,
shutdown與close依然是互相取代的。目前我還是找不到,在 UNIX 上,呼叫了shutdown還必須再呼叫close的說法。jeffhung
James Chan,
工程就是妥協,一切都要看「時程」啊。
如果時間不夠,是我當然也是 tool chain 換一換,有問題的地方直接拿掉,最後不能跑再 call 廠商解決。:-p
不過也要看啦,之前 porting AIX,IBM 的協力 SI 廠商碰到問題一樣是一問三不知,還是得什麼都自己來。
evil jeffhung
Hi Jeffhung,
這幾個 figure 以及其說明, 是從 wire 上發生的事情來看 application 的 call flow.
如 figure 7.8, client 的 call flow 是
write->shutdown(SHUT_WR) ->read(blocks) ->read(returns 0), 其中並沒有以close收尾.在這個例子中, client 最後若呼叫了
close, wire 上並不會發生任何事情,close只會單純的 dereference (and possibly incur relevant garbage collection). 因此我認為,close沒有出現在這 figure 上並不奇怪.另, 若 socket descriptor 在
shutdown後不需要close卻還能被操作, 那擁有該 descriptor 的 process 何時把該 descriptor dereference 才對呢? 總不會有等所有的 descriptor 都 go out of scope 然後自動回收的機制吧 (類 shared_ptr)? 還是需要 programmer 呼叫close, 否則就要等到 process 結束.Hi jeffhung,
關於這個
close/shutdown的問題, 或許你我都在心中有個預設答案, 因此各自把 UNP 以及其他地方找到的不是那麼一翻兩瞪眼的資訊解讀成為支持 (或至少不推翻) 自己認知的說法. 反省中...Anyway, 還是請你考慮一下我上一個留言最後一段留下的問題.
fr3@K,
你提的這點,真的很有說服力耶:
我要好好再想一想,並更努力地再找一下資料,才能確定究竟應該如何
close/shutdown。XDjeffhung
Post a Comment