網路socket編程指南 (轉貼)1

來源:互聯網
上載者:User
   網路socket編程指南 (轉貼)1
http://www.chinaunix.net 作者:流浪者  發表於:2003-04-11 17:09:07 

Beej網路socket編程指南 
-------------------------------------------------------------------------------- 
介紹 
  Socket 編程讓你沮喪嗎?從man pages中很難得到有用的資訊嗎?你想跟上時代去編Internet相關的程式,但是為你在調用 connect() 前的bind() 的結構而不知所措?等等…  
    好在我已經將這些事完成了,我將和所有人共用我的知識了。如果你瞭解 C 語言並想穿過網路編程的沼澤,那麼你來對地方了。 
-------------------------------------------------------------------------------- 
讀者對象  
  這個文檔是一個指南,而不是參考書。如果你剛開始 socket 編程並想找一本入門書,那麼你是我的讀者。但這不是一本完全的 socket 編程書。 
-------------------------------------------------------------------------------- 
平台和編譯器  
  這篇文檔中的大多數代碼都在 Linux 平台PC 上用 GNU 的 gcc 成功編譯過。而且它們在 HPUX平台 上用 gcc 也成功編譯過。但是注意,並不是每個程式碼片段都獨立測試過。 
-------------------------------------------------------------------------------- 
目錄: 
1) 什麼是通訊端?  
2) Internet 通訊端的兩種類型  
3) 網路理論  
4) 結構體 
5) 本機轉換 
6) IP 地址和如何處理它們  
7) socket()函數 
8) bind()函數 
9) connect()函數 
10) listen()函數 
11) accept()函數 
12) send()和recv()函數 
13) sendto()和recvfrom()函數 
14) close()和shutdown()函數 
15) getpeername()函數 
16) gethostname()函數 
17) 網域名稱服務 (DNS)(DNS) 
18) 客戶-伺服器背景知識  
19) 簡單的伺服器 
20) 簡單的用戶端 
21) 資料通訊端Socket 
22) 阻塞 
23) select()--多路同步I/O 
24) 參考資料  
-------------------------------------------------------------------------------- 
什麼是 socket?  
  你經常聽到人們談論著 “socket”,或許你還不知道它的確切含義。現在讓我告訴你:它是使用 標準Unix 檔案描述符 (file descriptor) 和其它程式通訊的方式。什嗎?你也許聽到一些Unix高手(hacker)這樣說過:“呀,Unix中的一切就是檔案!”那個傢伙也許正在說到一個事實:Unix 程式在執行任何形式的 I/O 的時候,程式是在讀或者寫一個檔案描述符。一個檔案描述符只是一個和開啟的檔案相關聯的整數。但是(注意後面的話),這個檔案可能是一個網路連接,FIFO,管道,終端,磁碟上的檔案或者什麼其它的東西。Unix 中所有的東西就是檔案!所以,你想和Internet上別的程式通訊的時候,你將要使用到檔案描述符。你必須理解剛才的話。現在你腦海中或許冒出這樣的念頭:“那麼我從哪裡得到網路通訊的檔案描述符呢?”,這個問題無論如何我都要回答:你利用系統調用 socket(),它返回通訊端描述符 (socket descriptor),然後你再通過它來進行send() 和 recv()調用。“但是...”,你可能有很大的疑惑,“如果它是個檔案描述符,那麼為什 麼不用一般調用read()和write()來進行通訊端通訊?”簡單的答案是:“你可以使用!”。詳細的答案是:“你可以,但是使用send()和recv()讓你更好的控制資料轉送。”存在這樣一個情況:在我們的世界上,有很多種通訊端。有DARPA Internet 地址 (Internet 通訊端),本地節點的路徑名 (Unix通訊端),CCITT X.25地址 (你可以將X.25 通訊端完全忽略)。也許在你的Unix 機器上還有其它的。我們在這裡只講第一種:Internet 通訊端。 
-------------------------------------------------------------------------------- 
Internet 通訊端的兩種類型  
  什麼意思?有兩種類型的Internet 通訊端?是的。不,我在撒謊。其實還有很多,但是我可不想嚇著你。我們這裡只講兩種。除了這些, 我打算另外介紹的 "Raw Sockets" 也是非常強大的,很值得查閱。 
