《網(wǎng)絡(luò)應(yīng)用程序設(shè)計(jì)》課件第3章 UDP套接字與原始套接字的編程_第1頁(yè)
《網(wǎng)絡(luò)應(yīng)用程序設(shè)計(jì)》課件第3章 UDP套接字與原始套接字的編程_第2頁(yè)
《網(wǎng)絡(luò)應(yīng)用程序設(shè)計(jì)》課件第3章 UDP套接字與原始套接字的編程_第3頁(yè)
《網(wǎng)絡(luò)應(yīng)用程序設(shè)計(jì)》課件第3章 UDP套接字與原始套接字的編程_第4頁(yè)
《網(wǎng)絡(luò)應(yīng)用程序設(shè)計(jì)》課件第3章 UDP套接字與原始套接字的編程_第5頁(yè)
已閱讀5頁(yè),還剩128頁(yè)未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

第3章UDP套接字與原始套接字的編程3.1概述3.2UDP套接字編程3.3連接UDP套接字的功能3.4UDP編程中的錯(cuò)誤檢測(cè)及處理方法3.5UDP套接字在OICQ服務(wù)中的應(yīng)用3.6原始套接字3.7服務(wù)器編程模型習(xí)題3.1概述 Internet協(xié)議簇支持一個(gè)面向無(wú)連接的傳輸協(xié)議

用戶數(shù)據(jù)報(bào)協(xié)議(UDP,UserDatagramProtocol)。UDP協(xié)議向應(yīng)用程序提供了一種發(fā)送經(jīng)過(guò)封裝的IP數(shù)據(jù)報(bào)的方法,而且不需要在發(fā)送方和接收方之間建立連接就可以進(jìn)行數(shù)據(jù)報(bào)通信。

UDP協(xié)議與TCP協(xié)議提供的服務(wù)不同,所以基于UDP協(xié)議的應(yīng)用程序同基于TCP的應(yīng)用程序有不相同的地方,它們的編程模型也不相同。圖3-1給出了典型的UDP客戶機(jī)/服務(wù)器程序的函數(shù)調(diào)用模型。圖3-1UDP客戶機(jī)/服務(wù)器程序的編程模型

在前面的章節(jié)中,已經(jīng)介紹了基于TCP套接字編程的函數(shù)調(diào)用模型,比較TCP和UDP編程模型可以看出,UDP協(xié)議不需要事先在客戶機(jī)、服務(wù)器程序之間建立連接。服務(wù)器端在調(diào)用socket函數(shù)生成一個(gè)UDP套接字后,利用bind函數(shù)將套接字與本地IP、端口號(hào)綁定,然后,服務(wù)器端調(diào)用recvfrom函數(shù)等待接收由客戶端發(fā)送來(lái)的數(shù)據(jù)??蛻魴C(jī)首先調(diào)用socket函數(shù)創(chuàng)建一個(gè)UDP套接字,然后利用sendto函數(shù)將請(qǐng)求包發(fā)送至服務(wù)器端;服務(wù)器端收到請(qǐng)求包后,根據(jù)請(qǐng)求進(jìn)行處理,調(diào)用sendto函數(shù)將處理結(jié)果作為應(yīng)答數(shù)據(jù)發(fā)送給客戶機(jī)??蛻魴C(jī)調(diào)用recvfrom函數(shù)接收服務(wù)器端發(fā)送來(lái)的應(yīng)答數(shù)據(jù)。當(dāng)通信結(jié)束后,客戶機(jī)調(diào)用close函數(shù)關(guān)閉UDP套接字,而服務(wù)器端可以保留已建立的UDP套接字繼續(xù)與其他客戶機(jī)進(jìn)行數(shù)據(jù)通信。3.2UDP套接字編程 3.1節(jié)中已介紹了基于UDP協(xié)議網(wǎng)絡(luò)編程的一般模型,其中涉及到了在套接字編程中曾簡(jiǎn)單介紹過(guò)的數(shù)據(jù)報(bào)發(fā)送和接收函數(shù)sendto和recvfrom,下面我們列出可用于數(shù)據(jù)報(bào)發(fā)送、接收的高級(jí)套接字函數(shù)。這些函數(shù)是:

#include<sys/types.h>

#include<sys/socket.h>

#include<sys/uio.h>

intsendto(intfd,char*buf,intlen,intflags,structsockaddr*toaddr,intaddrlen);

intrecvfrom(intfd,char*buf,intlen,intflags,structsockaddr*fromaddr,

intaddrlen);

intsendmsg(intfd,structmsghdr*msgp,intflags);

intrecvmsg(intfd,structmsghdr*msgp,intflags); UDP套接字在通信中發(fā)送和接收的數(shù)據(jù)是以數(shù)據(jù)報(bào)為單位的。當(dāng)應(yīng)用程序調(diào)用函數(shù)sendto發(fā)送數(shù)據(jù)時(shí),首先應(yīng)將數(shù)據(jù)封裝生成一個(gè)UDP數(shù)據(jù)報(bào),然后發(fā)送;當(dāng)應(yīng)用程序調(diào)用函數(shù)recvfrom接收數(shù)據(jù)時(shí)。UDP協(xié)議將返回一個(gè)完整的UDP數(shù)據(jù)報(bào)數(shù)據(jù)內(nèi)容。在使用UDP套接字進(jìn)行編程時(shí),我們必須注意以下幾個(gè)問(wèn)題:

(1)?UDP套接字在發(fā)送數(shù)據(jù)時(shí)不會(huì)因發(fā)送緩沖區(qū)而出現(xiàn)阻塞。UDP協(xié)議沒(méi)有專門(mén)為UDP套接字設(shè)置發(fā)送緩沖區(qū),當(dāng)應(yīng)用程序通過(guò)調(diào)用函數(shù)sendto來(lái)發(fā)送數(shù)據(jù)時(shí),該函數(shù)將要發(fā)送的數(shù)據(jù)從用戶緩沖區(qū)拷貝到系統(tǒng)緩沖區(qū),然后返回。UDP協(xié)議進(jìn)一步把數(shù)據(jù)封裝成一個(gè)UDP數(shù)據(jù)報(bào),然后將這個(gè)UDP數(shù)據(jù)報(bào)傳送給低層的IP協(xié)議,從而完成UDP數(shù)據(jù)報(bào)的發(fā)送任務(wù)。UDP協(xié)議是不可靠的協(xié)議,它沒(méi)有必要保留已經(jīng)發(fā)送的UDP數(shù)據(jù)報(bào)內(nèi)容。所以,UDP套接字只有一個(gè)發(fā)送緩沖區(qū)大小,而這個(gè)大小就是可以發(fā)送的UDP數(shù)據(jù)報(bào)的最大長(zhǎng)度。如果應(yīng)用程序發(fā)送的數(shù)據(jù)量大于這個(gè)限制值,函數(shù)sendto將以錯(cuò)誤返回,錯(cuò)誤類型是EMSGSIZE。UDP套接字的發(fā)送緩沖區(qū)大小是不會(huì)發(fā)生變化的,所以,只要應(yīng)用程序保證調(diào)用函數(shù)sendto發(fā)送的數(shù)據(jù)量小于這個(gè)限制值,發(fā)送操作總能夠成功。因此,應(yīng)用程序使用UDP套接字發(fā)送數(shù)據(jù)時(shí),不會(huì)因發(fā)送緩沖區(qū)而出現(xiàn)阻塞。圖3-2UDP套接字接收緩沖區(qū)

UDP套接字的接收緩沖區(qū)的大小是有限制的,當(dāng)接收到新的UDP數(shù)據(jù)報(bào)時(shí),如果這個(gè)UDP套接字的接收緩沖區(qū)隊(duì)列已經(jīng)滿了,那么UDP協(xié)議將丟棄這個(gè)數(shù)據(jù)報(bào),并且不向發(fā)送方返回任何錯(cuò)誤信息。這種操作也是由UDP協(xié)議不保證接收數(shù)據(jù)的可靠性的特點(diǎn)所決定的。

