計(jì)算機(jī)基礎(chǔ)知識(shí)——linux socket套接字udp連接分析

2016.7.5

今天早上對(duì)項(xiàng)目頂層文件(daemon.c)進(jìn)行了分析,對(duì)其中的UDP連接進(jìn)行了具體代碼級(jí)分析。

1、需求分析

同樣,首先我們得知道用UDP的需求分析,從昨天的分析中知道UDP支持?jǐn)?shù)據(jù)量小,不支持可靠服務(wù)的傳輸,從項(xiàng)目文檔“測(cè)試機(jī)程序結(jié)構(gòu)”分析可以知道,接收服務(wù)器端下發(fā)的命令是用的UDP,執(zhí)行測(cè)試的結(jié)果最后也是以UDP的形式發(fā)送給服務(wù)器。同時(shí)還要監(jiān)聽測(cè)試結(jié)果進(jìn)程,將測(cè)試結(jié)果發(fā)送給套接字中。

個(gè)人理解上來看,執(zhí)行最后的測(cè)試結(jié)果有應(yīng)該是一個(gè)大文件,并且傳輸?shù)臅r(shí)候應(yīng)該保證有效性,應(yīng)該選用TCP連接,這里是個(gè)疑問?

2、UDP連接原理分析

前面已經(jīng)講述了socket套接字的說明,包括其數(shù)據(jù)結(jié)構(gòu)是怎么樣的,形象的比喻就是:socket就是一個(gè)口袋,一個(gè)洞,用戶可以通過這個(gè)洞直接與網(wǎng)絡(luò)協(xié)議棧打交道,完成網(wǎng)絡(luò)通信,基于它就是因?yàn)槌橄蠼涌诜庋b,簡(jiǎn)單。這里就直接給出了UDP連接的通信流程:

典型的UDP客戶端/服務(wù)器通訊過程如下圖所示:

從這個(gè)圖上可以看出,由于UDP不需要維護(hù)連接,程序邏輯簡(jiǎn)單了很多,但是UDP協(xié)議是不可靠的,實(shí)際上有很多保證通訊可靠性的機(jī)制需要在應(yīng)用層實(shí)現(xiàn),可能反而會(huì)需要更多代碼。

可以看到,我們的測(cè)試機(jī)仍然是服務(wù)器端,服務(wù)器端比直接TCP的程序流程要簡(jiǎn)單,沒有監(jiān)聽listen()這個(gè)動(dòng)作了,直接調(diào)用recvfrom()函數(shù)對(duì)端口號(hào)的監(jiān)聽,這個(gè)函數(shù)沒有數(shù)據(jù)到來時(shí)候一直阻塞,有請(qǐng)求后就知道通過地址,知道數(shù)據(jù)來自哪個(gè)端口號(hào),從而判斷是什么數(shù)據(jù)。

3、UDP連接代碼分析(需要說明的是:這里以項(xiàng)目的daemon.c代碼為例 )

按照上述的服務(wù)器端的流程,我們一步步按照流程進(jìn)行代碼級(jí)的分析:

(1)定義一個(gè)socket套接字

定義套接字時(shí)候會(huì)要傳入對(duì)應(yīng)的參數(shù),如上圖所示,對(duì)套接字的數(shù)據(jù)結(jié)構(gòu)的說明,在之前的TCP分析中已經(jīng)有說明了。

(2)bind綁定套接字的地址和端口號(hào)

bind的用法和TCP連接一樣,沒有什么區(qū)別。

(3)recvfrom阻塞等待客戶端請(qǐng)求

recvfrom()函數(shù)的原型解釋:

recvfrom()
簡(jiǎn)述:
  接收一個(gè)數(shù)據(jù)報(bào)并保存源地址。
  #include <winsock.h>
  int PASCAL FAR recvfrom( SOCKET s, char FAR* buf, int len, int flags,
  struct sockaddr FAR* from, int FAR* fromlen);
  s:標(biāo)識(shí)一個(gè)已連接套接口的描述字。
  buf:接收數(shù)據(jù)緩沖區(qū)。
  len:緩沖區(qū)長(zhǎng)度。
  flags:調(diào)用操作方式。
  from:(可選)指針,指向裝有源地址的緩沖區(qū)。
  fromlen:(可選)指針,指向from緩沖區(qū)長(zhǎng)度值。

