socket編程(C 知識分享:Socket 編程詳解,萬字長文)

時間:2023-12-05 11:33:16 閱讀:4

C++知識分享:Socket 編程詳解,萬字長文



先容


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


幸而我以前將這些事完成了,我將和一切人共享我的知識了。假如你了解 C 言語并想穿過網絡編程的沼澤,那么你來對場合了。


讀者目標


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


平臺和編譯器


這篇文檔中的大大多代碼都在 Linux 平臺PC 上用 GNU 的 gcc 告捷編譯過。并且它們在 HPUX平臺 上用 gcc 也告捷編譯過。但是注意,并不是每個代碼片斷都獨立測試過。


目次:


1) 什么是套接字?


2) Internet 套接字的兩品種型


3) 網絡實際


4) 布局體


5) 本機轉換


6) IP 地點和怎樣處理它們


7) socket()函數


8) bind()函數


9) connect()函數


10) listen()函數


11) accept()函數


12) send()和recv()函數


13) sendto()和recvfrom()函數


14) close()和shutdown()函數


15) getpeername()函數


16) gethostname()函數


17) 域名辦事(DNS)


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


19) 簡便的辦事器


20) 簡便的客戶端


21) 數據報套接字Socket


22) 壅閉


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


24) 參考材料


1)什么是 socket?


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


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


存在如此一個情況:在我們的天下上,有很多種套接字。有DARPA Internet 地點 (Internet 套接字),當地節點的途徑名 (Unix套接字),CCITT X.25地點 (你可以將X.25 套接字完全忽略)。約莫在你的Unix 機器上另有別的的。我們在這里只講第一種:Internet 套接字。


2)Internet 套接字的兩品種型


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


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


那么數據報套接字呢?為什么它叫無毗連呢?為什么它是不成靠的呢?有如此的一些內幕:假如你發送一個數據報,它約莫會抵達,它約莫序次顛倒了。假如它抵達,那么在這個包的內里是無錯誤的。數據報也使用 IP 作路由,但是它不使用 TCP。它使用“用戶數據報協議 (User Datagram Protocol)”,也叫 “UDP” (請參考 RFC-768。)


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


你約莫會想:“假定命據喪失了這些步驟怎樣正常事情?”我的伙伴,每個步驟在 UDP 上有本人的協議。比如,tftp 協議每發射的一個被接遭到包,收到者必需發回一個包來說“我收到了!” (一個“下令準確應對”也叫“ACK” 包)。假如在一定時間內(比如5秒),發送方沒有收到應對,它將重新發送,直到取得 ACK。這一ACK歷程在完成 SOCK_DGRAM 使用步驟的時分十分緊張。


3)網絡實際


既然我剛剛提到了協議層,那么如今是討論網絡畢竟怎樣事情和一些 關于 SOCK_DGRAM 包是怎樣創建的例子。固然,你也可以跳過這一段, 假如你以為以前熟習的話。


如今是學習數據封裝 (Data Encapsulation) 的時分了!它十分十分緊張。它緊張性緊張到你在網絡課程學習中無論怎樣也得也得把握它(圖1:數據封裝)。主要 的內容是:一個包,先是被第一個協議(在這里是TFTP )在它的報頭(約莫 是報尾)包裝(“封裝”),然后,整個數據(包含 TFTP 頭)被別的一個協議 (在這里是 UDP )封裝,然后下一個( IP ),不休反復下去,直到硬件(物理) 層( 這里是以太網 )。
當別的一臺機器吸收到包,硬件先剝去以太網頭,內核剝去IP和UDP 頭,TFTP步驟再剝去TFTP頭,最初取得數據。


如今我們終于講到聲名狼藉的網絡分層模子 (Layered Network Model)。這種網絡模子在形貌網絡體系上相對別的模子有很多優點。比如, 你可以寫一個套接字步驟而不必體貼數據的物理傳輸(串行口,以太網,連 接單位接口 (AUI) 照舊別的介質),由于底層的步驟會為你處理它們。實踐 的網絡硬件和拓撲關于步驟員來說是純透的。


不說別的空話了,我如今列出整個條理模子。假如你要到場網絡測驗, 可一定要記?。?/p>


使用層 (Application)


表現層 (Presentation)


會話層 (Session)


傳輸層(Transport)


網絡層(Network)


數據鏈路層(Data Link)


