九色国产,午夜在线视频,新黄色网址,九九色综合,天天做夜夜做久久做狠狠,天天躁夜夜躁狠狠躁2021a,久久不卡一区二区三区

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費(fèi)電子書(shū)等14項(xiàng)超值服

開(kāi)通VIP
網(wǎng)絡(luò)socket編程指南
Beej網(wǎng)絡(luò)socket編程指南
--------------------------------------------------------------------------------
介紹
  Socket 編程讓你沮喪嗎?從man pages中很難得到有用的信息嗎?你想跟上時(shí)代去編Internet相關(guān)的程序,但是為你在調(diào)用 connect() 前的bind() 的結(jié)構(gòu)而不知所措?等等…  
   好在我已經(jīng)將這些事完成了,我將和所有人共享我的知識(shí)了。如果你了解 C 語(yǔ)言并想穿過(guò)網(wǎng)絡(luò)編程的沼澤,那么你來(lái)對(duì)地方了。
--------------------------------------------------------------------------------
讀者對(duì)象  
  這個(gè)文檔是一個(gè)指南,而不是參考書(shū)。如果你剛開(kāi)始 socket 編程并想找一本入門(mén)書(shū),那么你是我的讀者。但這不是一本完全的 socket 編程書(shū)。
--------------------------------------------------------------------------------
平臺(tái)和編譯器  
  這篇文檔中的大多數(shù)代碼都在 Linux 平臺(tái)PC 上用 GNU 的 gcc 成功編譯過(guò)。而且它們?cè)?HPUX平臺(tái) 上用 gcc 也成功編譯過(guò)。但是注意,并不是每個(gè)代碼片段都獨(dú)立測(cè)試過(guò)。
--------------------------------------------------------------------------------
目錄:
1) 什么是套接字?  
2) Internet 套接字的兩種類(lèi)型  
3) 網(wǎng)絡(luò)理論  
4) 結(jié)構(gòu)體
5) 本機(jī)轉(zhuǎn)換
6) IP 地址和如何處理它們  
7) socket()函數(shù)
8) bind()函數(shù)
9) connect()函數(shù)
10) listen()函數(shù)
11) accept()函數(shù)
12) send()和recv()函數(shù)
13) sendto()和recvfrom()函數(shù)
14) close()和shutdown()函數(shù)
15) getpeername()函數(shù)
16) gethostname()函數(shù)
17) 域名服務(wù)(DNS)
18) 客戶(hù)-服務(wù)器背景知識(shí)  
19) 簡(jiǎn)單的服務(wù)器
20) 簡(jiǎn)單的客戶(hù)端
21) 數(shù)據(jù)報(bào)套接字Socket
22) 阻塞
23) select()--多路同步I/O
24) 參考資料  
--------------------------------------------------------------------------------
什么是 socket?  
  你經(jīng)常聽(tīng)到人們談?wù)撝?“socket”,或許你還不知道它的確切含義。現(xiàn)在讓我告訴你:它是使用 標(biāo)準(zhǔn)Unix 文件描述符 (file descriptor) 和其它程序通訊的方式。什么?你也許聽(tīng)到一些Unix高手(hacker)這樣說(shuō)過(guò):“呀,Unix中的一切就是文件!”那個(gè)家伙也許正在說(shuō)到一個(gè)事實(shí):Unix 程序在執(zhí)行任何形式的 I/O 的時(shí)候,程序是在讀或者寫(xiě)一個(gè)文件描述符。一個(gè)文件描述符只是一個(gè)和打開(kāi)的文件相關(guān)聯(lián)的整數(shù)。但是(注意后面的話(huà)),這個(gè)文件可能是一個(gè)網(wǎng)絡(luò)連接,F(xiàn)IFO,管道,終端,磁盤(pán)上的文件或者什么其它的東西。Unix 中所有的東西就是文件!所以,你想和Internet上別的程序通訊的時(shí)候,你將要使用到文件描述符。你必須理解剛才的話(huà)。現(xiàn)在你腦海中或許冒出這樣的念頭:“那么我從哪里得到網(wǎng)絡(luò)通訊的文件描述符呢?”,這個(gè)問(wèn)題無(wú)論如何我都要回答:你利用系統(tǒng)調(diào)用 socket(),它返回套接字描述符 (socket descriptor),然后你再通過(guò)它來(lái)進(jìn)行send() 和 recv()調(diào)用。“但是...”,你可能有很大的疑惑,“如果它是個(gè)文件描述符,那么為什 么不用一般調(diào)用read()和write()來(lái)進(jìn)行套接字通訊?”簡(jiǎn)單的答案是:“你可以使用!”。詳細(xì)的答案是:“你可以,但是使用send()和recv()讓你更好的控制數(shù)據(jù)傳輸。”存在這樣一個(gè)情況:在我們的世界上,有很多種套接字。有DARPA Internet 地址 (Internet 套接字),本地節(jié)點(diǎn)的路徑名 (Unix套接字),CCITT X.25地址 (你可以將X.25 套接字完全忽略)。也許在你的Unix 機(jī)器上還有其它的。我們?cè)谶@里只講第一種:Internet 套接字。
--------------------------------------------------------------------------------
Internet 套接字的兩種類(lèi)型  
  什么意思?有兩種類(lèi)型的Internet 套接字?是的。不,我在撒謊。其實(shí)還有很多,但是我可不想嚇著你。我們這里只講兩種。除了這些, 我打算另外介紹的 "Raw Sockets" 也是非常強(qiáng)大的,很值得查閱。
那么這兩種類(lèi)型是什么呢?一種是"Stream Sockets"(流格式),另外一種是"Datagram Sockets"(數(shù)據(jù)包格式)。我們以后談到它們的時(shí)候也會(huì)用到 "SOCK_STREAM" 和 "SOCK_DGRAM"。數(shù)據(jù)報(bào)套接字有時(shí)也叫“無(wú)連接套接字”(如果你確實(shí)要連接的時(shí)候可以用connect()。) 流式套接字是可靠的雙向通訊的數(shù)據(jù)流。如果你向套接字按順序輸出“1,2”,那么它們將按順序“1,2”到達(dá)另一邊。它們是無(wú)錯(cuò)誤的傳遞的,有自己的錯(cuò)誤控制,在此不討論。
   有什么在使用流式套接字?你可能聽(tīng)說(shuō)過(guò) telnet,不是嗎?它就使用流式套接字。你需要你所輸入的字符按順序到達(dá),不是嗎?同樣,WWW瀏覽器使用的 HTTP 協(xié)議也使用它們來(lái)下載頁(yè)面。實(shí)際上,當(dāng)你通過(guò)端口80 telnet 到一個(gè) WWW 站點(diǎn),然后輸入 “GET pagename” 的時(shí)候,你也可以得到 HTML 的內(nèi)容。為什么流式套接字可以達(dá)到高質(zhì)量的數(shù)據(jù)傳輸?這是因?yàn)樗褂昧?#8220;傳輸控制協(xié)議 (The Transmission Control Protocol)”,也叫 “TCP” (請(qǐng)參考 RFC-793 獲得詳細(xì)資料。)TCP 控制你的數(shù)據(jù)按順序到達(dá)并且沒(méi)有錯(cuò)
