欧美日韩国产一区二区三区不卡,欧洲一区二区三区精品,日韩一区不卡,成人国产二区

socket編程(C 知識(shí)分享:Socket 編程詳解,萬(wàn)字長(zhǎng)文)

時(shí)間:2023-12-05 11:33:16 閱讀:4

C++知識(shí)分享:Socket 編程詳解,萬(wàn)字長(zhǎng)文



先容


Socket編程讓你懊喪嗎?從man pages中很難取得有效的信息嗎?你想跟上年代去編Internet干系的步驟,但是為你在調(diào)用 connect() 前的bind() 的布局而不知所措?等等…


幸而我以前將這些事完成了,我將和一切人共享我的知識(shí)了。假如你了解 C 言語(yǔ)并想穿過(guò)網(wǎng)絡(luò)編程的沼澤,那么你來(lái)對(duì)場(chǎng)合了。


讀者目標(biāo)


這個(gè)文檔是一個(gè)指南,而不是參考書(shū)。假如你剛開(kāi)頭 socket 編程并想找一本入門書(shū),那么你是我的讀者。但這不是一本完全的 socket 編程書(shū)。


平臺(tái)和編譯器


這篇文檔中的大大多代碼都在 Linux 平臺(tái)PC 上用 GNU 的 gcc 告捷編譯過(guò)。并且它們?cè)?HPUX平臺(tái) 上用 gcc 也告捷編譯過(guò)。但是注意,并不是每個(gè)代碼片斷都獨(dú)立測(cè)試過(guò)。


目次:


1) 什么是套接字?


2) Internet 套接字的兩品種型


3) 網(wǎng)絡(luò)實(shí)際


4) 布局體


5) 本機(jī)轉(zhuǎn)換


6) IP 地點(diǎn)和怎樣處理它們


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) 域名辦事(DNS)


18) 客戶-辦事器背景知識(shí)


19) 簡(jiǎn)便的辦事器


20) 簡(jiǎn)便的客戶端


21) 數(shù)據(jù)報(bào)套接字Socket


22) 壅閉


23) select()--多路同步I/O


24) 參考材料


1)什么是 socket?


你常常聽(tīng)到人們議論著 “socket”,大概你還不曉得它的確切涵義。如今讓我報(bào)告你:它是使用標(biāo)準(zhǔn)Unix 文件形貌符 (file descriptor) 和別的步驟通訊的辦法。 什么? 你約莫聽(tīng)到一些Unix妙手(hacker)如此說(shuō)過(guò):“呀,Unix中的統(tǒng)統(tǒng)就是文件!”誰(shuí)人家伙約莫正在說(shuō)到一個(gè)內(nèi)幕:Unix 步驟在實(shí)行任何情勢(shì)的 I/O 的時(shí)分,步驟是在讀大概寫(xiě)一個(gè)文件形貌符。一個(gè)文件形貌符只是一個(gè)和掀開(kāi)的文件干系聯(lián)的整數(shù)。但是(注意后方的話),這個(gè)文件約莫是一個(gè)網(wǎng)絡(luò)毗連,F(xiàn)IFO,管道,終端,磁盤(pán)上的文件大概什么別的的東西。Unix 中一切的東西就是文件!以是,你想和Internet上別的步驟通訊的時(shí)分,你將要使用到文件形貌符。你必需了解剛剛的話。如今你腦海中大概冒出如此的動(dòng)機(jī):“那么我從何處取得網(wǎng)絡(luò)通訊的文件形貌符呢?”,這個(gè)成績(jī)無(wú)論怎樣我都要回復(fù):你使用體系調(diào)用 socket(),它前往套接字形貌符 (socket descriptor),然后你再經(jīng)過(guò)它來(lái)舉行send() 和 recv()調(diào)用。


“但是...”,你約莫有很大的疑惑,“假如它是個(gè)文件形貌符,那么為什 么不必尋常調(diào)用read()和write()來(lái)舉行套接字通訊?”簡(jiǎn)便的答案是:“你可以使用!”。具體的答案是:“你可以,但是使用send()和recv()讓你更好的控制數(shù)據(jù)傳輸?!?/p>


存在如此一個(gè)情況:在我們的天下上,有很多種套接字。有DARPA Internet 地點(diǎn) (Internet 套接字),當(dāng)?shù)毓?jié)點(diǎn)的途徑名 (Unix套接字),CCITT X.25地點(diǎn) (你可以將X.25 套接字完全忽略)。約莫在你的Unix 機(jī)器上另有別的的。我們?cè)谶@里只講第一種:Internet 套接字。


2)Internet 套接字的兩品種型


什么意思?有兩品種型的Internet 套接字?是的。不,我在扯謊。但是另有很多,但是我可不想嚇著你。我們這里只講兩種。除了這些, 我方案別的先容的 "Raw Sockets" 也好壞常強(qiáng)壯的,很值得查閱。
那么這兩品種型是什么呢?一種是"Stream Sockets"(流格式),別的一種是"Datagram Sockets"(數(shù)據(jù)包格式)。我們今后談到它們的時(shí)分也會(huì)用到 "SOCK_STREAM" 和 "SOCK_DGRAM"。數(shù)據(jù)報(bào)套接字偶爾也叫“無(wú)毗連套接字”(假如你的確要毗連的時(shí)分可以用connect()。) 流式套接字是可靠的雙向通訊的數(shù)據(jù)流。假如你向套接字按排序輸入“1,2”,那么它們將按排序“1,2”抵達(dá)另一邊。它們是無(wú)錯(cuò)誤的轉(zhuǎn)達(dá)的,有本人的錯(cuò)誤控制,在此不討論。