注釋:

本函數(shù)由于從(已連接)套接口上接收數(shù)據(jù),并捕獲數(shù)據(jù)發(fā)送源的地址。

對(duì)于SOCK_STREAM類型的套接口,最多可接收緩沖區(qū)大小個(gè)數(shù)據(jù)。如果套接口被設(shè)置為線內(nèi)接收帶外數(shù)據(jù)(選項(xiàng)為SO_OOBINLINE),且有帶外數(shù)據(jù)未讀入,則返回帶外數(shù)據(jù)。應(yīng)用程序可通過調(diào)用ioctlsocket()的SOCATMARK命令來確定是否有帶外數(shù)據(jù)待讀入。對(duì)于SOCK_STREAM類型套接口,忽略from和fromlen參數(shù)。

對(duì)于數(shù)據(jù)報(bào)類套接口,隊(duì)列中第一個(gè)數(shù)據(jù)報(bào)中的數(shù)據(jù)被解包,但最多不超過緩沖區(qū)的大小。如果數(shù)據(jù)報(bào)大于緩沖區(qū),那么緩沖區(qū)中只有數(shù)據(jù)報(bào)的前面部分,其他的數(shù)據(jù)都丟失了,并且recvfrom()函數(shù)返回WSAEMSGSIZE錯(cuò)誤。

若from非零,且套接口為SOCK_DGRAM類型,則發(fā)送數(shù)據(jù)源的地址被復(fù)制到相應(yīng)的sockaddr結(jié)構(gòu)中。fromlen所指向的值初始化時(shí)為這個(gè)結(jié)構(gòu)的大小,當(dāng)調(diào)用返回時(shí)按實(shí)際地址所占的空間進(jìn)行修改。

如果沒有數(shù)據(jù)待讀,那么除非是非阻塞模式,不然的話套接口將一直等待數(shù)據(jù)的到來,此時(shí)將返回SOCKET_ERROR錯(cuò)誤,錯(cuò)誤代碼是WSAEWOULDBLOCK。用select()或WSAAsynSelect()可以獲知何時(shí)數(shù)據(jù)到達(dá)。

如果套接口為SOCK_STREAM類型,并且遠(yuǎn)端“優(yōu)雅”地中止了連接,那么recvfrom()一個(gè)數(shù)據(jù)也不讀取,立即返回。如果立即被強(qiáng)制中止,那么recv()將以WSAECONNRESET錯(cuò)誤失敗返回。

在套接口的所設(shè)選項(xiàng)之上,還可用標(biāo)志位flag來影響函數(shù)的執(zhí)行方式。也就是說,本函數(shù)的語義既取決于套接口選項(xiàng),也取決于標(biāo)志位參數(shù)。標(biāo)志位可取下列值:

值意義:

MSG_PEEK 查看當(dāng)前數(shù)據(jù)。數(shù)據(jù)將被復(fù)制到緩沖區(qū)中,但并不從輸入隊(duì)列中刪除。
MSG_OOB 處理帶外數(shù)據(jù)(參見2.2.3節(jié)具體討論)。

返回值:

若無錯(cuò)誤發(fā)生,recvfrom()返回讀入的字節(jié)數(shù)。如果連接已中止,返回0。否則的話,返回SOCKET_ERROR錯(cuò)誤,應(yīng)用程序可通過WSAGetLastError()獲取相應(yīng)錯(cuò)誤代碼。