那麼這兩種類型是什麼呢?一種是"Stream Sockets"(流格式),另外一種是"Datagram Sockets"(資料包格式)。我們以後談到它們的時候也會用到 "SOCK_STREAM" 和 "SOCK_DGRAM"。資料通訊端有時也叫“無串連通訊端”(如果你確實要串連的時候可以用connect()。) 流式通訊端是可靠的雙向通訊的資料流。如果你向通訊端按順序輸出“1,2”,那麼它們將按順序“1,2”到達另一邊。它們是無錯誤的傳遞的,有自己的錯誤控制,在此不討論。 
    有什麼在使用流式通訊端?你可能聽說過 telnet,不是嗎?它就使用流式通訊端。你需要你所輸入的字元按順序到達,不是嗎?同樣,WWW瀏覽器使用的 HTTP 協議也使用它們來下載頁面。實際上,當你通過連接埠80 telnet 到一個 WWW 網站,然後輸入 “GET pagename” 的時候,你也可以得到 HTML 的內容。為什麼流式通訊端可以達到高品質的資料轉送?這是因為它使用了“傳輸控制通訊協定 (The Transmission Control Protocol)”,也叫 “TCP” (請參考 RFC-793 獲得詳細資料。)TCP 控制你的資料按順序到達並且沒有錯 
誤。你也許聽到 “TCP” 是因為聽到過 “TCP/IP”。這裡的 IP 是指“Internet 協議”(請參考 RFC-791。) IP 只是處理 Internet 路由而已。  
    那麼資料通訊端呢?為什麼它叫無串連呢?為什麼它是不可靠的呢?有這樣的一些事實:如果你發送一個資料報,它可能會到達,它可能次序顛倒了。如果它到達,那麼在這個包的內部是無錯誤的。資料報也使用 IP 作路由,但是它不使用 TCP。它使用“使用者資料包通訊協定 (User Datagram Protocol)”,也叫 “UDP” (請參考 RFC-768。)  
    為什麼它們是不需連線的呢?主要是因為它並不象流式通訊端那樣維持一個串連。你只要建立一個包,構造一個有目標資訊的IP 頭,然後發出去。無需串連。它們通常使用於傳輸包-包資訊。簡單的應用程式有:tftp, bootp等等。 
    你也許會想:“假如資料丟失了這些程式如何正常工作?”我的朋友,每個程式在 UDP 上有自己的協議。例如,tftp 協議每發出的一個被接受到包,收到者必鬚髮回一個包來說“我收到了!” (一個“命令正確應答”也叫“ACK” 包)。如果在一定時間內(例如5秒),發送方沒有收到應答,它將重新發送,直到得到 ACK。這一ACK過程在實現 SOCK_DGRAM 應用程式的時候非常重要。 
-------------------------------------------------------------------------------- 
網路理論 
  既然我剛才提到了協議層,那麼現在是討論網路究竟如何工作和一些 關於 SOCK_DGRAM 包是如何建立的例子。當然,你也可以跳過這一段, 如果你認為已經熟悉的話。  
    現在是學習資料封裝 (Data Encapsulation) 的時候了!它非常非常重 要。它重要性重要到你在網路課程學(圖1:資料封裝)習中無論如何也得也得掌握它。主要 的內容是:一個包,先是被第一個協議(在這裡是TFTP )在它的前序(也許 是報尾)封裝(“封裝”),然後,整個資料(包括 TFTP 頭)被另外一個協議 (在這裡是 UDP )封裝,然後下一個( IP ),一直重複下去,直到硬體(物理) 層( 這裡是乙太網路 )。  