物理層(Physical)


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


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


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


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


網絡拜候層 (Network Access Layer) (網絡層,數據鏈路層和物理層)


如今,你約莫看到這些條理怎樣和諧來封裝原始的數據了。


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


4)布局體


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


起首是簡便的一個:socket形貌符。它是底下的典范:


int


僅僅是一個稀有的 int。


從如今起,事變變得不成思議了,而你所需做的就是持續看下去。注 意如此的內幕:有兩種字節分列排序:緊張的字節 (偶爾叫 "octet",即八 位位組) 在前方,大概不緊張的字節在前方。前一種叫“網絡字節排序 (Network Byte Order)”。有些機器在內里是依照這個排序儲存數據,而別的 一些則不然。當我說某數據必需依照 NBO 排序,那么你要調用函數(比如 htons() )來將它從本機字節排序 (Host Byte Order) 轉換過去。假如我沒有 提到 NBO, 那么就讓它堅持本機字節排序。


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


struct sockaddr {   unsigned short sa_family; /* 地點家屬, AF_xxx */   char sa_data[14]; /*14字節協議地點*/ };



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


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


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



用這個數據布局可以輕松處理套接字地點的基本元素。注意 sin_zero (它被到場到這個布局,并且長度和 struct sockaddr 一樣) 應該使用函數 bzero() 或 memset() 來全部置零。 同時,這一緊張的字節,一個指向 sockaddr_in布局體的指針也可以被指向布局體sockaddr并且代替它。如此的話即使 socket() 想要的是 struct sockaddr *,你仍舊可以使用 struct sockaddr_in,并且在最初轉換。同時,注意 sin_family 和 struct sockaddr 中的 sa_family 一律并可以設置為 "AF_INET"。最初,sin_port和 sin_addr 必需是網絡字節排序 (Network Byte Order)!


你約莫會反對道:"但是,怎樣讓整個數據布局 struct in_addr sin_addr 依照網絡字節排序呢?" 要曉得這個成績的答案,我們就要仔細的看一看這 個數據布局: struct in_addr, 有如此一個團結 (unions):


/* Internet 地點 (一個與汗青有關的布局) */ struct in_addr {   unsigned long s_addr; };



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


5)本機轉換


我們如今到了新的章節。我們以前講了很多網絡到本機字節排序的轉 換,如今可以實踐了! 你可以轉換兩品種型: short (兩個字節)和 long (四個字節)。這個函 數關于變量典范 unsigned 也實用。假定你想將 short 從本機字節排序轉 換為網絡字節排序。用 "h" 表現 "本機 (host)",接著是 "to",然后用 "n" 表 示 "網絡 (network)",最初用 "s" 表現 "short": h-to-n-s, 大概 htons() ("Host to Network Short")。


太簡便了... ,假如不是太傻的話,你一定想到了由"n","h","s",和 "l"構成的準確 組合,比如這里一定沒有stolh() ("Short to Long Host") 函數,不僅在這里 沒有,一切場合都沒有。但是這里有:


htons()--"Host to Network Short"


htonl()--"Host to Network Long"


ntohs()--"Network to Host Short"


ntohl()--"Network to Host Long"


如今,你約莫想你以前曉得它們了。你也約莫想:“假如我想改動 char 的排序要怎樣辦呢?” 但是你約莫立刻就想到,“用不著思索的”。你約莫 會想到:我的 68000 機器以前使用了網絡字節排序,我沒有必要去調用 htonl() 轉換 IP 地點。你約莫是對的,但是當你移植你的步驟到別的機器 上的時分,你的步驟將失敗。可移植性!這里是 Unix 天下!記?。涸谀?將數據放到網絡上的時分,確信它們是網絡字節排序的。


最初一點:為什么在數據布局 struct sockaddr_in 中, sin_addr 和 sin_port 必要轉換為網絡字節排序,而sin_family 需不必要呢? 答案是: sin_addr 和 sin_port 分散封裝在包的 IP 和 UDP 層。因此,它們必必要 是網絡字節排序。但是 sin_family 域只是被內核 (kernel) 使用來決定在數 據布局中包含什么典范的地點,以是它必需是本機字節排序。同時, sin_family 沒有發送到網絡上,它們可以是本機字節排序。


6)IP 地點和怎樣處理它們