錯(cuò)誤代碼:
WSANOTINITIALISED:在使用此API之前應(yīng)首先成功地調(diào)用WSAStartup()。
WSAENETDOWN:WINDOWS套接口實(shí)現(xiàn)檢測(cè)到網(wǎng)絡(luò)子系統(tǒng)失效。
WSAEFAULT:fromlen參數(shù)非法;from緩沖區(qū)大小無法裝入端地址。
WSAEINTR:阻塞進(jìn)程被WSACancelBlockingCall()取消。
WSAEINPROGRESS:一個(gè)阻塞的WINDOWS套接口調(diào)用正在運(yùn)行中。
WSAEINVAL:套接口未用bind()進(jìn)行捆綁。
WSAENOTCONN:套接口未連接(僅適用于SOCK_STREAM類型)。
WSAENOTSOCK:描述字不是一個(gè)套接口。
WSAEOPNOTSUPP:指定了MSG_OOB,但套接口不是SOCK_STREAM類型的。
WSAESHUTDOWN:套接口已被關(guān)閉。當(dāng)一個(gè)套接口以0或2的how參數(shù)調(diào)用shutdown()關(guān)閉后,無法再用recv()接收數(shù)據(jù)。
WSAEWOULDBLOCK:套接口標(biāo)識(shí)為非阻塞模式,但接收操作會(huì)產(chǎn)生阻塞。
WSAEMSGSIZE:數(shù)據(jù)報(bào)太大無法全部裝入緩沖區(qū),故被剪切。
WSAECONNABORTED:由于超時(shí)或其他原因,虛電路失效。
WSAECONNRESET:遠(yuǎn)端強(qiáng)制中止了虛電路。

看實(shí)例代碼中是如何運(yùn)用的:

recvfrom操作自帶有阻塞功能,當(dāng)沒有接受到請(qǐng)求的時(shí)候自己阻塞,等待請(qǐng)求的到來,接受到了請(qǐng)求,同時(shí)接受請(qǐng)求 的數(shù)據(jù),將數(shù)據(jù)放到buf中,因?yàn)閡dp是少數(shù)據(jù)流的協(xié)議控制,所以說很少有不能一次copy所有的情況,所以傳來的數(shù)據(jù)直接到buf里面即可,后面的工作就是對(duì)buf的數(shù)據(jù)進(jìn)行相應(yīng)的操作了。

ret是recv的返回值,表示接受數(shù)據(jù)的大小。

前面討論過我們的測(cè)試系統(tǒng)實(shí)際上的udp的套接字完成了兩個(gè)端口的監(jiān)聽,第一個(gè)服務(wù)器端的監(jiān)聽,第二個(gè)是用本地通信端口的監(jiān)聽,所以在實(shí)際代碼中還有另外一個(gè)套接字,完成的也是上述的定義、綁定初始化,然后進(jìn)行recv監(jiān)聽,代碼如下:

分析和上面的調(diào)用一樣,沒有什么好分析的。

(4)sendto()發(fā)送數(shù)據(jù)給客戶端

在我們系統(tǒng)測(cè)試完整個(gè)操作之后,測(cè)試結(jié)果通過UDP本地進(jìn)程的通信發(fā)送給UDP的buf中,然后在通過UDP連接從buf中把數(shù)據(jù)發(fā)送到服務(wù)器上,完成結(jié)果的交付工作。

那么這樣存在一個(gè)問題,他們的buf是不是一樣的???會(huì)不會(huì)有重疊的問題出現(xiàn)????

select函數(shù)對(duì)max_sd套接字進(jìn)行可讀性的監(jiān)聽,所以任何時(shí)候可以同時(shí)監(jiān)聽到這三個(gè)套接字。但是我們的代碼是順序執(zhí)行的,buf是共用的,buf首先會(huì)給tcp使用,再然后給udp使用,每個(gè)使用過程中,會(huì)最后將buf下發(fā)下去,數(shù)據(jù)也就沒有用了。之后在給下一個(gè)用。