誤。你也許聽(tīng)到 “TCP” 是因?yàn)槁?tīng)到過(guò) “TCP/IP”。這里的 IP 是指“Internet 協(xié)議”(請(qǐng)參考 RFC-791。) IP 只是處理 Internet 路由而已。  
   那么數(shù)據(jù)報(bào)套接字呢?為什么它叫無(wú)連接呢?為什么它是不可靠的呢?有這樣的一些事實(shí):如果你發(fā)送一個(gè)數(shù)據(jù)報(bào),它可能會(huì)到達(dá),它可能次序顛倒了。如果它到達(dá),那么在這個(gè)包的內(nèi)部是無(wú)錯(cuò)誤的。數(shù)據(jù)報(bào)也使用 IP 作路由,但是它不使用 TCP。它使用“用戶(hù)數(shù)據(jù)報(bào)協(xié)議 (User Datagram Protocol)”,也叫 “UDP” (請(qǐng)參考 RFC-768。)  
   為什么它們是無(wú)連接的呢?主要是因?yàn)樗⒉幌罅魇教捉幼帜菢泳S持一個(gè)連接。你只要建立一個(gè)包,構(gòu)造一個(gè)有目標(biāo)信息的IP 頭,然后發(fā)出去。無(wú)需連接。它們通常使用于傳輸包-包信息。簡(jiǎn)單的應(yīng)用程序有:tftp, bootp等等。
   你也許會(huì)想:“假如數(shù)據(jù)丟失了這些程序如何正常工作?”我的朋友,每個(gè)程序在 UDP 上有自己的協(xié)議。例如,tftp 協(xié)議每發(fā)出的一個(gè)被接受到包,收到者必須發(fā)回一個(gè)包來(lái)說(shuō)“我收到了!” (一個(gè)“命令正確應(yīng)答”也叫“ACK” 包)。如果在一定時(shí)間內(nèi)(例如5秒),發(fā)送方?jīng)]有收到應(yīng)答,它將重新發(fā)送,直到得到 ACK。這一ACK過(guò)程在實(shí)現(xiàn) SOCK_DGRAM 應(yīng)用程序的時(shí)候非常重要。
--------------------------------------------------------------------------------
網(wǎng)絡(luò)理論
  既然我剛才提到了協(xié)議層,那么現(xiàn)在是討論網(wǎng)絡(luò)究竟如何工作和一些 關(guān)于 SOCK_DGRAM 包是如何建立的例子。當(dāng)然,你也可以跳過(guò)這一段, 如果你認(rèn)為已經(jīng)熟悉的話(huà)。  
   現(xiàn)在是學(xué)習(xí)數(shù)據(jù)封裝 (Data Encapsulation) 的時(shí)候了!它非常非常重 要。它重要性重要到你在網(wǎng)絡(luò)課程學(xué)(圖1:數(shù)據(jù)封裝)習(xí)中無(wú)論如何也得也得掌握它。主要 的內(nèi)容是:一個(gè)包,先是被第一個(gè)協(xié)議(在這里是TFTP )在它的報(bào)頭(也許 是報(bào)尾)包裝(“封裝”),然后,整個(gè)數(shù)據(jù)(包括 TFTP 頭)被另外一個(gè)協(xié)議 (在這里是 UDP )封裝,然后下一個(gè)( IP ),一直重復(fù)下去,直到硬件(物理) 層( 這里是以太網(wǎng) )。  
當(dāng)另外一臺(tái)機(jī)器接收到包,硬件先剝?nèi)ヒ蕴W(wǎng)頭,內(nèi)核剝?nèi)P和UDP 頭,TFTP程序再剝?nèi)FTP頭,最后得到數(shù)據(jù)?,F(xiàn)在我們終于講到聲名狼藉的網(wǎng)絡(luò)分層模型 (Layered Network Model)。這種網(wǎng)絡(luò)模型在描述網(wǎng)絡(luò)系統(tǒng)上相對(duì)其它模型有很多優(yōu)點(diǎn)。例如, 你可以寫(xiě)一個(gè)套接字程序而不用關(guān)心數(shù)據(jù)的物理傳輸(串行口,以太網(wǎng),連 接單元接口 (AUI) 還是其它介質(zhì)),因?yàn)榈讓拥某绦驎?huì)為你處理它們。實(shí)際 的網(wǎng)絡(luò)硬件和拓?fù)鋵?duì)于程序員來(lái)說(shuō)是透明的。
不說(shuō)其它廢話(huà)了,我現(xiàn)在列出整個(gè)層次模型。如果你要參加網(wǎng)絡(luò)考試, 可一定要記住:  
應(yīng)用層 (Application)
表示層 (Presentation)
會(huì)話(huà)層 (Session)
傳輸層(Transport)
網(wǎng)絡(luò)層(Network)
數(shù)據(jù)鏈路層(Data Link)
物理層(Physical)
物理層是硬件(串口,以太網(wǎng)等等)。應(yīng)用層是和硬件層相隔最遠(yuǎn)的--它 是用戶(hù)和網(wǎng)絡(luò)交互的地方。  
這個(gè)模型如此通用,如果你想,你可以把它作為修車(chē)指南。把它對(duì)應(yīng) 到 Unix,結(jié)果是:
應(yīng)用層(Application Layer) (telnet, ftp,等等)
傳輸層(Host-to-Host Transport Layer) (TCP, UDP)
Internet層(Internet Layer) (IP和路由)
網(wǎng)絡(luò)訪問(wèn)層 (Network Access Layer) (網(wǎng)絡(luò)層,數(shù)據(jù)鏈路層和物理層)
現(xiàn)在,你可能看到這些層次如何協(xié)調(diào)來(lái)封裝原始的數(shù)據(jù)了。  
看看建立一個(gè)簡(jiǎn)單的數(shù)據(jù)包有多少工作?哎呀,你將不得不使用 "cat" 來(lái)建立數(shù)據(jù)包頭!這僅僅是個(gè)玩笑。對(duì)于流式套接字你要作的是 send() 發(fā) 送數(shù)據(jù)。對(duì)于數(shù)據(jù)報(bào)式套接字,你按照你選擇的方式封裝數(shù)據(jù)然后使用 sendto()。內(nèi)核將為你建立傳輸層和 Internet 層,硬件完成網(wǎng)絡(luò)訪問(wèn)層。 這就是現(xiàn)代科技。  
現(xiàn)在結(jié)束我們的網(wǎng)絡(luò)理論速成班。哦,忘記告訴你關(guān)于路由的事情了。 但是我不準(zhǔn)備談它,如果你真的關(guān)心,那么參考 IP RFC。
--------------------------------------------------------------------------------
結(jié)構(gòu)體  
  終于談到編程了。在這章,我將談到被套接字用到的各種數(shù)據(jù)類(lèi)型。 因?yàn)樗鼈冎械囊恍﹥?nèi)容很重要了。  