有什么在使用流式套接字?你約莫聽(tīng)說(shuō)過(guò) telnet,不是嗎?它就使用流式套接字。你必要你所輸入的字符按排序抵達(dá),不是嗎?相反,WWW欣賞器使用的 HTTP 協(xié)議也使用它們來(lái)下載頁(yè)面。實(shí)踐上,當(dāng)你經(jīng)過(guò)端口80 telnet 到一個(gè) WWW 站點(diǎn),然后輸入 “GET pagename” 的時(shí)分,你也可以取得 HTML 的內(nèi)容。為什么流式套接字可以到達(dá)高質(zhì)量的數(shù)據(jù)傳輸?這是由于它使用了“傳輸控制協(xié)議 (The Transmission Control Protocol)”,也叫 “TCP” (請(qǐng)參考 RFC-793 取得具體材料。)TCP 控制你的數(shù)據(jù)按排序抵達(dá)并且沒(méi)有錯(cuò)誤。你約莫聽(tīng)到 “TCP” 是由于聽(tīng)到過(guò) “TCP/IP”。這里的 IP 是指“Internet 協(xié)議”(請(qǐng)參考 RFC-791。) IP只是處理 Internet 路由罷了。


那么數(shù)據(jù)報(bào)套接字呢?為什么它叫無(wú)毗連呢?為什么它是不成靠的呢?有如此的一些內(nèi)幕:假如你發(fā)送一個(gè)數(shù)據(jù)報(bào),它約莫會(huì)抵達(dá),它約莫序次顛倒了。假如它抵達(dá),那么在這個(gè)包的內(nèi)里是無(wú)錯(cuò)誤的。數(shù)據(jù)報(bào)也使用 IP 作路由,但是它不使用 TCP。它使用“用戶數(shù)據(jù)報(bào)協(xié)議 (User Datagram Protocol)”,也叫 “UDP” (請(qǐng)參考 RFC-768。)


為什么它們是無(wú)毗連的呢?主要是由于它并不象流式套接字那樣維持一個(gè)毗連。你只需創(chuàng)建一個(gè)包,布局一個(gè)有目標(biāo)信息的IP 頭,然后發(fā)射去。無(wú)需毗連。它們通常使用于傳輸包-包信息。簡(jiǎn)便的使用步驟有:tftp, bootp等等。


你約莫會(huì)想:“假定命據(jù)喪失了這些步驟怎樣正常事情?”我的伙伴,每個(gè)步驟在 UDP 上有本人的協(xié)議。比如,tftp 協(xié)議每發(fā)射的一個(gè)被接遭到包,收到者必需發(fā)回一個(gè)包來(lái)說(shuō)“我收到了!” (一個(gè)“下令準(zhǔn)確應(yīng)對(duì)”也叫“ACK” 包)。假如在一定時(shí)間內(nèi)(比如5秒),發(fā)送方?jīng)]有收到應(yīng)對(duì),它將重新發(fā)送,直到取得 ACK。這一ACK歷程在完成 SOCK_DGRAM 使用步驟的時(shí)分十分緊張。


3)網(wǎng)絡(luò)實(shí)際


既然我剛剛提到了協(xié)議層,那么如今是討論網(wǎng)絡(luò)畢竟怎樣事情和一些 關(guān)于 SOCK_DGRAM 包是怎樣創(chuàng)建的例子。固然,你也可以跳過(guò)這一段, 假如你以為以前熟習(xí)的話。


如今是學(xué)習(xí)數(shù)據(jù)封裝 (Data Encapsulation) 的時(shí)分了!它十分十分緊張。它緊張性緊張到你在網(wǎng)絡(luò)課程學(xué)習(xí)中無(wú)論怎樣也得也得把握它(圖1:數(shù)據(jù)封裝)。主要 的內(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ù)。


如今我們終于講到聲名狼藉的網(wǎng)絡(luò)分層模子 (Layered Network Model)。這種網(wǎng)絡(luò)模子在形貌網(wǎng)絡(luò)體系上相對(duì)別的模子有很多優(yōu)點(diǎn)。比如, 你可以寫(xiě)一個(gè)套接字步驟而不必體貼數(shù)據(jù)的物理傳輸(串行口,以太網(wǎng),連 接單位接口 (AUI) 照舊別的介質(zhì)),由于底層的步驟會(huì)為你處理它們。實(shí)踐 的網(wǎng)絡(luò)硬件和拓?fù)潢P(guān)于步驟員來(lái)說(shuō)是純透的。


不說(shuō)別的空話了,我如今列出整個(gè)條理模子。假如你要到場(chǎng)網(wǎng)絡(luò)測(cè)驗(yàn), 可一定要記?。?/p>


使用層 (Application)


表現(xiàn)層 (Presentation)


會(huì)話層 (Session)


傳輸層(Transport)


網(wǎng)絡(luò)層(Network)


數(shù)據(jù)鏈路層(Data Link)


物理層(Physical)


物理層是硬件(串口,以太網(wǎng)等等)。使用層是和硬件層相隔最遠(yuǎn)的--它 是用戶和網(wǎng)絡(luò)交互的場(chǎng)合。 這個(gè)模子云云通用,假如你想,你可以把它作為修車指南。把它對(duì)應(yīng) 到 Unix,后果是:


使用層(Application Layer) (telnet, ftp,等等)


傳輸層(Host-to-Host Transport Layer) (TCP, UDP)


Internet層(Internet Layer) (IP和路由)


網(wǎng)絡(luò)拜候?qū)?(Network Access Layer) (網(wǎng)絡(luò)層,數(shù)據(jù)鏈路層和物理層)


如今,你約莫看到這些條理怎樣和諧來(lái)封裝原始的數(shù)據(jù)了。


看看創(chuàng)建一個(gè)簡(jiǎn)便的數(shù)據(jù)包有幾多事情?哎呀,你將不得不使用 "cat" 來(lái)創(chuàng)建數(shù)據(jù)包頭!這僅僅是個(gè)打趣。關(guān)于流式套接字你要作的是 send() 發(fā) 送數(shù)據(jù)。關(guān)于數(shù)據(jù)報(bào)式套接字,你依照你選擇的辦法封裝數(shù)據(jù)然后使用 sendto()。內(nèi)核將為你創(chuàng)建傳輸層和 Internet 層,硬件完成網(wǎng)絡(luò)拜候?qū)印?這就是古代科技。 如今完畢我們的網(wǎng)絡(luò)實(shí)際速成班。哦,忘記報(bào)告你關(guān)于路由的事變了。 但是我禁絕備談它,假如你真的體貼,那么參考 IP RFC。


4)布局體


終于談到編程了。在這章,我將談到被套接字用到的種種數(shù)據(jù)典范。 由于它們中的一些內(nèi)容很緊張了。


