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