(3)?UDP服務(wù)器采用循環(huán)服務(wù)器的工作方式,不會(huì)被某一個(gè)客戶機(jī)獨(dú)占,但客戶機(jī)可能被阻塞。UDP通信模式中,服務(wù)器一般采用循環(huán)服務(wù)器工作模式。在服務(wù)器與客戶機(jī)之間不需要建立連接,UDP服務(wù)器能夠交替地處理來(lái)自多個(gè)客戶機(jī)的請(qǐng)求,這就意味著服務(wù)器在前后兩次循環(huán)處理的請(qǐng)求可以是不同客戶機(jī)的請(qǐng)求,任何一個(gè)客戶機(jī)都無(wú)法獨(dú)占服務(wù)器。 (4)發(fā)送數(shù)據(jù)時(shí)需指定接收方的地址。UDP套接字是面向無(wú)連接的套接字的,所以在套接字?jǐn)?shù)據(jù)結(jié)構(gòu)中不會(huì)保存接收方的IP地址及其端口號(hào)。如果應(yīng)用程序要發(fā)送數(shù)據(jù),就需要在調(diào)用發(fā)送函數(shù)sendto的同時(shí)指定接收方的地址。當(dāng)應(yīng)用程序接收數(shù)據(jù)報(bào)時(shí),如果需要知道發(fā)送者的地址,則可以在調(diào)用接收函數(shù)recvfrom中提供空間由內(nèi)核來(lái)填充;如果不關(guān)心對(duì)方的地址,則可以將函數(shù)recvfrom的參數(shù)from設(shè)置為空指針NULL,同時(shí)也必須將參數(shù)addrlen設(shè)置為NULL。

(5)在需要多點(diǎn)傳送數(shù)據(jù)時(shí),使用UDP套接字。目前,TCP協(xié)議不支持多點(diǎn)傳送數(shù)據(jù),因?yàn)椋绻褂肨CP協(xié)議進(jìn)行多點(diǎn)傳送的話,就必須要為每一個(gè)傳送建立一個(gè)連接。所以,在需要多點(diǎn)傳送數(shù)據(jù)時(shí)就往往采用UDP協(xié)議。 /**********************函數(shù)udps_respon負(fù)責(zé)處理數(shù)據(jù)通信***********************/ voidudps_respon(intsockfd) { intn; charmsg[1024]; intaddrlen; structsockaddr_inaddr; for(;;) { n=recvfrom(sockfd,msg,1024,0,(struct sockaddr*)&addr,&addrlen); /*

響應(yīng)客戶機(jī)請(qǐng)求

*/ sendto(sockfd,msg,n,0,addr,addrlen); } } /******************************以下為主程序部分*******************************/ intmain(intargc,char*argv[]) { intsockfd; structsockaddr_inaddr; /*

創(chuàng)建一個(gè)UDP數(shù)據(jù)報(bào)類型的套接字

*/ sockfd=socket(AF_INET,SOCK_DGRAM,0); if(sockfd<0) { fprintf(stderr,"Socketerror"); exit(1); } bzero(&addr,sizeof(addr)); addr.sin_family=AF_INET; addr.sin_addr.s_addr=htonl(INADDR_ANY); addr.sin_port=htons(SERVER_PORT);

/*

服務(wù)器為套接字綁定一個(gè)端口號(hào)

*/ if(bind(sockfd,(structsockaddr*)&addr,sizeof(addr))<0) { fprintf(stderr,"Binderror"); exit(1); } /*

調(diào)用通信函數(shù)與客戶端進(jìn)行通信

*/ udps_respon(sockfd); /*

關(guān)閉套接字

*/ close(sockfd); }

這是一個(gè)簡(jiǎn)單的UDP服務(wù)器,它不需要在通信前與客戶機(jī)建立固定連接,直接使用UDP套接字來(lái)接收客戶機(jī)發(fā)送的UDP數(shù)據(jù)報(bào)。但是,在接受客戶機(jī)請(qǐng)求前,服務(wù)器必須設(shè)置自己的公認(rèn)的地址和端口號(hào)。它一次只能處理一個(gè)客戶機(jī)的請(qǐng)求,而不能并發(fā)的處理多個(gè)客戶機(jī)的請(qǐng)求,所以它是一個(gè)典型的循環(huán)服務(wù)器。它不被一個(gè)客戶端所獨(dú)立占有,能夠交替處理多個(gè)客戶機(jī)的請(qǐng)求,當(dāng)一個(gè)客戶機(jī)出現(xiàn)錯(cuò)誤時(shí)不會(huì)影響服務(wù)器對(duì)來(lái)自其他客戶機(jī)請(qǐng)求的處理。 3.2.2UDP客戶機(jī)編程示例 客戶機(jī):

#include<sys/type.h> #include<sys/socket.h> #include<netinet/in.h> #include<stdio.h> #defineSERVER_PORT8080 /**********************函數(shù)udps_requ負(fù)責(zé)處理數(shù)據(jù)通信*************************/ voidudpc_requ(intsockfd,conststructsockaddr_in*addr,intlen) { charbuf[1024]; intn; for(;fgets(buf,1024,stdin)!=NULL;) { /*

向服務(wù)器端發(fā)送數(shù)據(jù)

*/ sendto(sockfd,buf,strlen(buf),0,addr,len); /*

接收服務(wù)器端的回應(yīng)

*/ n=recvfrom(sockfd,buf,1024,0,NULL,NULL); buf[n]='\0'; fputs(buf,stdout); } } /******************************主程序部分*******************************/ intmain(intargc,char*argv[]) { intsockfd; structsockaddr_inaddr; if(argc!=3) { fprintf(stderr,

"usage:clientipaddrport"); exit(1); } /*

創(chuàng)建一個(gè)UDP數(shù)據(jù)報(bào)類型的套接字

*/ sockfd=socket(AF_INET,SOCK_DGRAM,0); if(sockfd<0) { fprintf(stderr,"Socketerror"); exit(1); }

/*

調(diào)用通信函數(shù)進(jìn)行數(shù)據(jù)通信

*/ udpc_requ(sockfd,&addr,sizeof(addr)); /*

關(guān)閉套接字

*/ close(sockfd); }

這是一個(gè)簡(jiǎn)單的UDP客戶端程序。由于采用面向無(wú)連接的通信模式,因此它不需要跟服務(wù)器端建立連接,直接在函數(shù)sendto中指定服務(wù)器端的地址,調(diào)用sendto函數(shù)向服務(wù)器發(fā)送數(shù)據(jù)。同時(shí),可以接收來(lái)自服務(wù)端的應(yīng)答數(shù)據(jù)報(bào)。當(dāng)數(shù)據(jù)傳輸發(fā)生錯(cuò)誤時(shí),服務(wù)端不會(huì)有阻塞的危險(xiǎn),但是客戶端可能會(huì)因?yàn)閿?shù)據(jù)報(bào)在傳輸過(guò)程中的丟失而在調(diào)用函數(shù)recvfrom處阻塞。因?yàn)閁DP協(xié)議不能夠保證數(shù)據(jù)可靠到達(dá),所以,對(duì)于可能遇到的問(wèn)題或錯(cuò)誤,用戶應(yīng)在程序中加以處理。3.3連接UDP套接字的功能

1.連接UDP套接字的建立

UDP套接字也可以調(diào)用connect函數(shù),調(diào)用的方法和流式套接字相同,但其調(diào)用的結(jié)果與流式套接字調(diào)用connect函數(shù)的結(jié)果不同。它沒(méi)有三次握手過(guò)程,因?yàn)閁DP協(xié)議中不需要在發(fā)送和接收方之間建立連接。UDP套接字只是記錄了目的方的IP地址和端口號(hào),這些信息被包含在調(diào)用connect函數(shù)的套接字中,并在調(diào)用后立即返回給進(jìn)程。我們把調(diào)用connect函數(shù)后的UDP套接字稱為連接UDP套接字,把未調(diào)用connet函數(shù)的UDP套接字稱為未連接UDP套接字。 UDP套接字調(diào)用connect函數(shù)后,將檢查每個(gè)到達(dá)的數(shù)據(jù)報(bào),UDP協(xié)議將數(shù)據(jù)報(bào)中的目的地址與connect函數(shù)的套接字中保存的IP地址進(jìn)行比較,二者一致時(shí)該套接字接收這個(gè)數(shù)據(jù)報(bào),反之丟棄這個(gè)數(shù)據(jù)報(bào)。UDP連接套接字具有以下一些特點(diǎn)。

(1)由于連接套接字已經(jīng)記錄了該套接字相應(yīng)的目的地址,因此發(fā)送數(shù)據(jù)時(shí)可以不用指定服務(wù)器的目的地址。UDP協(xié)議將自動(dòng)根據(jù)保存的地址填充要發(fā)送的UDP數(shù)據(jù)報(bào)。