首先是簡(jiǎn)單的一個(gè):socket描述符。它是下面的類(lèi)型:  
int  
僅僅是一個(gè)常見(jiàn)的 int。  
從現(xiàn)在起,事情變得不可思議了,而你所需做的就是繼續(xù)看下去。注 意這樣的事實(shí):有兩種字節(jié)排列順序:重要的字節(jié) (有時(shí)叫 "octet",即八 位位組) 在前面,或者不重要的字節(jié)在前面。前一種叫“網(wǎng)絡(luò)字節(jié)順序 (Network Byte Order)”。有些機(jī)器在內(nèi)部是按照這個(gè)順序儲(chǔ)存數(shù)據(jù),而另外 一些則不然。當(dāng)我說(shuō)某數(shù)據(jù)必須按照 NBO 順序,那么你要調(diào)用函數(shù)(例如 htons() )來(lái)將它從本機(jī)字節(jié)順序 (Host Byte Order) 轉(zhuǎn)換過(guò)來(lái)。如果我沒(méi)有 提到 NBO, 那么就讓它保持本機(jī)字節(jié)順序。
我的第一個(gè)結(jié)構(gòu)(在這個(gè)技術(shù)手冊(cè)TM中)--struct sockaddr.。這個(gè)結(jié)構(gòu) 為許多類(lèi)型的套接字儲(chǔ)存套接字地址信息:  
struct sockaddr {  
   unsigned short sa_family; /* 地址家族, AF_xxx */  
   char sa_data[14]; /*14字節(jié)協(xié)議地址*/  
   };  
sa_family 能夠是各種各樣的類(lèi)型,但是在這篇文章中都是 "AF_INET"。 sa_data包含套接字中的目標(biāo)地址和端口信息。這好像有點(diǎn) 不明智。  
為了處理struct sockaddr,程序員創(chuàng)造了一個(gè)并列的結(jié)構(gòu): struct sockaddr_in ("in" 代表 "Internet"。)
struct sockaddr_in {  
   short int sin_family; /* 通信類(lèi)型 */  
   unsigned short int sin_port; /* 端口 */  
   struct in_addr sin_addr; /* Internet 地址 */  
   unsigned char sin_zero[8]; /* 與sockaddr結(jié)構(gòu)的長(zhǎng)度相同*/  
   };  
用這個(gè)數(shù)據(jù)結(jié)構(gòu)可以輕松處理套接字地址的基本元素。注意 sin_zero (它被加入到這個(gè)結(jié)構(gòu),并且長(zhǎng)度和 struct sockaddr 一樣) 應(yīng)該使用函數(shù) bzero() 或 memset() 來(lái)全部置零。 同時(shí),這一重要的字節(jié),一個(gè)指向 sockaddr_in結(jié)構(gòu)體的指針也可以被指向結(jié)構(gòu)體sockaddr并且代替它。這 樣的話(huà)即使 socket() 想要的是 struct sockaddr *,你仍然可以使用 struct sockaddr_in,并且在最后轉(zhuǎn)換。同時(shí),注意 sin_family 和 struct sockaddr 中的 sa_family 一致并能夠設(shè)置為 "AF_INET"。最后,sin_port和 sin_addr 必須是網(wǎng)絡(luò)字節(jié)順序 (Network Byte Order)!
你也許會(huì)反對(duì)道:"但是,怎么讓整個(gè)數(shù)據(jù)結(jié)構(gòu) struct in_addr sin_addr 按照網(wǎng)絡(luò)字節(jié)順序呢?" 要知道這個(gè)問(wèn)題的答案,我們就要仔細(xì)的看一看這 個(gè)數(shù)據(jù)結(jié)構(gòu): struct in_addr, 有這樣一個(gè)聯(lián)合 (unions):  
/* Internet 地址 (一個(gè)與歷史有關(guān)的結(jié)構(gòu)) */  
   struct in_addr {  
   unsigned long s_addr;  
   };  
它曾經(jīng)是個(gè)最壞的聯(lián)合,但是現(xiàn)在那些日子過(guò)去了。如果你聲明 "ina" 是數(shù)據(jù)結(jié)構(gòu) struct sockaddr_in 的實(shí)例,那么 "ina.sin_addr.s_addr" 就儲(chǔ) 存4字節(jié)的 IP 地址(使用網(wǎng)絡(luò)字節(jié)順序)。如果你不幸的系統(tǒng)使用的還是恐 怖的聯(lián)合 struct in_addr ,你還是可以放心4字節(jié)的 IP 地址并且和上面 我說(shuō)的一樣(這是因?yàn)槭褂昧?#8220;#define”。)  
--------------------------------------------------------------------------------
本機(jī)轉(zhuǎn)換
  我們現(xiàn)在到了新的章節(jié)。我們?cè)?jīng)講了很多網(wǎng)絡(luò)到本機(jī)字節(jié)順序的轉(zhuǎn) 換,現(xiàn)在可以實(shí)踐了!  