所以對(duì)于udp來說,udp如果監(jiān)聽到了msg套接字的連接請(qǐng)求,相應(yīng)后將數(shù)據(jù)放到對(duì)應(yīng)的buf中,然后這個(gè)時(shí)候才建立一個(gè)新的socket,這個(gè)套接字用于想服務(wù)器傳送之前的數(shù)據(jù),但是這個(gè)時(shí)候其實(shí)我們是知道服務(wù)器的地址的端口號(hào)的,不需要在進(jìn)行新的綁定了,建立了這個(gè)心的socket,直接向服務(wù)器端發(fā)送該數(shù)據(jù)即可。

首先定義一個(gè)傳輸?shù)男绿捉幼郑?/p>

sockfd = socket(AF_INET, SOCK_DGRAM, 0)

SOCK_DGRAM是無保障的面向消息的socket,主要用于在網(wǎng)絡(luò)上發(fā)廣播消息。

兩個(gè)重要的類型是SOCK_STREAM和SOCK_DGRAM。SOCK_STREAM表明數(shù)據(jù)向字符流一樣通過socket,但是SOCK_DGRAM則表明數(shù)據(jù)是以數(shù)據(jù)報(bào)的形式通過socket的

這里定義的套接字規(guī)定了其發(fā)送的數(shù)據(jù)是數(shù)據(jù)包的形式,證明了是一個(gè)包,對(duì)方需要對(duì)這個(gè)包解析,有頭部的。

然后進(jìn)行設(shè)備套接字選項(xiàng),設(shè)備此套接字可以重用本地的端口號(hào)和地址:

setsockpt()函數(shù)說明如下:

int setsockopt (
  SOCKET s,                
  int level,              
  int optname,             
  const char FAR * optval, 
  int optlen               
);

The Windows Sockets setsockopt function sets a socket option.
中文解釋好像是:設(shè)置套接字的選項(xiàng)。
先看如下代碼:

setsockopt(SockRaw,IPPROTO_IP,IP_HDRINCL,(char *)&flag,sizeof(int))

這里是設(shè)置SockRaw這個(gè)套接字的ip選項(xiàng)中的IP_HDRINCL
參考以下資料:


Linux網(wǎng)絡(luò)編程--8. 套接字選項(xiàng)

有時(shí)候我們要控制套接字的行為(如修改緩沖區(qū)的大小),這個(gè)時(shí)候我們就要控制套接字的選項(xiàng)了.

8.1 getsockopt和setsockopt 

int getsockopt(int sockfd,int level,int optname,void *optval,socklen_t *optlen)
int setsockopt(int sockfd,int level,int optname,const void *optval,socklen_t *optlen)

level指定控制套接字的層次.可以取三種值:
1)SOL_SOCKET:通用套接字選項(xiàng).
2)IPPROTO_IP:IP選項(xiàng).
3)IPPROTO_TCP:TCP選項(xiàng). 

optname指定控制的方式(選項(xiàng)的名稱),我們下面詳細(xì)解釋 
optval獲得或者是設(shè)置套接字選項(xiàng).根據(jù)選項(xiàng)名稱的數(shù)據(jù)類型進(jìn)行轉(zhuǎn)換 

選項(xiàng)名稱        說明                  數(shù)據(jù)類型

========================================================================

            SOL_SOCKET

------------------------------------------------------------------------

SO_BROADCAST      允許發(fā)送廣播數(shù)據(jù)            int

SO_DEBUG        允許調(diào)試                int

SO_DONTROUTE      不查找路由               int

SO_ERROR        獲得套接字錯(cuò)誤             int

SO_KEEPALIVE      保持連接                int

SO_LINGER        延遲關(guān)閉連接              struct linger

SO_OOBINLINE      帶外數(shù)據(jù)放入正常數(shù)據(jù)流         int

SO_RCVBUF        接收緩沖區(qū)大小             int

SO_SNDBUF        發(fā)送緩沖區(qū)大小             int

SO_RCVLOWAT       接收緩沖區(qū)下限             int

SO_SNDLOWAT       發(fā)送緩沖區(qū)下限             int

SO_RCVTIMEO       接收超時(shí)                struct timeval