如今我們很僥幸,由于我們有很多的函數來便利地利用 IP 地點。沒有 必要用手工盤算它們,也沒有必要用"<<"利用來儲存發展整字型。 起首,假定你以前有了一個sockaddr_in布局體ina,你有一個IP地 址"132.241.5.10"要儲存在此中,你就要用到函數inet_addr(),將IP地點從 點數格式轉換成無標記長整型。使用辦法如下:


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



注意,inet_addr()前往的地點以前是網絡字節格式,以是你無需再調用 函數htonl()。 我們如今發覺外表的代碼片斷不好壞常完備的,由于它沒有錯誤反省。 不言而喻,當inet_addr()產生錯誤時前往-1。記取這些二進制數字?(無符 號數)-1僅僅和IP地點255.255.255.255切合合!這但是廣播地點!大錯特 錯!記取要優秀行錯誤反省。


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


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



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


char *a1, *a2; …… a1 = inet_ntoa(ina1.sin_addr); /* 這是198.92.129.1 */ a2 = inet_ntoa(ina2.sin_addr); /* 這是132.241.5.10 */ printf("address 1: %s\n",a1); printf("address 2: %s\n",a2); 輸入如下: address 1: 132.241.5.10 address 2: 132.241.5.10



假定你必要保存這個IP地點,使用strcopy()函數來指向你本人的字符 指針。


外表就是關于這個主題的先容。稍后,你將學習將一個類 似"wintehouse.gov"的字符串轉換成它所對應的IP地點(查閱域名辦事,稍 后)。


7)socket()函數


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


底下是具體先容:


#include <sys/types.h> #include <sys/socket.h> int socket(int domain, int type, int protocol);



但是它們的參數是什么? 起首,domain 應該設置成 "AF_INET",就 象外表的數據布局struct sockaddr_in 中一樣。然后,參數 type 報告內核 是 SOCK_STREAM 典范照舊 SOCK_DGRAM 典范。最初,把 protocol 設置為 "0"。(注意:有很多種 domain、type,我不成能逐一列出了,請看 socket() 的 man協助。固然,另有一個"更好"的辦法去取得 protocol,同 時請查閱 getprotobyname() 的 man 協助。) socket() 只是前往你今后在體系調用種約莫用到的 socket 形貌符,或 者在錯誤的時分前往-1。全局變量 errno 中將儲存前往的錯誤值。(請參考 perror() 的 man 協助。)


8)bind()函數


一旦你有一個套接字,你約莫要將套接字和機器上的一定的端口關聯 起來。(假如你想用listen()來偵聽一定端口的數據,這是必要一步--MUD 告 訴你說用下令 "telnet x.y.z 6969"。)假如你只想用 connect(),那么這個步 驟沒有必要。但是無論怎樣,請持續讀下去。


這里是體系調用 bind() 的約莫:


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



sockfd 是調用 socket 前往的文件形貌符。my_addr 是指向數據布局 struct sockaddr 的指針,它保存你的地點(即端口和 IP 地點) 信息。 addrlen 設置為 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); /*必要錯誤反省 */   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 是網絡字節排序, my_addr.sin_addr.s_addr 也是的。別的要注意到的事變是因體系的不同, 包含的頭文件也不盡相反,請查閱當地的 man 協助文件。 在 bind() 主題中最初要說的話是,在處理本人的 IP 地點和/或端口的 時分,有些事情是可以主動處理的。


my_addr.sin_port = 0; /* 隨機選擇一個沒有使用的端口 */


my_addr.sin_addr.s_addr = INADDR_ANY; /* 使用本人的IP地點 */


經過將0賦給 my_addr.sin_port,你報告 bind() 本人選擇切合的端 口。相反,將 my_addr.sin_addr.s_addr 設置為 INADDR_ANY,你報告 它主動填上它所運轉的機器的 IP 地點。


假如你從來警惕審慎,那么你約莫注意到我沒有將 INADDR_ANY 轉 換為網絡字節排序!這是由于我曉得內里的東西:INADDR_ANY 實踐上就 是 0!即使你改動字節的排序,0仍然是0。但是完善主義者說應該到處一 致,INADDR_ANY大概是12呢?你的代碼就不克不及事情了,那么就看底下 的代碼:


my_addr.sin_port = htons(0); /* 隨機選擇一個沒有使用的端口 */


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


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


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