(2)對(duì)于連接套接字,UDP協(xié)議在內(nèi)核中檢查連接套接字收到的數(shù)據(jù)報(bào),并使得連接套接字只接收那些來(lái)自目的地址的UDP數(shù)據(jù)報(bào)。 UDP協(xié)議檢查每個(gè)到達(dá)的數(shù)據(jù)報(bào),根據(jù)數(shù)據(jù)報(bào)的目的端口號(hào)選擇接收套接字,UDP協(xié)議檢查該套接字是否是連接套接字。如果這個(gè)套接字為UDP未連接套接字,則協(xié)議將數(shù)據(jù)報(bào)存放在該套接字接收緩沖區(qū)隊(duì)列中。如果這個(gè)套接字為UDP連接套接字,則協(xié)議將數(shù)據(jù)報(bào)的目的IP地址與套接字保存的IP地址比較,只有在相同時(shí),才將數(shù)據(jù)報(bào)存放在套接字的接受緩沖區(qū)隊(duì)列中,否則,丟棄。因此,當(dāng)UDP客戶機(jī)只與一個(gè)服務(wù)器通信時(shí),調(diào)用函數(shù)connect,將這個(gè)UDP套接字轉(zhuǎn)化為UDP連接套接字,保證只接收這個(gè)服務(wù)器的信息。 2.?dāng)?shù)據(jù)報(bào)發(fā)送以及錯(cuò)誤返回情況

UDP協(xié)議在進(jìn)行數(shù)據(jù)報(bào)通信時(shí),可能會(huì)有以下幾種情況發(fā)生:

(1)數(shù)據(jù)報(bào)成功到達(dá)服務(wù)器端,并且被服務(wù)器接收。

(2)數(shù)據(jù)報(bào)成功到達(dá)服務(wù)器端,但是服務(wù)器端的UDP套接字接收緩沖區(qū)已滿,此時(shí)服務(wù)器端將自動(dòng)丟棄這個(gè)數(shù)據(jù)報(bào),并且不向客戶機(jī)返回任何錯(cuò)誤信息。 (3)數(shù)據(jù)報(bào)成功到達(dá)服務(wù)器端,但是數(shù)據(jù)報(bào)指定的目的端口上沒(méi)有接收此數(shù)據(jù)報(bào)的進(jìn)程。此時(shí)服務(wù)器端上的UDP協(xié)議將丟棄這個(gè)數(shù)據(jù)報(bào),并且向客戶機(jī)返回一個(gè)ICMP錯(cuò)誤報(bào)文,通知客戶機(jī)在目的端口上沒(méi)有接收進(jìn)程。當(dāng)這個(gè)ICMP消息到達(dá)客戶機(jī)UDP協(xié)議之后,UDP協(xié)議將向客戶機(jī)報(bào)告這個(gè)錯(cuò)誤。我們調(diào)用函數(shù)sendto發(fā)送數(shù)據(jù),該函數(shù)只要將數(shù)據(jù)報(bào)發(fā)送至目的系統(tǒng)的緩沖區(qū)就完成調(diào)用返回,而ICMP錯(cuò)誤報(bào)文是在sendto()函數(shù)調(diào)用完成之后返回的,錯(cuò)誤的產(chǎn)生和發(fā)現(xiàn)時(shí)間是不一致的,所以該錯(cuò)誤被稱為異步錯(cuò)誤。對(duì)于UDP套接字,當(dāng)這個(gè)錯(cuò)誤到達(dá)時(shí),如果客戶機(jī)正在進(jìn)行系統(tǒng)調(diào)用,則系統(tǒng)將返回一個(gè)ECONNRESET類型的錯(cuò)誤。對(duì)于未連接UDP套接字,Linux系統(tǒng)也返回ECONNRESET。 (4)數(shù)據(jù)報(bào)未成功到達(dá)服務(wù)器。這種情況又分為兩種:①

如果數(shù)據(jù)報(bào)因?yàn)槟康牡刂凡豢傻竭_(dá)而在網(wǎng)絡(luò)中被丟棄,并且這個(gè)數(shù)據(jù)報(bào)的傳送經(jīng)過(guò)了路由器,那么傳送路徑上的某個(gè)路由器將向客戶機(jī)UDP協(xié)議返回ICMP錯(cuò)誤消息,通知發(fā)送端目的地址不可到達(dá)。客戶機(jī)UDP協(xié)議接收到這個(gè)ICMP錯(cuò)誤消息之后,如果客戶機(jī)正在進(jìn)行系統(tǒng)調(diào)用,則這個(gè)系統(tǒng)調(diào)用將以ENETUNREACH或EHOSTUNREACH類型的錯(cuò)誤返回。②

如果數(shù)據(jù)報(bào)在網(wǎng)絡(luò)中傳輸時(shí),由于字節(jié)發(fā)生錯(cuò)誤而被路由器丟棄,或者由于路由器的緩沖區(qū)滿,該數(shù)據(jù)報(bào)也將被丟棄,而且發(fā)送端也不會(huì)得到任何返回的錯(cuò)誤信息。

在進(jìn)行基于UDP套接字編程時(shí),我們應(yīng)根據(jù)實(shí)際情況去選擇使用連接UDP套接字還是未連接UDP套接字。通??蛻魴C(jī)進(jìn)程只與一個(gè)服務(wù)器進(jìn)程通信時(shí),使用連接套接字比較方便,這樣可以避免服務(wù)器端接收來(lái)自其他客戶端的UDP數(shù)據(jù)報(bào)。當(dāng)服務(wù)器進(jìn)程需要同多個(gè)客戶端進(jìn)程進(jìn)行數(shù)據(jù)報(bào)通信,并且是循環(huán)服務(wù)的方式時(shí),使用未連接套接字。這是一般采用的原則,最終采用什么方法還應(yīng)根據(jù)具體要求加以變通。 3.連接UDP套接字的取消 連接UDP套接字的取消與TCP套接字的關(guān)閉不同,它不像后者有專門(mén)的close套接字函數(shù)來(lái)關(guān)閉連接,連接UDP套接字只需再次調(diào)用connect函數(shù),使用一個(gè)非法的套接字地址對(duì)這個(gè)套接字調(diào)用connect函數(shù),執(zhí)行此調(diào)用,connect函數(shù)套接字將會(huì)丟棄原來(lái)保存的地址。設(shè)置套接字地址的地址簇為AF_UNSPEC,則connect函數(shù)調(diào)用以錯(cuò)誤返回,其錯(cuò)誤類型為EAFNOSUPPORT,表示UDP協(xié)議不支持AF_UNSPEC類型的套接字地址。這樣,這個(gè)UDP連接套接字的地址信息被清除,可以取消連接UDP套接字。

取消連接UDP套接字的操作如下:

structsockaddr_inaddr; intsockfd;

… addr.sin_family=AF_UNSPEC;

… connect(sockfd,(structsockaddr*)&addr,sizeof(addr));

對(duì)一個(gè)連接UDP套接字再次調(diào)用connect函數(shù),可以完成以下兩個(gè)任務(wù):

(1)斷開(kāi)已連接套接字。

(2)指定新的IP地址和端口號(hào),即創(chuàng)建一個(gè)新的連接。3.4UDP編程中的錯(cuò)誤檢測(cè)及處理方法

1.UDP協(xié)議不保證數(shù)據(jù)報(bào)可靠到達(dá) 如果應(yīng)用程序要求實(shí)現(xiàn)傳送的UDP數(shù)據(jù)報(bào)可靠地到達(dá)接收方,我們必須在應(yīng)用程序中檢測(cè)并處理各種可能的錯(cuò)誤。例如,采用數(shù)據(jù)重傳和超時(shí)重發(fā)來(lái)實(shí)現(xiàn):發(fā)送方保存需要發(fā)送的數(shù)據(jù)報(bào),接收方應(yīng)用程序接收到數(shù)據(jù)報(bào)之后,向發(fā)送者返回一個(gè)確認(rèn)數(shù)據(jù)報(bào),然后發(fā)送方才將這個(gè)數(shù)據(jù)報(bào)從緩沖區(qū)中釋放出去。因?yàn)榘l(fā)送的數(shù)據(jù)報(bào)和對(duì)方返回的確認(rèn)數(shù)據(jù)報(bào)都有可能丟失,所以,如果發(fā)送者在指定的時(shí)間內(nèi)沒(méi)有收到接收方的確認(rèn)信息,將重新發(fā)送這個(gè)數(shù)據(jù)報(bào)。

