re: Socket Programming in C 常犯的錯誤
Posted on April 22nd, 2008 at 12:25 by fr3@K

昨天在網上閒逛, 在樂多上看到篇名為 “Socket Programming in C 常犯的錯誤” 的短文. 這短文作者的動機很好, 指出常見到的錯誤並提供個人看法與可能較好的作法. 這樣的行為很值得鼓勵. 但極可惜的是, 結果卻是很不理想的. 容我說句重話, 如果是考試的話該文是得不到我的分數的.

本想直接在該文回應作者, 但考慮到文中的兩個問題其實是很多不夠老練的 programmer 容易犯的毛病, 因此決定把我的評論寫在這裡, 自己留個紀錄, 也希望能供更多人參考.

原文照貼:

在一個簡單的網路程式,似乎有滿多人都會這樣寫:

char tmp[32];
memset( tmp, ‘\0′, sizeof(tmp));
int ret = recv( sock, tmp, sizeof(tmp), 0 );

把 buffer 填滿固然是一件好事,但是字串填滿之後,結果沒有\0了
就會發生意想不到的事情,可能會出現程式記憶體區段錯誤的問題
( 字元陣列的結尾正常結束都要有個 \0 才是正確的 )

假設我們不看 ret 回傳收到多大的資料,考慮使用 strcat 來把回傳的 buffer
串到更大的 buffer 字元陣列,應該要扣掉1來使用,避免發生記憶體區段錯誤!

char tmp[32];
memset( tmp, ‘\0′, sizeof(tmp));
int ret = recv( sock, tmp, sizeof(tmp) – 1, 0 );

首先是作者在文中指出需要在 buffer 最後保留一個 0x00, 以利後續字串操作. 這原則上是對的, 只是文中舉的例子是 recv, 以 recv/send/read/write 等 API 作 I/O 的時候讀寫的內容是 binary data 而不是 null-terminated string.1 以 socket I/O 來說, 除非 protocol 是如 HTTP 等的文本協議, 否則讀上來的有效 data 很可能中間就有值為 0x00 的 byte.2 而將最後有個 0x00 的 binary data 拿來當作 null-terminated string 操作, 後果可是很難設想的. 雖不至於造成記憶體錯誤, 功能上卻不是正確的. 又由於這樣的 bug 不總是容易重現, 造成的傷害反而可能更大. 所有讀上來的 data 應當要搭配上 recv 的非錯誤 return value (也就是讀出的 byte 數) 才能加以操作, 而不能將它視為字串, 拿來餵給 strlen/strcat 等 API .

另外作者說到, 在使用前先以 0x00 填滿 (fill) 整個 buffer 是好事. 但其實這是沒有任何意義的. 若 recv 失敗, buffer 指向的 memory 根本不會被拿來參考. 要是 recv 成功了, buffer 會被參考的部份則已經被覆蓋掉了. 如果 protocol 是文本協議, 只需如作者所說的在 recv 時先保留一個 byte, 並在成功時在最後補上個 null 就足夠了:

char tmp[32];
int ret = recv( sock, tmp, sizeof(tmp) - 1, 0 );
//if(ret != -1)
//{
//  tmp[ret] = 0;
//}
if(ret > 0)
{
  tmp[ret] = 0;
  // tmp is ready to be manipulated as string
}
else
{
  // handle error/EOF here
}
  1. 當然, binary data 本身也可以是 null-terminated string []
  2. 印象中, 即便是 HTTP, message 的 content 中間也可能出現值為 0x00 的 byte []
del.icio.us:re: Socket Programming in C 常犯的錯誤 digg:re: Socket Programming in C 常犯的錯誤 spurl:re: Socket Programming in C 常犯的錯誤 newsvine:re: Socket Programming in C 常犯的錯誤 furl:re: Socket Programming in C 常犯的錯誤 Y!:re: Socket Programming in C 常犯的錯誤 黑米共享書籤:re: Socket Programming in C 常犯的錯誤 推推王:re: Socket Programming in C 常犯的錯誤
Previous Post
« Video made for Microsoft’s sales team «
Next Post
» Strong Guarantee using Transaction »