你能夠轉(zhuǎn)換兩種類(lèi)型: short (兩個(gè)字節(jié))和 long (四個(gè)字節(jié))。這個(gè)函 數(shù)對(duì)于變量類(lèi)型 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")。
太簡(jiǎn)單了...  
如果不是太傻的話(huà),你一定想到了由"n","h","s",和 "l"形成的正確 組合,例如這里肯定沒(méi)有stolh() ("Short to Long Host") 函數(shù),不僅在這里 沒(méi)有,所有場(chǎng)合都沒(méi)有。但是這里有:
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é)順序,我沒(méi)有必要去調(diào)用 htonl() 轉(zhuǎn)換 IP 地址。你可能是對(duì)的,但是當(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) 使用來(lái)決定在數(shù) 據(jù)結(jié)構(gòu)中包含什么類(lèi)型的地址,所以它必須是本機(jī)字節(jié)順序。同時(shí), sin_family 沒(méi)有發(fā)送到網(wǎng)絡(luò)上,它們可以是本機(jī)字節(jié)順序。  
--------------------------------------------------------------------------------
IP 地址和如何處理它們
現(xiàn)在我們很幸運(yùn),因?yàn)槲覀冇泻芏嗟暮瘮?shù)來(lái)方便地操作 IP 地址。沒(méi)有 必要用手工計(jì)算它們,也沒(méi)有必要用"<<"操作來(lái)儲(chǔ)存成長(zhǎng)整字型。 首先,假設(shè)你已經(jīng)有了一個(gè)sockaddr_in結(jié)構(gòu)體ina,你有一個(gè)IP地 址"132.241.5.10"要儲(chǔ)存在其中,你就要用到函數(shù)inet_addr(),將IP地址從 點(diǎn)數(shù)格式轉(zhuǎn)換成無(wú)符號(hào)長(zhǎng)整型。使用方法如下:
ina.sin_addr.s_addr = inet_addr("132.241.5.10");
注意,inet_addr()返回的地址已經(jīng)是網(wǎng)絡(luò)字節(jié)格式,所以你無(wú)需再調(diào)用 函數(shù)htonl()。
我們現(xiàn)在發(fā)現(xiàn)上面的代碼片斷不是十分完整的,因?yàn)樗鼪](méi)有錯(cuò)誤檢查。 顯而易見(jiàn),當(dāng)inet_addr()發(fā)生錯(cuò)誤時(shí)返回-1。記住這些二進(jìn)制數(shù)字?(無(wú)符 號(hào)數(shù))-1僅僅和IP地址255.255.255.255相符合!這可是廣播地址!大錯(cuò)特 錯(cuò)!記住要先進(jìn)行錯(cuò)誤檢查。
好了,現(xiàn)在你可以將IP地址轉(zhuǎn)換成長(zhǎng)整型了。有沒(méi)有其相反的方法呢? 它可以將一個(gè)in_addr結(jié)構(gòu)體輸出成點(diǎn)數(shù)格式?這樣的話(huà),你就要用到函數(shù) inet_ntoa()("ntoa"的含義是"network to ascii"),就像這樣:  
printf("%s",inet_ntoa(ina.sin_addr));
它將輸出IP地址。需要注意的是inet_ntoa()將結(jié)構(gòu)體in-addr作為一 個(gè)參數(shù),不是長(zhǎng)整形。同樣需要注意的是它返回的是一個(gè)指向一個(gè)字符的 指針。它是一個(gè)由inet_ntoa()控制的靜態(tài)的固定的指針,所以每次調(diào)用 inet_ntoa(),它就將覆蓋上次調(diào)用時(shí)所得的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
假如你需要保存這個(gè)IP地址,使用strcopy()函數(shù)來(lái)指向你自己的字符 指針。
上面就是關(guān)于這個(gè)主題的介紹。稍后,你將學(xué)習(xí)將一個(gè)類(lèi) 似"wintehouse.gov"的字符串轉(zhuǎn)換成它所對(duì)應(yīng)的IP地址(查閱域名服務(wù),稍 后)。
--------------------------------------------------------------------------------
socket()函數(shù)  
我想我不能再不提這個(gè)了-下面我將討論一下socket()系統(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 類(lèi)型還是 SOCK_DGRAM 類(lèi)型。最后,把 protocol 設(shè)置為 "0"。(注意:有很多種 domain、type,我不可能一一列出了,請(qǐng)看 socket() 的 man幫助。當(dāng)然,還有一個(gè)"更好"的方式去得到 protocol。同 時(shí)請(qǐng)查閱 getprotobyname() 的 man 幫助。)  
socket() 只是返回你以后在系統(tǒng)調(diào)用種可能用到的 socket 描述符,或 者在錯(cuò)誤的時(shí)候返回-1。全局變量 errno 中將儲(chǔ)存返回的錯(cuò)誤值。(請(qǐng)參考 perror() 的 man 幫助。)  
--------------------------------------------------------------------------------
bind()函數(shù)
  一旦你有一個(gè)套接字,你可能要將套接字和機(jī)器上的一定的端口關(guān)聯(lián) 起來(lái)。(如果你想用listen()來(lái)偵聽(tīng)一定端口的數(shù)據(jù),這是必要一步--MUD 告 訴你說(shuō)用命令 "telnet x.y.z 6969"。)如果你只想用 connect(),那么這個(gè)步 驟沒(méi)有必要。但是無(wú)論如何,請(qǐng)繼續(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)。  
簡(jiǎn)單得很不是嗎? 再看看例子:  
#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); /*需要錯(cuò)誤檢查 */
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),; /* 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)的不同, 包含的頭文件也不盡相同,請(qǐng)查閱本地的 man 幫助文件。
在 bind() 主題中最后要說(shuō)的話(huà)是,在處理自己的 IP 地址和/或端口的 時(shí)候,有些工作是可以自動(dòng)處理的。
my_addr.sin_port = 0; /* 隨機(jī)選擇一個(gè)沒(méi)有使用的端口 */  
  my_addr.sin_addr.s_addr = INADDR_ANY; /* 使用自己的IP地址 */  