調(diào)用alarm()函數(shù)是最常用的超時(shí)控制方法,編程也比較簡(jiǎn)單。因?yàn)樾盘?hào)可以中斷函數(shù)的阻塞,而alarm()函數(shù)可以在設(shè)定的時(shí)限到達(dá)時(shí)發(fā)出SIGALRM信號(hào),所以我們可以在程序中捕獲SIGALRM信號(hào),喚醒用戶進(jìn)程作下一步的操作。在Linux內(nèi)核2.4~20,i386體系源程序代碼中,alarm()函數(shù)所對(duì)應(yīng)的系統(tǒng)調(diào)用函數(shù)名稱是sys_alarm(),在linux/kernel/timer.c中實(shí)現(xiàn),函數(shù)定義如下:

asmlinkageunsignedlongsys_alarm(unsignedintseconds)

注意,該函數(shù)的定時(shí)單位是秒,當(dāng)參數(shù)設(shè)為0時(shí)函數(shù)將取消定時(shí)操作。alarm()函數(shù)與進(jìn)程相關(guān),而與文件描述符或者說(shuō)是與輸入輸出通道無(wú)關(guān)。當(dāng)我們使用多個(gè)輸入輸出通道時(shí),我們可能無(wú)法區(qū)分究竟在哪個(gè)通道上發(fā)生了阻塞。當(dāng)超時(shí)到達(dá)時(shí),alarm()函數(shù)將發(fā)出信號(hào)SIGALRM,這個(gè)信號(hào)的默認(rèn)操作是終止進(jìn)程,這就意味著如果我們不捕獲SIGALRM,我們的程序在阻塞一段時(shí)間后便會(huì)自動(dòng)結(jié)束。當(dāng)我們捕獲了SIGALRM信號(hào)并處理后,一般情況下會(huì)給阻塞函數(shù)返回EINTR。 此外,還有一點(diǎn)需要引起注意:alarm()是一次性函數(shù),而不是周期性函數(shù)。

下面我們給出處理這種錯(cuò)誤時(shí)的部分示例:

#include<signal.h> … voidsig_handler(); intmain() { inttimde_out,n;

structsigactionact; intsockfd; charmsg[100],buff[100]; structsockaddr_inaddr; /*

創(chuàng)建一個(gè)UDP數(shù)據(jù)報(bào)類型的套接字

*/ sockfd=socket(AF_INET,SOCK_DGRAM,0); if(sockfd<0) { fprintf(stderr,"Socketerror"); exit(1); } bzero(&addr,sizeof(addr)); addr.sin_family=AF_INET; addr.sin_addr.s_addr=htonl(INADDR_ANY); addr.sin_port=htons(SERVER_PORT); /*

套接字綁定一個(gè)端口號(hào)

*/ if(bind(sockfd,(structsockaddr*)&addr,sizeof(addr))<0) { fprintf(stderr,"Binderror"); exit(1); } act.sa_handler=sig_handler; sigemptyset(&act.sa_mask); act.sa_flags=0; sigaction(SIGALRM,&act,NULL); /*

調(diào)用通信函數(shù)與客戶端進(jìn)行通信

*/ strcpy(msg,"message"); sendto(sockfd,msg,sizeof(msg),0,addr,addrlen); for(;;) { timde_out=0; /*

設(shè)立信號(hào)機(jī)制,發(fā)送后等待20s后,若無(wú)返回,則認(rèn)為發(fā)送超時(shí),重新發(fā)送

*/ alarm(20); /*

讀應(yīng)答

*/ n=recvfrom(sockfd,buf,size(buf),0,(structsockaddr*)&addr,&addrlen); if(n<0&&errno==EINTR) { if(timed_out) {printf("Servernotresponding,

retrying...\n"); /*重新發(fā)送數(shù)據(jù)

*/ proc_timeout(); }else continue;…}}/*for*/}/*main*/

voidhandler()/*信號(hào)處理函數(shù)*/{timed_out=1;};

proc_timeout(){/*

重新發(fā)送數(shù)據(jù)

*/};

由上面的程序可見(jiàn),利用alarm()函數(shù)實(shí)現(xiàn)操作控制的方法比較簡(jiǎn)單:首先設(shè)置信號(hào)SIGALRM的處理函數(shù),在調(diào)用讀函數(shù)之前,調(diào)用alarm()函數(shù)設(shè)置在超時(shí)到達(dá)時(shí)發(fā)送信號(hào)SIGALRM。如果讀函數(shù)被信號(hào)SIGALRM中斷,則表示超時(shí)到達(dá)。

2.UDP協(xié)議不保證數(shù)據(jù)報(bào)順序到達(dá) 因?yàn)閁DP協(xié)議是面向無(wú)連接的,該協(xié)議不保證數(shù)據(jù)報(bào)能夠順序到達(dá),這就意味著所有的UDP數(shù)據(jù)報(bào)并不能按照發(fā)送的先后順序到達(dá)接收方被處理。如果應(yīng)用程序要求數(shù)據(jù)報(bào)必須是按照順序到達(dá)并處理的,我們就要在設(shè)計(jì)UDP報(bào)文時(shí)對(duì)每個(gè)發(fā)送的數(shù)據(jù)報(bào)進(jìn)行順序編號(hào)。接收方在接收到一個(gè)數(shù)據(jù)報(bào)之后,根據(jù)數(shù)據(jù)報(bào)中的數(shù)據(jù)序號(hào)將其放入緩沖區(qū)的合適的位置,符合時(shí)才處理這個(gè)數(shù)據(jù)報(bào),否則等待接收順序靠前的數(shù)據(jù)報(bào)。

3.UDP協(xié)議沒(méi)有流量控制

UDP協(xié)議本身不提供流量控制功能,雖然UDP協(xié)議為每個(gè)套接字建立了一個(gè)接收緩沖區(qū)隊(duì)列,接收到的數(shù)據(jù)報(bào)被拷貝到該隊(duì)列中,但是如果在通信過(guò)程中數(shù)據(jù)報(bào)發(fā)送速度大于接收速度,當(dāng)套接字接收緩沖區(qū)滿后,UDP協(xié)議將丟棄之后到達(dá)的數(shù)據(jù)報(bào),從而造成大量的數(shù)據(jù)報(bào)丟失。針對(duì)這種情況,我們可以在確保UDP數(shù)據(jù)報(bào)可靠到達(dá)的基礎(chǔ)上進(jìn)行如下的流量控制: 發(fā)送方應(yīng)用程序創(chuàng)建一個(gè)發(fā)送緩沖區(qū)隊(duì)列,每發(fā)送一個(gè)數(shù)據(jù)報(bào),首先將它拷貝到該隊(duì)列中,然后再發(fā)送給接收方。每個(gè)數(shù)據(jù)報(bào)在接收到接收方返回的確認(rèn)信息前將一直保存在這個(gè)緩沖區(qū)隊(duì)列中。如果發(fā)送緩沖區(qū)隊(duì)列已滿,則暫停發(fā)送新的數(shù)據(jù)報(bào),直到緩沖區(qū)隊(duì)列再次出現(xiàn)空間為止。通過(guò)這種方法可以實(shí)現(xiàn)簡(jiǎn)單的流量控制。3.5UDP套接字在OICQ服務(wù)中的應(yīng)用 UDP協(xié)議經(jīng)常使用于語(yǔ)音、圖像、文字等格式的數(shù)據(jù)傳輸中,很多即時(shí)通信程序都使用UDP套接字編程來(lái)實(shí)現(xiàn)數(shù)據(jù)通信,例如,目前使用的最廣泛的聊天程序OICQ。本節(jié)我們將給出兩個(gè)代碼片段,來(lái)分別說(shuō)明OICQ服務(wù)器端和客戶機(jī)程序的基本實(shí)現(xiàn)原理。

先來(lái)看實(shí)現(xiàn)發(fā)送數(shù)據(jù)的客戶端程序:

#include<stdio.h> #include<stdlib.h> #include<ermo.h> #include<string.h> #include<sys/types.h> #include<netinet/in.h> #include<sys/socket.h> #defineSERVER_PORT8003 #defineMSG_BUF_SIZE512 intport=SERVER_PORT; voidmain() { intsockfd; intcount=0; intflag; charbuf[MSG_BUF_SIZE]; structsockaddr_inaddress;

/*

創(chuàng)建一個(gè)數(shù)據(jù)報(bào)類型的套接字

*/ if((sockfd=socket(AF_INET,SOCK_DGRAM,0))==-1) { fprintf(stderr,"socketerror"); exit(1); } memset(&address,0,sizeof(address)); address.sin_family=AF_INET; address.sin_addr.s_addr=inet_addr(""); address.sin_port=htons(port); flag=1; /*

發(fā)送數(shù)據(jù)

*/do{sprintf(buf,"packet%d\n",count);if(count>30){sprintf(buf,"over");flag=0;}

sendto(sockfd,buf,sizeof(buf),0,(structsockaddr*)&address,sizeof(address));count++;}while(flag); }

