• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            洗塵齋

            三懸明鏡垂鴻韻,九撩清泉洗塵心

            常用鏈接

            統(tǒng)計(jì)

            最新評論

            SOCKET常用函數(shù)簡介(ZZ)

            什么是 socket?
            你始終聽到人們談?wù)撝?"socket",而你不知道他的確切含義。那么,現(xiàn)在我告訴你: 他是使用 Unix 文件描述符 (fiel descriptor) 和其他程序通訊的方式。
            什么?
            Ok --你也許聽到一些 Unix 高手 (hacker) 這樣說:“呀,Unix 中所有的東西就是文件!”那個(gè)家伙也許正在說到一個(gè)事實(shí):Unix 程序在執(zhí)行任何形式的 I/O 的時(shí)候,程序是在讀或者寫一個(gè)文件描述符。一個(gè)文件描述符只是一個(gè)和打開的文件相關(guān)聯(lián)的整數(shù)。但是(注意后面的話),這個(gè)文件可能是一個(gè)網(wǎng)絡(luò)連接, FIFO,管道,終端,磁盤上的文件 或者什么其他的東西。Unix 中所有的東西是文件!因此,你想和 Internet 上別 的程序通訊的時(shí)候,你將要通過文件描述符。最好相信剛才的話。
            現(xiàn)在你腦海中或許冒出這樣的念頭:“那么我從哪里得到網(wǎng)絡(luò)通訊的文件描述符呢, 聰明 人?”無論如何,我要回答這個(gè)問題:你利用系統(tǒng)調(diào)用 socket()。他返回套接口描 述符 (socket descriptor),然后你再通過他來調(diào)用 send() 和 recv()。
            “但是...”,你可能現(xiàn)在叫起來,“如果他是個(gè)文件描述 符,那么為什么不用一般的調(diào)用 read() 和 write() 來通過套接口通訊?”簡單的答案是:“你可以使用 一般的函數(shù)!”。詳細(xì)的答案是:“你可以,但是使用 send() 和 recv() 讓你更好的控制數(shù)據(jù)傳輸?!?
            有這樣一個(gè)事實(shí):在我們的 世界上,有很多種套接口。有 DARPA Internet 地址 (Internet 套接口),本地節(jié)點(diǎn)的路徑名 (Unix 套接口),CCITT X.25 地址 (你可以完全忽略 X.25 套接口)。 也許在你的 Unix 機(jī)器上還有其他的。我們在這里只講第一種:Internet 套接口。
            ----------------------------------------------------------------------------
            Internet 套接口的兩種類型
            什 么意思?有兩種 Internet 套接口?是的。不,我在撒謊。其實(shí)還有很多,但是我可不想 嚇著你。我們這里只講兩種。 Except for this sentence, where I'm going to tell you that "Raw Sockets" are also very powerful and you should look them up.
            好了,好了。那兩種類型是什么呢?一種是 "Stream Sockets",另外一種是 "Datagram Sockets"。我們以后談到他們的時(shí)候也會(huì)用到 "SOCK_STREAM" 和 "SOCK_DGRAM"。數(shù)據(jù)報(bào)套接口有時(shí)也叫“無連接套接口”(如果你確實(shí)要連接的時(shí)候用 connect()。)
            流式套接口是可靠的雙向通訊的數(shù)據(jù)流。如果你向套接口安順序輸出“1,2”,那么他們 將安順序“1,2”到達(dá)另一邊。他們也是無錯(cuò)誤的傳遞的,有自己的錯(cuò)誤控制。
            有 誰在使用流式套接口?你可能聽說過 telnet,不是嗎?他就使用流式套接口。你需要你所輸入的字符按順序到達(dá),不是 嗎?同樣,WWW 瀏覽器使用的 HTTP 協(xié)議也使用他們。實(shí)際上,當(dāng)你通過端口80 telnet 到一個(gè) WWW 站點(diǎn),然后輸入 “GET pagename” 的時(shí)候,你也可以得到 HTML 的內(nèi)容。
            為什么流式套接口可以達(dá)到高質(zhì)量的數(shù)據(jù)傳輸?他使用了“傳輸控制協(xié)議 (The Transmission Control Protocol)”,也叫 “TCP” (請參考 RFC-793 獲得詳細(xì)資料。)TCP 控制你的數(shù)據(jù) 按順序到達(dá)并且沒有錯(cuò)誤。你也許聽到 “TCP” 是因?yàn)槁牭竭^ “TCP/IP”。這里的 IP 是指 “Internet 協(xié)議”(請參考 RFC-791.) IP 只是處理 Internet 路由而已。
            那么數(shù)據(jù)報(bào)套接口呢?為什么他叫無連接呢?為什么他是不可靠的呢?恩,有這樣的事實(shí):如果你發(fā)送一個(gè)數(shù)據(jù)報(bào),他可能到達(dá),他可能次序顛倒了。如果他到達(dá),那么在這個(gè)包的內(nèi)部是無錯(cuò)誤的。
            數(shù)據(jù)報(bào)也使用 IP 作路由,但是他不選擇 TCP。他使用“用戶數(shù)據(jù)報(bào)協(xié)議 (User Datagram Protocol)”,也叫 “UDP” (請參考 RFC-768.)
            為什么他們是無連接的呢?主要原因是因?yàn)樗⒉幌罅魇教捉涌谀菢泳S持一個(gè)連接。 你只要建立一個(gè)包,在目標(biāo)信息中構(gòu)造一個(gè) IP 頭,然后發(fā)出去。不需要連接。應(yīng)用程序有: tftp, bootp 等等。
            “夠 了!”你也許會(huì)想,“如果數(shù)據(jù)丟失了這些程序如何正常工作?”我的朋友,每個(gè)程序在 UDP 上有自己的協(xié)議。例如,tftp 協(xié)議每發(fā)出一個(gè)包,收到者發(fā)回一個(gè)包來說“我收到了!” (一個(gè)“命令正確應(yīng)答”也叫“ACK” 包)。如果在一定時(shí)間內(nèi)(例如5秒),發(fā)送方?jīng)]有收到應(yīng)答, 他將重新發(fā)送,直到得到 ACK。這一點(diǎn)在實(shí)現(xiàn) SOCK_DGRAM 應(yīng)用程序的時(shí)候非常重要。
            ----------------------------------------------------------------------------
            網(wǎng)絡(luò)理論
            既然我剛才提到了協(xié)議層,那么現(xiàn)在是討論網(wǎng)絡(luò)究竟如何工作和演示 SOCK_DGRAM 的工作。當(dāng)然,你也可以跳過這一段,如果你認(rèn)為 已經(jīng)熟悉的話。
            朋 友們,現(xiàn)在是學(xué)習(xí) 數(shù)據(jù)封裝 (Data Encapsulation) 的時(shí)候了! 這非常非常重要。It's so important that you might just learn about it if you take the networks course here at Chico State . 主要的內(nèi)容是:一個(gè)包,先是被第一個(gè)協(xié)議(在這里是 TFTP )包裝(“封裝”), 然后,整個(gè)數(shù)據(jù)(包括 TFTP 頭)被另外一個(gè)協(xié)議(在這里是 UDP )封裝,然后下 一個(gè)( IP ),一直重復(fù)下去,直到硬件(物理)層( Ethernet )。
            當(dāng)另外一臺機(jī)器接收到包,硬件先剝?nèi)?Ethernet 頭,內(nèi)核剝?nèi)?IP 和 UDP 頭,TFTP 程序再剝?nèi)?TFTP 頭,最后得到數(shù)據(jù)。
            現(xiàn) 在我們終于講到臭名遠(yuǎn)播的 網(wǎng)絡(luò)分層模型 (Layered Network Model)。這種網(wǎng)絡(luò)模型在描述網(wǎng)絡(luò)系統(tǒng)上相對其他模型有很多優(yōu)點(diǎn)。例如,你可以寫一個(gè)套接口 程序而不用關(guān)心數(shù)據(jù)的物理傳輸(串行口,以太網(wǎng),連接單元接口 (AUI) 還是其他介質(zhì)。 因?yàn)榈讓拥某绦驗(yàn)槟闾幚硭麄?。?shí)際的網(wǎng)絡(luò)硬件和拓?fù)鋵τ诔绦騿T來說是透明的。
            不說其他廢話了,我現(xiàn)在列出整個(gè)層次模型。如果你要參加網(wǎng)絡(luò)考試,可一定要記?。?
            應(yīng)用層 (Application)
            表示層 (Presentation)
            會(huì)話層 (Session)
            傳輸層 (Transport)
            網(wǎng)絡(luò)層 (Network)
            數(shù)據(jù)鏈路層 (Data Link)
            物理層 (Physical)
            物理層是硬件(串口,以太網(wǎng)等等)。應(yīng)用層是和硬件層相隔最遠(yuǎn)的--他是用戶和網(wǎng)絡(luò) 交互的地方。
            這個(gè)模型如此通用,如果你想,你可以把他作為修車指南。把他應(yīng)用到 Unix,結(jié)果是:
            應(yīng)用層 (Application Layer) (telnet, ftp, 等等)
            傳輸層 (Host-to-Host Transport Layer) (TCP, UDP)
            Internet 層 (Internet Layer) (IP 和路由)
            網(wǎng)絡(luò)訪問層 (Network Access Layer) (網(wǎng)絡(luò)層,數(shù)據(jù)鏈路層和物理層)
            現(xiàn)在,你可能看到這些層次如何協(xié)調(diào)來封裝原始的數(shù)據(jù)了。
            看 看建立一個(gè)簡單的數(shù)據(jù)包有多少工作?哎呀,你將不得不使用 "cat" 來完成 他們!簡直是笑話。對于流式套接口你要作的是 send() 發(fā)送數(shù)據(jù)。對于數(shù)據(jù)報(bào) 式套接口你按照你選擇的方式封裝數(shù)據(jù)然后用 sendto()。內(nèi)核將為你建立傳輸 層和 Internet 層,硬件完成網(wǎng)絡(luò)訪問層。這就是現(xiàn)代科技。
            現(xiàn)在結(jié)束我們的網(wǎng)絡(luò)理論速成班。哦,忘記告訴你關(guān)于路由的事情了。但是我不準(zhǔn)備談他。 如果你真的想知道,那么參考 IP RFC。如果你從來不曾了解他,也沒有 關(guān)系,你還活著不是嗎。
            ----------------------------------------------------------------------------
            structs
            終于到達(dá)這里了,終于談到編程了。在這章,我將談到被套接口用到的各種數(shù)據(jù)類型。因?yàn)?他們中的一些太重要了。
            首先是簡單的一個(gè):socket descriptor。他是下面的類型:
            int
            僅僅是一個(gè)常見的 int。
            從 現(xiàn)在起,事情變得不可思議了。請跟我一起忍受苦惱吧。注意這樣的事實(shí): 有兩種字節(jié)排列順序:重要的字節(jié)在前面(有時(shí)叫 "octet"),或者不重要的字節(jié)在前面。 前一種叫“網(wǎng)絡(luò)字節(jié)順序 (Network Byte Order)”。有些機(jī)器在內(nèi)部是按照這個(gè)順序儲存數(shù)據(jù),而另外一些則不然。當(dāng)我說某數(shù)據(jù)必須按照 NBO 順序,那么你要調(diào)用函數(shù)(例 如 htons() )來將他從本機(jī)字節(jié)順序 (Host Byte Order) 轉(zhuǎn)換過來。如果我 沒有提到 NBO, 那么就讓他是本機(jī)字節(jié)順序吧。
            我的第一個(gè)結(jié)構(gòu)(TM)--struct sockaddr. 這個(gè)數(shù)據(jù)結(jié)構(gòu) 為許多類型的套接口儲存套接口地址信息:
            struct sockaddr {
            unsigned short sa_family; /* address family, AF_xxx */
            char sa_data[14]; /* 14 bytes of protocol address */
            };
            sa_family 能夠是各種各樣的事情,但是在這篇文章中是 "AF_INET"。 sa_data 為套接口儲存目標(biāo)地址和端口信息。看上去很笨拙,不是嗎。
            為了對付 struct sockaddr,程序員創(chuàng)造了一個(gè)并列的結(jié)構(gòu): struct sockaddr_in ("in" 代表 "Internet".)
            struct sockaddr_in {
            short int sin_family; /* Address family */
            unsigned short int sin_port; /* Port number */
            struct in_addr sin_addr; /* Internet address */
            unsigned char sin_zero[8]; /* Same size as struct sockaddr */

            };
            這 個(gè)數(shù)據(jù)結(jié)構(gòu)讓可以輕松處理套接口地址的基本元素。注意 sin_zero (他 被加入到這個(gè)結(jié)構(gòu),并且長度和 struct sockaddr 一樣) 應(yīng)該使用函數(shù) bzero() 或 memset() 來全部置零。 Also, and this is the important bit, a pointer to a struct sockaddr_in can be cast to a pointer to a struct sockaddr and vice-versa. 這樣的話 即使 socket() 想要的是 struct sockaddr *, 你仍然可以使用 struct sockaddr_in,and cast it at the last minute! 同時(shí),注意 sin_family 和 struct sockaddr 中的 sa_family 一致并能夠設(shè)置為 "AF_INET"。最后, sin_port 和 sin_addr 必須是網(wǎng)絡(luò)字節(jié)順序 (Network Byte Order)!
            你也許會(huì)反對道:"但是,怎么讓整個(gè)數(shù)據(jù)結(jié)構(gòu) struct in_addr sin_addr 按照網(wǎng)絡(luò)字節(jié)順序呢?" 要知道這個(gè)問題的答案,我們就要仔細(xì)的看一 看這個(gè)數(shù)據(jù)結(jié)構(gòu): struct in_addr, 有這樣一個(gè)聯(lián)合 (unions):
            /* Internet address (a structure for historical reasons) */
            struct in_addr {
            unsigned long s_addr;
            };
            他 曾經(jīng)是個(gè)最壞的聯(lián)合,但是現(xiàn)在那些日子過去了。如果你聲明 "ina" 是 數(shù)據(jù)結(jié)構(gòu) struct sockaddr_in 的實(shí)例,那么 "ina.sin_addr.s_addr" 就儲存4字節(jié)的 IP 地址(網(wǎng)絡(luò)字節(jié)順序)。如果你不幸的 系統(tǒng)使用的還是恐怖的聯(lián)合 struct in_addr ,你還是可以放心4字 節(jié)的 IP 地址是和上面我說的一樣(這是因?yàn)?#define。)
            ----------------------------------------------------------------------------
            Convert the Natives!
            我們現(xiàn)在到達(dá)下個(gè)章節(jié)。我們曾經(jīng)講了很多網(wǎng)絡(luò)到本機(jī)字節(jié)順序,現(xiàn)在是采取行動(dòng)的時(shí)刻了!
            你 能夠轉(zhuǎn)換兩種類型: short (兩個(gè)字節(jié))和 long (四個(gè)字節(jié))。這個(gè) 函數(shù)對于變量類型 unsigned 也適用。假設(shè)你想將 short 從本機(jī)字節(jié)順序 轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)順序。用 "h" 表示 "本機(jī) (host)",接著是 "to",然后用 "n" 表示 "網(wǎng)絡(luò) (network)",最后用 "s" 表示 "short": h-to-n-s, 或者 htons() ("Host to Network Short")。
            太簡單了...
            如果不是太傻的話,你一定想到了組合 "n","h","s",和 "l"。但是這里沒有 stolh() ("Short to Long Host") 函數(shù),但是這里有:
            htons()--"Host to Network Short"
            htonl()--"Host to Network Long"
            ntohs()--"Network to Host Short"
            ntohl()--"Network to Host Long"
            現(xiàn) 在,你可能想你已經(jīng)知道他們了。你也可能想:"如果我改變 char 的順序會(huì) 怎么樣呢? 我的 68000 機(jī)器已經(jīng)使用了網(wǎng)絡(luò)字節(jié)順序,我沒有必要去調(diào)用 htonl() 轉(zhuǎn)換 IP 地址。" 你可能是對的,但是當(dāng)你移植你的程序到別的機(jī)器上的時(shí)候,你的程序?qū)?失敗??梢浦残?!這里是 Unix 世界!記?。涸谀銓?shù)據(jù)放到網(wǎng)絡(luò)上的時(shí)候,確信他們是網(wǎng)絡(luò)字 節(jié)順序。
            最后一點(diǎn):為什么在數(shù)據(jù)結(jié)構(gòu) struct sockaddr_in 中, sin_addr 和 sin_port 需要轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)順序,而 sin_family 不需要呢? 答案是:sin_addr 和 sin_port 分別封裝在包的 IP 和 UDP 層。因此,他們必須要是網(wǎng)絡(luò)字節(jié)順序。 但是 sin_family 域只是被內(nèi)核 (kernel) 使用來決定在數(shù)據(jù)結(jié)構(gòu)中包含什么類型的地址,所以他應(yīng)該是本機(jī)字節(jié)順序。也即 sin_family 沒有 發(fā) 送到網(wǎng)絡(luò)上,他們可以是本機(jī)字節(jié)順序。
            ----------------------------------------------------------------------------
            IP 地址和如何處理他們
            現(xiàn)在我們很幸運(yùn),因?yàn)槲覀冇泻芏嗟暮瘮?shù)來方便地操作 IP 地址。沒有必要用手工計(jì)算 他們,也沒有必要用 << 操作符來操作 long。
            首 先,假設(shè)你用 struct sockaddr_in ina,你想將 IP 地址 "132.241.5.10" 儲存到其中。你要用的函數(shù)是 inet_addr(),轉(zhuǎn)換 numbers-and-dots 格式的 IP 地址到 unsigned long。這個(gè)工作可以這樣來做:
            ina.sin_addr.s_addr = inet_addr("132.241.5.10");
            注意:inet_addr() 返回的地址已經(jīng)是按照網(wǎng)絡(luò)字節(jié)順序的,你沒有必要再去調(diào)用 htonl()。
            上 面的代碼可不是很健壯 (robust),因?yàn)闆]有錯(cuò)誤檢查。inet_addr() 在發(fā)生錯(cuò)誤 的時(shí)候返回-1。記得二進(jìn)制數(shù)嗎? 在 IP 地址為 255.255.255.255 的時(shí)候返回的是 (unsigned)-1!這是個(gè)廣播地址!記住正確的使用錯(cuò)誤檢查。
            好了,你現(xiàn) 在可以轉(zhuǎn)換字符串形式的 IP 地址為 long 了。那么你有一個(gè)數(shù)據(jù)結(jié)構(gòu) struct in_addr,該如何按照 numbers-and-dots 格式打印呢? 在這個(gè) 時(shí)候,也許你要用函數(shù) inet_ntoa() ("ntoa" 意思是 "network to ascii"):
            printf("%s",inet_ntoa(ina.sin_addr));
            他將打印 IP 地址。注意的是:函數(shù) inet_ntoa() 的參數(shù)是 struct in_addr,而不是 long。同時(shí)要注意的是他返回的是一個(gè)指向字符的指針。 在 inet_ntoa 內(nèi)部的指針靜態(tài)地儲存字符數(shù)組,因此每次你調(diào)用 inet_ntoa() 的時(shí)候他將覆蓋以前的內(nèi)容。例如:
            char *a1, *a2;
            .
            .
            a1 = inet_ntoa(ina1.sin_addr); /* this is 198.92.129.1 */
            a2 = inet_ntoa(ina2.sin_addr); /* this is 132.241.5.10 */
            printf("address 1: %s\n",a1);
            printf("address 2: %s\n",a2);
            運(yùn)行結(jié)果是:
            address 1: 132.241.5.10
            address 2: 132.241.5.10
            如果你想保存地址,那么用 strcpy() 保存到自己的字符數(shù)組中。
            這就是這章的內(nèi)容了。以后,我們將學(xué)習(xí)轉(zhuǎn)換 "whitehouse.gov" 形式的字符串到正確 的 IP 地址(請看后面的 DNS 一章。)
            ----------------------------------------------------------------------------
            socket()--得到文件描述符!
            我猜我不會(huì)再扯遠(yuǎn)了--我必須講 socket() 這個(gè)系統(tǒng)調(diào)用了。這里是詳細(xì)的定義:
            #include <sys/types.h>
            #include <sys/socket.h>
            int socket(int domain, int type, int protocol);
            但 是他們的參數(shù)怎么用? 首先,domain 應(yīng)該設(shè)置成 "AF_INET",就象上面的 數(shù)據(jù)結(jié)構(gòu) struct sockaddr_in 中一樣。然后,參數(shù) type 告訴內(nèi)核是 SOCK_STREAM 類型還是 SOCK_DGRAM 類型。最后,把 protocol 設(shè)置為 "0"。(注意:有很多種 domain、type, 我不可能一一列出了,請看 socket() 的 man page。當(dāng)然,還有一個(gè)"更好"的方式 去得到 protocol。請看 getprotobyname() 的 man page。)
            socket() 只是返回你以后在系統(tǒng)調(diào)用種可能用到的 socket 描述符,或者在錯(cuò)誤 的時(shí)候返回-1。全局變量 errno 中儲存錯(cuò)誤值。(請參考 perror() 的 man page。)
            ----------------------------------------------------------------------------
            bind()--我在哪個(gè)端口?
            一 旦你得到套接口,你可能要將套接口和機(jī)器上的一定的端口關(guān)聯(lián)起來。(如果你想用 listen() 來偵聽一定端口的數(shù)據(jù),這是必要一步--MUD 經(jīng)常告訴你說用命令 "telnet x.y.z 6969".)如果你只想用 connect(),那么這個(gè)步驟沒有必要。但是無論如何,請繼續(xù)讀下去。
            這里是系統(tǒng)調(diào)用 bind() 的大略:
            #include <sys/types.h>
            #include <sys/socket.h>
            int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
            sockfd 是調(diào)用 socket 返回的文件描述符。my_addr 是指向 數(shù)據(jù)結(jié)構(gòu) struct sockaddr 的指針,他保存你的地址(即端口和 IP 地址) 信息。addrlen 設(shè)置為 sizeof(struct sockaddr)。
            簡單得很不是嗎? 再看看例子:
            #include <string.h>
            #include <sys/types.h>
            #include <sys/socket.h>
            #define MYPORT 3490
            main()
            {
            int sockfd;
            struct sockaddr_in my_addr;
            sockfd = socket(AF_INET, SOCK_STREAM, 0); /* do some error checking! */
            my_addr.sin_family = AF_INET; /* host byte order */
            my_addr.sin_port = htons(MYPORT); /* short, network byte order */
            my_addr.sin_addr.s_addr = inet_addr("132.241.5.10");
            bzero(&(my_addr.sin_zero), 8); /* zero the rest of the struct */
            /* don't forget your error checking for bind(): */
            bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));
            .
            .
            .
            這里也有要注意的幾件事情。my_addr.sin_port 是網(wǎng)絡(luò)字節(jié)順序,my_addr.sin_addr.s_addr 也是的。另外要注意到的事情是因系統(tǒng)的不同, 包含的頭文件也不盡相同,請查閱自己的 man page。
            在 bind() 主題中最后要說的話是,在處理自己的 IP 地址和/或端口的時(shí)候,有些工作 是可以自動(dòng)處理的。
            my_addr.sin_port = 0; /* choose an unused port at random */
            my_addr.sin_addr.s_addr = INADDR_ANY; /* use my IP address */
            通過將0賦給 my_addr.sin_port,你告訴 bind() 自己選擇合適的端口。同樣, 將 y_addr.sin_addr.s_addr 設(shè)置為 INADDR_ANY,你告訴他自動(dòng)填上 他所運(yùn)行的機(jī)器的 IP 地址。
            如果你一向小心謹(jǐn)慎,那么你可能注意到我沒有將 INADDR_ANY 轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)順序!這是因?yàn)槲抑纼?nèi)部的東西:INADDR_ANY 實(shí)際上就是 0!即使你 改變字節(jié)的順序,0依然是0。但是完美主義者說安全第一,那么看下面的代碼:
            my_addr.sin_port = htons(0); /* choose an unused port at random */
            my_addr.sin_addr.s_addr = htonl(INADDR_ANY); /* use my IP address */
            你可能不相信,上面的代碼將可以隨便移植。
            bind() 在錯(cuò)誤的時(shí)候依然是返回-1,并且設(shè)置全局變量 errno。
            在你調(diào)用 bind() 的時(shí)候,你要小心的另一件事情是:不要采用小于1024的端口號。所有小于1024的端口號都 被系統(tǒng)保留!你可以選擇從1024到65535(如果他們沒有被別的程序使用的話)。
            你 要注意的另外一件小事是:有時(shí)候你根本不需要調(diào)用他。如果你使用 connect() 來和遠(yuǎn)程機(jī)器通訊,你不要關(guān)心你的本地端口號(就象你在使用 telnet 的時(shí)候),你只要 簡單的調(diào)用 connect() 就夠可,他會(huì)檢查套接口是否綁定,如果沒有,他會(huì)自己綁定 一個(gè)沒有使用的本地端口。
            ----------------------------------------------------------------------------
            connect()--Hello!
            現(xiàn) 在我們假設(shè)你是個(gè) telnet 程序。你的用戶命令你(就象電影 TRON 中一樣)得到套接口 的文件描述符。你聽從命令調(diào)用了 socket()。下一步,你的用戶告訴你通過端口23(標(biāo) 準(zhǔn) telnet 端口)連接到"132.241.5.10"。你該怎么做呢?
            幸運(yùn)的是,你正在瘋狂地閱讀 connect()--如何連接到遠(yuǎn)程主機(jī)這一章。你可不想讓 你的用戶失望。
            connect() 系統(tǒng)調(diào)用是這樣的:
            #include <sys/types.h>
            #include <sys/socket.h>
            int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
            sockfd 是系統(tǒng)調(diào)用 socket() 返回的套接口文件描述符。serv_addr 是保存著目的地端口和 IP 地址的數(shù)據(jù)結(jié)構(gòu) struct sockaddr。addrlen 設(shè)置為 sizeof(struct sockaddr)。
            讓我們來看個(gè)例子:
            #include <string.h>
            #include <sys/types.h>
            #include <sys/socket.h>
            #define DEST_IP "132.241.5.10"
            #define DEST_PORT 23
            main()
            {
            int sockfd;
            struct sockaddr_in dest_addr; /* will hold the destination addr */
            sockfd = socket(AF_INET, SOCK_STREAM, 0); /* do some error checking! */
            dest_addr.sin_family = AF_INET; /* host byte order */
            dest_addr.sin_port = htons(DEST_PORT); /* short, network byte order */
            dest_addr.sin_addr.s_addr = inet_addr(DEST_IP);
            bzero(&(dest_addr.sin_zero), 8); /* zero the rest of the struct */
            /* don't forget to error check the connect()! */
            connect(sockfd, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr));
            .
            .
            .
            再一次,你應(yīng)該檢查 connect() 的返回值--他在錯(cuò)誤的時(shí)候返回-1,并 設(shè)置全局變量 errno。
            同時(shí),你可能看到,我沒有調(diào)用 bind()。另外,我也沒有管本地的端口號。我只關(guān)心 我在連接。內(nèi)核將為我選擇一個(gè)合適的端口號,而我們所連接的地方也自動(dòng)地獲得這些信息。
            ----------------------------------------------------------------------------
            listen()--Will somebody please call me?
            Ok, time for a change of pace. What if you don't want to connect to a remote host. Say, just for kicks, that you want to wait for incoming connections and handle them in some way. 處理過程分兩步:首先,你聽--listen(),然后,你接受--accept() (請看 下面的內(nèi)容)。
            除了要一點(diǎn)解釋外,系統(tǒng)調(diào)用 listen 相當(dāng)簡單。
            int listen(int sockfd, int backlog);
            sockfd 是調(diào)用 socket() 返回的套接口文件描述符。backlog 是 在進(jìn)入隊(duì)列中允許的連接數(shù)目。是什么意思呢? 進(jìn)入的連接是在隊(duì)列中一直等待直到你接受 (accept() 請看下面的文章)的連接。他們的數(shù)目限制于隊(duì)列的允許。大多數(shù)系統(tǒng)的允許數(shù)目是20,你也可以設(shè)置為5到10。
            和別的函數(shù)一樣,在發(fā)生錯(cuò)誤的時(shí)候返回-1,并設(shè)置全局變量 errno。
            你可能想象到了,在你調(diào)用 listen() 前你或者要調(diào)用 bind() 或者讓 內(nèi)核隨便選擇一個(gè)端口。如果你想偵聽進(jìn)入的連接,那么系統(tǒng)調(diào)用的順序可能是這樣的:
            socket();
            bind();
            listen();
            /* accept() goes here */
            因?yàn)樗喈?dāng)?shù)拿髁?,我將在這里不給出例子了。(在 accept() 那一章的代碼將更加 完全。)真正麻煩的部分在 accept()。
            ----------------------------------------------------------------------------
            accept()--"Thank you for calling port 3490."
            準(zhǔn) 備好了,系統(tǒng)調(diào)用 accept() 會(huì)有點(diǎn)古怪的地方的!你可以想象發(fā)生這樣的事情: 有人從很遠(yuǎn)的地方通過一個(gè)你在偵聽 (listen()) 的端口連接 (connect()) 到你的機(jī)器。他的連接將加入到等待接受 (accept()) 的隊(duì)列中。你調(diào)用 accept() 告訴他你有空閑的連接。他將返回一個(gè)新的套接口文件描述符! 原來的一個(gè)還在偵聽你的那個(gè)端口,新的最后在準(zhǔn)備發(fā)送 (send()) 和接收 ( recv()) 數(shù)據(jù)。這就是這個(gè)過程!
            函數(shù)是這樣定義的:
            #include <sys/socket.h>
            int accept(int sockfd, void *addr, int *addrlen);
            sockfd 相當(dāng)簡單,是和 listen() 中一樣的套接口描述符。addr 是個(gè)指向局部的數(shù)據(jù)結(jié)構(gòu) struct sockaddr_in 的指針。This is where the information about the incoming connection will go (and you can determine which host is calling you from which port). 在他的地址傳遞給 accept 之前,addrlen 是個(gè)局部的整形變量,設(shè)置為 sizeof(struct sockaddr_in)。accept 將 不會(huì)將多余的字節(jié)給 addr。如果你放入的少些,那么在 addrlen 的值中反映 出來。
            同樣,在錯(cuò)誤時(shí)返回-1并設(shè)置全局變量 errno。
            現(xiàn)在是你應(yīng)該熟悉的代碼片段。
            #include <string.h>
            #include <sys/types.h>
            #include <sys/socket.h>
            #define MYPORT 3490 /* the port users will be connecting to */
            #define BACKLOG 10 /* how many pending connections queue will hold */
            main()
            {
            int sockfd, new_fd; /* listen on sock_fd, new connection on new_fd */
            struct sockaddr_in my_addr; /* my address information */
            struct sockaddr_in their_addr; /* connector's address information */
            int sin_size;
            sockfd = socket(AF_INET, SOCK_STREAM, 0); /* do some error checking! */
            my_addr.sin_family = AF_INET; /* host byte order */
            my_addr.sin_port = htons(MYPORT); /* short, network byte order */
            my_addr.sin_addr.s_addr = INADDR_ANY; /* auto-fill with my IP */
            bzero(&(my_addr.sin_zero), 8); /* zero the rest of the struct */
            /* don't forget your error checking for these calls: */
            bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));
            listen(sockfd, BACKLOG);
            sin_size = sizeof(struct sockaddr_in);
            new_fd = accept(sockfd, &their_addr, &sin_size);
            .
            .
            .
            注意,在系統(tǒng)調(diào)用 send() 和 recv() 中你應(yīng)該使用新的文件描述符。 如果你只想讓一個(gè)連接進(jìn)來,那么你可以使用 close() 去關(guān)閉原來的文件描述 符 sockfd 來避免同一個(gè)端口更多的連接。
            ----------------------------------------------------------------------------
            send() and recv()--Talk to me, baby!
            這兩個(gè)函數(shù)用于流式套接口和數(shù)據(jù)報(bào)套接口的通訊。如果你喜歡使用無連接的數(shù)據(jù)報(bào) 套接口,你應(yīng)該看一看下面關(guān)于 sendto() 和 recvfrom() 的章節(jié)。
            send() 是這樣的:
            int send(int sockfd, const void *msg, int len, int flags);
            sockfd 是你想發(fā)送數(shù)據(jù)的套接口描述符(或者是調(diào)用 socket() 或者是 accept() 返回的。)msg 是指向你想發(fā)送的數(shù)據(jù)的指針。len 是 數(shù)據(jù)的長度。把 flags 設(shè)置為 0 就可以了。(詳細(xì)的資料請看 send() 的 man page)。
            這里是一些可能的例子:
            char *msg = "Beej was here!";
            int len, bytes_sent;
            .
            .
            len = strlen(msg);
            bytes_sent = send(sockfd, msg, len, 0);
            .
            .
            .
            send() 返回實(shí)際發(fā)送的數(shù)據(jù)的字節(jié)數(shù)--他可能小于你要求發(fā)送的數(shù)目!也即你告訴他要發(fā)送一堆數(shù)據(jù)可是他不能處理成功。他只是發(fā)送他可能發(fā)送的數(shù)據(jù),然后 希望你以后能夠發(fā)送其他的數(shù)據(jù)。記住,如果 send() 返回的數(shù)據(jù)和 len 不 匹配,你應(yīng)該發(fā)送其他的數(shù)據(jù)。但是這里也有個(gè)好消息:如果你要發(fā)送的包很小(小于大約 1K),他可能處理讓數(shù)據(jù)一次發(fā)送完。最后,在錯(cuò)誤的時(shí)候返回-1,并設(shè)置 errno。
            recv() 函數(shù)很相似:
            int recv(int sockfd, void *buf, int len, unsigned int flags);
            sockfd 是要讀的套接口描述符。buf 是要讀的信息的緩沖。len 是 緩沖的最大長度。flags 也可以設(shè)置為0。(請參考recv() 的 man page。)
            recv() 返回實(shí)際讀入緩沖的數(shù)據(jù)的字節(jié)數(shù)?;蛘咴阱e(cuò)誤的時(shí)候返回-1,同時(shí)設(shè)置 errno。
            很簡單,不是嗎? 你現(xiàn)在可以在流式套接口上發(fā)送數(shù)據(jù)和接收數(shù)據(jù)了。你現(xiàn)在是 Unix 網(wǎng)絡(luò)程序員了!
            ----------------------------------------------------------------------------
            sendto() 和 recvfrom()--Talk to me, DGRAM-style
            "這很不錯(cuò)啊",我聽到你說,"但是你還沒有講無連接數(shù)據(jù)報(bào)套接口呢。"沒問題,現(xiàn)在我們開始 這個(gè)內(nèi)容。
            既然數(shù)據(jù)報(bào)套接口不是連接到遠(yuǎn)程主機(jī)的,那么在我們發(fā)送一個(gè)包之前需要什么信息呢? 不錯(cuò),是目標(biāo)地址!看下面的:
            int sendto(int sockfd, const void *msg, int len, unsigned int flags,
            const struct sockaddr *to, int tolen);
            你 已經(jīng)看到了,除了另外的兩個(gè)信息外,其余的和函數(shù) send() 是一樣的。 to 是個(gè)指向數(shù)據(jù)結(jié)構(gòu) struct sockaddr 的指針,他包含了目的地的 IP 地址和斷口信息。tolen 可以簡單地設(shè)置為 sizeof(struct sockaddr)。
            和函數(shù) send() 類似,sendto() 返回實(shí)際發(fā)送的字節(jié)數(shù)(他也可能小于你 想要發(fā)送的字節(jié)數(shù)!),或者在錯(cuò)誤的時(shí)候返回 -1。
            相似的還有函數(shù) recv() 和 recvfrom()。recvfrom() 的定義是 這樣的:
            int recvfrom(int sockfd, void *buf, int len, unsigned int flags
            struct sockaddr *from, int *fromlen);
            又 一次,除了一點(diǎn)多余的參數(shù)外,這個(gè)函數(shù)和 recv() 也是一樣的。from 是 一個(gè)指向局部數(shù)據(jù)結(jié)構(gòu) struct sockaddr 的指針,他的內(nèi)容是源機(jī)器 的 IP 地址和端口信息。fromlen 是個(gè) int 型的局部指針,他的初始值 為 sizeof(struct sockaddr)。函數(shù)調(diào)用后,fromlen 保存著 實(shí)際儲存在 from 中的地址的長度。
            recvfrom() 返回收到的字節(jié)長度,或者在發(fā)生錯(cuò)誤后返回 -1。
            記住,如果你是用 connect() 連接一個(gè)數(shù)據(jù)報(bào)套接口,你可以簡單的調(diào)用 send() 和 recv() 來滿足你的要求。這個(gè)時(shí)候依然是數(shù)據(jù)報(bào)套接口,依然使用 UDP,系統(tǒng) 自動(dòng)的加上了目標(biāo)和源的信息。
            ----------------------------------------------------------------------------
            close() 和 shutdown()--Get outta my face!
            你已經(jīng)整天都在發(fā)送 (send()) 和接收 (recv()) 數(shù)據(jù)了,現(xiàn)在你準(zhǔn)備 關(guān)閉你的套接口描述符了。這很簡單,你可以使用一般的 Unix 文件描述符的 close() 函 數(shù):
            close(sockfd);
            他將防止套接口上更多的數(shù)據(jù)的讀寫。任何在另一端讀寫套接口的企圖都將返回錯(cuò)誤信息。
            如果你想在如何關(guān)閉套接口上有多一點(diǎn)的控制,你可以使用函數(shù) shutdown()。他能夠讓 你將一定方向的通訊或者雙向的通訊(就象 close() 一樣)關(guān)閉,你可以使用:
            int shutdown(int sockfd, int how);
            sockfd 是你想要關(guān)閉的套接口文件描述復(fù)。how 的值是下面的其中之一:
            0 - Further receives are disallowed
            1 - Further sends are disallowed
            2 - Further sends and receives are disallowed (和 close() 一樣
            shutdown() 成功時(shí)返回 0,失敗時(shí)返回 -1(同時(shí)設(shè)置 errno。)
            如果在無連接的數(shù)據(jù)報(bào)套接口中使用 shutdown(),那么只不過是讓 send() 和 recv() 不能使用(記得你在數(shù)據(jù)報(bào)套接口中使用了 connect 后是可以 使用他們的嗎?)
            ----------------------------------------------------------------------------
            getpeername()--Who are you?
            這個(gè)函數(shù)太簡單了。
            他太簡單了,以至我都不想單列一章。但是我還是這樣做了。
            函數(shù) getpeername() 告訴你在連接的流式套接口上誰在另外一邊。函數(shù)是這樣的:
            #include <sys/socket.h>
            int getpeername(int sockfd, struct sockaddr *addr, int *addrlen);
            sockfd 是連接的流式套接口的描述符。addr 是一個(gè)指向結(jié)構(gòu) struct sockaddr (或者是 struct sockaddr_in) 的指針,他保存著 連接的另一邊的信息。addrlen 是一個(gè) int 型的指針,他初始化為 sizeof(struct sockaddr)。
            函數(shù)在錯(cuò)誤的時(shí)候返回 -1,設(shè)置相應(yīng)的 errno。
            一 旦你獲得他們的地址,你可以使用 inet_ntoa() 或者 gethostbyaddr() 來打印或者獲得更多的信息。但是你不能得到他的帳號。(如果他運(yùn)行著愚蠢的守護(hù)進(jìn)程,這是 可能的,但是他的討論已經(jīng)超出了本文的范圍,請參考 RFC-1413 以獲得更多的信息。)
            -------------------------------------------------------------------------------
            gethostname()--Who am I?
            甚至比 getpeername() 還簡單的函數(shù)是 gethostname()。他返回你程序 所運(yùn)行的機(jī)器的主機(jī)名字。然后你可以使用 gethostbyname() 以獲得你的機(jī)器的 IP 地址。
            下面是定義:
            #include <unistd.h>
            int gethostname(char *hostname, size_t size);
            參數(shù)很簡單:hostname 是一個(gè)字符數(shù)組指針,他將在函數(shù)返回時(shí)保存 主機(jī)名。size 是 hostname 數(shù)組的字節(jié)長度。
            函數(shù)調(diào)用成功時(shí)返回 0,失敗時(shí)返回 -1,并設(shè)置 errno。
            --------------------------------------------------------------------------------
            DNS--You say "whitehouse.gov", I say "198.137.240.100"
            如 果你不知道 DNS 的意思,那么我告訴你,他代表"域名服務(wù) (Domain Name Service)"。他主要的功能是:你給他一個(gè)容易記憶的某站點(diǎn)的地址,他給你 IP 地址(然后你就可以 使用 bind(), connect(), sendto() 或者其他函數(shù)。)當(dāng)一個(gè)人 輸入:
            $ telnet whitehouse.gov
            telnet 能知道他將連接 (connect()) 到 "198.137.240.100"。
            但是這是如何工作的呢? 你可以調(diào)用函數(shù) gethostbyname():
            #include <netdb.h>
            struct hostent *gethostbyname(const char *name);
            很明白的是,他返回一個(gè)指向 struct hostent 的指針。這個(gè)數(shù)據(jù)結(jié)構(gòu)是 這樣的:
            struct hostent {
            char *h_name;
            char **h_aliases;
            int h_addrtype;
            int h_length;
            char **h_addr_list;
            };
            #define h_addr h_addr_list[0]
            這里是這個(gè)數(shù)據(jù)結(jié)構(gòu)的詳細(xì)資料: struct hostent:
            h_name - Official name of the host.
            h_aliases - A NULL-terminated array of alternate names for the host.
            h_addrtype - The type of address being returned; usually AF_INET.
            h_length - The length of the address in bytes.
            h_addr_list - A zero-terminated array of network addresses for the host. Host addresses are in Network Byte Order.
            h_addr - The first address in h_addr_list.
            gethostbyname() 成功時(shí)返回一個(gè)指向 struct hostent 的 指針,或者是個(gè)空 (NULL) 指針。(但是和以前不同,errno 不設(shè)置,h_errno 設(shè)置錯(cuò)誤信息。請看下面的 herror()。)
            但是如何使用呢? 這個(gè)函數(shù)可不象他看上去那么難用。
            這里是個(gè)例子:
            #include <stdio.h>
            #include <stdlib.h>
            #include <errno.h>
            #include <netdb.h>
            #include <sys/types.h>
            #include <netinet/in.h>
            int main(int argc, char *argv[])
            {
            struct hostent *h;
            if (argc != 2) { /* error check the command line */
            fprintf(stderr,"usage: getip address\n");
            exit(1);
            }
            if ((h=gethostbyname(argv[1])) == NULL) { /* get the host info */
            herror("gethostbyname");
            exit(1);
            }
            printf("Host name : %s\n", h->h_name);
            printf("IP Address : %s\n",inet_ntoa(*((struct in_addr *)h->h_addr)));
            return 0;
            }
            在使用 gethostbyname() 的時(shí)候,你不能用 perror() 打印錯(cuò)誤信息(因 為 errno 沒有使用),你應(yīng)該調(diào)用 herror()。
            相當(dāng)簡單,你只是傳遞一個(gè)保存機(jī)器名的自負(fù)串(例如 "whitehouse.gov") 給 gethostbyname(),然后從返回的數(shù)據(jù)結(jié)構(gòu) struct hostent 中 收集信息。
            唯一讓人迷惑的是打印 IP 地址信息。h->h_addr 是一個(gè) char *, 但是 inet_ntoa() 需要的是 struct in_addr。因此,我 轉(zhuǎn)換 h->h_addr 成 struct in_addr *,然后得到數(shù)據(jù)。
            --------------------------------------------------------------------------------
            Client-Server Background
            這 里是個(gè)客戶--服務(wù)器的世界。在網(wǎng)絡(luò)上的所有東西都是在處理客戶進(jìn)程和服務(wù)器進(jìn)程的交談。 舉個(gè) telnet 的例子。當(dāng)你用 telnet (客戶)通過 23 號端口登陸到主機(jī),主機(jī)上運(yùn)行 的一個(gè)程序(一般叫 telnetd,服務(wù)器)激活。他處理這個(gè)連接,顯示登陸界面,等等。
            Figure 2. The Client-Server Relationship.
            圖 2 說明了客戶和服務(wù)器之間的信息交換。
            注 意,客--服務(wù)器之間可以使用SOCK_STREAM、SOCK_DGRAM 或者其他(只要他們采用相同的)。一些很好的客戶--服務(wù)器的例子有 telnet/telnetd、 ftp/ftpd 和 bootp/bootpd。每次你使用 ftp 的 時(shí)候,在遠(yuǎn)端都有一個(gè) ftpd 為你服務(wù)。
            一般,在服務(wù)端只有一個(gè)服務(wù)器,他采用 fork() 來處理多個(gè)客戶的連接。基本的 程序是:服務(wù)器等待一個(gè)連接,接受 (accept()) 連接,然后 fork() 一個(gè) 子進(jìn)程處理他。這是下一章我們的例子中會(huì)講到的。
            --------------------------------------------------------------------------------
            簡單的服務(wù)器
            這個(gè)服務(wù)器所做的全部工作是在留式連接上發(fā)送字符串 "Hello, World!\n"。你要 測試這個(gè)程序的話,可以在一臺機(jī)器上運(yùn)行該程序,然后在另外一機(jī)器上登陸:
            $ telnet remotehostname 3490
            remotehostname 是該程序運(yùn)行的機(jī)器的名字。
            服務(wù)器代碼:
            #include <stdio.h>
            #include <stdlib.h>
            #include <errno.h>
            #include <string.h>
            #include <sys/types.h>
            #include <netinet/in.h>
            #include <sys/socket.h>
            #include <sys/wait.h>
            #define MYPORT 3490 /* the port users will be connecting to */
            #define BACKLOG 10 /* how many pending connections queue will hold */
            main()
            {
            int sockfd, new_fd; /* listen on sock_fd, new connection on new_fd */
            struct sockaddr_in my_addr; /* my address information */
            struct sockaddr_in their_addr; /* connector's address information */
            int sin_size;
            if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
            perror("socket");
            exit(1);
            }
            my_addr.sin_family = AF_INET; /* host byte order */
            my_addr.sin_port = htons(MYPORT); /* short, network byte order */
            my_addr.sin_addr.s_addr = INADDR_ANY; /* auto-fill with my IP */
            bzero(&(my_addr.sin_zero), 8); /* zero the rest of the struct */
            if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) \
            == -1) {
            perror("bind");
            exit(1);
            }
            if (listen(sockfd, BACKLOG) == -1) {
            perror("listen");
            exit(1);
            }
            while(1) { /* main accept() loop */
            sin_size = sizeof(struct sockaddr_in);
            if ((new_fd = accept(sockfd, (struct sockaddr *)&their_addr, \
            &sin_size)) == -1) {
            perror("accept");
            continue;
            }
            printf("server: got connection from %s\n", \
            inet_ntoa(their_addr.sin_addr));
            if (!fork()) { /* this is the child process */
            if (send(new_fd, "Hello, world!\n", 14, 0) == -1)
            perror("send");
            close(new_fd);
            exit(0);
            }
            close(new_fd); /* parent doesn't need this */
            while(waitpid(-1,NULL,WNOHANG) > 0); /* clean up child processes */
            }
            }
            如果你很挑剔的話,一定不滿意我所有的代碼都在一個(gè)很大的 main() 函數(shù) 中。如果你不喜歡,可以劃分得更細(xì)點(diǎn)。
            你也可以用我們下一章中的程序得到服務(wù)器端發(fā)送的字符串。
            --------------------------------------------------------------------------------
            簡單的客戶程序
            這個(gè)程序比服務(wù)器還簡單。這個(gè)程序的所有工作是通過 3490 端口連接到命令行中制定的主機(jī), 然后得到服務(wù)器的字符串。
            客戶代碼:
            #include <stdio.h>
            #include <stdlib.h>
            #include <errno.h>
            #include <string.h>
            #include <netdb.h>
            #include <sys/types.h>
            #include <netinet/in.h>
            #include <sys/socket.h>
            #define PORT 3490 /* the port client will be connecting to */
            #define MAXDATASIZE 100 /* max number of bytes we can get at once */
            int main(int argc, char *argv[])
            {
            int sockfd, numbytes;
            char buf[MAXDATASIZE];
            struct hostent *he;
            struct sockaddr_in their_addr; /* connector's address information */
            if (argc != 2) {
            fprintf(stderr,"usage: client hostname\n");
            exit(1);
            }
            if ((he=gethostbyname(argv[1])) == NULL) { /* get the host info */
            herror("gethostbyname");
            exit(1);
            }
            if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
            perror("socket");

            exit(1);

            }

            their_addr.sin_family = AF_INET; /* host byte order */

            their_addr.sin_port = htons(PORT); /* short, network byte order */

            their_addr.sin_addr = *((struct in_addr *)he->h_addr);

            bzero(&(their_addr.sin_zero), 8); /* zero the rest of the struct */

            if (connect(sockfd, (struct sockaddr *)&their_addr, \
            sizeof(struct sockaddr)) == -1) {
            perror("connect");
            exit(1);
            }
            if ((numbytes=recv(sockfd, buf, MAXDATASIZE, 0)) == -1) {
            perror("recv");
            exit(1);
            }
            buf[numbytes] = '\0';
            printf("Received: %s",buf);
            close(sockfd);
            return 0;
            }
            注意,如果你在運(yùn)行服務(wù)器之前運(yùn)行客戶程序,connect() 將返回 "Connection refused" 信息。
            --------------------------------------------------------------------------------
            數(shù)據(jù)報(bào) Sockets
            我不想講更多了,所以我給出代碼 talker.c 和 listener.c。
            listener 在機(jī)器上等待在端口 4590 來的數(shù)據(jù)包。talker 發(fā)送數(shù)據(jù)包到一定的 機(jī)器,他包含用戶在命令行輸入的東西。
            這里就是 listener.c:
            #include <stdio.h>
            #include <stdlib.h>
            #include <errno.h>
            #include <string.h>
            #include <sys/types.h>
            #include <netinet/in.h>
            #include <sys/socket.h>
            #include <sys/wait.h>
            #define MYPORT 4950 /* the port users will be sending to */
            #define MAXBUFLEN 100
            main()
            {
            int sockfd;
            struct sockaddr_in my_addr; /* my address information */
            struct sockaddr_in their_addr; /* connector's address information */
            int addr_len, numbytes;
            char buf[MAXBUFLEN];
            if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
            perror("socket");
            exit(1);
            }
            my_addr.sin_family = AF_INET; /* host byte order */
            my_addr.sin_port = htons(MYPORT); /* short, network byte order */
            my_addr.sin_addr.s_addr = INADDR_ANY; /* auto-fill with my IP */
            bzero(&(my_addr.sin_zero), 8); /* zero the rest of the struct */
            if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) \
            == -1) {
            perror("bind");
            exit(1);
            }
            addr_len = sizeof(struct sockaddr);
            if ((numbytes=recvfrom(sockfd, buf, MAXBUFLEN, 0, \
            (struct sockaddr *)&their_addr, &addr_len)) == -1) {
            perror("recvfrom");
            exit(1);
            }
            printf("got packet from %s\n",inet_ntoa(their_addr.sin_addr));
            printf("packet is %d bytes long\n",numbytes);
            buf[numbytes] = '\0';
            printf("packet contains \"%s\"\n",buf);
            close(sockfd);
            }
            注意在我們的調(diào)用 socket(),我們最后使用了 SOCK_DGRAM。同時(shí),沒有 必要去使用 listen() 或者 accept()。我們在使用無連接的數(shù)據(jù)報(bào)套接口!
            下面是 talker.c:
            #include <stdio.h>
            #include <stdlib.h>
            #include <errno.h>
            #include <string.h>
            #include <sys/types.h>
            #include <netinet/in.h>
            #include <netdb.h>
            #include <sys/socket.h>
            #include <sys/wait.h>
            #define MYPORT 4950 /* the port users will be sending to */
            int main(int argc, char *argv[])
            {
            int sockfd;
            struct sockaddr_in their_addr; /* connector's address information */
            struct hostent *he;
            int numbytes;
            if (argc != 3) {
            fprintf(stderr,"usage: talker hostname message\n");
            exit(1);
            }
            if ((he=gethostbyname(argv[1])) == NULL) { /* get the host info */
            herror("gethostbyname");
            exit(1);
            }
            if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
            perror("socket");
            exit(1);
            }
            their_addr.sin_family = AF_INET; /* host byte order */
            their_addr.sin_port = htons(MYPORT); /* short, network byte order */
            their_addr.sin_addr = *((struct in_addr *)he->h_addr);
            bzero(&(their_addr.sin_zero), 8); /* zero the rest of the struct */
            if ((numbytes=sendto(sockfd, argv[2], strlen(argv[2]), 0, \
            (struct sockaddr *)&their_addr, sizeof(struct sockaddr))) == -1) {
            perror("sendto");
            exit(1);
            }
            printf("sent %d bytes to %s\n",numbytes,inet_ntoa(their_addr.sin_addr));
            close(sockfd);
            return 0;
            }
            這就是所有的了。在一臺機(jī)器上運(yùn)行 listener,然后在另外一臺機(jī)器上運(yùn)行 talker。觀察他們的通訊!
            Except for one more tiny detail that I've mentioned many times in the past: connected datagram sockets. I need to talk about this here, since we're in the datagram section of the document. Let's say that talker calls connect() and specifies the listener's address. From that point on, talker may only sent to and receive from the address specified by connect(). For this reason, you don't have to use sendto() and recvfrom(); you can simply use send() and recv().
            --------------------------------------------------------------------------------
            阻塞
            阻 塞,你也許早就聽說了。"阻塞"是 "sleep" 的科技行話。你可能注意到前面運(yùn)行的 listener 程序,他在那里不停地運(yùn)行,等待數(shù)據(jù)包的到來。實(shí)際在運(yùn)行的是 他調(diào)用 recvfrom(),然后沒有數(shù)據(jù),因此 recvfrom() 說"阻塞 (block)" 直到數(shù)據(jù)的到來。
            很多函數(shù)都利用阻塞。accept() 阻塞,所有的 recv*() 函數(shù)阻塞。他們之所以能這樣做是因?yàn)樗麄儽辉试S這樣做。當(dāng)你第一次調(diào)用 socket() 建立套接口描述符的時(shí)候,內(nèi)核就將他設(shè)置為阻塞。如果你不想套接口阻塞,你就要調(diào)用函數(shù) fcntl():
            #include <unistd.h>
            #include <fcntl.h>
            .
            .
            sockfd = socket(AF_INET, SOCK_STREAM, 0);
            fcntl(sockfd, F_SETFL, O_NONBLOCK);
            .
            .
            通過設(shè)置套接口為非阻塞,你能夠有效地"詢問"套接口以獲得信息。如果你嘗試著 從一個(gè)非阻塞的套接口讀信息并且沒有任何數(shù)據(jù),他不會(huì)變成阻塞--他將返回 -1 并 將 errno 設(shè)置為 EWOULDBLOCK。
            但是一般說來,這種輪詢不是個(gè)好主意。如果你讓你的程序在忙等狀態(tài)查詢套接口的數(shù)據(jù), 你將浪費(fèi)大量的 CPU 時(shí)間。更好的解決之道是用下一章講的 select() 去查詢 是否有數(shù)據(jù)要讀進(jìn)來。
            --------------------------------------------------------------------------------
            select()--多路同步 I/O
            雖然這個(gè)函數(shù)有點(diǎn)奇怪,但是他很有用。假設(shè)這樣的情況:你是個(gè)服務(wù)器,你一邊在不停地 從連接上讀數(shù)據(jù),一邊在偵聽連接上的信息。
            沒 問題,你可能會(huì)說,不就是一個(gè) accept() 和兩個(gè) recv() 嗎? 這么容易 嗎,朋友? 如果你在調(diào)用 accept() 的時(shí)候阻塞呢? 你怎么能夠同時(shí)接受 recv() 數(shù)據(jù)? "用非阻塞的套接口啊!" 不行!你不想耗盡所有的 CPU,不是嗎? 那么,該如何是好?
            select() 讓你可以同時(shí)監(jiān)視多個(gè)套接口。如果你想知道的話,那么他就會(huì)告訴你哪個(gè)套接口準(zhǔn)備讀,哪個(gè)又 準(zhǔn)備好了寫,哪個(gè)套接口又發(fā)生了例外 (exception)。
            閑話少說,下面是 select():
            #include <sys/time.h>
            #include <sys/types.h>
            #include <unistd.h>
            int select(int numfds, fd_set *readfds, fd_set *writefds,
            fd_set *exceptfds, struct timeval *timeout);
            這 個(gè)函數(shù)監(jiān)視一系列文件描述符,特別是 readfds、writefds 和 exceptfds。如果你想知道你是否能夠從標(biāo)準(zhǔn)輸入和套接口描述符 sockfd 讀 入數(shù)據(jù),你只要將文件描述符 0 和 sockfd 加入到集合 readfds 中。 參數(shù) numfds 應(yīng)該等于最高的文件描述符的值加1。在這個(gè)例子中,你應(yīng)該設(shè)置該值 為 sockfd+1。因?yàn)樗欢ù笥跇?biāo)準(zhǔn)輸入的文件描述符 (0)。
            當(dāng)函數(shù) select() 返回的時(shí)候,readfds 的值修改為反映你選擇的哪個(gè)文件 描述符可以讀。你可以用下面講到的宏 FD_ISSET() 來測試。
            在我們繼續(xù)下去之前,讓我來講講如何對這些集合進(jìn)行操作。每個(gè)集合類型都是 fd_set。 下面有一些宏來對這個(gè)類型進(jìn)行操作:
            FD_ZERO(fd_set *set) - clears a file descriptor set
            FD_SET(int fd, fd_set *set) - adds fd to the set
            FD_CLR(int fd, fd_set *set) - removes fd from the set
            FD_ISSET(int fd, fd_set *set) - tests to see if fd is in the set
            最 后,是有點(diǎn)古怪的數(shù)據(jù)結(jié)構(gòu) struct timeval。有時(shí)你可不想永遠(yuǎn)等待別人發(fā)送數(shù)據(jù)過來。也許什么事情都沒有發(fā)生的時(shí)候你也想每隔96秒在終端 上打印字符串 "Still Going..."。這個(gè)數(shù)據(jù)結(jié)構(gòu)允許你設(shè)定一個(gè)時(shí)間,如果時(shí)間到了, 而 select() 還沒有找到一個(gè)準(zhǔn)備好的文件描述符,他將返回讓你繼續(xù)處理。
            數(shù)據(jù)結(jié)構(gòu) struct timeval 是這樣的:
            struct timeval {
            int tv_sec; /* seconds */
            int tv_usec; /* microseconds */
            };
            只 要將 tv_sec 設(shè)置為你要等待的秒數(shù),將 tv_usec 設(shè)置為你要等待的微秒數(shù)就可以了。是的,是微秒而不是毫秒。1,000微秒等于1豪秒,1,000毫秒等于1秒。也就是說,1秒等于1,000,000微 秒。為什么用符號 "usec" 呢? 字母 "u" 很象希臘字母 Mu, 而 Mu 表示 "微" 的意思。當(dāng)然,函數(shù)返回的時(shí)候 timeout 可能是剩余的 時(shí)間,之所以是可能,是因?yàn)樗蕾囉谀愕?Unix 操作系統(tǒng)。
            哈!我們現(xiàn)在有一個(gè)微秒級的定時(shí)器!不要計(jì)算了,標(biāo)準(zhǔn)的 Unix 系統(tǒng)的時(shí)間片是100毫秒,所以 無論你如何設(shè)置你的數(shù)據(jù)結(jié)構(gòu) struct timeval,你都要等待那么長的 時(shí)間。
            還 有一些有趣的事情:如果你設(shè)置數(shù)據(jù)結(jié)構(gòu) struct timeval 中的 數(shù)據(jù)為 0,select() 將立即超時(shí),這樣就可以有效地輪詢集合中的 所有的文件描述符。如果你將參數(shù) timeout 賦值為 NULL,那么將永遠(yuǎn)不會(huì)發(fā)生超時(shí),即一直等到第一個(gè)文件描述符就緒。最后,如果你不是很關(guān)心等待多長時(shí)間,那么 就把他賦為 NULL 吧。
            下面的代碼演示了在標(biāo)準(zhǔn)輸入上等待 2.5 秒:
            #include <sys/time.h>
            #include <sys/types.h>
            #include <unistd.h>
            #define STDIN 0 /* file descriptor for standard input */
            main()
            {
            struct timeval tv;
            fd_set readfds;
            tv.tv_sec = 2;
            tv.tv_usec = 500000;
            FD_ZERO(&readfds);
            FD_SET(STDIN, &readfds);
            /* don't care about writefds and exceptfds: */
            select(STDIN+1, &readfds, NULL, NULL, &tv);
            if (FD_ISSET(STDIN, &readfds))
            printf("A key was pressed!\n");
            else
            printf("Timed out.\n");
            }
            如果你是在一個(gè) line buffered 終端上,那么你敲的鍵應(yīng)該是回車 (RETURN),否則無論如何 他都會(huì)超時(shí)。
            現(xiàn)在,你可能回認(rèn)為這就是在數(shù)據(jù)報(bào)套接口上等待數(shù)據(jù)的方式--你是對的:他可能是。 有些 Unix 系統(tǒng)可以按這種方式,而另外一些則不能。你在嘗試以前可能要先看看本系統(tǒng) 的 man page 了。
            最后一件關(guān)于 select() 的事情:如果你有一個(gè)正在偵聽 (listen()) 的套接口,你可以通過將該套接口的文件描述符加入到 readfds 集合中來看 是否有新的連接。
            這就是我關(guān)于函數(shù) select() 要講的所有的東西。

            posted on 2006-04-27 12:11 芥之舟 閱讀(2962) 評論(0)  編輯 收藏 引用 所屬分類: socket網(wǎng)絡(luò)編程

            久久青青草视频| 97热久久免费频精品99| 国产精品一久久香蕉产线看| 激情五月综合综合久久69| 久久99精品国产99久久| 国内精品九九久久久精品| 久久久婷婷五月亚洲97号色 | 精品久久久无码人妻中文字幕| 久久久久亚洲AV成人网人人网站 | 热久久这里只有精品| 国内精品久久久久影院优 | 9久久9久久精品| 久久人人妻人人爽人人爽| 久久久久久久97| 1000部精品久久久久久久久| 9久久9久久精品| 国产一区二区三精品久久久无广告| 精品久久久久中文字幕一区| 久久久久久极精品久久久 | 久久精品中文无码资源站| 久久免费的精品国产V∧| 国产 亚洲 欧美 另类 久久| 一本久久免费视频| 亚洲va中文字幕无码久久不卡| 久久久女人与动物群交毛片| 久久线看观看精品香蕉国产| 青青草国产97免久久费观看| 久久综合九色综合网站| 精品水蜜桃久久久久久久| 久久亚洲sm情趣捆绑调教| 精品久久久久久久| 一本久久综合亚洲鲁鲁五月天亚洲欧美一区二区 | 久久er国产精品免费观看2| 久久免费观看视频| 99999久久久久久亚洲| 亚洲精品97久久中文字幕无码| 热re99久久精品国99热| 久久青青国产| 国产一久久香蕉国产线看观看 | 色婷婷综合久久久久中文字幕 | 久久精品国产99国产精品亚洲|