當另外一台機器接收到包,硬體先剝去乙太網路頭,核心剝去IP和UDP 頭,TFTP程式再剝去TFTP頭,最後得到資料。現在我們終於講到聲名狼藉的網路分層模型 (Layered Network Model)。這種網路模型在描述網路系統上相對其它模型有很多優點。例如, 你可以寫一個通訊端程式而不用關心資料的物理傳輸(串列口,乙太網路,連 接單元介面 (AUI) 還是其它介質),因為底層的程式會為你處理它們。實際 的網路硬體和拓撲對於程式員來說是透明的。 
不說其它廢話了,我現在列出整個層次模型。如果你要參加網路考試, 可一定要記住:  
應用程式層 (Application) 
展示層 (Presentation) 
會話層 (Session) 
傳輸層(Transport) 
網路層(Network) 
資料連結層(Data Link) 
物理層(Physical) 
物理層是硬體(串口,乙太網路等等)。應用程式層是和硬體層相隔最遠的--它 是使用者和網路互動的地方。  
這個模型如此通用,如果你想,你可以把它作為修車指南。把它對應 到 Unix,結果是: 
應用程式層(Application Layer) (telnet, ftp,等等) 
傳輸層(Host-to-Host Transport Layer) (TCP, UDP) 
Internet層(Internet Layer) (IP和路由) 
網路訪問層 (Network Access Layer) (網路層,資料連結層和物理層) 
現在,你可能看到這些層次如何協調來封裝原始的資料了。  
看看建立一個簡單的資料包有多少工作?哎呀,你將不得不使用 "cat" 來建立資料包頭!這僅僅是個玩笑。對於流式通訊端你要作的是 send() 發 送資料。對於資料報式通訊端,你按照你選擇的方式封裝資料然後使用 sendto()。核心將為你建立傳輸層和 Internet 層,硬體完成網路訪問層。 這就是現代科技。  
現在結束我們的網路理論速成班。哦,忘記告訴你關於路由的事情了。 但是我不準備談它,如果你真的關心,那麼參考 IP RFC。 
-------------------------------------------------------------------------------- 
結構體  
  終於談到編程了。在這章,我將談到被通訊端用到的各種資料類型。 因為它們中的一些內容很重要了。  
首先是簡單的一個:socket描述符。它是下面的類型:  
int  
僅僅是一個常見的 int。  
從現在起,事情變得不可思議了,而你所需做的就是繼續看下去。注 意這樣的事實:有兩種位元組排列順序:重要的位元組 (有時叫 "octet",即八 位位組) 在前面,或者不重要的位元組在前面。前一種叫“網路位元組順序 (Network Byte Order)”。有些機器在內部是按照這個順序儲存資料,而另外 一些則不然。當我說某資料必須按照 NBO 順序,那麼你要調用函數(例如 htons() )來將它從本機位元組順序 (Host Byte Order) 轉換過來。如果我沒有 提到 NBO, 那麼就讓它保持本機位元組順序。 
我的第一個結構(在這個技術手冊TM中)--struct sockaddr.。這個結構 為許多類型的通訊端儲存通訊端地址資訊:  
struct sockaddr {  
   unsigned short sa_family; /* 地址家族, AF_xxx */  
   char sa_data[14]; /*14位元組協議地址*/  
   };  
sa_family 能夠是各種各樣的類型,但是在這篇文章中都是 "AF_INET"。 sa_data包含通訊端中的目標地址和連接埠資訊。這好像有點 不明智。  
為了處理struct sockaddr,程式員創造了一個並列的結構: struct sockaddr_in ("in" 代表 "Internet"。) 
struct sockaddr_in {  
   short int sin_family; /* 通訊類型 */  
   unsigned short int sin_port; /* 連接埠 */  
   struct in_addr sin_addr; /* Internet 地址 */  
   unsigned char sin_zero[8]; /* 與sockaddr結構的長度相同*/  
   };  
用這個資料結構可以輕鬆處理通訊端地址的基本元素。注意 sin_zero (它被加入到這個結構,並且長度和 struct sockaddr 一樣) 應該使用函數 bzero() 或 memset() 來全部置零。 同時,這一重要的位元組,一個指向 sockaddr_in結構體的指標也可以被指向結構體sockaddr並且代替它。這 樣的話即使 socket() 想要的是 struct sockaddr *,你仍然可以使用 struct sockaddr_in,並且在最後轉換。同時,注意 sin_family 和 struct sockaddr 中的 sa_family 一致並能夠設定為 "AF_INET"。最後,sin_port和 sin_addr 必須是網路位元組順序 (Network Byte Order)! 
你也許會反對道:"但是,怎麼讓整個資料結構 struct in_addr sin_addr 按照網路位元組順序呢?" 要知道這個問題的答案,我們就要仔細的看一看這 個資料結構: struct in_addr, 有這樣一個聯合 (unions):  
/* Internet 地址 (一個與曆史有關的結構) */  
   struct in_addr {  
   unsigned long s_addr;  
   };  