服務(wù)器端程序:

#include<stdio.h> #include<stdlib.h> #include<ermo.h> #include<string.h> #include<sys/types.h> #include<netinet/in.h> #include<sys/socket.h> #defineSERVER_PORT8003 #defineMSG_BUF_SIZE512

intport=SERVER_PORT; /*ip地址表示本機(jī)

*/ char*hostname=""; voidmain() {

intsinlen;intport=SERVER_PORT;charmessage[MSG_BUF_SIZE];intsockfd;structsockaddr_insin;structhostent*server_host_name;if((sockfd=socket(PF_INET,SOCK_DGRAM,0))==-1){fprintf(stderr,"socketerror");exit(1);} server_host_name=gethostbyname(hostname);bzero(&sin,sizeof(sin));sin.sin_family=AF_INET;sin.sin_addr.s_addr=htonl(INADDR_ANY);sin.sin_port=htons(port); if((bind(sockfd,(structsockaddr*)&sin,sizeof(sin)))==-1){fprintf(stderr,"binderror");exit(1);}

/*

接收數(shù)據(jù)

*/ while(1) { sinlen=sizeof(sin);recvfrom(sockfd,message,256,0,(structsockaddr*)&sin,&sinlen);printf("nDatacomefromserver:%s\n",message);if(strncmp(message,"over",4)==0)break;}close(sockfd);}

上面兩個(gè)程序經(jīng)編譯調(diào)試通過(guò)后,在一臺(tái)主機(jī)分別運(yùn)行接收程序和發(fā)送程序,就可以看到通過(guò)UDP協(xié)議的數(shù)據(jù)通信過(guò)程了。當(dāng)然,也可以在兩臺(tái)機(jī)器上啟動(dòng)兩個(gè)不同的進(jìn)程來(lái)運(yùn)行,只要修改上述程序中的IP地址,就可以實(shí)現(xiàn)客戶機(jī)和服務(wù)器的通信模擬。3.6原始套接字 3.6.1

原始套接字定義 使用流式套接字或數(shù)據(jù)報(bào)套接字,應(yīng)用程序可以實(shí)現(xiàn)基于TCP協(xié)議或UDP協(xié)議的數(shù)據(jù)交互。這種交互屬于比較高層次的網(wǎng)絡(luò)通信方式,它向程序員屏蔽了TCP、UDP和IP數(shù)據(jù)包的具體格式,簡(jiǎn)化了編程工作;但同時(shí)也限制了應(yīng)用程序?qū)νㄐ艆f(xié)議的支持范圍,降低了用戶對(duì)數(shù)據(jù)的操作能力,影響了編程的靈活性。原始套接字則支持我們直接對(duì)IP數(shù)據(jù)包進(jìn)行操作,可以允許用戶訪問(wèn)ICMP和IGMP等多種協(xié)議的數(shù)據(jù)包,允許用戶訪問(wèn)內(nèi)核不處理的IP數(shù)據(jù)包,允許用戶讀寫(xiě)包括首部在內(nèi)的IP數(shù)據(jù)包,允許用戶基于IP層開(kāi)發(fā)新的高層通信協(xié)議。

原始套接字的使用分為三個(gè)步驟:原始套接字的創(chuàng)建、屬性的設(shè)置以及數(shù)據(jù)的發(fā)送和接收。

1.原始套接字的創(chuàng)建 將函數(shù)socket(intdomain,inttype,intprotocol)中的參數(shù)type設(shè)為SOCK_RAW,并將參數(shù)protocol設(shè)為某種指定類型(如IPPROTO_ICMP、IPPROTO_IGMP、IPPROTO_IP等),我們就可以創(chuàng)建一個(gè)原始套接字。

#include<linux/in.h> ... intsockfd sockfd=socket(AF_INET,SOCK_RAW,IPPROTO_ICMP); ....

標(biāo)準(zhǔn)的協(xié)議類型通常由系統(tǒng)頭文件<linux/in.h>定義,在Linux2.4.20內(nèi)核中,它們位于第23~47行。protocol等于IPPROTO_ICMP,表示這是一個(gè)使用ICMP協(xié)議工作的原始套接字;protocol等于IPPROTO_IGMP,表示這是一個(gè)使用IGMP協(xié)議工作的原始套接字;protocol等于IPPROTO_IP,表示這個(gè)原始套接字可以接收內(nèi)核送達(dá)的任何類型的IP數(shù)據(jù)包。 原始套接字直接發(fā)送和接收IP數(shù)據(jù)包,是一種面向無(wú)連接的套接字,而且它只能由超級(jí)用戶或系統(tǒng)管理員來(lái)創(chuàng)建。

無(wú)論是否設(shè)置了IP_HDRINCL屬性,原始套接字接收的都是整個(gè)IP數(shù)據(jù)包,即我們的接收緩存區(qū)中的數(shù)據(jù)包含IP數(shù)據(jù)包的首部。

3.?dāng)?shù)據(jù)發(fā)送和接收 端口是TCP、UDP等傳輸層協(xié)議中的概念,在原始套接字中不存在端口。原始套接字通過(guò)IP地址來(lái)識(shí)別主機(jī)??梢允褂胹endto()、sendmsg()、recvfrom()和recvmsg()函數(shù),來(lái)收發(fā)數(shù)據(jù)包;也可以先調(diào)用connect()、bind()函數(shù)綁定對(duì)方或本地地址,然后再使用write()、writev()、send()、read()、readv()和recv()等函數(shù)來(lái)收發(fā)數(shù)據(jù)。 2.原始套接字屬性的設(shè)置

IP數(shù)據(jù)包由首部和數(shù)據(jù)實(shí)體組成,如果沒(méi)有對(duì)原始套接字設(shè)置IP_HDRINCL屬性,則在發(fā)送數(shù)據(jù)時(shí),數(shù)據(jù)緩存區(qū)中存放的是IP數(shù)據(jù)包的數(shù)據(jù)實(shí)體部分;如果設(shè)置了IP_HDRINCL屬性,則數(shù)據(jù)發(fā)送緩存區(qū)中存放的是整個(gè)IP數(shù)據(jù)包,包括IP數(shù)據(jù)包的首部。IP_HDRINCL是通過(guò)調(diào)用函數(shù)setsockopt()來(lái)進(jìn)行設(shè)置的:

intoptval=1; if(setsockopt(sockfd,

IPPROTO_IP,

IP_HDRINCL,

&optval,

sizeof(optval))<0) exit(1); … 3.6.2ICMP協(xié)議中原始套接字的應(yīng)用 在第1章中,我們已經(jīng)知道ICMP(Internet消息控制協(xié)議)是TCP/IP協(xié)議簇的一個(gè)組成部分,主要作用是通過(guò)傳遞網(wǎng)絡(luò)故障、網(wǎng)絡(luò)擁塞、路由錯(cuò)誤、中間主機(jī)崩潰及重啟等信息,來(lái)協(xié)調(diào)路由器、源主機(jī)和中間主機(jī)之間的工作。 使用ICMP協(xié)議通信時(shí),一般不設(shè)置IP_HDRINCL選項(xiàng),在數(shù)據(jù)發(fā)送緩存區(qū)僅僅填寫(xiě)ICMP數(shù)據(jù)包,不需要考慮IP首部;但在接收數(shù)據(jù)時(shí),接收緩存區(qū)內(nèi)存放的是IP首部加ICMP數(shù)據(jù)包,所以首先必須找到ICMP數(shù)據(jù)包的起始位置,然后才能取出ICMP數(shù)據(jù)包進(jìn)行處理。通常我們采用結(jié)構(gòu)來(lái)讀寫(xiě)ICMP消息的固定長(zhǎng)度部分,包括描述IP數(shù)據(jù)包首部的結(jié)構(gòu)和描述ICMP數(shù)據(jù)包首部的結(jié)構(gòu)。我們可以自行定義這些結(jié)構(gòu),也可以采用系統(tǒng)頭文件中的結(jié)構(gòu)定義:iphdr和icmphdr。 iphdr描述了IP數(shù)據(jù)包的首部,在Linux2.4.20內(nèi)核中,它們位于<linux/in.h>文件的第116~136行,其代碼及注釋如下:

structiphdr{ #ifdefined(__LITTLE_ENDIAN_BITFIELD)__u8ihl:4,

/*

首部長(zhǎng)度,以4字節(jié)為單 位進(jìn)行計(jì)量

*/version:4; /*

版本

*/ #elifdefined(__BIG_ENDIAN_BITFIELD)__u8version:4,

ihl:4; #else #error"Pleasefix<asm/byteorder.h>" #endif__u8tos; /*

服務(wù)類型

*/__u16tot_len; /*

數(shù)據(jù)包總長(zhǎng)

*/__u16id; /*

標(biāo)識(shí)

*/__u16frag_off; /*

標(biāo)識(shí)位和碎片偏移

*/__u8ttl; /*

生存時(shí)間(timetolive)*/ __u8protocol;/*

協(xié)議:TCP、UDP、ICMP等

*/__u16check; /*

首部校驗(yàn)和

*/__u32saddr; /*

源IP地址

*/__u32daddr; /*

目的IP地址

*/ }; icmphdr描述了ICMP數(shù)據(jù)包的首部,在Linux2.4.20內(nèi)核中,它們位于<linux/icmp.h>文件的第66~81行,其代碼如下: structicmphdr{ __u8type; __u8code;__u16checksum;union{struct{__u16id;__u16sequence;}echo; __u32gateway;struct{__u16__unused;__u16mtu;}frag;}un; };

下面我們以一段Ping程序代碼片斷為例,來(lái)說(shuō)明 如何使用原始套接字實(shí)現(xiàn)ICMP協(xié)議的數(shù)據(jù)交互。在 這個(gè)例子中,源主機(jī)向目的主機(jī)發(fā)出回顯請(qǐng)求(ICMP_ECHO,type=0,code=0),目的主機(jī)返回回顯響應(yīng)(ICMP_ECHOREPLY,type=8,code=0),相關(guān)的數(shù)據(jù)包格式如圖3-3所示。其中,標(biāo)識(shí)符是源主機(jī)的進(jìn)程號(hào),序列碼用來(lái)標(biāo)識(shí)發(fā)出回顯請(qǐng)求的次序,時(shí)間戳表示數(shù)據(jù)包發(fā)出的時(shí)刻,通過(guò)比較回顯響應(yīng)時(shí)刻和源主機(jī)當(dāng)前時(shí)刻的差值,可以測(cè)出ICMP數(shù)據(jù)包的往返時(shí)間。在這個(gè)例子中,用戶進(jìn)程一共向目的主機(jī)發(fā)送三次回顯請(qǐng)求。圖3-3ICMP回顯請(qǐng)求和響應(yīng)的?數(shù)據(jù)包格式#include<linux/in.h>...intnTimeout=0; /*

超時(shí)標(biāo)志

*/voidrecv_process(char*buf);/*

處理接收到的數(shù)據(jù)

*/shortCheckSum(short*buf,intnSize)/*

計(jì)算校驗(yàn)和

*/{ unsignedlongchksum=0; short*buf2=buf; chksum=*buf2;buf2++;buf2++; for(inti=2;i<nSize-1;i++) {chksum+=*buf;buf++;}chksum=(chksum>>16)+(chksum&0xffff); chksum+=(chksum>>16); short*ps=(short*)&chksum; return~(*ps);}voidFillIcmpHdr(char*pIcmpHdr,intnDataSize) /*

填充ICMP數(shù)據(jù)包

*/{ icmphdr*pIcmph; staticintnSeq=0; pIcmph=(icmphdr*)pIcmpHdr; pIcmph->type=ICMP_ECHO; pIcmph->code=0; pIcmph->un.echo.id=htons(getpid()); pIcmph->un.echo.sequence=htons(nSeq);nSeq++; pIcmph->checksum=htons(CheckSum((short*)pIcmpPack,nDataSize));}voidsigalrm_handler(intsig){nTimeout=1;}

intmain(intargc,char*argv[]) /*

主程序入口

*/{unsignedlongbuf[64];intsockfd;structsockaddr_inaddr1;structsigactionact;timevaltv;act.sa_handler=sigalrm_handler; /*

設(shè)置超時(shí)信號(hào)捕獲函數(shù)

*/act.sa_mask=0;act.sa_flags=0;sigaction(SIGALRM,&act,NULL);sockfd=socket(AF_INET,SOCK_RAW,IPPROTO_ICMP); /*

創(chuàng)建原始套接字

*/if(sockfd<0) exit(1);bzero(&addr1,sizeof(addr1));if(inet_aton(argv[1],&addr1.sin_addr)==0) /*

綁定對(duì)方主機(jī)IP地址

*/exit(2);intn=0,nlen;while(n<3){ alarm(5); /*

設(shè)置超時(shí)值為5s*/ gettimeofday(&tv,NULL); /*

獲得當(dāng)前時(shí)間戳

*/

*(buf+2)=htonl(tv.sec);

*(buf+3)=htonl(tv.usec); FillIcmpHdr((char*)buf,8); /*

填寫(xiě)數(shù)據(jù)包,按雙字節(jié)計(jì)算數(shù)據(jù)長(zhǎng)度

*/ nlen=sizeof(addr1); sendto(sockfd,buf,128,0,(structsockaddr*)&addr1,sizeof(addr1)); n=recvfrom(sockfd,buf,128,0,(structsockaddr*)&addr1,&nlen); if(n>0) recv_process(buf); /*

處理接收到的數(shù)據(jù)

*/ alarm(0); nTimeout=0;n++;}close(sockfd);}

函數(shù)recv_process()用來(lái)處理對(duì)方主機(jī)返回的回顯響應(yīng)數(shù)據(jù)包,這個(gè)數(shù)據(jù)包內(nèi)含有IP數(shù)據(jù)首部,必須將其過(guò)濾掉。recv_process()的主要代碼如下:

voidrecv_process(char*buf) /*

填充ICMP數(shù)據(jù)包

*/ { iphdr*pIph; icmphdr*pIcmph; intiplen,icmplen; pIph=(iphdr*)buf; /*

確定IP數(shù)據(jù)包起始位置

*/ iplen=ip->ihl<<2; pIcmph=(icmphdr*)(buf+iplen); /*

確定ICMP數(shù)據(jù)包起始位置

*/ /*

確定收到的數(shù)據(jù)包是否是針對(duì)當(dāng)前進(jìn)程的回顯響應(yīng)

*/if(pIcmph->type!=ICMP_ECHOREPLY||pIcmph->un.echo.id!=htons(getpid())) exit(3); pl=(unsignedlong*)pIcmph; ptv1=(timeval*)(pl+2); gettimeofday(tv2,NULL); tv2=GetInterval(*ptv1,tv2); /*

編制函數(shù),計(jì)算時(shí)間差值

*/ ... /*

顯示接收結(jié)果

*/} IGMP(Internet組管理協(xié)議)是另一種最常使用原始套接字進(jìn)行操作的協(xié)議,這個(gè)協(xié)議用來(lái)協(xié)調(diào)多播路由器與主機(jī)之間的工作:多播路由器使用IGMP協(xié)議來(lái)查詢多播組內(nèi)有哪些主機(jī);主機(jī)則在加入和退出多播組時(shí)使用IGMP協(xié)議向路由器發(fā)出通告,或者使用IGMP協(xié)議響應(yīng)多播路由器的查詢。 與ICMP協(xié)議類似,IGMP數(shù)據(jù)包也是嵌入在IP數(shù)據(jù)包內(nèi)進(jìn)行傳輸?shù)?,我們可以通過(guò)調(diào)用sockfd=socket(AF_INET,SOCK_RAW,IPPROTO_IGMP)創(chuàng)建一個(gè)支持協(xié)議的原始套接字。IGMP協(xié)議中原始套接字的使用方法與ICMP協(xié)議中原始套接字使用方法基本一致,只是協(xié)議的具體格式有所差別,其編程方法可參閱前面有關(guān)套接字編程的例子。 3.6.3IP_HDRINCL選項(xiàng) 當(dāng)對(duì)原始套接字設(shè)置了IP_HDRINCL屬性后,我們就可以對(duì)IP數(shù)據(jù)包的首部進(jìn)行操作,可以對(duì)封裝在IP數(shù)據(jù)包內(nèi)的任何協(xié)議(如TCP、UDP等)進(jìn)行操作,也可以定制自己的協(xié)議首部。在創(chuàng)建原始套接字時(shí),應(yīng)將協(xié)議類型設(shè)為IPPROTO_IP(值為0):

intsockfd sockfd=socket(AF_INET,SOCK_RAW,

IPPROTO_IP);

下面是一個(gè)構(gòu)造TCP數(shù)據(jù)包的函數(shù),其中用到的tcphdr結(jié)構(gòu)由linux2.4.20內(nèi)核頭文件<linux/tcp.h>的第23~56行定義。