通過(guò)將0賦給 my_addr.sin_port,你告訴 bind() 自己選擇合適的端 口。同樣,將 my_addr.sin_addr.s_addr 設(shè)置為 INADDR_ANY,你告訴 它自動(dòng)填上它所運(yùn)行的機(jī)器的 IP 地址。
如果你一向小心謹(jǐn)慎,那么你可能注意到我沒(méi)有將 INADDR_ANY 轉(zhuǎn) 換為網(wǎng)絡(luò)字節(jié)順序!這是因?yàn)槲抑纼?nèi)部的東西:INADDR_ANY 實(shí)際上就 是 0!即使你改變字節(jié)的順序,0依然是0。但是完美主義者說(shuō)應(yīng)該處處一 致,INADDR_ANY或許是12呢?你的代碼就不能工作了,那么就看下面 的代碼:
my_addr.sin_port = htons(0); /* 隨機(jī)選擇一個(gè)沒(méi)有使用的端口 */  
my_addr.sin_addr.s_addr = htonl(INADDR_ANY);/* 使用自己的IP地址 */  
你或許不相信,上面的代碼將可以隨便移植。我只是想指出,既然你 所遇到的程序不會(huì)都運(yùn)行使用htonl的INADDR_ANY。
bind() 在錯(cuò)誤的時(shí)候依然是返回-1,并且設(shè)置全局錯(cuò)誤變量errno。  
在你調(diào)用 bind() 的時(shí)候,你要小心的另一件事情是:不要采用小于 1024的端口號(hào)。所有小于1024的端口號(hào)都被系統(tǒng)保留!你可以選擇從1024 到65535的端口(如果它們沒(méi)有被別的程序使用的話(huà))。
你要注意的另外一件小事是:有時(shí)候你根本不需要調(diào)用它。如果你使 用 connect() 來(lái)和遠(yuǎn)程機(jī)器進(jìn)行通訊,你不需要關(guān)心你的本地端口號(hào)(就象 你在使用 telnet 的時(shí)候),你只要簡(jiǎn)單的調(diào)用 connect() 就可以了,它會(huì)檢 查套接字是否綁定端口,如果沒(méi)有,它會(huì)自己綁定一個(gè)沒(méi)有使用的本地端口。
--------------------------------------------------------------------------------
connect()程序
  現(xiàn)在我們假設(shè)你是個(gè) telnet 程序。你的用戶(hù)命令你得到套接字的文件 描述符。你聽(tīng)從命令調(diào)用了socket()。下一步,你的用戶(hù)告訴你通過(guò)端口 23(標(biāo)準(zhǔn) telnet 端口)連接到"132.241.5.10"。你該怎么做呢? 幸運(yùn)的是,你正在閱讀 connect()--如何連接到遠(yuǎn)程主機(jī)這一章。你可 不想讓你的用戶(hù)失望。  
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)。  
想知道得更多嗎?讓我們來(lái)看個(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; /* 目的地址*/  
sockfd = socket(AF_INET, SOCK_STREAM, 0); /* 錯(cuò)誤檢查 */  
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),; /* 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è)置全局錯(cuò)誤變量 errno。  
同時(shí),你可能看到,我沒(méi)有調(diào)用 bind()。因?yàn)槲也辉诤醣镜氐亩丝谔?hào)。 我只關(guān)心我要去那。內(nèi)核將為我選擇一個(gè)合適的端口號(hào),而我們所連接的 地方也自動(dòng)地獲得這些信息。一切都不用擔(dān)心。  
--------------------------------------------------------------------------------
listen()函數(shù)
  是換換內(nèi)容得時(shí)候了。假如你不希望與遠(yuǎn)程的一個(gè)地址相連,或者說(shuō), 僅僅是將它踢開(kāi),那你就需要等待接入請(qǐng)求并且用各種方法處理它們。處 理過(guò)程分兩步:首先,你聽(tīng)--listen(),然后,你接受--accept() (請(qǐng)看下面的 內(nèi)容)。
除了要一點(diǎn)解釋外,系統(tǒng)調(diào)用 listen 也相當(dāng)簡(jiǎn)單。
int listen(int sockfd, int backlog);  
sockfd 是調(diào)用 socket() 返回的套接字文件描述符。backlog 是在進(jìn)入 隊(duì)列中允許的連接數(shù)目。什么意思呢? 進(jìn)入的連接是在隊(duì)列中一直等待直 到你接受 (accept() 請(qǐng)看下面的文章)連接。它們的數(shù)目限制于隊(duì)列的允許。 大多數(shù)系統(tǒng)的允許數(shù)目是20,你也可以設(shè)置為5到10。
和別的函數(shù)一樣,在發(fā)生錯(cuò)誤的時(shí)候返回-1,并設(shè)置全局錯(cuò)誤變量 errno。
你可能想象到了,在你調(diào)用 listen() 前你或者要調(diào)用 bind() 或者讓內(nèi) 核隨便選擇一個(gè)端口。如果你想偵聽(tīng)進(jìn)入的連接,那么系統(tǒng)調(diào)用的順序可 能是這樣的:  
socket();  
  bind();  
listen();  
  /* accept() 應(yīng)該在這 */  
因?yàn)樗喈?dāng)?shù)拿髁?,我將在這里不給出例子了。(在 accept() 那一章的 代碼將更加完全。)真正麻煩的部分在 accept()。  
--------------------------------------------------------------------------------
accept()函數(shù)
  準(zhǔn)備好了,系統(tǒng)調(diào)用 accept() 會(huì)有點(diǎn)古怪的地方的!你可以想象發(fā)生 這樣的事情:有人從很遠(yuǎn)的地方通過(guò)一個(gè)你在偵聽(tīng) (listen()) 的端口連接 (connect()) 到你的機(jī)器。它的連接將加入到等待接受 (accept()) 的隊(duì)列 中。你調(diào)用 accept() 告訴它你有空閑的連接。它將返回一個(gè)新的套接字文 件描述符!這樣你就有兩個(gè)套接字了,原來(lái)的一個(gè)還在偵聽(tīng)你的那個(gè)端口, 新的在準(zhǔn)備發(fā)送 (send()) 和接收 ( recv()) 數(shù)據(jù)。這就是這個(gè)過(guò)程!