起首是簡(jiǎn)便的一個(gè):socket形貌符。它是底下的典范:


int


僅僅是一個(gè)稀有的 int。


從如今起,事變變得不成思議了,而你所需做的就是持續(xù)看下去。注 意如此的內(nèi)幕:有兩種字節(jié)分列排序:緊張的字節(jié) (偶爾叫 "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ò)去。假如我沒(méi)有 提到 NBO, 那么就讓它堅(jiān)持本機(jī)字節(jié)排序。


我的第一個(gè)布局(在這個(gè)武藝手冊(cè)TM中)--struct sockaddr.。這個(gè)布局 為很多典范的套接字儲(chǔ)存套接字地點(diǎn)信息:


struct sockaddr {   unsigned short sa_family; /* 地點(diǎn)家屬, AF_xxx */   char sa_data[14]; /*14字節(jié)協(xié)議地點(diǎn)*/ };



sa_family 可以是種種千般的典范,但是在這篇文章中都是 "AF_INET"。 sa_data包含套接字中的目標(biāo)地點(diǎn)和端口信息。這仿佛有點(diǎn) 不明智。


為了處理struct sockaddr,步驟員創(chuàng)造了一個(gè)并列的布局: struct sockaddr_in ("in" 代表 "Internet"。)


struct sockaddr_in {   short int sin_family; /* 通訊典范 */   unsigned short int sin_port; /* 端口 */   struct in_addr sin_addr; /* Internet 地點(diǎn) */   unsigned char sin_zero[8]; /* 與sockaddr布局的長(zhǎng)度相反*/ };



用這個(gè)數(shù)據(jù)布局可以輕松處理套接字地點(diǎn)的基本元素。注意 sin_zero (它被到場(chǎng)到這個(gè)布局,并且長(zhǎng)度和 struct sockaddr 一樣) 應(yīng)該使用函數(shù) bzero() 或 memset() 來(lái)全部置零。 同時(shí),這一緊張的字節(jié),一個(gè)指向 sockaddr_in布局體的指針也可以被指向布局體sockaddr并且代替它。如此的話即使 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ù)布局 struct in_addr sin_addr 依照網(wǎng)絡(luò)字節(jié)排序呢?" 要曉得這個(gè)成績(jī)的答案,我們就要仔細(xì)的看一看這 個(gè)數(shù)據(jù)布局: struct in_addr, 有如此一個(gè)團(tuán)結(jié) (unions):


/* Internet 地點(diǎn) (一個(gè)與汗青有關(guān)的布局) */ struct in_addr {   unsigned long s_addr; };



它以前是個(gè)最壞的團(tuán)結(jié),但是如今那些日子已往了。假如你聲明 "ina" 是數(shù)據(jù)布局 struct sockaddr_in 的實(shí)例,那么 "ina.sin_addr.s_addr" 就儲(chǔ) 存4字節(jié)的 IP 地點(diǎn)(使用網(wǎng)絡(luò)字節(jié)排序)。假如你不幸的體系使用的照舊恐 怖的團(tuán)結(jié) struct in_addr ,你照舊可以安心4字節(jié)的 IP 地點(diǎn)并且和外表 我說(shuō)的一樣(這是由于使用了“#define”。)


5)本機(jī)轉(zhuǎn)換


我們?nèi)缃竦搅诵碌恼鹿?jié)。我們以前講了很多網(wǎng)絡(luò)到本機(jī)字節(jié)排序的轉(zhuǎn) 換,如今可以實(shí)踐了! 你可以轉(zhuǎn)換兩品種型: short (兩個(gè)字節(jié))和 long (四個(gè)字節(jié))。這個(gè)函 數(shù)關(guān)于變量典范 unsigned 也實(shí)用。假定你想將 short 從本機(jī)字節(jié)排序轉(zhuǎn) 換為網(wǎng)絡(luò)字節(jié)排序。用 "h" 表現(xiàn) "本機(jī) (host)",接著是 "to",然后用 "n" 表 示 "網(wǎng)絡(luò) (network)",最初用 "s" 表現(xiàn) "short": h-to-n-s, 大概 htons() ("Host to Network Short")。


太簡(jiǎn)便了... ,假如不是太傻的話,你一定想到了由"n","h","s",和 "l"構(gòu)成的準(zhǔn)確 組合,比如這里一定沒(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"


如今,你約莫想你以前曉得它們了。你也約莫想:“假如我想改動(dòng) char 的排序要怎樣辦呢?” 但是你約莫立刻就想到,“用不著思索的”。你約莫 會(huì)想到:我的 68000 機(jī)器以前使用了網(wǎng)絡(luò)字節(jié)排序,我沒(méi)有必要去調(diào)用 htonl() 轉(zhuǎn)換 IP 地點(diǎn)。你約莫是對(duì)的,但是當(dāng)你移植你的步驟到別的機(jī)器 上的時(shí)分,你的步驟將失敗??梢浦残裕∵@里是 Unix 天下!記取:在你 將數(shù)據(jù)放到網(wǎng)絡(luò)上的時(shí)分,確信它們是網(wǎng)絡(luò)字節(jié)排序的。


最初一點(diǎn):為什么在數(shù)據(jù)布局 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ù)布局中包含什么典范的地點(diǎn),以是它必需是本機(jī)字節(jié)排序。同時(shí), sin_family 沒(méi)有發(fā)送到網(wǎng)絡(luò)上,它們可以是本機(jī)字節(jié)排序。


6)IP 地點(diǎn)和怎樣處理它們


如今我們很僥幸,由于我們有很多的函數(shù)來(lái)便利地利用 IP 地點(diǎn)。沒(méi)有 必要用手工盤(pán)算它們,也沒(méi)有必要用"<<"利用來(lái)儲(chǔ)存發(fā)展整字型。 起首,假定你以前有了一個(gè)sockaddr_in布局體ina,你有一個(gè)IP地 址"132.241.5.10"要儲(chǔ)存在此中,你就要用到函數(shù)inet_addr(),將IP地點(diǎn)從 點(diǎn)數(shù)格式轉(zhuǎn)換成無(wú)標(biāo)記長(zhǎng)整型。使用辦法如下:


ina.sin_addr.s_addr = inet_addr("132.241.5.10");