SO_SNDTIMEO       發(fā)送超時(shí)                struct timeval

linux下,如果一個(gè)進(jìn)程幫定某個(gè)port,那當(dāng)進(jìn)程結(jié)束時(shí),該port仍然會(huì)被繼續(xù)占用幾十秒,在這段時(shí)間內(nèi)嘗試對(duì)

該port的綁定都會(huì)返回失敗。解決方法:調(diào)用setsockopt()啟用SO_REUSERADDR屬性

SO_REUSERADDR      允許重用本地地址和端口         int

SO_TYPE         獲得套接字類型             int

SO_BSDCOMPAT      與BSD系統(tǒng)兼容              int

==========================================================================

            IPPROTO_IP

--------------------------------------------------------------------------

IP_HDRINCL       在數(shù)據(jù)包中包含IP首部          int

IP_OPTINOS       IP首部選項(xiàng)               int

IP_TOS         服務(wù)類型

IP_TTL         生存時(shí)間                int

==========================================================================

            IPPRO_TCP

--------------------------------------------------------------------------

TCP_MAXSEG       TCP最大數(shù)據(jù)段的大小           int

TCP_NODELAY       不使用Nagle算法             int

=========================================================================

詳細(xì)的選項(xiàng)請(qǐng)用 man ioctl_list 查看.

udp固定端口發(fā)送

 sockaddr_in         cliaddr;   
 cliaddr.sin_family   =   AF_INET;   
 cliaddr.sin_port   =   htons(1025);//BTW:1024到5000間的端口是系統(tǒng)用于自動(dòng)分配的,小心了   
 cliaddr.sin_addr.s_addr   =   htonl(INADDR_ANY);   
 bind(你的客戶端socket,   (sockaddr*)&cliaddr,   sizeof(cliaddr));

最后想指定的端口發(fā)送對(duì)應(yīng)的數(shù)據(jù)包即可,使用額是sendto函數(shù),函數(shù) 原型如下:

sendto()

簡(jiǎn)述:

向一指定目的地發(fā)送數(shù)據(jù)。

  #include <winsock.h>
  int PASCAL FAR sendto( SOCKET s, const char FAR* buf, int len, int flags,
  const struct sockaddr FAR* to, int tolen);

  s:一個(gè)標(biāo)識(shí)套接口的描述字。
  buf:包含待發(fā)送數(shù)據(jù)的緩沖區(qū)。
  len:buf緩沖區(qū)中數(shù)據(jù)的長(zhǎng)度。
  flags:調(diào)用方式標(biāo)志位。
  to:(可選)指針,指向目的套接口的地址。
  tolen:to所指地址的長(zhǎng)度。

注釋:

sendto()適用于已連接的數(shù)據(jù)報(bào)或流式套接口發(fā)送數(shù)據(jù)。對(duì)于數(shù)據(jù)報(bào)類套接口,必需注意發(fā)送數(shù)據(jù)長(zhǎng)度不應(yīng)超過通訊子網(wǎng)的IP包最大長(zhǎng)度。IP包最大長(zhǎng)度在WSAStartup()調(diào)用返回的WSAData的iMaxUdpDg元素中。如果數(shù)據(jù)太長(zhǎng)無法自動(dòng)通過下層協(xié)議,則返回WSAEMSGSIZE錯(cuò)誤,數(shù)據(jù)不會(huì)被發(fā)送。請(qǐng)注意成功地完成sendto()調(diào)用并不意味著數(shù)據(jù)傳送到達(dá)。

sendto()函數(shù)主要用于SOCK_DGRAM類型套接口向to參數(shù)指定端的套接口發(fā)送數(shù)據(jù)報(bào)。對(duì)于SOCK_STREAM類型套接口,to和tolen參數(shù)被忽略;這種情況下sendto()等價(jià)于send()。