函數(shù)是這樣定義的:  
#include <sys/socket.h>
int accept(int sockfd, void *addr, int *addrlen);  
sockfd 相當(dāng)簡(jiǎn)單,是和 listen() 中一樣的套接字描述符。addr 是個(gè)指 向局部的數(shù)據(jù)結(jié)構(gòu) sockaddr_in 的指針。這是要求接入的信息所要去的地 方(你可以測(cè)定那個(gè)地址在那個(gè)端口呼叫你)。在它的地址傳遞給 accept 之 前,addrlen 是個(gè)局部的整形變量,設(shè)置為 sizeof(struct sockaddr_in)。 accept 將不會(huì)將多余的字節(jié)給 addr。如果你放入的少些,那么它會(huì)通過(guò)改
變 addrlen 的值反映出來(lái)。  
同樣,在錯(cuò)誤時(shí)返回-1,并設(shè)置全局錯(cuò)誤變量 errno。  
現(xiàn)在是你應(yīng)該熟悉的代碼片段。  
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#define MYPORT 3490 /*用戶(hù)接入端口*/  
#define BACKLOG 10 /* 多少等待連接控制*/  
main()  
   {  
  int sockfd, new_fd; /* listen on sock_fd, new connection on new_fd */  
  struct sockaddr_in my_addr; /* 地址信息 */  
  struct sockaddr_in their_addr; /* connector‘s address information */  
  int sin_size;  
sockfd = socket(AF_INET, SOCK_STREAM, 0); /* 錯(cuò)誤檢查*/  
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),; /* 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)該使用新的套接字描述符 new_fd。如果你只想讓一個(gè)連接進(jìn)來(lái),那么你可以使用 close() 去關(guān)閉原 來(lái)的文件描述符 sockfd 來(lái)避免同一個(gè)端口更多的連接。  
--------------------------------------------------------------------------------
send() and recv()函數(shù)
  這兩個(gè)函數(shù)用于流式套接字或者數(shù)據(jù)報(bào)套接字的通訊。如果你喜歡使 用無(wú)連接的數(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ù)的長(zhǎng)度。 把 flags 設(shè)置為 0 就可以了。(詳細(xì)的資料請(qǐng)看 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ù) 目! 注意,有時(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ā)送完。最后要說(shuō)得就是,它在錯(cuò)誤的時(shí)候返回-1,并設(shè)置 errno。
recv() 函數(shù)很相似:
int recv(int sockfd, void *buf, int len, unsigned int flags);
sockfd 是要讀的套接字描述符。buf 是要讀的信息的緩沖。len 是緩 沖的最大長(zhǎng)度。flags 可以設(shè)置為0。(請(qǐng)參考recv() 的 man page。) recv() 返回實(shí)際讀入緩沖的數(shù)據(jù)的字節(jié)數(shù)。或者在錯(cuò)誤的時(shí)候返回-1, 同時(shí)設(shè)置 errno。
很簡(jiǎn)單,不是嗎? 你現(xiàn)在可以在流式套接字上發(fā)送數(shù)據(jù)和接收數(shù)據(jù)了。 你現(xiàn)在是 Unix 網(wǎng)絡(luò)程序員了!
--------------------------------------------------------------------------------
sendto() 和 recvfrom()函數(shù)
  “這很不錯(cuò)啊”,你說(shuō),“但是你還沒(méi)有講無(wú)連接數(shù)據(jù)報(bào)套接字呢?” 沒(méi)問(wèn)題,現(xiàn)在我們開(kāi)始這個(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 可以簡(jiǎn)單地設(shè)置為 sizeof(struct sockaddr)。 和函數(shù) send() 類(lèi)似,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);
又一次,除了兩個(gè)增加的參數(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í)際儲(chǔ)存在 from 中的地址的長(zhǎng)度。
recvfrom() 返回收到的字節(jié)長(zhǎng)度,或者在發(fā)生錯(cuò)誤后返回 -1。
記住,如果你用 connect() 連接一個(gè)數(shù)據(jù)報(bào)套接字,你可以簡(jiǎn)單的調(diào) 用 send() 和 recv() 來(lái)滿(mǎn)足你的要求。這個(gè)時(shí)候依然是數(shù)據(jù)報(bào)套接字,依 然使用 UDP,系統(tǒng)套接字接口會(huì)為你自動(dòng)加上了目標(biāo)和源的信息。
--------------------------------------------------------------------------------
close()和shutdown()函數(shù)
  你已經(jīng)整天都在發(fā)送 (send()) 和接收 (recv()) 數(shù)據(jù)了,現(xiàn)在你準(zhǔn)備關(guān) 閉你的套接字描述符了。這很簡(jiǎn)單,你可以使用一般的 Unix 文件描述符 的 close() 函數(shù):
  close(sockfd);
它將防止套接字上更多的數(shù)據(jù)的讀寫(xiě)。任何在另一端讀寫(xiě)套接字的企 圖都將返回錯(cuò)誤信息。
如果你想在如何關(guān)閉套接字上有多一點(diǎn)的控制,你可以使用函數(shù) shutdown()。它允許你將一定方向上的通訊或者雙向的通訊(就象close()一 樣)關(guān)閉,你可以使用:
int shutdown(int sockfd, int how);  
sockfd 是你想要關(guān)閉的套接字文件描述復(fù)。how 的值是下面的其中之 一:
  0 – 不允許接受
  1 – 不允許發(fā)送
  2 – 不允許發(fā)送和接受(和 close() 一樣)
shutdown() 成功時(shí)返回 0,失敗時(shí)返回 -1(同時(shí)設(shè)置 errno。) 如果在無(wú)連接的數(shù)據(jù)報(bào)套接字中使用shutdown(),那么只不過(guò)是讓 send() 和 recv() 不能使用(記住你在數(shù)據(jù)報(bào)套接字中使用了 connect 后 是可以使用它們的)。
--------------------------------------------------------------------------------
getpeername()函數(shù)
  這個(gè)函數(shù)太簡(jiǎn)單了。
它太簡(jiǎn)單了,以至我都不想單列一章。但是我還是這樣做了。 函數(shù) getpeername() 告訴你在連接的流式套接字上誰(shuí)在另外一邊。函 數(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() 來(lái)打印或者獲得更多的信息。但是你不能得到它的賬號(hào)。(如果它運(yùn)行著愚 蠢的守護(hù)進(jìn)程,這是可能的,但是它的討論已經(jīng)超出了本文的范圍,請(qǐng)參 考 RFC-1413 以獲得更多的信息。)  
--------------------------------------------------------------------------------
gethostname()函數(shù)
  甚至比 getpeername() 還簡(jiǎn)單的函數(shù)是 gethostname()。它返回你程 序所運(yùn)行的機(jī)器的主機(jī)名字。然后你可以使用 gethostbyname() 以獲得你 的機(jī)器的 IP 地址。
  下面是定義:
  #include <unistd.h>
int gethostname(char *hostname, size_t size);
參數(shù)很簡(jiǎn)單:hostname 是一個(gè)字符數(shù)組指針,它將在函數(shù)返回時(shí)保存
主機(jī)名。size是hostname 數(shù)組的字節(jié)長(zhǎng)度。
函數(shù)調(diào)用成功時(shí)返回 0,失敗時(shí)返回 -1,并設(shè)置 errno。
--------------------------------------------------------------------------------
域名服務(wù)(DNS)
  如果你不知道 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 – 地址的正式名稱(chēng)。
  h_aliases – 空字節(jié)-地址的預(yù)備名稱(chēng)的指針。
  h_addrtype –地址類(lèi)型; 通常是AF_INET。  
  h_length – 地址的比特長(zhǎng)度。
  h_addr_list – 零字節(jié)-主機(jī)網(wǎng)絡(luò)地址指針。網(wǎng)絡(luò)字節(jié)順序。
  h_addr - h_addr_list中的第一地址。
gethostbyname() 成功時(shí)返回一個(gè)指向結(jié)構(gòu)體 hostent 的指針,或者 是個(gè)空 (NULL) 指針。(但是和以前不同,不設(shè)置errno,h_errno 設(shè)置錯(cuò) 誤信息。請(qǐng)看下面的 herror()。)  
但是如何使用呢? 有時(shí)候(我們可以從電腦手冊(cè)中發(fā)現(xiàn)),向讀者灌輸 信息是不夠的。這個(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) { /* 檢查命令行 */
   fprintf(stderr,"usage: getip address\n");
   exit(1);
   }
if ((h=gethostbyname(argv[1])) == NULL) { /* 取得地址信息 */
   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ò)誤信息 (因?yàn)?errno 沒(méi)有使用),你應(yīng)該調(diào)用 herror()。