注意,inet_addr()前往的地點(diǎn)以前是網(wǎng)絡(luò)字節(jié)格式,以是你無(wú)需再調(diào)用 函數(shù)htonl()。 我們?nèi)缃癜l(fā)覺(jué)外表的代碼片斷不好壞常完備的,由于它沒(méi)有錯(cuò)誤反省。 不言而喻,當(dāng)inet_addr()產(chǎn)生錯(cuò)誤時(shí)前往-1。記取這些二進(jìn)制數(shù)字?(無(wú)符 號(hào)數(shù))-1僅僅和IP地點(diǎn)255.255.255.255切合合!這但是廣播地點(diǎn)!大錯(cuò)特 錯(cuò)!記取要優(yōu)秀行錯(cuò)誤反省。


好了,如今你可以將IP地點(diǎn)轉(zhuǎn)換發(fā)展整型了。有沒(méi)有其相反的辦法呢? 它可以將一個(gè)in_addr布局體輸入成點(diǎn)數(shù)格式?如此的話,你就要用到函數(shù) inet_ntoa()("ntoa"的涵義是"network to ascii"),就像如此:


printf("%s",inet_ntoa(ina.sin_addr));



它將輸入IP地點(diǎn)。必要注意的是inet_ntoa()將布局體in-addr作為一個(gè)參數(shù),不是長(zhǎng)整形。相反必要注意的是它前往的是一個(gè)指向一個(gè)字符的 指針。它是一個(gè)由inet_ntoa()控制的靜態(tài)的安穩(wěn)的指針,以是每次調(diào)用 inet_ntoa(),它就將掩蓋前次調(diào)用時(shí)所得的IP地點(diǎn)。比如:


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地點(diǎn),使用strcopy()函數(shù)來(lái)指向你本人的字符 指針。


外表就是關(guān)于這個(gè)主題的先容。稍后,你將學(xué)習(xí)將一個(gè)類 似"wintehouse.gov"的字符串轉(zhuǎn)換成它所對(duì)應(yīng)的IP地點(diǎn)(查閱域名辦事,稍 后)。


7)socket()函數(shù)


我想我不克不及再不提這個(gè)了-底下我將討論一下socket()體系調(diào)用。


底下是具體先容:


#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ù)布局struct sockaddr_in 中一樣。然后,參數(shù) type 報(bào)告內(nèi)核 是 SOCK_STREAM 典范照舊 SOCK_DGRAM 典范。最初,把 protocol 設(shè)置為 "0"。(注意:有很多種 domain、type,我不成能逐一列出了,請(qǐng)看 socket() 的 man協(xié)助。固然,另有一個(gè)"更好"的辦法去取得 protocol,同 時(shí)請(qǐng)查閱 getprotobyname() 的 man 協(xié)助。) socket() 只是前往你今后在體系調(diào)用種約莫用到的 socket 形貌符,或 者在錯(cuò)誤的時(shí)分前往-1。全局變量 errno 中將儲(chǔ)存前往的錯(cuò)誤值。(請(qǐng)參考 perror() 的 man 協(xié)助。)


8)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ù)讀下去。


這里是體系調(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ù)布局 struct sockaddr 的指針,它保存你的地點(diǎn)(即端口和 IP 地點(diǎn)) 信息。 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 也是的。別的要注意到的事變是因體系的不同, 包含的頭文件也不盡相反,請(qǐng)查閱當(dāng)?shù)氐?man 協(xié)助文件。 在 bind() 主題中最初要說(shuō)的話是,在處理本人的 IP 地點(diǎn)和/或端口的 時(shí)分,有些事情是可以主動(dòng)處理的。


my_addr.sin_port = 0; /* 隨機(jī)選擇一個(gè)沒(méi)有使用的端口 */


my_addr.sin_addr.s_addr = INADDR_ANY; /* 使用本人的IP地點(diǎn) */


經(jīng)過(guò)將0賦給 my_addr.sin_port,你報(bào)告 bind() 本人選擇切合的端 口。相反,將 my_addr.sin_addr.s_addr 設(shè)置為 INADDR_ANY,你報(bào)告 它主動(dòng)填上它所運(yùn)轉(zhuǎn)的機(jī)器的 IP 地點(diǎn)。


假如你從來(lái)警惕審慎,那么你約莫注意到我沒(méi)有將 INADDR_ANY 轉(zhuǎn) 換為網(wǎng)絡(luò)字節(jié)排序!這是由于我曉得內(nèi)里的東西:INADDR_ANY 實(shí)踐上就 是 0!即使你改動(dòng)字節(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地點(diǎn) */


你大概不信賴,外表的代碼將可以任意移植。我只是想指出,既然你 所碰到的步驟不會(huì)都運(yùn)利用用htonl的INADDR_ANY。


bind() 在錯(cuò)誤的時(shí)分仍然是前往-1,并且設(shè)置全局錯(cuò)誤變量errno。


在你調(diào)用 bind() 的時(shí)分,你要警惕的另一件事變是:不要接納小于 1024的端標(biāo)語(yǔ)。一切小于1024的端標(biāo)語(yǔ)都被體系保存!你可以選擇從1024 到65535的端口(假如它們沒(méi)有被別的步驟使用的話)。
你要注意的別的一件小事是:偶爾分你基本不必要調(diào)用它。假如你使 用 connect() 來(lái)和長(zhǎng)程機(jī)器舉行通訊,你不必要體貼你的當(dāng)?shù)囟藰?biāo)語(yǔ)(就象 你在使用 telnet 的時(shí)分),你只需簡(jiǎn)便的調(diào)用 connect() 就可以了,它會(huì)檢 查套接字對(duì)否綁定端口,假如沒(méi)有,它會(huì)本人綁定一個(gè)沒(méi)有使用的當(dāng)?shù)囟?口。


9)connect()步驟


如今我們假定你是個(gè) telnet 步驟。你的用戶下令你取得套接字的文件 形貌符。你聽(tīng)從下令調(diào)用了socket()。下一步,你的用戶報(bào)告你經(jīng)過(guò)端口 23(標(biāo)準(zhǔn) telnet 端口)毗連到"132.241.5.10"。你該怎樣做呢? 僥幸的是,你正在閱讀 connect()--怎樣毗連到長(zhǎng)程主機(jī)這一章。你可 不想讓你的用戶掃興。


