前沿拓展:
本文主要給大家分享網(wǎng)絡(luò)七層概念之網(wǎng)絡(luò)編程socket,前邊的章節(jié)已經(jīng)給大家講述了鏈路層、物理層、網(wǎng)絡(luò)層、應(yīng)用層、傳輸層等,歡迎學(xué)習(xí)嵌入式網(wǎng)絡(luò)編程的朋友關(guān)注、轉(zhuǎn)載和發(fā)表評論!
(絕對的好文,建議先收藏和轉(zhuǎn)載?。?/p>
*/ 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);
……
……
}
注意:我們使用了套接字描述符 new_fd 用來進(jìn)行所有的 send() 和 recv() 調(diào)用。如果你只想獲
得一個單獨(dú)的連接,那么你可以將原來的 sock_fd 關(guān)掉(調(diào)用 close()),這樣的話就可以阻止以后的連接了。
在面向連接的通信中客戶機(jī)要做如下一些事:
· 調(diào)用 socket() 函數(shù)創(chuàng)建一個套接字。
· 調(diào)用 connect() 函數(shù)試圖連接服務(wù)。
· 如果連接成功調(diào)用 write() 函數(shù)請求數(shù)據(jù),調(diào)用 read() 函數(shù)接收引入的應(yīng)答。
8.5.6 send()和 recv()函數(shù)
這兩個函數(shù)是最基本的,通過連接的套接字流進(jìn)行通訊的函數(shù)。
如果你想使用無連接的用戶數(shù)據(jù)報的話,請參考下面的 sendto() 和 recvfrom() 函數(shù)。
send() 函數(shù)的聲明:
#include <sys/types.h>
#include <sys/socket.h>
int send(int sockfd, const void *msg, int len, int flags);
send 的參數(shù)含義如下:
· sockfd 是代表你與遠(yuǎn)程程序連接的套接字描述符。
· msg 是一個指針,指向你想發(fā)送的信息的地址。
· len 是你想發(fā)送信息的長度。
· flags 發(fā)送標(biāo)記。一般都設(shè)為 0(你可以查看 send 的 man pages 來獲得其他的參數(shù)值并且明白各個參數(shù)所代表的含義)。
下面看看有關(guān) send() 函數(shù)的代碼片段: char *msg = ” Hello! World!”;
int len, bytes_sent;
……
……
len = strlen(msg);
bytes_sent = send(sockfd, msg, len, 0);
……
……
……
send()函數(shù)在調(diào)用后會返回它真正發(fā)送數(shù)據(jù)的長度。
注意:send() 所發(fā)送的數(shù)據(jù)可能少于你給它的參數(shù)所指定的長度!
因?yàn)槿绻憬o send() 的參數(shù)中包含的數(shù)據(jù)的長度遠(yuǎn)遠(yuǎn)大于 send() 所能一次發(fā)送的數(shù)據(jù),則
send() 函數(shù)只發(fā)送它所能發(fā)送的最大數(shù)據(jù)長度,第二它相信你會把剩下的數(shù)據(jù)再次調(diào)用它來進(jìn)行第二次發(fā)送。
所以,記住如果 send() 函數(shù)的返回值小于 len 的話,則你需要再次發(fā)送剩下的數(shù)據(jù)。幸運(yùn)的是, 如果包足夠小(小于 1K),那么 send() 一般都會一次發(fā)送光的。
像上面的函數(shù)一樣,send() 函數(shù)如果發(fā)生錯誤,則返回 – 1,錯誤代碼存儲在全局變量 errno中。
下面我們來看看 recv() 函數(shù)。
函數(shù) recv() 調(diào)用在許多方面都和 send() 很相似,下面是 recv() 函數(shù)的聲明:
#include <sys/types.h>
#include <sys/socket.h>
int recv(int sockfd, void *buf, int len, unsigned int flags);
recv()的參數(shù)含義如下:
· sockfd 是你要讀取數(shù)據(jù)的套接字描述符。
· buf 是一個指針,指向你能存儲數(shù)據(jù)的內(nèi)存緩存區(qū)域。
· len 是緩存區(qū)的最大尺寸。
· flags 是 recv() 函數(shù)的一個標(biāo)志, 一般都為 0(具體的其他數(shù)值和含義請參考 recv() 的man pages)。
recv() 返回它所真正收到的數(shù)據(jù)的長度。(也就是存到 buf 中數(shù)據(jù)的長度)。如果返回 –1 則代表發(fā)生了錯誤(比如網(wǎng)絡(luò)以外中斷、對方關(guān)閉了套接字連接等),全局變量 errno 里面存儲了錯誤代碼。
很簡單,不是嗎?現(xiàn)在你已經(jīng)可以使用套接字連接進(jìn)行網(wǎng)絡(luò)發(fā)送數(shù)據(jù)和接受數(shù)據(jù)了!
Ya! 你現(xiàn)在已經(jīng)成為了一個 Linux 下的網(wǎng)絡(luò)程序員了!
8.5.7 sendto()和 recvfrom()函數(shù)
這兩個函數(shù)是進(jìn)行無連接的 UDP 通訊時使用的。使用這兩個函數(shù),則數(shù)據(jù)會在沒有建立過任何連接的網(wǎng)絡(luò)上傳輸。因?yàn)閿?shù)據(jù)報套接字無法對遠(yuǎn)程主機(jī)進(jìn)行連接,想想我們在發(fā)送數(shù)據(jù)前需要知道些什么 呢?
對了!是遠(yuǎn)程主機(jī)的 IP 地址和端口!
下面是 sendto()函數(shù)和 recvfrom()函數(shù)的聲明:
#include <sys/types.h>
#include <sys/socket.h>
int sendto(int sockfd, const void *msg, int len, unsigned int flags, const struct sockaddr *to, int tolen);
和你所看到的一樣,這個函數(shù)和 send()函數(shù)基本一致。
· sockfd 是代表你與遠(yuǎn)程程序連接的套接字描述符。
· msg 是一個指針,指向你想發(fā)送的信息的地址。
· len 是你想發(fā)送信息的長度。
· flags 發(fā)送標(biāo)記。一般都設(shè)為 0。 (你可以查看 send 的 man pages 來獲得其他的參數(shù)值并且明白各個參數(shù)所代表的含義)
· to 是一個指向 struct sockaddr 結(jié)構(gòu)的指針,里面包含了遠(yuǎn)程主機(jī)的
IP 地址和端口數(shù)據(jù)。
· tolen 只是指出了 struct sockaddr 在內(nèi)存中的大小sizeof(struct sockaddr)。
和 send() 一樣,sendto() 返回它所真正發(fā)送的字節(jié)數(shù)(當(dāng)然也和 send() 一樣,它所真正發(fā)送的字節(jié)數(shù)可能小于你所給它的數(shù)據(jù)的字節(jié)數(shù))。 當(dāng)它發(fā)生錯誤的時候,也是返回– 1,同時全局變量
errno 存儲了錯誤代碼。
同樣的,recvfrom() 函數(shù)和 recv() 函數(shù)也基本一致。
recvfrom() 的聲明為:
#include <sys/types.h>
#include <sys/socket.h>
int recvfrom(int sockfd, void *buf, int len, unsigned int flags struct sockaddr *from, int *fromlen);
其參數(shù)含義如下:
· sockfd 是你要讀取數(shù)據(jù)的套接字描述符。
· buf 是一個指針,指向你能存儲數(shù)據(jù)的內(nèi)存緩存區(qū)域。
· len 是緩存區(qū)的最大尺寸。
· flags 是 recvfrom() 函數(shù)的一個標(biāo)志, 一般都為 0 (具體的其他數(shù)值和含義請參考 recvfrom() 的 man pages)。
· from 是一個本地指針,指向一個 struct sockaddr 的結(jié)構(gòu)(里面存有源 IP 地址和端口數(shù))。
· fromlen 是一個指向一個 int 型數(shù)據(jù)的指針,它的大小應(yīng)該是sizeof(struct sockaddr).當(dāng)函數(shù)返回的時候,
formlen 指向的數(shù)據(jù)是 form 指向的 struct sockaddr 的實(shí)際大小。
recvfrom() 返回它接收到的字節(jié)數(shù),如果發(fā)生了錯誤,它就返回 – 1,全局變量 errno 存儲了錯誤代碼。
如果一個信息大得緩沖區(qū)都放不下,那么附加信息將被砍掉。該調(diào)用可以立即返回,也可以**的 等待。這取決于你把 flags 設(shè)置成什么類型。你甚至可以設(shè)置超時(timeout)值。
在說明書(man pages)中可以找到 recvfrom 的更多信息。
注意:如果你使用 cnnect() 連接到了一個數(shù)據(jù)報套接字的服務(wù)器程序上,那么你就可以使用
send() 和 recv() 函數(shù)來傳輸你的數(shù)據(jù)。不要以為你在使用一個流式的套接字,你所使用的仍然是一個用戶數(shù)據(jù)報的套接字,只不過套接字界面在 send() 和 recv()的時候自動幫助你加上了目標(biāo)地址, 目標(biāo)端口的信息。
8.5.8 close()和 shutdown()函數(shù)
程序進(jìn)行網(wǎng)絡(luò)傳輸完畢后,你需要關(guān)閉這個套接字描述符所表示的連接。實(shí)現(xiàn)這個非常簡單,只需 要使用標(biāo)準(zhǔn)的關(guān)閉文件的函數(shù):close()。
使 用 方 法 : close(sockfd);
執(zhí)行 close()之后,套接字將不會在允許進(jìn)行讀**作和寫**作。任何有關(guān)對套接字描述符進(jìn)行讀和寫的**作都會接收到一個錯誤。
如果你想對網(wǎng)絡(luò)套接字的關(guān)閉進(jìn)行進(jìn)一步的**作的話,你可以使用函數(shù) shutdown()。它允許你進(jìn)行單向的關(guān)閉**作,或是全部禁止掉。
shutdown()的聲明為:
#include <sys/socket.h>
int shutdown(int sockfd, int how); 它的參數(shù)含義如下:
· sockfd 是一個你所想關(guān)閉的套接字描述符。
· how 可以取下面的值。0 表示不允許以后數(shù)據(jù)的接收**;1 表示不允許以后數(shù)據(jù)的發(fā)送**作;
2 表示和 close() 一樣,不允許以后的任何**作(包括接收,發(fā)送數(shù)據(jù))。
shutdown() 如果執(zhí)行成功將返回 0,如果在調(diào)用過程中發(fā)生了錯誤,它將返回–1,全局變量 errno中存儲了錯誤代碼。
如果你在一個未連接的數(shù)據(jù)報套接字上使用 shutdown() 函數(shù)(還記得可以對數(shù)據(jù)報套接字 UDP 進(jìn)行 connect() **作嗎?),它將什么也不做。
8.5.9 setsockopt()和 getsockopt()函數(shù)
Linux 所提供的 socket 庫含有一個錯誤(bug)。此錯誤表現(xiàn)為你不能為一個套接字重新啟用同一個端口號,即使在你正常關(guān)閉該套接字以后。例如,比方說,你編寫一個服務(wù)器在一個套接字上等待的 程序。服務(wù)器打開套接字并在其上偵聽是沒有問題的。無論如何,總有一些原因(不管是正常還是非正 常的結(jié)束程序)使你的程序需要重新啟動。然而重啟動后你就不能把它綁定在原來那個端口上了。從
bind() 系統(tǒng)調(diào)用返回的錯誤代碼總是報告說你試圖連接的端口已經(jīng)被別的進(jìn)程所綁定。
問題就是 Linux 內(nèi)核在一個綁定套接字的進(jìn)程結(jié)束后從不把端口標(biāo)記為未用。在大多數(shù)
Linux/UNIX 系統(tǒng)中,端口可以被一個進(jìn)程重復(fù)使用,甚至可以被其它進(jìn)程使用。
在 Linux 中繞開這個問題的辦法是,當(dāng)套接字已經(jīng)打開但尚未有連接的時候用 setsockopt() 系統(tǒng)調(diào)用在其上設(shè)定選項(xiàng)(options)。而 getsockopt() 可以從給定的套接字取得選項(xiàng)。
這里是這些調(diào)用的語法:
#include<sys/types.h>
#include<sys/socket.h>
int getsockopt(int sockfd, int level, int name,
char *value, int *optlen); int setsockopt(int sockfd, int level, int name,
char *value, int *optlen); 下面是兩個調(diào)用的參數(shù)說明:
· sockfd 必須是一個已打開的套接字。
· level 是函數(shù)所使用的協(xié)議標(biāo)準(zhǔn)(protocol level) (TCP/IP 協(xié)議使用
PPROTO_TCP,套接字標(biāo)準(zhǔn)的選項(xiàng)實(shí)用 SOL_SOCKET)。
· name 選項(xiàng)在套接字說明書中(man page)有詳細(xì)說明。
· value 指向?yàn)?getsockopt()函數(shù)所獲取的值,setsockopt()函數(shù)所設(shè)置的值的地址。
· optlen 指針指向一個整數(shù),該整數(shù)包含參數(shù)以字節(jié)計(jì)算的長度。
現(xiàn)在我們再回到 Linux 的錯誤上來.當(dāng)你打開一個套接字時必須同時用下面的代碼段來調(diào)用
setsockopt() 函數(shù):
/* 設(shè)定參數(shù)數(shù)值 */
opt = 1; len = sizeof(opt);
/* 設(shè)置套接字屬性 */ setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,&len);
setsockopt()函數(shù)還有很多其他用法,請參考幫助頁(man pages)。
8.5.10 getpeername() 函數(shù)
這個函數(shù)可以取得一個已經(jīng)連接上的套接字的遠(yuǎn)程信息(比如 IP 地址和端口),告訴你在遠(yuǎn)程和你連接的到底是誰。
它的聲明為:
#include <sys/socket.h>
int getpeername(int sockfd, struct sockaddr *addr, int *addrlen); 下面是參數(shù)說明:
· sockfd 是你想取得遠(yuǎn)程信息的那個套接字描述符。
· addr 是一個指向 struct sockaddr (或是 struct sockaddr_in)的指針。
· addrlen 是一個指向 int 的指針,應(yīng)該賦于 sizeof(struct sockaddr)的大小。
如果在函數(shù)執(zhí)行過程中出現(xiàn)了錯誤,函數(shù)將返回 –1,并且錯誤代碼儲存在全局變量 errno 中。當(dāng)你擁有了遠(yuǎn)程連接用戶的 IP 地址,你就可以使用 inet_ntoa() 或 gethostbyaddr() 來輸出
信息或是做進(jìn)一步的處理。
8.5.11 gethostname()函數(shù)
gethostname() 函數(shù)可以取得本地主機(jī)的信息.它比 getpeername() 要容易使用一些。
它返回正在執(zhí)行它的計(jì)算機(jī)的名字。返回的這個名字可以被 gethostbyname() 函數(shù)使用,由此可以得到本地主機(jī)的 IP 地址。
下面是它的聲明:
#include <unistd.h>
int gethostname(char *hostname, size_t size); 參數(shù)說明如下:
· hostname 是一個指向字符數(shù)組的指針,當(dāng)函數(shù)返回的時候,它里面的數(shù)據(jù)就是本地的主機(jī)的名字。
· size 是 hostname 指向的數(shù)組的長度。
函數(shù)如果成功執(zhí)行,它返回 0,如果出現(xiàn)錯誤,則返回–1,全局變量 errno 中存儲著錯誤代碼。
拓展知識:
原創(chuàng)文章,作者:九賢生活小編,如若轉(zhuǎn)載,請注明出處:http://xiesong.cn/49869.html