Thursday, March 15, 2012

Java socket程式 -- 絕對避免println() / PrintStream!

我先簡要的說一下原因,internet protocol用來分隔的是CRLF(\r\n) 以及CRLFCRLF(後者用以區隔header跟content)。PrintWriter的println(以及所有stream family的println, 比方說PrintStream)所傳出的分隔都是依系統而變(也就是,很不幸,這個函數並不是跨平台)

珍惜生命 遠離println / printstream(全文完)

--------
基本上Client來說,看到這裡應該就夠了,下面是比較詳細的說明。換行(也就是println會幫你做的事情)會幫你插入一個換行符號,而這個換行符號在各個系統都不一樣,所以基本上各個系統輸出都不同。比方說Windows就是CRLF(難得反而windows是正統的) 而unix系的含OSX絕大多數都是CR。

這是java少數幾個無法跨平台的地方,你可以想像一下同樣一串碼

out.println("This is SPARTA!");

其實在Windows底下是輸出
This is SPARTA!\r\n
而Unix系卻是
This is SPARTA!\r

這個println如果是輸出到螢幕上的話,沒差,大家看起來都一樣
阿輸出到socket就有差了,後者readline會根本讀不到

那當然照慣例就要問了:那要怎麼辦?
簡單,out.print("This is SPARTA!\r\n");

當然,也別忘了out.flush();

請銘記在心,寫Java Socket程式絕對要遠離任何println/printstream,看到他請當瘟神一樣離開他

ok,接下來談談server對應的部分。事實上當你寫server的時候,很多時候你是沒得選擇的。根據我自己的經驗,會犯下這種錯誤的coder其實比率是相當高的,只是絕大多數都是在windows下作業跟跑server,有時候是一些3rd party,server來講你很難做些什麼。畢竟code不是你寫的,你也沒source code,你更沒辦法把寫client的人抓過來狗幹一頓(我個人是稱為「友善的爭論」XD)

既然改不了client,那我們只好改server,讓server吃得下這些錯誤的東西

在談讓server怎麼吃這些東西而不至於拉肚子以前,我先講個瘟腥小故事。

IE7以前的世界,事實上browser是非常「友善」的,友善到一些明明是錯的東西他照樣吃。比方說有<p>沒</p>,或者<body><p> ....... </body></p>照吃不誤。這樣其實大家寫code都很開心,問題是每個browser都用不一樣的方法去解譯它,所以造成了同一組code的效果不見得一樣。

這造成了html界的大混亂,也間接導致了嚴格規範的xhtml誕生。(不過這東西始終沒有來的及正式長大,就被html 5.0取代了)

請注意,我們現在做的事情就跟以前「友善的」server一樣,是不對的,但是這是一種沒辦法中的辦法

說穿了很簡單,我們改用一個字元一個字元的去讀它(沒buffer想必這會很耗力)。我們讀取到\r的時候,我們會打開一個flag, 然後將\r後面多寫一個\n,下一個如果是\n的時候我們就把flag關閉並且跳過這個\n,不是的話關閉flag繼續。

具體的code大約是這樣(我個人是用filterinputstream處理這種東西)

boolean LRFlag = false;
while(char input = in.read()) {
if(input == '\r') {
LRFlag = true;
out.write(input);
out.write('\n');
continue;
}

if(input == '\n' && flag == true) {
flag = false;
continue;
}

flag = false;
out.write(input);
}


繼承fliterinputstream在read裡面插入這一段(也有一種寫法是override skip(),方法大同小異,我是比較喜歡override skip()就是,不過這比較少為人知)

hope it helps