connect() 體系調(diào)用是如此的:


#include <sys/types.h> #include <sys/socket.h> int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);



sockfd 是體系調(diào)用 socket() 前往的套接字文件形貌符。serv_addr 是 保存著目標(biāo)地端口和 IP 地點(diǎn)的數(shù)據(jù)布局 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; /* 目標(biāo)地點(diǎn)*/   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()。由于我不在乎當(dāng)?shù)氐亩藰?biāo)語(yǔ)。 我只體貼我要去那。內(nèi)核將為我選擇一個(gè)切合的端標(biāo)語(yǔ),而我們所毗連的 場(chǎng)合也主動(dòng)地取得這些信息。統(tǒng)統(tǒng)都不必?fù)?dān)心。


10)listen()函數(shù)


是換換內(nèi)容得時(shí)分了。假定你不渴望與長(zhǎng)程的一個(gè)地點(diǎn)相連,大概說(shuō), 僅僅是將它踢開(kāi),那你就必要等候接入哀求并且用種種辦法處理它們。處 理歷程分兩步:起首,你聽(tīng)--listen(),然后,你承受--accept() (請(qǐng)看底下的 內(nèi)容)。


除了要一點(diǎn)表明外,體系調(diào)用 listen 也相當(dāng)簡(jiǎn)便。


int listen(int sockfd, int backlog);



sockfd 是調(diào)用 socket() 前往的套接字文件形貌符。backlog 是在進(jìn)入 行列中允許的毗連數(shù)目。什么意思呢? 進(jìn)入的毗連是在行列中不休等候直 到你承受 (accept() 請(qǐng)看底下的文章)毗連。它們的數(shù)目限定于行列的允許。 大大多體系的允許數(shù)目是20,你也可以設(shè)置為5到10。


和別的函數(shù)一樣,在產(chǎn)生錯(cuò)誤的時(shí)分前往-1,并設(shè)置全局錯(cuò)誤變量 errno。


你約莫想象到了,在你調(diào)用 listen() 前你大提要調(diào)用 bind() 大概讓內(nèi) 核任意選擇一個(gè)端口。假如你想偵聽(tīng)進(jìn)入的毗連,那么體系調(diào)用的排序可 能是如此的:


socket();


bind();


listen();


/* accept() 應(yīng)該在這 */


由于它相當(dāng)?shù)拿靼?,我將在這里不給出例子了。(在 accept() 那一章的 代碼將愈加完全。)真正貧苦的局部在 accept()。


11)accept()函數(shù)


準(zhǔn)備好了,體系調(diào)用 accept() 會(huì)有點(diǎn)乖僻的場(chǎng)合的!你可以想象產(chǎn)生 如此的事變:有人從很遠(yuǎn)的場(chǎng)合經(jīng)過(guò)一個(gè)你在偵聽(tīng) (listen()) 的端口毗連 (connect()) 到你的機(jī)器。它的毗連將到場(chǎng)到等候承受 (accept()) 的行列 中。你調(diào)用 accept() 報(bào)告它你有空閑的毗連。它將前往一個(gè)新的套接字文 件形貌符!如此你就有兩個(gè)套接字了,原本的一個(gè)還在偵聽(tīng)你的誰(shuí)人端口, 新的在準(zhǔn)備發(fā)送 (send()) 和吸收 ( recv()) 數(shù)據(jù)。這就是這個(gè)歷程!


函數(shù)是如此界說(shuō)的:


#include <sys/socket.h> int accept(int sockfd, void *addr, int *addrlen);



sockfd 相當(dāng)簡(jiǎn)便,是和 listen() 中一樣的套接字形貌符。addr 是個(gè)指 向局部的數(shù)據(jù)布局 sockaddr_in 的指針。這是要求接入的信息所要去的地 方(你可以測(cè)定誰(shuí)人地點(diǎn)在誰(shuí)人端口召喚你)。在它的地點(diǎn)轉(zhuǎn)達(dá)給 accept 之 前,addrlen 是個(gè)局部的整形變量,設(shè)置為 sizeof(struct sockaddr_in)。 accept 將不會(huì)將多余的字節(jié)給 addr。假如你放入的少些,那么它會(huì)經(jīng)過(guò)改 變 addrlen 的值反應(yīng)出來(lái)。


相反,在錯(cuò)誤時(shí)前往-1,并設(shè)置全局錯(cuò)誤變量 errno。


如今是你應(yīng)該熟習(xí)的代碼片斷。


#include <string.h> #include <sys/socket.h> #include <sys/types.h> #define MYPORT 3490 /*用戶接入端口*/ #define BACKLOG 10 /* 幾多等候毗連控制*/ main() {   int sockfd, new_fd; /* listen on sock_fd, new connection on new_fd */   struct sockaddr_in my_addr; /* 地點(diǎn)信息 */   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);   ……



注意,在體系調(diào)用 send() 和 recv() 中你應(yīng)該使用新的套接字形貌符 new_fd。假如你只想讓一個(gè)毗連過(guò)來(lái),那么你可以使用 close() 去關(guān)閉原 來(lái)的文件形貌符 sockfd 來(lái)制止同一個(gè)端口更多的毗連。


12)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 就可以了。(具體的材料請(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ù) 目! 注意,偶爾分你報(bào)告它要發(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)便,不是嗎? 你如今可以在流式套接字上發(fā)送數(shù)據(jù)和吸收數(shù)據(jù)了。 你如今是 Unix 網(wǎng)絡(luò)步驟員了!


13)sendto() 和 recvfrom()函數(shù)


“這很不錯(cuò)啊”,你說(shuō),“但是你還沒(méi)有講無(wú)毗連數(shù)據(jù)報(bào)套接字呢?” 沒(méi)成績(jī),如今我們開(kāi)頭這個(gè)內(nèi)容。 既然數(shù)據(jù)報(bào)套接字不是毗連到長(zhǎng)程主機(jī)的,那么在我們發(fā)送一個(gè)包之 前必要什么信息呢? 不錯(cuò),是目標(biāo)地點(diǎn)!看看底下的:


int sendto(int sockfd, const void *msg, int len, unsigned int flags, const struct sockaddr *to, int tolen);