10 Comments »

Comment #4369

基本上一般來講只有 return value > 0 才有必要被觀看
然後依照 ret 的數字大小直接對 buffer[ret] = 0×0; 就夠了

會寫這篇文章的主因是針對一般簡單的範例會把收到的資料透過 printf 出來
而在這個時候 %s 如果沒有抓到 就會引起很大的錯誤

for 初學者而言, 我覺得這個細節上探討是沒有必要的 :)

Comment by lilohuang — April 22, 2008 @ 17:56


Comment #4410

這的確是蠻嚴重的觀念偏差…
可能會誤導很多人,就像我現在maintain的專案裡,
都還是會看到很多無謂的memset,看到心理都不是很舒服…
程式經驗不足的人,反而會完全認同這種初始化動作是比較安全的說法…
你要好好的回應那個作者啊~~

Comment by person — May 9, 2008 @ 14:38


Comment #4411

Hi person,

看了你的回應, 自己又讀了一遍這篇的文字.

關於這個主題, 我以為我想說的都已經在文中清楚說明了, 暫時沒有想到要補充的地方. 雖然不確定我理解的對不對, lilohuang 的留言似乎表示他是初學者且沒興趣探討細節. 因此我沒打算回應, 抱歉.

若你對這主題有其他想法或是有想討論的地方, 還請不吝賜教.

Comment by fr3@K — May 9, 2008 @ 17:53


Comment #4432

To fr3@K

無知的你可能不曉得莉洛他的程式功力到什麼境界,
去多逛逛台灣最大的PTT BBS吧!少在這邊發這種廢文!!
莉洛大這篇文章是直接拷貝大陸的範例來解說的,
那篇原文本來就是用 memset,你真的很好笑ㄟ

Comment by ik — May 18, 2008 @ 22:24


Comment #4433

Hi ik,

請原諒我的孤陋寡聞, 小弟我的確不知道莉洛是那位或是功力有多”牛”. 我在這篇文字所表達的看法與莉洛本人或他的其他豐功偉業無關.

你給我的意見我不是理解的很清楚, 是說只要是莉洛說的話就是真理而不可有不一樣的看法嗎? 如果是這樣的話還請恕小弟不能同意. 抱歉.

希望我們在這裡的交流能夠將焦點放在技術層面, 感謝.

ps. 這個回應挖苦的目標不是莉洛, 而是另有其人. 不禮貌的地方還請莉洛包涵.

Comment by fr3@K — May 19, 2008 @ 0:36


Comment #4755

你好,向你請教一事
請問一下,如何用C去傳資料給WEB端程式
我的程式是SERVER端程式來檢查我的資料庫
而客戶那邊是WEB的程式,可以用POST或GET傳遞都OK
謝謝你

Comment by joney — December 11, 2008 @ 10:12


Comment #4756

先聲明我沒實作/研究過. 但我知道有很多現成的 Free/OpenSource library.

可以用 libwww, 也可以試試 libcurl.

另, 這一 有很多功能相近的 library.

Comment by fr3@K — December 11, 2008 @ 14:34


Comment #4757

First of all, in my experiences, libwww sucks!! Libwww is full of leaks and other bugs. We should try our best to avoid it.

Secondary, I’d rather use memset() to aggressively prevent any possible errors, unless the profiling result showed that it is the performance bottleneck. After all, the usage of the buffer, currently recv(), may change to other way in the future. Resetting the buffer *right after* defining it would be good, no matter how it would be used later.

Comment by jeffhung — December 11, 2008 @ 16:22


Comment #4758

If

libwww sucks!!

Any alternative you would recommend to joney?

Comment by fr3@K — December 12, 2008 @ 19:05


Comment #4760

Libcurl is much better than libwww, though we still encounter some issues in high loading.

Comment by jeffhung — December 14, 2008 @ 23:12


Comments RSS TrackBack URI

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>