相當(dāng)簡(jiǎn)單,你只是傳遞一個(gè)保存機(jī)器名的字符串(例如 "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ù)。
--------------------------------------------------------------------------------
客戶(hù)-服務(wù)器背景知識(shí)
  這里是個(gè)客戶(hù)--服務(wù)器的世界。在網(wǎng)絡(luò)上的所有東西都是在處理客戶(hù)進(jìn) 程和服務(wù)器進(jìn)程的交談。舉個(gè)telnet 的例子。當(dāng)你用 telnet (客戶(hù))通過(guò)23 號(hào)端口登陸到主機(jī),主機(jī)上運(yùn)行的一個(gè)程序(一般叫 telnetd,服務(wù)器)激活。 它處理這個(gè)連接,顯示登陸界面,等等。


圖2:客戶(hù)機(jī)和服務(wù)器的關(guān)系
圖 2 說(shuō)明了客戶(hù)和服務(wù)器之間的信息交換。  
注意,客戶(hù)--服務(wù)器之間可以使用SOCK_STREAM、SOCK_DGRAM 或者其它(只要它們采用相同的)。一些很好的客戶(hù)--服務(wù)器的例子有 telnet/telnetd、 ftp/ftpd 和 bootp/bootpd。每次你使用 ftp 的時(shí)候,在遠(yuǎn) 端都有一個(gè) ftpd 為你服務(wù)。  
一般,在服務(wù)端只有一個(gè)服務(wù)器,它采用 fork() 來(lái)處理多個(gè)客戶(hù)的連 接。基本的程序是:服務(wù)器等待一個(gè)連接,接受 (accept()) 連接,然后 fork() 一個(gè)子進(jìn)程處理它。這是下一章我們的例子中會(huì)講到的。
--------------------------------------------------------------------------------
簡(jiǎn)單的服務(wù)器
  這個(gè)服務(wù)器所做的全部工作是在流式連接上發(fā)送字符串 "Hello, World!\n"。你要測(cè)試這個(gè)程序的話(huà),可以在一臺(tái)機(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 /*定義用戶(hù)連接端口*/  
#define BACKLOG 10 /*多少等待連接控制*/  
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),; /* 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 */  
   }  
   }  
如果你很挑剔的話(huà),一定不滿(mǎn)意我所有的代碼都在一個(gè)很大的main() 函數(shù)中。如果你不喜歡,可以劃分得更細(xì)點(diǎn)。
你也可以用我們下一章中的程序得到服務(wù)器端發(fā)送的字符串。
--------------------------------------------------------------------------------
簡(jiǎn)單的客戶(hù)程序  
  這個(gè)程序比服務(wù)器還簡(jiǎn)單。這個(gè)程序的所有工作是通過(guò) 3490 端口連接到命令行中指定的主機(jī),然后得到服務(wù)器發(fā)送的字符串。  