你以前看到了,除了別的的兩個(gè)信息外,其他的和函數(shù) send() 是一樣 的。 to 是個(gè)指向數(shù)據(jù)布局 struct sockaddr 的指針,它包含了目標(biāo)地的 IP 地點(diǎn)和端口信息。tolen 可以簡(jiǎn)便地設(shè)置為 sizeof(struct sockaddr)。 和函數(shù) send() 相似,sendto() 前往實(shí)踐發(fā)送的字節(jié)數(shù)(它也約莫小于 你想要發(fā)送的字節(jié)數(shù)!),大概在錯(cuò)誤的時(shí)分前往 -1。


相似的另有函數(shù) recv() 和 recvfrom()。recvfrom() 的界說(shuō)是如此的:


int recvfrom(int sockfd, void *buf, int len, unsigned int flags,   struct sockaddr *from, int *fromlen);



又一次,除了兩個(gè)增長(zhǎng)的參數(shù)外,這個(gè)函數(shù)和 recv() 也是一樣的。from 是一個(gè)指向局部數(shù)據(jù)布局 struct sockaddr 的指針,它的內(nèi)容是源機(jī)器的 IP 地點(diǎn)和端口信息。fromlen 是個(gè) int 型的局部指針,它的初始值為 sizeof(struct sockaddr)。函數(shù)調(diào)用前往后,fromlen 保存著實(shí)踐儲(chǔ)存在 from 中的地點(diǎn)的長(zhǎng)度。


recvfrom() 返吸收到的字節(jié)長(zhǎng)度,大概在產(chǎn)生錯(cuò)誤后前往 -1。


記取,假如你用 connect() 毗連一個(gè)數(shù)據(jù)報(bào)套接字,你可以簡(jiǎn)便的調(diào) 用 send() 和 recv() 來(lái)滿意你的要求。這個(gè)時(shí)分仍然是數(shù)據(jù)報(bào)套接字,依 然使用 UDP,體系套接字接口會(huì)為你主動(dòng)加上了目標(biāo)和源的信息。


14)close()和shutdown()函數(shù)


你以前整天都在發(fā)送 (send()) 和吸收 (recv()) 數(shù)據(jù)了,如今你準(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(),那么只不外是讓 send() 和 recv() 不克不及使用(記取你在數(shù)據(jù)報(bào)套接字中使用了 connect 后 是可以使用它們的)。


15)getpeername()函數(shù)


這個(gè)函數(shù)太簡(jiǎn)便了。 它太簡(jiǎn)便了,致使我都不想單列一章。但是我照舊如此做了。 函數(shù) getpeername() 報(bào)告你在毗連的流式套接字上誰(shuí)在別的一邊。函 數(shù)是如此的:


#include <sys/socket.h> int getpeername(int sockfd, struct sockaddr *addr, int *addrlen);



sockfd 是毗連的流式套接字的形貌符。addr 是一個(gè)指向布局 struct sockaddr (大概是 struct sockaddr_in) 的指針,它保存著毗連的另一邊的 信息。addrlen 是一個(gè) int 型的指針,它初始化為 sizeof(struct sockaddr)。 函數(shù)在錯(cuò)誤的時(shí)分前往 -1,設(shè)置相應(yīng)的 errno。


一旦你取得它們的地點(diǎn),你可以使用 inet_ntoa() 大概 gethostbyaddr() 來(lái)打印大概取得更多的信息。但是你不克不及取得它的帳號(hào)。(假如它運(yùn)轉(zhuǎn)著愚 蠢的保衛(wèi)歷程,這是約莫的,但是它的討論以前超出了本文的范圍,請(qǐng)參 考 RFC-1413 以取得更多的信息。)


16)gethostname()函數(shù)


乃至比 getpeername() 還簡(jiǎn)便的函數(shù)是 gethostname()。它前往你程 序所運(yùn)轉(zhuǎn)的機(jī)器的主機(jī)名字。然后你可以使用 gethostbyname() 以取得你 的機(jī)器的 IP 地點(diǎn)。


底下是界說(shuō):


#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。


17)域名辦事(DNS)


假如你不曉得 DNS 的意思,那么我報(bào)告你,它代表域名辦事(Domain Name Service)。它主要的功效是:你給它一個(gè)容易影象的某站點(diǎn)的地點(diǎn), 它給你 IP 地點(diǎn)(然后你就可以使用 bind(), connect(), sendto() 大概別的 函數(shù)) 。當(dāng)一一局部輸入:


$ 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ù)布局 是如此的:


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ù)布局的具體材料:


h_name – 地點(diǎn)的正式稱呼。


h_aliases – 空字節(jié)-地點(diǎn)的準(zhǔn)備稱呼的指針。


h_addrtype –地點(diǎn)典范; 通常是AF_INET。


h_length – 地點(diǎn)的比專長(zhǎng)度。


h_addr_list – 零字節(jié)-主機(jī)網(wǎng)絡(luò)地點(diǎn)指針。網(wǎng)絡(luò)字節(jié)排序。


h_addr - h_addr_list中的第一地點(diǎn)。


gethostbyname() 告捷時(shí)前往一個(gè)指向布局體 hostent 的指針,大概 是個(gè)空 (NULL) 指針。(但是和從前不同,不設(shè)置errno,h_errno 設(shè)置錯(cuò) 誤信息,請(qǐng)看底下的 herror()。) 但是怎樣使用呢? 偶爾分(我們可以從電腦手冊(cè)中發(fā)覺(jué)),向讀者貫注 信息是不夠的。這個(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) { /* 取得地點(diǎn)信息 */   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 沒(méi)有使用),你應(yīng)該調(diào)用 herror()。


相當(dāng)簡(jiǎn)便,你只是轉(zhuǎn)達(dá)一個(gè)保存機(jī)器名的字符串(比如 "whitehouse.gov") 給 gethostbyname(),然后從前往的數(shù)據(jù)布局 struct hostent 中獲取信息。


唯一約莫讓人不解的是輸入 IP 地點(diǎn)信息。h->h_addr 是一個(gè) char *, 但是 inet_ntoa() 必要的是 struct in_addr。因此,我轉(zhuǎn)換 h->h_addr 成 struct in_addr *,然后取得數(shù)據(jù)。