intsend_syn(char*buf,intnSize,unsignedshortport,structsockaddraddr) { iphdr*pIph; tcphdr*pTcph; bzero(buf,nSize); pIph=(iphdr*)buf; pTcph=(tcphdr*)(buf+sizeof(iphdr)); pTcph->source=htons(9090); /*

填寫(xiě)TCP數(shù)據(jù)首部

*/ pTcph->dest=port; pTcph->seq=random(); pTcph->doff=5; pTcph->syn=1; pIph->version=4; /*

填寫(xiě)IP數(shù)據(jù)首部

*/ pIph->ihl=sizeof(iphdr)>>2; pIph->tot_len=sizeof(iphdr)+sizeof(tcphdr); pIph->ttl=128; pIph->protocol=IPPROTO_TCP; pIph->saddr=random(); pIph->daddr=addr->sin_addr; pTcph->check=ChkSum(buf); /*

計(jì)算整個(gè)數(shù)據(jù)包的校驗(yàn)和

*/ return*(pIph->tot_len); /*

返回需要發(fā)送的數(shù)據(jù)總長(zhǎng)

*/ }

主程序可以不斷地調(diào)用這個(gè)函數(shù),然后向服務(wù)器發(fā)送。這將引發(fā)用戶進(jìn)程與服務(wù)器的三次握手過(guò)程(syn=1)。由于用戶進(jìn)程送出的源地址是一個(gè)隨機(jī)數(shù),幾乎全都是不可達(dá)地址,因此三次握手將無(wú)法完成。服務(wù)器由于偵聽(tīng)套接字的連接隊(duì)列滿而被阻塞,從而引起服務(wù)失效。3.7服務(wù)器編程模型

3.7.1循環(huán)服務(wù)器

1.UDP循環(huán)服務(wù)器 基于UDP的編程常采用循環(huán)服務(wù)器的編程模式,循環(huán)服務(wù)器的實(shí)現(xiàn)較為簡(jiǎn)單:UDP服務(wù)器每次從套接字上讀取一個(gè)客戶端的請(qǐng)求并處理請(qǐng)求,然后將結(jié)果返回給客戶機(jī)。循環(huán)服務(wù)器的處理過(guò)程如圖3-4所示。圖3-4循環(huán)服務(wù)器處理過(guò)程

循環(huán)服務(wù)器的程序處理方法為:intsockfd;structsockaddr_inaddr,clientaddr;intn,

addrlen;charbuf[512];if((sockfd=socket(AF_INETSOCK_DGRAM0))<0){printf("socketerror.\n");exit(1);}bzero(&addr,sizeof(servaddr));addr.sin_family=AF_INET;addr.sin_port=htons(serverport); /*

端口號(hào)

*/addr.sin_addr.s_addr=htonl(INADDR_ANY);

if(bind(sockfd,…)<0){printf("binderror.\n");exit(1);}for(;;){addrlen=sizeof(clientaddr);n=recvfrom(sockfdbufsizeof(buf)0(structsockaddr*)&clientaddr&addrlen);if(n<0&&errno==EINTR)continue;

elseif(n<0){printf("recvfromerror:%s\n"strerror(errno));continue;}doit(sockfd); */處理請(qǐng)求

*/sendto(sockfdbufsizeof(buf)0(structsockaddr*)&clientaddraddrlen);}

由于基于UDP的通信是面向無(wú)連接的,因此沒(méi)有一個(gè)客戶機(jī)可以獨(dú)占服務(wù)器,只要處理過(guò)程不是死循環(huán),服務(wù)器對(duì)于每一個(gè)客戶機(jī)的請(qǐng)求總是能夠滿足的。 圖3-5為一個(gè)小型控制系統(tǒng)示例,所用計(jì)算機(jī)都工作在端口PORT1上,計(jì)算機(jī)1負(fù)責(zé)從工業(yè)現(xiàn)場(chǎng)采集數(shù)據(jù),對(duì)原始數(shù)據(jù)進(jìn)行初步處理(濾波、去偽等)后原始發(fā)送給計(jì)算機(jī)2(服務(wù)器),計(jì)算機(jī)2對(duì)數(shù)據(jù)進(jìn)行處理(濾波、修正、轉(zhuǎn)換等),然后將結(jié)果以廣播形式發(fā)送出去。收到結(jié)果數(shù)據(jù)后,計(jì)算機(jī)1和計(jì)算機(jī)2不作處理,計(jì)算機(jī)3判斷是否需要報(bào)警,計(jì)算機(jī)4將結(jié)果送去顯示,計(jì)算機(jī)5將數(shù)據(jù)存檔。如何區(qū)分?jǐn)?shù)據(jù)來(lái)源則由應(yīng)用層的數(shù)據(jù)協(xié)議來(lái)保證,例如,每個(gè)數(shù)據(jù)包中都標(biāo)明數(shù)據(jù)包類型是原始數(shù)據(jù)、結(jié)果數(shù)據(jù),還是其他數(shù)據(jù)。圖3-5一個(gè)小型控制系統(tǒng)示例

計(jì)算機(jī)2的程序代碼如下:#include<...>#definePORT18989#defineCOMPUTERX"55"...

intmain(){charbuf[256];intsockfd;structsockaddr_inaddr_2;/*

計(jì)算機(jī)2的地址結(jié)構(gòu)

*/structsockaddr_inaddr_x;/*

廣播地址結(jié)構(gòu)

*/sockfd=socket(AF_INET,SOCK_DGRAM,0);if(sockfd<0){ fprintf(stderr,"Socketerror"); exit(1);} bzero(&addr_2,sizeof(addr_2));addr_2.sin_family=AF_INET;addr_2.sin_addr.s_addr=htonl(INADDR_ANY);addr_2.sin_port=htons(PORT1); if(bind(sockfd,(structsockaddr*)&addr_2,sizeof(addr_2))<0){ fprintf(stderr,"Inet_atonerror"); exit(1);} bzero(&servaddr,sizeof(addr_x));addr_x.sin_family=AF_INET;addr_x.sin_port=htons(PORT1);if(inet_aton(COMPUTERX,&addr_x.sin_addr)==0) exit(1); intnDataComeFrom=0; /*

用來(lái)存放數(shù)據(jù)包類型代碼

*/ intn=recvfrom(sockfd,buf,256,0,(structsockaddr*)&addr_1,sizeof(addr_1)); ... /*

解析接收到的數(shù)據(jù)包,并將數(shù)據(jù)包類型代碼放入nDataComeFrom*/

if(nDataComeFrom==1){process(buf,&nlen...); /*

數(shù)據(jù)處理,結(jié)果放在buf中,數(shù)據(jù)長(zhǎng)度放在nlen中

*//*

以廣播形式將處理結(jié)果數(shù)據(jù)發(fā)送出去

*/sendto(sockfd,buf,nlen,0,(structsockaddr*)&addr_x,sizeof(addr_x));}close(sockfd);} 2.TCP循環(huán)服務(wù)器 現(xiàn)在我們考慮基于TCP的服務(wù)器采用循環(huán)服務(wù)器工作模式的實(shí)現(xiàn)方法。TCP服務(wù)器接受一個(gè)客戶端的連接,然后進(jìn)行處理,直到完成這個(gè)客戶機(jī)的所有請(qǐng)求后,斷開(kāi)連接。TCP循環(huán)服務(wù)器一次只能處理一個(gè)客戶端的請(qǐng)求,只有在這個(gè)客戶的所有請(qǐng)求都滿足后,服務(wù)器才可以繼續(xù)后面的請(qǐng)求。這樣,如果有一個(gè)客戶端占住服務(wù)器不放時(shí),其他的客戶機(jī)都不能工作了,因此,TCP服務(wù)器一般很少用循環(huán)服務(wù)器模型。只有當(dāng)數(shù)據(jù)處理工作所需時(shí)間很短(如時(shí)鐘服務(wù))或者服務(wù)器只能為單一用戶提供服務(wù)時(shí)才被使用,而且這時(shí)應(yīng)該設(shè)置超時(shí)控制。