在你調用 bind() 的時分,你要警惕的另一件事變是:不要接納小于 1024的端標語。一切小于1024的端標語都被體系保存!你可以選擇從1024 到65535的端口(假如它們沒有被別的步驟使用的話)。
你要注意的別的一件小事是:偶爾分你基本不必要調用它。假如你使 用 connect() 來和長程機器舉行通訊,你不必要體貼你的當地端標語(就象 你在使用 telnet 的時分),你只需簡便的調用 connect() 就可以了,它會檢 查套接字對否綁定端口,假如沒有,它會本人綁定一個沒有使用的當地端 口。


9)connect()步驟


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


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


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



sockfd 是體系調用 socket() 前往的套接字文件形貌符。serv_addr 是 保存著目標地端口和 IP 地點的數據布局 struct sockaddr。addrlen 設置 為 sizeof(struct sockaddr)。 想曉得得更多嗎?讓我們來看個例子:


#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); /* 錯誤反省 */   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));   ……



再一次,你應該反省 connect() 的前往值--它在錯誤的時分前往-1,并 設置全局錯誤變量 errno。 同時,你約莫看到,我沒有調用 bind()。由于我不在乎當地的端標語。 我只體貼我要去那。內核將為我選擇一個切合的端標語,而我們所毗連的 場合也主動地取得這些信息。統統都不必擔心。


10)listen()函數


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


除了要一點表明外,體系調用 listen 也相當簡便。


int listen(int sockfd, int backlog);



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


和別的函數一樣,在產生錯誤的時分前往-1,并設置全局錯誤變量 errno。


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


socket();


bind();


listen();


/* accept() 應該在這 */


由于它相當的明白,我將在這里不給出例子了。(在 accept() 那一章的 代碼將愈加完全。)真正貧苦的局部在 accept()。


11)accept()函數


準備好了,體系調用 accept() 會有點乖僻的場合的!你可以想象產生 如此的事變:有人從很遠的場合經過一個你在偵聽 (listen()) 的端口毗連 (connect()) 到你的機器。它的毗連將到場到等候承受 (accept()) 的行列 中。你調用 accept() 報告它你有空閑的毗連。它將前往一個新的套接字文 件形貌符!如此你就有兩個套接字了,原本的一個還在偵聽你的誰人端口, 新的在準備發送 (send()) 和吸收 ( recv()) 數據。這就是這個歷程!


函數是如此界說的:


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



sockfd 相當簡便,是和 listen() 中一樣的套接字形貌符。addr 是個指 向局部的數據布局 sockaddr_in 的指針。這是要求接入的信息所要去的地 方(你可以測定誰人地點在誰人端口召喚你)。在它的地點轉達給 accept 之 前,addrlen 是個局部的整形變量,設置為 sizeof(struct sockaddr_in)。 accept 將不會將多余的字節給 addr。假如你放入的少些,那么它會經過改 變 addrlen 的值反應出來。


相反,在錯誤時前往-1,并設置全局錯誤變量 errno。


如今是你應該熟習的代碼片斷。


#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; /* 地點信息 */   struct sockaddr_in their_addr; /* connector's address information */   int sin_size;   sockfd = socket(AF_INET, SOCK_STREAM, 0); /* 錯誤反省*/   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);   ……



注意,在體系調用 send() 和 recv() 中你應該使用新的套接字形貌符 new_fd。假如你只想讓一個毗連過來,那么你可以使用 close() 去關閉原 來的文件形貌符 sockfd 來制止同一個端口更多的毗連。


12)send() and recv()函數


這兩個函數用于流式套接字大概數據報套接字的通訊。假如你喜好使 用無毗連的數據報套接字,你應該看一看底下關于sendto() 和 recvfrom() 的章節。


send() 是如此的:


int send(int sockfd, const void *msg, int len, int flags);



sockfd 是你想發送數據的套接字形貌符(大概是調用 socket() 大概是 accept() 前往的。)msg 是指向你想發送的數據的指針。len 是數據的長度。 把 flags 設置為 0 就可以了。(具體的材料請看 send() 的 man page)。 這里是一些約莫的例子:


char *msg = "Beej was here!"; int len, bytes_sent; …… len = strlen(msg); bytes_sent = send(sockfd, msg, len, 0); ……