18)客戶-辦事器背景知識(shí)


這里是個(gè)客戶--辦事器的天下。在網(wǎng)絡(luò)上的一切東西都是在處理客戶進(jìn) 程和辦事器歷程的扳談。舉個(gè)telnet 的例子。當(dāng)你用 telnet (客戶)經(jīng)過(guò)23 號(hào)端口登岸到主機(jī),主機(jī)上運(yùn)轉(zhuǎn)的一個(gè)步驟(尋常叫 telnetd,辦事器)激活。 它處理這個(gè)毗連,體現(xiàn)登岸界面,等等。


圖 2 分析白客戶和辦事器之間的信息互換。


注意,客戶--辦事器之間可以使用SOCK_STREAM、SOCK_DGRAM 大概別的(只需它們接納相反的)。一些很好的客戶--辦事器的例子有 telnet/telnetd、 ftp/ftpd 和 bootp/bootpd。每次你使用 ftp 的時(shí)分,在遠(yuǎn) 端都有一個(gè) ftpd 為你辦事。


尋常,在辦事端僅有一個(gè)辦事器,它接納 fork() 來(lái)處理多個(gè)客戶的連 接?;镜牟襟E是:辦事器等候一個(gè)毗連,承受 (accept()) 毗連,然后 fork() 一個(gè)子歷程處理它。這是下一章我們的例子中會(huì)講到的。


19)簡(jiǎn)便的辦事器


這個(gè)辦事器所做的全部事情是在流式毗連上發(fā)送字符串 "Hello, World!\n"。你要測(cè)試這個(gè)步驟的話,可以在一臺(tái)機(jī)器上運(yùn)轉(zhuǎn)該步驟,然后 在別的一機(jī)器上登岸:


$ telnet remotehostname 3490


remotehostname 是該步驟運(yùn)轉(zhuǎn)的機(jī)器的名字。


辦事器代碼:


#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 /*界說(shuō)用戶毗連端口*/ #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 */ } }



假如你很挑剔的話,一定不滿意我一切的代碼都在一個(gè)很大的main() 函數(shù)中。假如你不喜好,可以區(qū)分得更細(xì)點(diǎn)。


你也可以用我們下一章中的步驟取得辦事器端發(fā)送的字符串。


20)簡(jiǎn)便的客戶步驟


這個(gè)步驟比辦事器還簡(jiǎn)便。這個(gè)步驟的一切事情是經(jīng)過(guò) 3490 端口毗連到下令行中指定的主機(jī),然后取得辦事器發(fā)送的字符串。


客戶代碼:


#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 /* 客戶機(jī)毗連長(zhǎng)程主機(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)轉(zhuǎn)辦事器之前運(yùn)轉(zhuǎn)客戶步驟,connect() 將前往 "Connection refused" 信息,這十分有效。


21)數(shù)據(jù)包 Sockets


我不想講更多了,以是我給出代碼 talker.c 和 listener.c。


listener 在機(jī)器上等候在端口 4590 來(lái)的數(shù)據(jù)包。talker 發(fā)送數(shù)據(jù)包到 一定的機(jī)器,它包含用戶本人令行輸入的內(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)轉(zhuǎn) listener,然后在別的一臺(tái)機(jī)器上 運(yùn)轉(zhuǎn) talker。察看它們的通訊!
除了一些我在外表提到的數(shù)據(jù)套接字毗連的小細(xì)節(jié)外,關(guān)于數(shù)據(jù)套接 字,我還得說(shuō)一些,當(dāng)一個(gè)發(fā)言者召喚connect()函數(shù)時(shí)并指定承受者的地 址時(shí),從這點(diǎn)可以看出,發(fā)言者只能向connect()函數(shù)指定的地點(diǎn)發(fā)送和接 受信息。因此,你不必要使用sendto()和recvfrom(),你完全可以用send() 和recv()代替。


22)壅閉


壅閉,你約莫早就聽(tīng)說(shuō)了。"壅閉"是 "sleep" 的科技行話。你約莫注意 到前方運(yùn)轉(zhuǎn)的 listener 步驟,它在那邊不休地運(yùn)轉(zhuǎn),等候數(shù)據(jù)包的到來(lái)。 實(shí)踐在運(yùn)轉(zhuǎn)的是它調(diào)用 recvfrom(),然后沒(méi)多數(shù)據(jù),因此 recvfrom() 說(shuō)" 壅閉 (block)",直到數(shù)據(jù)的到來(lái)。


很多函數(shù)都使用壅閉。accept() 壅閉,一切的 recv*() 函數(shù)壅閉。它 們之以是能如此做是由于它們被允許如此做。當(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è)置套接字為非壅閉,你可以好效地"扣問(wèn)"套接字以取得信息。如 果你實(shí)驗(yàn)著從一個(gè)非壅閉的套接字讀信息并且沒(méi)有任何數(shù)據(jù),它不允許阻 塞--它將前往 -1 并將 errno 設(shè)置為 EWOULDBLOCK。


但是尋常說(shuō)來(lái),這種扣問(wèn)不是個(gè)好想法。假如你讓你的步驟在忙等狀 態(tài)查詢套接字的數(shù)據(jù),你將糜費(fèi)多量的 CPU 時(shí)間。更好的處理之道是用 下一章講的 select() 去查詢對(duì)否多數(shù)據(jù)要讀過(guò)來(lái)。


23)select()--多路同步 I/O


固然這個(gè)函數(shù)有點(diǎn)奇異,但是它很有效。假定如此的情況:你是個(gè)服 務(wù)器,你一邊在不休地從毗連上讀數(shù)據(jù),一邊在偵聽(tīng)毗連上的信息。 沒(méi)成績(jī),你約莫會(huì)說(shuō),不就是一個(gè) accept() 和兩個(gè) recv() 嗎? 這么 容易嗎,伙伴? 假如你在調(diào)用 accept() 的時(shí)分壅閉呢? 你怎樣可以同時(shí)接 受 recv() 數(shù)據(jù)? “用非壅閉的套接字??!” 不可!你不想耗盡一切的 CPU 吧? 那么,該怎樣是好?