圖3-6是一個(gè)使用TCP循環(huán)服務(wù)的例子,這是一個(gè)通過(guò)網(wǎng)絡(luò)控制的可移動(dòng)機(jī)械手,服務(wù)器程序運(yùn)行在機(jī)械手的移動(dòng)平臺(tái)上,用戶可以通過(guò)網(wǎng)絡(luò)遠(yuǎn)程操控機(jī)械手完成搬運(yùn)物體的工作,這個(gè)機(jī)械手不允許多人同時(shí)操作。若采用UDP協(xié)議來(lái)實(shí)現(xiàn)這個(gè)系統(tǒng),由于UDP協(xié)議的不可靠性,很可能出現(xiàn)后發(fā)出的指令被首先執(zhí)行,先發(fā)出的指令被延后執(zhí)行的狀況,用戶將感到機(jī)械手不可控制。當(dāng)然,我們也可以在應(yīng)用層的協(xié)議里加上時(shí)序控制,但那樣將加大編程的難度。而采用TCP的循環(huán)模式,并對(duì)阻塞函數(shù)添加超時(shí)控制,情況將會(huì)比較理想。有關(guān)TCP循環(huán)服務(wù)的程序代碼,可參見(jiàn)前面套接字的內(nèi)容,這里不再贅述。圖3-6一個(gè)使用TCP循環(huán)服務(wù)的例子

3.7.2并發(fā)服務(wù)器 針對(duì)上述TCP循環(huán)服務(wù)器的缺陷,人們提出了并發(fā)服務(wù)器的編程模型。并發(fā)服務(wù)器的思想是:每一個(gè)客戶機(jī)的請(qǐng)求并不由服務(wù)器偵聽(tīng)進(jìn)程直接處理,而是由服務(wù)器偵聽(tīng)進(jìn)程創(chuàng)建一個(gè)自己的子進(jìn)程負(fù)責(zé)處理服務(wù)請(qǐng)求,父進(jìn)程仍負(fù)責(zé)偵聽(tīng)客戶機(jī)的請(qǐng)求。并發(fā)服務(wù)器的處理流程如圖3-7所示。圖3-7并發(fā)服務(wù)器的處理流程

1.TCP并發(fā)服務(wù)器 基本的TCP并發(fā)服務(wù)器采用這樣的流程:先創(chuàng)建一個(gè)偵聽(tīng)套接字,等待客戶機(jī)的請(qǐng)求,每當(dāng)接受一個(gè)客戶機(jī)請(qǐng)求時(shí),就創(chuàng)建一個(gè)子進(jìn)程,并在子進(jìn)程中進(jìn)行數(shù)據(jù)處理,父進(jìn)程則繼續(xù)等待新的客戶機(jī)請(qǐng)求,直到服務(wù)程序滿足退出條件。TCP并發(fā)服務(wù)器的程序處理方法為:

intsockfd,newsockfd; structsockaddr_inaddr;if((sockfd=socket(AF_INET,SOCK_STREAM,0))<0){printf("socketerror.\n");exit(1);}bzero(&addr,sizeof(servaddr));addr.sin_family=AF_INET;addr.sin_port=htons(serverport);/*

端口號(hào)

*/addr.sin_addr.s_addr=htonl(INADDR_ANY);if(bind(sockfd,…)<0){printf("binderror.\n");exit(1);}if((listen(sockfd,5)<0){printf("listenerror.\n");exit(1);}for(;;){newsockfd=accept(sockfd,…,…);if(newsockfd<0&&errno==EINTR)continue;elseif(newsockfd<0){printf("accepterror.\n");exit(1);}if(fork()==0){close(sockfd);doit(newsockfd); /*

處理請(qǐng)求

*/…exit(0);}close(newsockfd);} 2.UDP并發(fā)服務(wù)器

UDP協(xié)議雖然在套接字函數(shù)處不大容易阻塞,但如果對(duì)某一個(gè)客戶機(jī)進(jìn)行數(shù)據(jù)處理的時(shí)間過(guò)長(zhǎng),則服務(wù)器在這段時(shí)間內(nèi)將不能接收其他客戶機(jī)的請(qǐng)求,同時(shí)UDP協(xié)議又是不可靠的通信協(xié)議,不保證數(shù)據(jù)是否能夠到達(dá)目的地址,所以就會(huì)造成數(shù)據(jù)包的丟失。這時(shí)可以采用并發(fā)的UDP服務(wù)結(jié)構(gòu):服務(wù)程序每收到一項(xiàng)請(qǐng)求,就單獨(dú)為這個(gè)客戶機(jī)創(chuàng)建一個(gè)進(jìn)程,完成相應(yīng)的數(shù)據(jù)處理任務(wù),然后關(guān)閉套接字描述符。UDP并發(fā)服務(wù)器處理方法如下: #include<...> #definePORT18989 voidsigchld_handler(intsig) { while(waitpid(-1,NULL,WNOHANG)>0){} return; }intmain(){charbuf[256];intsockfd;structsockaddr_inaddr; intpid;structsigactionsigact; sigact.sa_handler=sigchld_handler; sigact.sa_mask=0; sigact.sa_flags=0; sigsigaction(SIGCHLD,&sigact,NULL); sockfd=socket(AF_INET,SOCK_DGRAM,0); if(sockfd<0) { fprintf(stderr,"Socketerror"); exit(1); } bzero(&addr,sizeof(addr));addr.sin_family=AF_INET;addr.sin_addr.s_addr=htonl(INADDR_ANY);addr.sin_port=htons(PORT1);if(bind(sockfd,(structsockaddr*)&addr,sizeof(addr))<0){ fprintf(stderr,"Inet_atonerror"); exit(1);}

while(1){intn=recvfrom(sockfd,buf,256,0,(structsockaddr*)&addr_1,sizeof(addr_1));if(n>0){if((pid=fork())==0) /*

子進(jìn)程,處理數(shù)據(jù)并返回結(jié)果

*/{sendto(sockfd,buf,256,0,(structsockaddr*)&addr,sizeof(addr));close(sockfd);exit(0);}elseif(pid<0){fprintf(stderr,"forkerror");exit(1);}/*

父進(jìn)程,繼續(xù)循環(huán)等待客戶請(qǐng)求

*/}}

創(chuàng)建子進(jìn)程是一項(xiàng)開(kāi)銷很大的工作,如果客戶非常多,而大多數(shù)的數(shù)據(jù)處理的時(shí)間又很短,那么服務(wù)器的大量時(shí)間和資源都消耗在創(chuàng)建和銷毀子進(jìn)程上,系統(tǒng)效率會(huì)很低,也難以及時(shí)響應(yīng)客戶的請(qǐng)求。我們可以將循環(huán)服務(wù)和并發(fā)服務(wù)進(jìn)行適當(dāng)?shù)鼐C合,采用延遲創(chuàng)建子進(jìn)程的方法,即平時(shí)服務(wù)器工作在循環(huán)狀態(tài),當(dāng)預(yù)測(cè)某一次服務(wù)耗時(shí)較長(zhǎng)就為它創(chuàng)建一個(gè)子進(jìn)程,而主進(jìn)程繼續(xù)工作在循環(huán)模式中。

在網(wǎng)絡(luò)上,常常會(huì)有這樣的情況:很多非法用戶或沒(méi)有操作權(quán)限的用戶與服務(wù)器建立了連接,并試圖進(jìn)行操作,這時(shí)服務(wù)器應(yīng)該先檢查收到的數(shù)據(jù)包是否為合法請(qǐng)求(這種檢查通常耗費(fèi)時(shí)間極短)。如果是非法請(qǐng)求,服務(wù)器就拒絕服務(wù),則繼續(xù)在循環(huán)方式下工作;如果是合法請(qǐng)求,則服務(wù)器創(chuàng)建一個(gè)子進(jìn)程進(jìn)行數(shù)據(jù)處理,主進(jìn)程依然在循環(huán)方式下工作。

我們還可以設(shè)置一個(gè)預(yù)測(cè)器,估計(jì)一項(xiàng)服務(wù)是否可以在很短時(shí)間內(nèi)完成。因?yàn)閷?shí)際上服務(wù)器的服務(wù)范圍是有限的,可以根據(jù)理論知識(shí)和經(jīng)驗(yàn)數(shù)據(jù)(即服務(wù)器在歷史上處理各類數(shù)據(jù)所消耗的時(shí)間)來(lái)進(jìn)行推理,考慮是否創(chuàng)建子進(jìn)程,如運(yùn)算服務(wù),如果發(fā)現(xiàn)是解二元一次方程、求幾個(gè)數(shù)的平均值等,則所需的服務(wù)時(shí)間極短,在循環(huán)服務(wù)中即可迅速完成;如果發(fā)現(xiàn)是求大型方陣的特征值、較大圖像的卷積運(yùn)算等,就創(chuàng)建子進(jìn)程;如果是一種以前未處理過(guò)的運(yùn)算類型,那么我

溫馨提示

  • 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

評(píng)論

0/150

提交評(píng)論