send() 前往實踐發送的數據的字節數--它約莫小于你要求發送的數 目! 注意,偶爾分你報告它要發送一堆數據但是它不克不及處理告捷。它只是 發送它約莫發送的數據,然后渴望你可以發送別的的數據。記取,假如 send() 前往的數據和 len 不婚配,你就應該發送別的的數據。但是這里也 有個好消息:假如你要發送的包很小(小于約莫 1K),它約莫處理讓數據一 次發送完。最初要說得就是,它在錯誤的時分前往-1,并設置 errno。


recv() 函數很相似:


int recv(int sockfd, void *buf, int len, unsigned int flags);



sockfd 是要讀的套接字形貌符。buf 是要讀的信息的緩沖。len 是緩 沖的最大長度。flags 可以設置為0。(請參考recv() 的 man page。) recv() 前往實踐讀入緩沖的數據的字節數。大概在錯誤的時分前往-1, 同時設置 errno。


很簡便,不是嗎? 你如今可以在流式套接字上發送數據和吸收數據了。 你如今是 Unix 網絡步驟員了!


13)sendto() 和 recvfrom()函數


“這很不錯啊”,你說,“但是你還沒有講無毗連數據報套接字呢?” 沒成績,如今我們開頭這個內容。 既然數據報套接字不是毗連到長程主機的,那么在我們發送一個包之 前必要什么信息呢? 不錯,是目標地點!看看底下的:


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



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


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


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



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


recvfrom() 返吸收到的字節長度,大概在產生錯誤后前往 -1。


記取,假如你用 connect() 毗連一個數據報套接字,你可以簡便的調 用 send() 和 recv() 來滿意你的要求。這個時分仍然是數據報套接字,依 然使用 UDP,體系套接字接口會為你主動加上了目標和源的信息。


14)close()和shutdown()函數


你以前整天都在發送 (send()) 和吸收 (recv()) 數據了,如今你準備關 閉你的套接字形貌符了。這很簡便,你可以使用尋常的 Unix 文件形貌符 的 close() 函數:


close(sockfd);



它將避免套接字上更多的數據的讀寫。任安在另一端讀寫套接字的企 圖都將前往錯誤信息。假如你想在怎樣關閉套接字上有多一點的控制,你可以使用函數 shutdown()。它允許你將一定朝向上的通訊大概雙向的通訊(就象close()一 樣)關閉,你可以使用:


int shutdown(int sockfd, int how);



sockfd 是你想要關閉的套接字文件形貌復。how 的值是底下的此中之 一:


0 – 不允許承受


1 – 不允許發送


2 – 不允許發送和承受(和 close() 一樣)


shutdown() 告捷時前往 0,失敗時前往 -1(同時設置 errno。) 假如在無毗連的數據報套接字中使用shutdown(),那么只不外是讓 send() 和 recv() 不克不及使用(記取你在數據報套接字中使用了 connect 后 是可以使用它們的)。


15)getpeername()函數


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


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



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


一旦你取得它們的地點,你可以使用 inet_ntoa() 大概 gethostbyaddr() 來打印大概取得更多的信息。但是你不克不及取得它的帳號。(假如它運轉著愚 蠢的保衛歷程,這是約莫的,但是它的討論以前超出了本文的范圍,請參 考 RFC-1413 以取得更多的信息。)


16)gethostname()函數


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


底下是界說:


#include <unistd.h> int gethostname(char *hostname, size_t size);



參數很簡便:hostname 是一個字符數組指針,它將在函數前往時保存 主機名。size是hostname 數組的字節長度。


函數調用告捷時前往 0,失敗時前往 -1,并設置 errno。


17)域名辦事(DNS)


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


$ telnet whitehouse.gov


telnet 能曉得它將毗連 (connect()) 到 "198.137.240.100"。 但是這是怎樣事情的呢? 你可以調用函數 gethostbyname():


#include <netdb.h> struct hostent *gethostbyname(const char *name);



很明白的是,它前往一個指向 struct hostent 的指針。這個數據布局 是如此的:


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]



這里是這個數據布局的具體材料:


h_name – 地點的正式稱呼。


h_aliases – 空字節-地點的準備稱呼的指針。


h_addrtype –地點典范; 通常是AF_INET。


h_length – 地點的比專長度。


h_addr_list – 零字節-主機網絡地點指針。網絡字節排序。


h_addr - h_addr_list中的第一地點。