為了發(fā)送廣播數(shù)據(jù)(僅適用于SOCK_DGRAM),in參數(shù)所含地址應(yīng)該把特定的IP地址INADDR_BROADCAST(winsock.h中有定義)和終端地址結(jié)合起來構(gòu)造。通常建議一個(gè)廣播數(shù)據(jù)報(bào)的大小不要大到以致產(chǎn)生碎片,也就是說數(shù)據(jù)報(bào)的數(shù)據(jù)部分(包括頭)不超過512字節(jié)。

如果傳送系統(tǒng)的緩沖區(qū)空間不夠保存需傳送的數(shù)據(jù),除非套接口處于非阻塞I/O方式,否則sendto()將阻塞。對(duì)于非阻塞SOCK_STREAM類型的套接口,實(shí)際寫的數(shù)據(jù)數(shù)目可能在1到所需大小之間,其值取決于本地和遠(yuǎn)端主機(jī)的緩沖區(qū)大小??捎胹elect()調(diào)用來確定何時(shí)能夠進(jìn)一步發(fā)送數(shù)據(jù)。

在相關(guān)套接口的選項(xiàng)之上,還可通過標(biāo)志位flag來影響函數(shù)的執(zhí)行方式。也就是說,本函數(shù)的語義既取決于套接口的選項(xiàng)也取決于標(biāo)志位。后者由以下一些值組成:

值意義

MSG_DONTROUTE 指明數(shù)據(jù)不選徑。一個(gè)WINDOWS套接口供應(yīng)商可以忽略此標(biāo)志;參見2.4節(jié)中關(guān)于SO_DONTROUTE的討論。

MSG_OOB 發(fā)送帶外數(shù)據(jù)(僅適用于SO_STREAM;參見2.2.3節(jié))。

返回值:

若無錯(cuò)誤發(fā)生,send()返回所發(fā)送數(shù)據(jù)的總數(shù)(請(qǐng)注意這個(gè)數(shù)字可能小于len中所規(guī)定的大?。?。否則的話,返回SOCKET_ERROR錯(cuò)誤,應(yīng)用程序可通過WSAGetLastError()獲取相應(yīng)錯(cuò)誤代碼。

看代碼中實(shí)例:

方框表示的就是特定的IP地址INADDR_BROADCAST(winsock.h中有定義)和終端地址結(jié)合起來構(gòu)造。

(5)關(guān)閉套接字

close(sockfd)

很簡(jiǎn)單,和之前的tcp關(guān)閉時(shí)一樣的,沒有什么多說的。。。。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • socket的基本概念 網(wǎng)絡(luò)上的兩個(gè)程序通過一個(gè)雙向的通信連接實(shí)現(xiàn)數(shù)據(jù)的交換,這個(gè)連接的一端稱為一個(gè)socket。...
    小葉大孟閱讀 775評(píng)論 0 0
  • 網(wǎng)絡(luò)編程 一.楔子 你現(xiàn)在已經(jīng)學(xué)會(huì)了寫python代碼,假如你寫了兩個(gè)python文件a.py和b.py,分別去運(yùn)...
    go以恒閱讀 2,252評(píng)論 0 6
  • 一 、Socket 網(wǎng)絡(luò)上的兩個(gè)程序通過一個(gè)雙向的通信連接實(shí)現(xiàn)數(shù)據(jù)的交換,這個(gè)連接的一端稱為一個(gè) Socket。S...
    空白Null閱讀 1,892評(píng)論 1 9
  • 網(wǎng)絡(luò)中進(jìn)程之間如何通信 為了方便大家獲取源代碼,可以移步這里,GitHub源代碼 進(jìn)程通信的概念最初來源于單機(jī)系統(tǒng)...
    batbattle閱讀 14,253評(píng)論 1 5
  • 1三個(gè)相關(guān)數(shù)據(jù)結(jié)構(gòu). 關(guān)于socket的創(chuàng)建,首先需要分析socket這個(gè)結(jié)構(gòu)體,這是整個(gè)的核心。 104 str...
    ice_camel閱讀 2,967評(píng)論 1 8

友情鏈接更多精彩內(nèi)容