面向無(wú)連接的協(xié)議直到這時(shí),我們實(shí)際了忽略了套接口通信的大部分內(nèi)容。相反,我們關(guān)注于創(chuàng)建套接口,綁定地址以及關(guān)閉套接口。現(xiàn)在我們要實(shí)際使用套接口了。
對(duì)于套接口有兩種基本的通信模式。他們是面向無(wú)連接的通信與面向連接的通信。
在這一章,我們將會(huì)了解下面內(nèi)容:
面向無(wú)連接通信與面向連接通信之間的區(qū)別
如何執(zhí)行無(wú)連接的輸入與輸出操作
如何編寫(xiě)一個(gè)數(shù)據(jù)報(bào)服務(wù)器
如何編寫(xiě)一個(gè)數(shù)據(jù)報(bào)客戶端
現(xiàn)在我們來(lái)關(guān)注一下面向無(wú)連接通信與面向連接通信之間的區(qū)別。
通信方法正如我們所想到的,面向無(wú)連接的通信在通信開(kāi)始之前并不需要建立連接。這就像一個(gè)拿著護(hù)音器在嘈雜中向一個(gè)人喊話一樣。對(duì)于每一次新的喊話,發(fā)送消息的人可以將他的話傳遞到另一個(gè)人,而不需要先前的同意。
相類(lèi)似的,在我們創(chuàng)建一個(gè)無(wú)連接的套接口以后,我們可以向?qū)?huì)接收我們消息的任何套接口發(fā)送消息。此時(shí)并沒(méi)有建立連接,而每一個(gè)消息可以直接發(fā)送給一個(gè)不同的接收套接口。
理解其優(yōu)點(diǎn)面向無(wú)連接的通信與面向連接的通信相比提供了一些優(yōu)點(diǎn)。這些優(yōu)點(diǎn)包括:
簡(jiǎn)單:不需要建立連接
靈活:可以將消息發(fā)送到不同的帶有消息發(fā)送操作的接收端
高效:不要求連接的建立與關(guān)閉,而這需要向網(wǎng)絡(luò)添加大量的消息包
快速:因?yàn)椴恍枰B接的建立也關(guān)閉,而只是發(fā)送消息本身
廣播功能:將一個(gè)消息發(fā)送到多個(gè)接收端的功能
一個(gè)面向無(wú)連接的協(xié)議的許多優(yōu)點(diǎn)都與高效與速度有關(guān)。為了建立連接,需要在數(shù)據(jù)交換之前在兩個(gè)端點(diǎn)之間交換大量的數(shù)據(jù)包。而一個(gè)已建立的通信頻道的關(guān)閉則需要額外的數(shù)據(jù)包交換。而這產(chǎn)生額外的時(shí)間花費(fèi)。
無(wú)連接協(xié)議還有一個(gè)優(yōu)點(diǎn)就是廣播功能。他可以將一個(gè)消息發(fā)送到多個(gè)接收端。這高效的利用了網(wǎng)絡(luò)帶度。
在這一章,我們將會(huì)學(xué)習(xí)UDP協(xié)議(用戶數(shù)據(jù)報(bào)協(xié)議),這是一個(gè)面向無(wú)連接的協(xié)議。這個(gè)協(xié)議充分利用了我們前面所談到的所有優(yōu)點(diǎn)。
理解無(wú)連接通信的缺點(diǎn)既然無(wú)連接的協(xié)議可以提供這些優(yōu)點(diǎn),我們也許想要知道為什么不總是使用這種協(xié)議。任何事物,即使是無(wú)連接的通信也有他的缺點(diǎn)。
UDP協(xié)議是一個(gè)非常簡(jiǎn)單的傳輸層協(xié)議,而且他是無(wú)連接的。對(duì)于UDP協(xié)議,存在著下面的缺點(diǎn):
協(xié)議不可靠
沒(méi)有多個(gè)數(shù)據(jù)報(bào)的序列
消息尺寸存在限制
可靠性問(wèn)題對(duì)于大多數(shù)程序來(lái)說(shuō)是最嚴(yán)重的限制。我們的程序可以將UDP數(shù)據(jù)報(bào)發(fā)送到網(wǎng)絡(luò),但是并不能保證接收端可以接收到。這個(gè)消息也許被丟棄,而沒(méi)有同一網(wǎng)絡(luò)的接收端所接收。相應(yīng)的,由于網(wǎng)絡(luò)路徑中多個(gè)路由器中的一個(gè)不能接收沒(méi)有檢驗(yàn)碼錯(cuò)誤的消息,所發(fā)送的消息也會(huì)丟失。當(dāng)一個(gè)數(shù)據(jù)包接收出錯(cuò)時(shí),UDP包只是簡(jiǎn)單的丟棄并且永遠(yuǎn)的丟失。
其他的問(wèn)題也會(huì)造成UDP數(shù)據(jù)包的丟失。如果接收主機(jī)或是路由器不能分配足夠的緩沖空間來(lái)存放UDP數(shù)據(jù)包,這些數(shù)據(jù)包也會(huì)丟失。因此,如果我們的UDP數(shù)所包需要傳輸很長(zhǎng)的距離,這些數(shù)據(jù)包就很有可能沿途丟失。
在我們決定為我們的程序使用UDP協(xié)議之前,還有另外一個(gè)問(wèn)題需要考慮。如果我們成功的向我們的目的地發(fā)送了兩個(gè)或是多個(gè)數(shù)據(jù)報(bào),很有可能這些數(shù)據(jù)報(bào)并不是順序接收的。UDP協(xié)議使用IP協(xié)議來(lái)傳輸最終的數(shù)據(jù)報(bào)。IP數(shù)據(jù)包可以由每一個(gè)傳輸進(jìn)行不同的路由,而路由是依據(jù)當(dāng)前的網(wǎng)絡(luò)環(huán)境而變化的。這通常會(huì)造成一些數(shù)據(jù)包在另一些數(shù)據(jù)包之前到達(dá)目的地。也就說(shuō)UDP數(shù)據(jù)包會(huì)并不會(huì)按順序到達(dá)。
最后存在數(shù)據(jù)報(bào)尺寸的問(wèn)題。理論上一個(gè)數(shù)據(jù)報(bào)的最大長(zhǎng)度要小于64KB。然而,許多UNIX主機(jī)所支持的最大長(zhǎng)度僅為32KB。其他的UNIX內(nèi)核內(nèi)建的尺寸限制更小,只有8KB。最后,接收套接口程序會(huì)將這個(gè)尺寸限制為接收緩沖區(qū)的尺寸。因?yàn)檫@個(gè)原因,一些程序?qū)DP的消息尺寸限制為512字節(jié)或是更小。
如果發(fā)送了一個(gè)大的UDP數(shù)據(jù)包,則必須將這個(gè)數(shù)據(jù)包分解為一些小的IP片段,然后在接收端重新進(jìn)行組合。組合過(guò)程需要分配緩沖區(qū)來(lái)存放接收到的IP片段。這通常會(huì)指定一個(gè)超時(shí)時(shí)限,直到整個(gè)數(shù)據(jù)包重新進(jìn)行組合。緩沖區(qū)的完整會(huì)造成重組的無(wú)約束,而這會(huì)造成我們的UDP數(shù)據(jù)包的丟失。UDP數(shù)據(jù)報(bào)在成功的到達(dá)目的地之前必須要經(jīng)歷些風(fēng)險(xiǎn)。
對(duì)于一些程序來(lái)說(shuō),這會(huì)將使得我們轉(zhuǎn)向更為可靠的面向連接的協(xié)議,例如TCP/IP。這些于其他的程序來(lái)說(shuō)并不是嚴(yán)重的限制。當(dāng)然只有我們來(lái)決定UDP是否適用我們的程序。
執(zhí)行數(shù)據(jù)報(bào)輸入/輸出操作在前面的介紹中,我們看到了當(dāng)我們要從套接口中讀取或是向套接口中寫(xiě)入時(shí),我們使用了read(2)和write(2)函數(shù)。在當(dāng)時(shí)我們并沒(méi)有指現(xiàn),socketpair(2)函數(shù)使用了面向連接的協(xié)議來(lái)創(chuàng)建一對(duì)套接口。相應(yīng)的,我們可以使用我們所熟悉的read(2)與write(2)函數(shù)來(lái)在這些套接口上執(zhí)行I/O操作。
然而,當(dāng)要發(fā)送與接收數(shù)據(jù)報(bào)時(shí),需要一對(duì)不同的函數(shù)。這是因?yàn)槊恳粋€(gè)要發(fā)送的消息實(shí)際上要發(fā)送到一個(gè)不同的目的地址。發(fā)送數(shù)據(jù)報(bào)的函數(shù)允許我們指定目的接收端的地址。同樣,當(dāng)我們要接收一個(gè)數(shù)據(jù)報(bào)時(shí),我們也需要指明他的來(lái)源。這些新函數(shù)必須為我們提供一個(gè)合適的方法來(lái)確定發(fā)送者的地址。
簡(jiǎn)介sendto(2)函數(shù)sendto(2)函數(shù)允許我們寫(xiě)入一個(gè)數(shù)據(jù)報(bào),同時(shí)指定目的接收端的地址。函數(shù)概要如下:
#include <sys/types.h>
#include <sys/socket.h>
int sendto(int s,
const void *msg,
int len,
unsigned flags,
const struct sockaddr *to,
int tolen);
這些參數(shù)描述如下:
1 第一個(gè)參數(shù)s為套接口號(hào)。我們可以從socket函數(shù)得到這個(gè)值。
2 參數(shù)msg為指向存放我們將要發(fā)送的消息的緩沖區(qū)的指針
3 參數(shù)len為字節(jié)形式長(zhǎng)度
4 flags允放我們指定一些選項(xiàng)位。在許多情況下,我們只需指定為0
5 參數(shù)to為指向我們已經(jīng)建立的通用套接口的指針。這是數(shù)據(jù)報(bào)接收端的地址
6 參數(shù)tolen為參數(shù)to地址的長(zhǎng)度
當(dāng)sendto函數(shù)執(zhí)行成功時(shí),會(huì)返回寫(xiě)入的字節(jié)數(shù)。當(dāng)發(fā)生錯(cuò)誤時(shí),則會(huì)返回-1,同時(shí)錯(cuò)誤號(hào)會(huì)記錄在errno中。
參數(shù)to指定了數(shù)據(jù)報(bào)將要發(fā)送到的地址。參數(shù)to必須指向一個(gè)可用的套接口地址,而參數(shù)tolen應(yīng)包含地址的正確長(zhǎng)度。我們已經(jīng)在前面的章節(jié)中成為格式化地址的專(zhuān)家,所以在這里我們應(yīng)感到得心應(yīng)手了。
flags可用值如下所示,當(dāng)然在大多數(shù)時(shí)候我們都將其指定為0。
標(biāo)志 十六進(jìn)制 意義
0 0x0000 普通-沒(méi)有選項(xiàng)
MSG_OOB 0x0001 處理超過(guò)邊界的數(shù)據(jù)
MSG_DONTROUTE 0x0004 旁路路由,使用直接接口
MSG_DONTWAIT 0x0040 不緩沖,直接寫(xiě)入
MSG_NOSIGNAL 0x4000 當(dāng)端點(diǎn)斷開(kāi)時(shí)不發(fā)送信號(hào)
簡(jiǎn)介recvfrom(2)函數(shù)與sendto函數(shù)相對(duì)就是recvfrom函數(shù)。這個(gè)函數(shù)與read函數(shù)的不同就在于他允許我們同時(shí)指定我們要接收的數(shù)據(jù)報(bào)的發(fā)送者的地址。函數(shù)概要如下:
#include <sys/types.h>
#include <sys/socket.h>
int recvfrom(int s,
void *buf,
int len,
unsigned flags,
struct sockaddr *from,
int *fromlen);
參數(shù)描述如下:
1 套接口s指定要從中接收數(shù)據(jù)報(bào)的套接口
2 buf指向開(kāi)始接收數(shù)據(jù)報(bào)的緩沖區(qū)
3 最大長(zhǎng)度len指定了buf的長(zhǎng)度
4 選項(xiàng)標(biāo)志flags
5 指向接收套接口的地址緩沖,這將會(huì)接收發(fā)送者的地址
6 指向最大長(zhǎng)度f(wàn)romlen的指針
與正常的read函數(shù)相類(lèi)似,接收緩沖區(qū)buf必須足夠大來(lái)接收數(shù)據(jù)報(bào)。最大長(zhǎng)度是通過(guò)len指定的。
如果函數(shù)執(zhí)行失敗則會(huì)返回-1,而錯(cuò)誤號(hào)則會(huì)存放在errno中。否則,函數(shù)將會(huì)返回實(shí)際接收到的字節(jié)數(shù)。這將是我們接收到的數(shù)據(jù)報(bào)的尺寸。
然而在這里要特別注意的是,最后一個(gè)參數(shù)是一個(gè)指向接收地址結(jié)構(gòu)長(zhǎng)度的指針。指針?biāo)赶虻膇nt值必須包含接收地址結(jié)構(gòu)from的最大字節(jié)尺寸。一旦由函數(shù)返回,返回的地址尺寸放置在int變量中。實(shí)際上,由fromlen所指向的值同時(shí)作為輸入值和返回值。
可用的標(biāo)志值如下所示:
標(biāo)志 十六進(jìn)制 意義
0 0x0000 普通
MSG_OOB 0x0001 處理超過(guò)邊界的數(shù)據(jù)
MSG_PEEK 0x0002 讀取一個(gè)數(shù)據(jù)報(bào)而不從內(nèi)核接收隊(duì)列中刪除
MSG_WAITALL 0x0100 請(qǐng)求操作塊直到全部請(qǐng)求都已滿足
MSG_ERRQUEUE 0x2000 從錯(cuò)誤隊(duì)列獲取一個(gè)數(shù)據(jù)包
MSG_NOSIGNAL 0x4000 當(dāng)另一端斷開(kāi)連接時(shí)為流套接口關(guān)閉信號(hào)
編寫(xiě)一個(gè)UDP數(shù)據(jù)報(bào)服務(wù)器現(xiàn)在我們已經(jīng)有足夠的知識(shí)來(lái)編寫(xiě)一個(gè)數(shù)據(jù)報(bào)客戶端與服務(wù)器程序了。在這一部分,我們將從編寫(xiě)一個(gè)數(shù)據(jù)報(bào)服務(wù)的例子開(kāi)始。這個(gè)程序接受一個(gè)strftime(3)格式字符串作為輸入并且返回格式化的日期與時(shí)間字符串作為響應(yīng)。在這一節(jié)我們將會(huì)編寫(xiě)一個(gè)數(shù)據(jù)報(bào)服務(wù)器,他可以獨(dú)立運(yùn)行,接受格式化字符串作為輸入數(shù)據(jù)報(bào)。在服務(wù)器使用strftime函數(shù)格式化日期字符串之后,他會(huì)將結(jié)果返回給客戶端程序。
/*
* dgramsrvr.c
*
* Example datagram server:
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
/*
* this function reports the error and
* exits back to the shell:
*/
static void bail(const char *on_what)
{
fputs(strerror(errno),stderr);
fputs(": ",stderr);
fputs(on_what,stderr);
fputs("\n",stderr);
exit(1);
}
int main(int argc,char **argv)
{
int z;
char *srvr_addr = NULL;
struct sockaddr_in adr_inet; /* AF_INET */
struct sockaddr_in adr_clnt; /* AF_INET */
int len_inet; /* length */
int s; /* socket */
char dgram[512]; /* recv buffer */
char dtfmt[512]; /* date/time result */
time_t td; /* current time and date */
struct tm tm; /* date time values */
/*
* use a server address from the command
* line,if one has been provided.
* otherwise,this program will default
* to using the arbitrary address
* 127.0.0.23:
*/
if(argc >=2)
{
/* addr on cmdline */
srvr_addr = argv[1];
}
else
{
/* use default address */
srvr_addr = "127.0.0.23";
}
/*
* create a UDP socket to use:
*/
s = socket(AF_INET,SOCK_DGRAM,0);
if(s==-1)
bail("socket()");
/*
* create a socket address,for use
* with bind:
*/
memset(&adr_inet,0,sizeof adr_inet);
adr_inet.sin_family = AF_INET;
adr_inet.sin_port = htons(9090);
adr_inet.sin_addr.s_addr = inet_addr(srvr_addr);
if(adr_inet.sin_addr.s_addr == INADDR_NONE)
bail("bad address");
len_inet = sizeof adr_inet;
/*
* bind a address to our socket,so that
* client program can contact this
* server:
*/
z = bind(s,(struct sockaddr *)&adr_inet,len_inet);
if(z==-1)
bail("bind()");
/*
* now wait for requests:
*/
for(;;)
{
/*
* block until the program receives a
* datagram at our address an port:
*/
len_inet = sizeof adr_clnt;
z = recvfrom(s, /* socket */
dgram, /* receiveing buffer */
sizeof dgram, /* max recv buf size */
0, /* flags */
(struct sockaddr *)&adr_clnt, /* addr */
&len_inet); /* addr len,in & out */
if(z<0)
bail("recvfrom()");
/*
* process the request:
*/
if(!strcasecmp(dgram,"QUIT"))
break; /* quit server */
/*
* get the current date and time:
*/
time(&td); /* get current time & date */
tm = *localtime(&td); /* get componets */
/*
* formate a new date and time string,
* based upon the input formate string:
*/
strftime(dtfmt, /* formatted result */
sizeof dtfmt, /* max result size */
dgram, /* input date/time format */
&tm); /* input date/time values */
/*
* send the formatted result back to the
* client program:
*/
z = sendto(s, /* socket to send result */
dtfmt, /* the datagram result to send */
strlen(dtfmt), /* the datagram length */
0, /* flags */
(struct sockaddr *)&adr_clnt, /* addr */
len_inet); /* client address length */
if(z<0)
bail("sendto()");
}
/*
* close the socket and exit:
*/
close(s);
return 0;
}
編寫(xiě)一個(gè)UDP數(shù)據(jù)報(bào)客戶端為了使用我們?cè)谇懊嫠帉?xiě)的數(shù)據(jù)報(bào)服務(wù)程序,我們需要一個(gè)數(shù)據(jù)報(bào)客戶端程序。下面例子所提供的數(shù)據(jù)報(bào)客戶端可以提示我們?yōu)閟trftime輸入格式化文本。
/*
* dgramclnt.c
*
* Example datagram client:
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
/*
* This function reports the error and
* exits back to the shell:
*/
static void bail(const char *on_what)
{
fputs(strerror(errno),stderr);
fputs(": ",stderr);
fputs(on_what,stderr);
fputs("\n",stderr);
exit(1);
}
int main(int argc,char **argv)
{
int z;
int x;
char *srvr_addr = NULL;
struct sockaddr_in adr_srvr; /* AF_INET */
struct sockaddr_in adr; /* AF_INET */
int len_inet; /* length */
int s; /* socket */
char dgram[512]; /* recv buffer */
/*
* use a server address from the command
* line,if one has been provided.
* otherwise,this programe will default
* to using the arbitrary address
* 127.0.0.23:
*/
if(argc>=2)
{
/* addr on command line: */
srvr_addr = argv[1];
}
else
{
srvr_addr = "127.0.0.23";
}
/*
* create a socket address,to use
* to contact the server with:
*/
memset(&adr_srvr,0,sizeof adr_srvr);
adr_srvr.sin_family = AF_INET;
adr_srvr.sin_port = htons(9090);
adr_srvr.sin_addr.s_addr = inet_addr(srvr_addr);
if(adr_srvr.sin_addr.s_addr == INADDR_NONE)
bail("bad address");
len_inet = sizeof adr_srvr;
/*
* create a UDP socket to use:
*/
s = socket(AF_INET,SOCK_DGRAM,0);
if(s==-1)
bail("socket()");
for(;;)
{
/*
* prompt user for a date formate string:
*/
fputs("\nEnter format string: ",stdout);
if(!fgets(dgram,sizeof dgram,stdin))
break; /* EOF */
/*
* send format string to server:
*/
z = sendto(s, /* socket to send result */
dgram, /* the datagram result to snd */
strlen(dgram), /* the datagram length */
0, /* flags */
(struct sockaddr *)&adr_srvr, /* addr */
len_inet); /* server address length */
if(z<0)
bail("sendto()");
/*
* test if we asked for a server shutdown:
*/
if(!strncmp(dgram,"QUIT",strlen(dgram)-1))
break; /* yes,we quit too */
/*
* wait for a response:
*/
x = sizeof adr;
z = recvfrom(s, /* socket */
dgram, /* receiving buffer */
sizeof dgram, /* max recv buf size */
0, /* flags */
(struct sockaddr *)&adr, /* addr */
&x); /* addr len,in & out */
if(z<0)
bail("recvfrom()");
dgram[z]=0;
/*
* report result
*/
printf("Result from %s port %u:\n\t‘%s‘\n",
inet_ntoa(adr.sin_addr),
(unsigned)ntohs(adr.sin_port),
dgram);
}
/*
* close the socket and exit:
*/
close(s);
putchar(‘\n‘);
return 0;
}
測(cè)試數(shù)據(jù)報(bào)客戶端與服務(wù)器我們將會(huì)在這里進(jìn)行的第一個(gè)測(cè)試適用于任何人,而無(wú)論你是否有可用的網(wǎng)絡(luò)或是獨(dú)立的PC。唯一的關(guān)鍵點(diǎn)就是我們需要將TCP/IP支持編譯進(jìn)入內(nèi)核。如果我們正在運(yùn)行一個(gè)獨(dú)立的發(fā)行版本,例如RH系列,那么我們已經(jīng)滿足這些條件了。
要執(zhí)行這個(gè)測(cè)試,我們需要執(zhí)行下面的步驟:
1 啟動(dòng)服務(wù)器程序
2 啟動(dòng)客戶端程序
3 輸入客戶程序輸入
4 輸入C-D來(lái)結(jié)束程序,或者是使用QUIT來(lái)退出程序。
啟動(dòng)服務(wù)器程序的輸出結(jié)果如下:
$ ./dgramsrvr &
[1] 4405
$
字符&將服務(wù)器程序放置在后臺(tái)運(yùn)行,這樣我們就可以繼續(xù)使用當(dāng)前的終端來(lái)運(yùn)行客戶端程序。
服務(wù)器程序啟動(dòng)運(yùn)行以后,我們就可以使用客戶程序來(lái)進(jìn)行測(cè)試了。下面顯示如何啟動(dòng)客戶程序并進(jìn)行測(cè)試:
$ ./dgramclnt
Enter format string: %D
Result from 127.0.0.23 port 9090 :
08/13/99‘
Enter format string: %A %D %H:%M:%S
Result from 127.0.0.23 port 9090 :
Friday 08/13/99 22:14:02‘
Enter format string: quit
[1]+ Done ./dgramsrvr
$
測(cè)試無(wú)服務(wù)器的情況下面的輸出顯示沒(méi)有服務(wù)器運(yùn)行時(shí)運(yùn)行客戶程序的輸出結(jié)果:
$ ./dgramclnt
Enter format string: %D
Connection refused: recvfrom(2)
$
在這里我們可以看到客戶程序可以運(yùn)行,并且可以創(chuàng)建套接口,要求輸入。甚至sendto函數(shù)也報(bào)告執(zhí)行成功。這就進(jìn)一步確認(rèn)了數(shù)據(jù)報(bào)的發(fā)送只是確保發(fā)送成功,而不確保接收成功。
在這種情況下,程序很幸運(yùn)的得到了錯(cuò)誤號(hào)來(lái)表明錯(cuò)誤原因 。錯(cuò)誤標(biāo)識(shí)是通過(guò)recvfrom函數(shù)調(diào)用得到的。當(dāng)客戶與服務(wù)器程序獨(dú)立運(yùn)行在一個(gè)大的網(wǎng)絡(luò)并有多個(gè)路由的情況下,也許就不會(huì)得到這個(gè)錯(cuò)誤號(hào)。
在實(shí)際使用中,如果另一端并沒(méi)有監(jiān)聽(tīng),我們不能依賴于得到錯(cuò)誤碼。因?yàn)檫@個(gè)原因,UDP程序通常包含定時(shí)器的使用,如果在一定的時(shí)間內(nèi)沒(méi)有收到響應(yīng)則認(rèn)為沒(méi)有建立連接。
使用其他的IP進(jìn)行測(cè)試在前一節(jié)我們談到可以在命令行指定IP地址。如果我們?cè)O(shè)置了我們自己的網(wǎng)絡(luò),我們可以試著在不同的主機(jī)上運(yùn)行客戶端與服務(wù)器程序。在下一個(gè)例子中,服務(wù)器程序運(yùn)行在192.168.0.1的主機(jī)上,而客戶端程序運(yùn)行192.168.0.2的主機(jī)上。服務(wù)器程序的啟動(dòng)如下所示:
$ ./dgramsrvr 192.168.0.1 &
[1] 4416
$
服務(wù)器成功啟動(dòng)以后,就可以在另一個(gè)主機(jī)上調(diào)用客戶端程序。客戶端的輸出結(jié)果如下所示:
$ ./dgramclnt 192.168.0.1
Enter format string: %D
Result from 192.168.0.1 port 9090 :
‘08/13/99‘
Enter format string: %A (%D)
Result from 192.168.0.1 port 9090 :
‘Friday (08/13/99)‘
Enter format string: QUIT
$
正如輸出結(jié)果所示,通過(guò)在命令行指定地址通知客戶程序服務(wù)器位于192.168.0.1主機(jī)上。在試驗(yàn)了兩個(gè)例子以后,輸入QUIT命令退出。
盡管這兩個(gè)例子可以正確的運(yùn)行,但是我們要認(rèn)識(shí)到UDP是不可靠的。如果客戶端程序不能由服務(wù)器得到響應(yīng),程序就會(huì)掛起。如果我們正在編寫(xiě)一個(gè)產(chǎn)品模型程序,我們需要提供超時(shí)代碼。當(dāng)原始的或是響應(yīng)的數(shù)據(jù)報(bào)丟失時(shí),這會(huì)允許程序修復(fù)響應(yīng)缺失。
在客戶程序中保留bind一些讀者也許已經(jīng)注意到在上個(gè)例子中所創(chuàng)建的套接口的客戶端程序并沒(méi)有調(diào)用bind函數(shù)。如果bind函數(shù)調(diào)用可以消除,那么為什么還要使用呢?
我們也許還記得在前面的章節(jié)中,“綁定地址到套接口”,其中有一節(jié)名為“接口與地址”的部分,在其中解釋了bind函數(shù)可用于限制用于執(zhí)行通信的接口。其中的例子是一個(gè)防火墻程序,他只與信任的內(nèi)部網(wǎng)絡(luò)進(jìn)行通信。如果現(xiàn)在這些對(duì)于我們來(lái)說(shuō)有一些茫然,那么我們需要返回來(lái)查閱bind的相關(guān)內(nèi)容。
而在我們的數(shù)據(jù)報(bào)例子程序中,bind函數(shù)調(diào)用被忽略了。這對(duì)于發(fā)送套接口有什么影響呢?正如我們?cè)诘谖逭滤私獾?,這實(shí)際表明這個(gè)程序可以接受任何流出的接口,這正是數(shù)據(jù)報(bào)到其目的地的路由所要求的。實(shí)際上,套接口據(jù)說(shuō)有一個(gè)寬套接口地址。然后,當(dāng)這個(gè)程序等待響應(yīng)時(shí),他也會(huì)接受從任何流入接口得到的輸入數(shù)據(jù)報(bào)。在這里也要注意套接口端口號(hào)也是寬的。在這個(gè)特定的程序中,任何客戶端口號(hào)都是可以接受的。
我們可以顯式的使用bind函數(shù)來(lái)請(qǐng)求一個(gè)寬地址和端口??梢允褂脤挼刂稩NADDR_NONE來(lái)完成這個(gè)工作。要請(qǐng)求一個(gè)寬端口號(hào),可以將端口號(hào)指定為0。通過(guò)組合IP地址的INADDR_NONE和0端口號(hào),我們已經(jīng)請(qǐng)求bind顯式的為我們提供一個(gè)寬地址,而這是與我們沒(méi)有使用bind調(diào)用而得到的寬地址相同。
回應(yīng)寬地址如果客戶程序的地址和端口號(hào)是寬的,我們也許想要知道服務(wù)器如何與回應(yīng)特定的端口。畢竟,我們應(yīng)如何縮寫(xiě)向沒(méi)有一個(gè)特定IP地址和UDP端口號(hào)的客戶程序返回響應(yīng)?
問(wèn)題的答案就在于:實(shí)際上當(dāng)數(shù)據(jù)報(bào)發(fā)送時(shí),就同時(shí)賦值了IP地址和端口號(hào)。我們前面的例子運(yùn)行在IP地址為192.168.0.2的主機(jī)上。當(dāng)客戶程序調(diào)用sendto函數(shù)時(shí),數(shù)據(jù)報(bào)知道將會(huì)發(fā)送到IP地址為192.168.0.1的主機(jī)上。路由表指明IP地址為192.168.0.2的以太網(wǎng)卡將會(huì)用于發(fā)送數(shù)據(jù)報(bào)。相應(yīng)的,發(fā)送的數(shù)據(jù)報(bào)有一個(gè)192.168.0.2的from地址。這是在服務(wù)器端看到的地址。然而,端口號(hào)是寬的,并且將會(huì)所選擇的IP地址選擇任意的自由端口號(hào)。
如果另一個(gè)數(shù)據(jù)報(bào)發(fā)送到一個(gè)不同的網(wǎng)絡(luò),那么from IP地址將會(huì)是不同的值。from地址返應(yīng)了用于發(fā)送數(shù)據(jù)報(bào)的網(wǎng)絡(luò)接口的IP地址。
這是需要理解的一個(gè)重要概念,而且也許對(duì)于初學(xué)者來(lái)說(shuō)也是最難理解的事情。如果我們現(xiàn)在并不能完全的理解這些內(nèi)容,那么我們就需要回顧一下第五章的內(nèi)容。作為練習(xí),我們可以在我們的例子程序中加入下面的printf語(yǔ)句:
printf("Client from %s port %u;\n",
inet_ntoa(adr_clnt.sin_addr),
(unsigned)ntohs(adr_clnt.sin_port));
編譯以后重新運(yùn)行程序,程序的運(yùn)行結(jié)果如下:
$ ./dgramsrvr &
[1] 733
$ ./dgramclnt
Enter format string: %D
Client from 127.0.0.23 port 1027;
Result from 127.0.0.23 port 9090 :
‘08/15/99‘
Enter format string: %A %D
Client from 127.0.0.23 port 1027;
Result from 127.0.0.23 port 9090 :
‘Sunday 08/15/99‘
Enter format string: QUIT
Client from 127.0.0.23 port 1027;
[1]+ Done ./dgramsrvr
$
在這里我們可以注意到所有的數(shù)據(jù)報(bào)發(fā)送到服務(wù)器,而數(shù)據(jù)報(bào)的from地址報(bào)告如下:
Client from 127.0.0.23 port 1027;
這再一次驗(yàn)證了當(dāng)在客戶端發(fā)送套接口沒(méi)有使用bind函數(shù)時(shí),會(huì)依據(jù)需求賦值合適的IP地址和最終的端口號(hào)。
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1676892