gethostbyname() 告捷時前往一個指向布局體 hostent 的指針,大概 是個空 (NULL) 指針。(但是和從前不同,不設置errno,h_errno 設置錯 誤信息,請看底下的 herror()。) 但是怎樣使用呢? 偶爾分(我們可以從電腦手冊中發覺),向讀者貫注 信息是不夠的。這個函數可不象它看上去那么難用。


這里是個例子:


#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() 的時分,你不克不及用 perror() 打印錯誤信息 (由于 errno 沒有使用),你應該調用 herror()。


相當簡便,你只是轉達一個保存機器名的字符串(比如 "whitehouse.gov") 給 gethostbyname(),然后從前往的數據布局 struct hostent 中獲取信息。


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


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


這里是個客戶--辦事器的天下。在網絡上的一切東西都是在處理客戶進 程和辦事器歷程的扳談。舉個telnet 的例子。當你用 telnet (客戶)經過23 號端口登岸到主機,主機上運轉的一個步驟(尋常叫 telnetd,辦事器)激活。 它處理這個毗連,體現登岸界面,等等。


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


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


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


19)簡便的辦事器


這個辦事器所做的全部事情是在流式毗連上發送字符串 "Hello, World!\n"。你要測試這個步驟的話,可以在一臺機器上運轉該步驟,然后 在別的一機器上登岸:


$ telnet remotehostname 3490


remotehostname 是該步驟運轉的機器的名字。


辦事器代碼:


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



假如你很挑剔的話,一定不滿意我一切的代碼都在一個很大的main() 函數中。假如你不喜好,可以區分得更細點。


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


20)簡便的客戶步驟


這個步驟比辦事器還簡便。這個步驟的一切事情是經過 3490 端口毗連到下令行中指定的主機,然后取得辦事器發送的字符串。


客戶代碼:


#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 /* 客戶機毗連長程主機的端口 */ #define MAXDATASIZE 100 /* 每次可以吸收的最大字節 */ 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; }



注意,假如你在運轉辦事器之前運轉客戶步驟,connect() 將前往 "Connection refused" 信息,這十分有效。


21)數據包 Sockets


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


listener 在機器上等候在端口 4590 來的數據包。talker 發送數據包到 一定的機器,它包含用戶本人令行輸入的內容。


這里就是 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); }



注意在我們的調用 socket(),我們最初使用了 SOCK_DGRAM。同時, 沒有必要去使用 listen() 大概 accept()。我們在使用無毗連的數據報套接 字!


底下是 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; }



這就是一切的了。在一臺機器上運轉 listener,然后在別的一臺機器上 運轉 talker。察看它們的通訊!
除了一些我在外表提到的數據套接字毗連的小細節外,關于數據套接 字,我還得說一些,當一個發言者召喚connect()函數時并指定承受者的地 址時,從這點可以看出,發言者只能向connect()函數指定的地點發送和接 受信息。因此,你不必要使用sendto()和recvfrom(),你完全可以用send() 和recv()代替。


22)壅閉


壅閉,你約莫早就聽說了。"壅閉"是 "sleep" 的科技行話。你約莫注意 到前方運轉的 listener 步驟,它在那邊不休地運轉,等候數據包的到來。 實踐在運轉的是它調用 recvfrom(),然后沒多數據,因此 recvfrom() 說" 壅閉 (block)",直到數據的到來。


很多函數都使用壅閉。accept() 壅閉,一切的 recv*() 函數壅閉。它 們之以是能如此做是由于它們被允許如此做。當你第一次調用 socket() 建 立套接字形貌符的時分,內核就將它設置為壅閉。假如你不想套接字壅閉, 你就要調用函數 fcntl():


#include <unistd.h>


#include <fontl.h>


……


sockfd = socket(AF_INET, SOCK_STREAM, 0);


fcntl(sockfd, F_SETFL, O_NONBLOCK);


……


過設置套接字為非壅閉,你可以好效地"扣問"套接字以取得信息。如 果你實驗著從一個非壅閉的套接字讀信息并且沒有任何數據,它不允許阻 塞--它將前往 -1 并將 errno 設置為 EWOULDBLOCK。


但是尋常說來,這種扣問不是個好想法。假如你讓你的步驟在忙等狀 態查詢套接字的數據,你將糜費多量的 CPU 時間。更好的處理之道是用 下一章講的 select() 去查詢對否多數據要讀過來。


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


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