select() 讓你可以同時(shí)監(jiān)督多個(gè)套接字。假如你想曉得的話,那么它就 會(huì)報(bào)告你哪個(gè)套接字準(zhǔn)備讀,哪個(gè)又準(zhǔn)備寫(xiě),哪個(gè)套接字又產(chǎn)生了例外 (exception)。


閑話少說(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。假如你想曉得你對(duì)否可以從標(biāo)準(zhǔn)輸入和套接字形貌符 sockfd 讀入數(shù)據(jù),你只需將文件形貌符 0 和 sockfd 到場(chǎng)到聚集 readfds 中。參 數(shù) numfds 應(yīng)該即是最高的文件形貌符的值加1。在這個(gè)例子中,你應(yīng)該 設(shè)置該值為 sockfd+1。由于它一定大于標(biāo)準(zhǔn)輸入的文件形貌符 (0)。 當(dāng)函數(shù) select() 前往的時(shí)分,readfds 的值修正為反應(yīng)你選擇的哪個(gè) 文件形貌符可以讀。你可以用底下講到的宏 FD_ISSET() 來(lái)測(cè)試。 在我們持續(xù)下去之前,讓我來(lái)講講怎樣對(duì)這些聚集舉行利用。每個(gè)集 合典范都是 fd_set。底下有一些宏來(lái)對(duì)這個(gè)典范舉行利用:


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對(duì)否在聚集中


最初,是有點(diǎn)乖僻的數(shù)據(jù)布局 struct timeval。偶爾你可不想永久等候 他人發(fā)送數(shù)據(jù)過(guò)去。約莫什么事變都沒(méi)有產(chǎn)生的時(shí)分你也想每隔96秒在終 端上打印字符串 "Still Going..."。這個(gè)數(shù)據(jù)布局允許你設(shè)定一個(gè)時(shí)間,假如 時(shí)間到了,而 select() 還沒(méi)有找到一個(gè)準(zhǔn)備好的文件形貌符,它將前往讓 你持續(xù)處理。


數(shù)據(jù)布局 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微秒。為什么用標(biāo)記 "usec" 呢? 字母 "u" 很象希臘字母 Mu,而 Mu 表現(xiàn) "微" 的意思。固然,函數(shù) 前往的時(shí)分 timeout 約莫是剩余的時(shí)間,之以是是約莫,是由于它依托于 你的 Unix 利用體系。


哈!我們?nèi)缃裼幸粋€(gè)微秒級(jí)的定時(shí)器!別盤(pán)算了,標(biāo)準(zhǔn)的 Unix 體系 的時(shí)間片是100毫秒,以是無(wú)論你怎樣設(shè)置你的數(shù)據(jù)布局 struct timeval, 你都要等候那么長(zhǎng)的時(shí)間。


另有一些幽默的事變:假如你設(shè)置數(shù)據(jù)布局 struct timeval 中的數(shù)據(jù)為 0,select() 將立刻超時(shí),如此就可以好效地輪詢聚集中的一切的文件形貌 符。假如你將參數(shù) timeout 賦值為 NULL,那么將永久不會(huì)產(chǎn)生超時(shí),即 不休比及第一個(gè)文件形貌符停當(dāng)。最初,假如你不是很體貼等候多長(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)該是回車 (RETURN),不然無(wú)論怎樣它都市超時(shí)。
如今,你約莫回以為這就是在數(shù)據(jù)報(bào)套接字上等候數(shù)據(jù)的辦法--你是對(duì) 的:它約莫是。有些 Unix 體系可以按這種辦法,而別的一些則不克不及。你 在實(shí)驗(yàn)從前約莫要先看看本體系的 man page 了。


最初一件關(guān)于 select() 的事變:假如你有一個(gè)正在偵聽(tīng) (listen()) 的套 接字,你可以經(jīng)過(guò)將該套接字的文件形貌符到場(chǎng)到 readfds 聚集中來(lái)看是 否有新的毗連。


這就是我關(guān)于函數(shù)select() 要講的一切的東西。



寫(xiě)在最初:學(xué)編程,但是每一局部都有本人的選擇,每一種編程言語(yǔ)的存在都有其使用的朝向,選擇你想從事的朝向,去舉行切合的選擇就對(duì)了!關(guān)于準(zhǔn)備學(xué)習(xí)編程的小伙伴,假如你想更好的提升你的編程中心才能(內(nèi)功)無(wú)礙從如今開(kāi)頭!

編程學(xué)習(xí)冊(cè)老實(shí)享:

編程學(xué)習(xí)視頻分享:

整理分享(多年學(xué)習(xí)的源碼、項(xiàng)目實(shí)戰(zhàn)視頻、項(xiàng)目條記,基本入門教程)

接待轉(zhuǎn)行和學(xué)習(xí)編程的伙伴,使用更多的材料學(xué)習(xí)發(fā)展比本人揣摩更快哦!

關(guān)于C/C++感興致可以眷注小編在背景私信我:【編程交換】一同來(lái)學(xué)習(xí)哦!可以提取一些C/C++的項(xiàng)目學(xué)習(xí)視頻材料哦!以前設(shè)置好了緊張?jiān)~主動(dòng)回復(fù),主動(dòng)提取就好了!

版權(quán)聲明:本文來(lái)自互聯(lián)網(wǎng)整理發(fā)布,如有侵權(quán),聯(lián)系刪除

原文鏈接:http://m.freetextsend.comhttp://m.freetextsend.com/wangluozixun/40505.html


Copyright ? 2021-2022 All Rights Reserved 備案編號(hào):閩ICP備2023009674號(hào) 網(wǎng)站地圖 聯(lián)系:dhh0407@outlook.com

主站蜘蛛池模板: 嵊泗县| 宁乡县| 博湖县| 朝阳区| 高邮市| 通道| 耿马| 平湖市| 德州市| 鸡泽县| 鲁甸县| 莲花县| 金塔县| 邓州市| 靖西县| 崇礼县| 阳泉市| 沈阳市| 肥乡县| 和硕县| 炎陵县| 博客| 如皋市| 漠河县| 会泽县| 邓州市| 阿合奇县| 祁阳县| 广安市| 山阳县| 肥城市| 阆中市| 甘洛县| 德庆县| 贵州省| 射阳县| 高清| 林芝县| 鲁山县| 唐海县| 木兰县|