客戶(hù)代碼:  
#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 PORT 3490 /* 客戶(hù)機(jī)連接遠(yuǎn)程主機(jī)的端口 */  
#define MAXDATASIZE 100 /* 每次可以接收的最大字節(jié) */  
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),; /* 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)行客戶(hù)程序,connect() 將返回 "Connection refused" 信息,這非常有用。
--------------------------------------------------------------------------------
數(shù)據(jù)包 Sockets  
  我不想講更多了,所以我給出代碼 talker.c 和 listener.c。  
listener 在機(jī)器上等待在端口 4590 來(lái)的數(shù)據(jù)包。talker 發(fā)送數(shù)據(jù)包到 一定的機(jī)器,它包含用戶(hù)在命令行輸入的內(nèi)容。  
這里就是 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),; /* 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í), 沒(méi)有必要去使用 listen() 或者 accept()。我們?cè)谑褂脽o(wú)連接的數(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 <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),; /* 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;  
   }  
這就是所有的了。在一臺(tái)機(jī)器上運(yùn)行 listener,然后在另外一臺(tái)機(jī)器上 運(yùn)行 talker。觀察它們的通訊!
除了一些我在上面提到的數(shù)據(jù)套接字連接的小細(xì)節(jié)外,對(duì)于數(shù)據(jù)套接 字,我還得說(shuō)一些,當(dāng)一個(gè)講話(huà)者呼叫connect()函數(shù)時(shí)并指定接受者的地 址時(shí),從這點(diǎn)可以看出,講話(huà)者只能向connect()函數(shù)指定的地址發(fā)送和接 受信息。因此,你不需要使用sendto()和recvfrom(),你完全可以用send() 和recv()代替。
--------------------------------------------------------------------------------
阻塞  
  阻塞,你也許早就聽(tīng)說(shuō)了。"阻塞"是 "sleep" 的科技行話(huà)。你可能注意 到前面運(yùn)行的 listener 程序,它在那里不停地運(yùn)行,等待數(shù)據(jù)包的到來(lái)。 實(shí)際在運(yùn)行的是它調(diào)用 recvfrom(),然后沒(méi)有數(shù)據(jù),因此 recvfrom() 說(shuō)" 阻塞 (block)",直到數(shù)據(jù)的到來(lái)。
很多函數(shù)都利用阻塞。accept() 阻塞,所有的 recv*() 函數(shù)阻塞。它 們之所以能這樣做是因?yàn)樗鼈儽辉试S這樣做。當(dāng)你第一次調(diào)用 socket() 建 立套接字描述符的時(shí)候,內(nèi)核就將它設(shè)置為阻塞。如果你不想套接字阻塞, 你就要調(diào)用函數(shù) fcntl():  
#include <unistd.h>
  #include <fontl.h>
   .  
   .  
   sockfd = socket(AF_INET, SOCK_STREAM, 0);  
   fcntl(sockfd, F_SETFL, O_NONBLOCK);  
   .  
   .  
  通過(guò)設(shè)置套接字為非阻塞,你能夠有效地"詢(xún)問(wèn)"套接字以獲得信息。如 果你嘗試著從一個(gè)非阻塞的套接字讀信息并且沒(méi)有任何數(shù)據(jù),它不允許阻 塞--它將返回 -1 并將 errno 設(shè)置為 EWOULDBLOCK。  
但是一般說(shuō)來(lái),這種詢(xún)問(wèn)不是個(gè)好主意。如果你讓你的程序在忙等狀 態(tài)查詢(xún)套接字的數(shù)據(jù),你將浪費(fèi)大量的 CPU 時(shí)間。更好的解決之道是用 下一章講的 select() 去查詢(xún)是否有數(shù)據(jù)要讀進(jìn)來(lái)。
--------------------------------------------------------------------------------
select()--多路同步 I/O
  雖然這個(gè)函數(shù)有點(diǎn)奇怪,但是它很有用。假設(shè)這樣的情況:你是個(gè)服 務(wù)器,你一邊在不停地從連接上讀數(shù)據(jù),一邊在偵聽(tīng)連接上的信息。 沒(méi)問(wèn)題,你可能會(huì)說(shuō),不就是一個(gè) accept() 和兩個(gè) recv() 嗎? 這么 容易嗎,朋友? 如果你在調(diào)用 accept() 的時(shí)候阻塞呢? 你怎么能夠同時(shí)接 受 recv() 數(shù)據(jù)? “用非阻塞的套接字??!” 不行!你不想耗盡所有的 CPU 吧? 那么,該如何是好?
select() 讓你可以同時(shí)監(jiān)視多個(gè)套接字。如果你想知道的話(huà),那么它就 會(huì)告訴你哪個(gè)套接字準(zhǔn)備讀,哪個(gè)又準(zhǔn)備寫(xiě),哪個(gè)套接字又發(fā)生了例外 (exception)。
閑話(huà)少說(shuō),下面是 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() 來(lái)測(cè)試。 在我們繼續(xù)下去之前,讓我來(lái)講講如何對(duì)這些集合進(jìn)行操作。每個(gè)集 合類(lèi)型都是 fd_set。下面有一些宏來(lái)對(duì)這個(gè)類(lèi)型進(jìn)行操作:  
FD_ZERO(fd_set *set) – 清除一個(gè)文件描述符集合
  FD_SET(int fd, fd_set *set) - 添加fd到集合  
  FD_CLR(int fd, fd_set *set) – 從集合中移去fd  
  FD_ISSET(int fd, fd_set *set) – 測(cè)試fd是否在集合中  
最后,是有點(diǎn)古怪的數(shù)據(jù)結(jié)構(gòu) struct timeval。有時(shí)你可不想永遠(yuǎn)等待 別人發(fā)送數(shù)據(jù)過(guò)來(lái)。也許什么事情都沒(méi)有發(fā)生的時(shí)候你也想每隔96秒在終 端上打印字符串 "Still Going..."。這個(gè)數(shù)據(jù)結(jié)構(gòu)允許你設(shè)定一個(gè)時(shí)間,如果 時(shí)間到了,而 select() 還沒(méi)有找到一個(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秒。也就是說(shuō),1秒等于1,000,000微秒。為什么用符號(hào) "usec" 呢? 字母 "u" 很象希臘字母 Mu,而 Mu 表示 "微" 的意思。當(dāng)然,函數(shù) 返回的時(shí)候 timeout 可能是剩余的時(shí)間,之所以是可能,是因?yàn)樗蕾?lài)于 你的 Unix 操作系統(tǒng)。  
哈!我們現(xiàn)在有一個(gè)微秒級(jí)的定時(shí)器!別計(jì)算了,標(biāo)準(zhǔn)的 Unix 系統(tǒng) 的時(shí)間片是100毫秒,所以無(wú)論你如何設(shè)置你的數(shù)據(jù)結(jié)構(gòu) struct timeval, 你都要等待那么長(zhǎng)的時(shí)間。  
還有一些有趣的事情:如果你設(shè)置數(shù)據(jù)結(jié)構(gòu) struct timeval 中的數(shù)據(jù)為 0,select() 將立即超時(shí),這樣就可以有效地輪詢(xún)集合中的所有的文件描述 符。如果你將參數(shù) timeout 賦值為 NULL,那么將永遠(yuǎn)不會(huì)發(fā)生超時(shí),即 一直等到第一個(gè)文件描述符就緒。最后,如果你不是很關(guān)心等待多長(zhǎng)時(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)該是回車(chē) (RETURN),否則無(wú)論如何它都會(huì)超時(shí)。
現(xiàn)在,你可能回認(rèn)為這就是在數(shù)據(jù)報(bào)套接字上等待數(shù)據(jù)的方式--你是對(duì) 的:它可能是。有些 Unix 系統(tǒng)可以按這種方式,而另外一些則不能。你 在嘗試以前可能要先看看本系統(tǒng)的 man page 了。
最后一件關(guān)于 select() 的事情:如果你有一個(gè)正在偵聽(tīng) (listen()) 的套 接字,你可以通過(guò)將該套接字的文件描述符加入到 readfds 集合中來(lái)看是 否有新的連接。
這就是我關(guān)于函數(shù)select() 要講的所有的東西。
  參考書(shū)目:  
  Internetworking with TCP/IP, volumes I-III by Douglas E. Comer and  
David L. Stevens. Published by Prentice Hall. Second edition ISBNs:  
0-13-468505-9, 0-13-472242-6, 0-13-474222-2. There is a third edition of  
this set which covers IPv6 and IP over ATM.  
  Using C on the UNIX System by David A. Curry. Published by  
O‘Reilly & Associates, Inc. ISBN 0-937175-23-4.  
  TCP/IP Network Administration by Craig Hunt. Published by O‘Reilly  
& Associates, Inc. ISBN 0-937175-82-X.  
  TCP/IP Illustrated, volumes 1-3 by W. Richard Stevens and Gary R.  
Wright. Published by Addison Wesley. ISBNs: 0-201-63346-9,  
0-201-63354-X, 0-201-63495-3.  
Unix Network Programming by W. Richard Stevens. Published by  
Prentice Hall. ISBN 0-13-949876-1.  
  On the web:  
  BSD Sockets: A Quick And Dirty Primer  
  (http://www.cs.umn.edu/~bentlema/unix/--has other great Unix  
system programming info, too!)  
Client-Server Computing  
  (http://pandonia.canberra.edu.au/ClientServer/socket.html)  
Intro to TCP/IP (gopher)  

(gopher://gopher-chem.ucdavis.edu/11/Index/Internet_aw/Intro_the_Inter
net/intro.to.ip/)  
Internet Protocol Frequently Asked Questions (France)  
  (http://web.cnam.fr/Network/TCP-IP/)  
The Unix Socket FAQ  
  (http://www.ibrado.com/sock-faq/)  
RFCs--the real dirt:  
  RFC-768 -- The User Datagram Protocol (UDP)  
   (ftp://nic.ddn.mil/rfc/rfc768.txt)  
RFC-791 -- The Internet Protocol (IP)  
  (ftp://nic.ddn.mil/rfc/rfc791.txt)  
RFC-793 -- The Transmission Control Protocol (TCP)  
   (ftp://nic.ddn.mil/rfc/rfc793.txt)  
RFC-854 -- The Telnet Protocol  
   (ftp://nic.ddn.mil/rfc/rfc854.txt)  
RFC-951 -- The Bootstrap Protocol (BOOTP)  
 (ftp://nic.ddn.mil/rfc/rfc951.txt)  
RFC-1350 -- The Trivial File Transfer Protocol (TFTP)  
   (ftp://nic.ddn.mil/rfc/rfc1350.txt)

本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶(hù)發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
Linux環(huán)境下的Socket編程 - C&C - Linux技術(shù)中堅(jiān)站
[ 永遠(yuǎn)的UNIX > Linux網(wǎng)絡(luò)編程--2. 初等網(wǎng)絡(luò)函數(shù)介紹(TCP) ]
Linux環(huán)境下的網(wǎng)絡(luò)編程
Socket網(wǎng)絡(luò)編程指導(dǎo)
TCP協(xié)議套接字
Socket數(shù)據(jù)傳輸
更多類(lèi)似文章 >>
生活服務(wù)
熱點(diǎn)新聞
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服