select() 讓你可以同時監督多個套接字。假如你想曉得的話,那么它就 會報告你哪個套接字準備讀,哪個又準備寫,哪個套接字又產生了例外 (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);


這個函數監督一系列文件形貌符,特別是 readfds、writefds 和 exceptfds。假如你想曉得你對否可以從標準輸入和套接字形貌符 sockfd 讀入數據,你只需將文件形貌符 0 和 sockfd 到場到聚集 readfds 中。參 數 numfds 應該即是最高的文件形貌符的值加1。在這個例子中,你應該 設置該值為 sockfd+1。由于它一定大于標準輸入的文件形貌符 (0)。 當函數 select() 前往的時分,readfds 的值修正為反應你選擇的哪個 文件形貌符可以讀。你可以用底下講到的宏 FD_ISSET() 來測試。 在我們持續下去之前,讓我來講講怎樣對這些聚集舉行利用。每個集 合典范都是 fd_set。底下有一些宏來對這個典范舉行利用:


FD_ZERO(fd_set *set) – 掃除一個文件形貌符聚集


FD_SET(int fd, fd_set *set) - 添加fd到聚集


FD_CLR(int fd, fd_set *set) – 從聚集中移去fd


FD_ISSET(int fd, fd_set *set) – 測試fd對否在聚集中


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


數據布局 struct timeval 是如此的:


struct timeval {


int tv_sec; /* seconds */


int tv_usec; /* microseconds */


};


只需將 tv_sec 設置為你要等候的秒數,將 tv_usec 設置為你要等候 的微秒數就可以了。是的,是微秒而不是毫秒。1,000微秒即是1毫秒,1,000 毫秒即是1秒。也就是說,1秒即是1,000,000微秒。為什么用標記 "usec" 呢? 字母 "u" 很象希臘字母 Mu,而 Mu 表現 "微" 的意思。固然,函數 前往的時分 timeout 約莫是剩余的時間,之以是是約莫,是由于它依托于 你的 Unix 利用體系。


哈!我們如今有一個微秒級的定時器!別盤算了,標準的 Unix 體系 的時間片是100毫秒,以是無論你怎樣設置你的數據布局 struct timeval, 你都要等候那么長的時間。


另有一些幽默的事變:假如你設置數據布局 struct timeval 中的數據為 0,select() 將立刻超時,如此就可以好效地輪詢聚集中的一切的文件形貌 符。假如你將參數 timeout 賦值為 NULL,那么將永久不會產生超時,即 不休比及第一個文件形貌符停當。最初,假如你不是很體貼等候多長時間, 那么就把它賦為 NULL 吧。


底下的代碼演示了在標準輸入上等候 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"); }



假如你是在一個 line buffered 終端上,那么你敲的鍵應該是回車 (RETURN),不然無論怎樣它都市超時。
如今,你約莫回以為這就是在數據報套接字上等候數據的辦法--你是對 的:它約莫是。有些 Unix 體系可以按這種辦法,而別的一些則不克不及。你 在實驗從前約莫要先看看本體系的 man page 了。


最初一件關于 select() 的事變:假如你有一個正在偵聽 (listen()) 的套 接字,你可以經過將該套接字的文件形貌符到場到 readfds 聚集中來看是 否有新的毗連。


這就是我關于函數select() 要講的一切的東西。



寫在最初:學編程,但是每一局部都有本人的選擇,每一種編程言語的存在都有其使用的朝向,選擇你想從事的朝向,去舉行切合的選擇就對了!關于準備學習編程的小伙伴,假如你想更好的提升你的編程中心才能(內功)無礙從如今開頭!

編程學習冊老實享:

編程學習視頻分享:

整理分享(多年學習的源碼、項目實戰視頻、項目條記,基本入門教程)

接待轉行和學習編程的伙伴,使用更多的材料學習發展比本人揣摩更快哦!

關于C/C++感興致可以眷注小編在背景私信我:【編程交換】一同來學習哦!可以提取一些C/C++的項目學習視頻材料哦!以前設置好了緊張詞主動回復,主動提取就好了!

版權聲明:本文來自互聯網整理發布,如有侵權,聯系刪除

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


Copyright ? 2021-2022 All Rights Reserved 備案編號:閩ICP備2023009674號 網站地圖 聯系:dhh0407@outlook.com

www.成人网