它曾經是個最壞的聯合,但是現在那些日子過去了。如果你聲明 "ina" 是資料結構 struct sockaddr_in 的執行個體,那麼 "ina.sin_addr.s_addr" 就儲 存4位元組的 IP 地址(使用網路位元組順序)。如果你不幸的系統使用的還是恐 怖的聯合 struct in_addr ,你還是可以放心4位元組的 IP 地址並且和上面 我說的一樣(這是因為使用了“#define”。)  
-------------------------------------------------------------------------------- 
本機轉換 
  我們現在到了新的章節。我們曾經講了很多網路到本機位元組順序的轉 換,現在可以實踐了!  
你能夠轉換兩種類型: short (兩個位元組)和 long (四個位元組)。這個函 數對於變數類型 unsigned 也適用。假設你想將 short 從本機位元組順序轉 換為網路位元組順序。用 "h" 表示 "本機 (host)",接著是 "to",然後用 "n" 表 示 "網路 (network)",最後用 "s" 表示 "short": h-to-n-s, 或者 htons() ("Host to Network Short")。 
太簡單了...  
如果不是太傻的話,你一定想到了由"n","h","s",和 "l"形成的正確 組合,例如這裡肯定沒有stolh() ("Short to Long Host") 函數,不僅在這裡 沒有,所有場合都沒有。但是這裡有: 
htons()--"Host to Network Short" 
  htonl()--"Host to Network Long" 
  ntohs()--"Network to Host Short" 
  ntohl()--"Network to Host Long" 
現在,你可能想你已經知道它們了。你也可能想:“如果我想改變 char 的順序要怎麼辦呢?” 但是你也許馬上就想到,“用不著考慮的”。你也許 會想到:我的 68000 機器已經使用了網路位元組順序,我沒有必要去調用 htonl() 轉換 IP 地址。你可能是對的,但是當你移植你的程式到別的機器 上的時候,你的程式將失敗。可移植性!這裡是 Unix 世界!記住:在你 將資料放到網路上的時候,確信它們是網路位元組順序的。  
最後一點:為什麼在資料結構 struct sockaddr_in 中, sin_addr 和 sin_port 需要轉換為網路位元組順序,而sin_family 需不需要呢? 答案是: sin_addr 和 sin_port 分別封裝在包的 IP 和 UDP 層。因此,它們必須要 是網路位元組順序。但是 sin_family 域只是被核心 (kernel) 使用來決定在數 據結構中包含什麼類型的地址,所以它必須是本機位元組順序。同時, sin_family 沒有發送到網路上,它們可以是本機位元組順序。  
-------------------------------------------------------------------------------- 
IP 地址和如何處理它們 
現在我們很幸運,因為我們有很多的函數來方便地操作 IP 地址。沒有 必要用手工計算它們,也沒有必要用"<<"操作來儲存成長整字型。 首先,假設你已經有了一個sockaddr_in結構體ina,你有一個IP地 址"132.241.5.10"要儲存在其中,你就要用到函數inet_addr(),將IP地址從 點數格式轉換成無符號長整型。使用方法如下: 
ina.sin_addr.s_addr = inet_addr("132.241.5.10"); 
注意,inet_addr()返回的地址已經是網路位元組格式,所以你無需再調用 函數htonl()。 
我們現在發現上面的代碼片斷不是十分完整的,因為它沒有錯誤檢查。 顯而易見,當inet_addr()發生錯誤時返回-1。記住這些位元字?(無符 號數)-1僅僅和IP地址255.255.255.255相符合!這可是廣播位址!大錯特 錯!記住要先進行錯誤檢查。 
好了,現在你可以將IP地址轉換成長整型了。有沒有其相反的方法呢? 它可以將一個in_addr結構體輸出成點數格式?這樣的話,你就要用到函數 inet_ntoa()("ntoa"的含義是"network to ascii"),就像這樣:  
printf("%s",inet_ntoa(ina.sin_addr)); 
它將輸出IP地址。需要注意的是inet_ntoa()將結構體in-addr作為一 個參數,不是長整形。同樣需要注意的是它返回的是一個指向一個字元的 指標。它是一個由inet_ntoa()控制的靜態固定的指標,所以每次調用 inet_ntoa(),它就將覆蓋上次調用時所得的IP地址。例如: 
char *a1, *a2; 


a1 = inet_ntoa(ina1.sin_addr); /* 這是198.92.129.1 */ 
a2 = inet_ntoa(ina2.sin_addr); /* 這是132.241.5.10 */ 
printf("address 1: %s\n",a1); 
printf("address 2: %s\n",a2); 
輸出如下: 
address 1: 132.241.5.10 
address 2: 132.241.5.10 
假如你需要儲存這個IP地址,使用strcopy()函數來指向你自己的字元 指標。 
上面就是關於這個主題的介紹。稍後,你將學習將一個類 似"wintehouse.gov"的字串轉換成它所對應的IP地址(查閱網域名稱服務 (DNS),稍 後)。 
-------------------------------------------------------------------------------- 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.