Linux下的廣播程式製作(轉)

BSDLite發表於2007-08-11
Linux下的廣播程式製作(轉)[@more@]TCP/IP網路的主要原理

在一個IP(Internet Protocol)網路中,每一臺計算機都有一個32位的IP地址。每臺計算機的IP地址都是唯一的。WWW是一個範圍十分大,並且不斷增長的IP網路,所以網路上的每臺計算機都必須有一個唯一的IP地址。IP地址是用.分隔開的4個十進位制數,例如16.42.0.9。實際上IP地址可以分為兩部分:一部分是網路地址,另一部分是主機地址,例如,在16.42.0.9中,16.42是網路地址,0.9則為主機地址。而主機地址又可以分為子網地址和主機地址。計算機的IP地址很不容易記憶,如果使用一個名字就可以方便得多。如果使用名字,則必須有某一種機制將名字轉化為IP地址。這些名字可以靜態地儲存在/etc/hosts檔案中,或者Linux系統請求域名伺服器(DNS伺服器)來轉換名字。如果使用DNS伺服器的話,本地的主機則必須知道一個或者多個DNS伺服器的IP地址,這些資訊儲存在/etc/resolv.conf檔案中。

當你和其他計算機相連時,系統要使用IP地址和其他計算機交換資料。資料儲存在IP資料包中。每一個IP資料包都有一個IP資料頭,其中包括源地址和目的地址,一個資料校驗和以及其他一些有關的資訊。IP資料包的大小隨傳輸介質的不同而不同,例如,乙太網的資料包要大於PPP的資料包。目的地址的主機在接收資料包後,必須再將資料裝配起來,然後傳送給接收的應用程式。

連線在同一個IP子網上的主機之間可以直接傳送IP資料包,而在不同子網之間的主機卻要使用閘道器。閘道器用來在不同的子網之間傳送資料包。

IP協議是一個傳輸層的協議,其他的協議可以利用IP協議來傳輸資料。TCP(Transmission Control Protocol)協議是一個可靠的點到點之間的協議,它使用IP協議來傳送和接收自己的資料包。TCP協議是基於連線的協議。需要通訊的兩個應用程式之間將建立起一條虛擬的連線線路,即使其中要經過很多子網、閘道器和路由器。TCP協議保證在兩個應用程式之間可靠地傳送和接收資料,並且可以保證沒有丟失的或者重複的資料包。當TCP協議使用IP協議傳送它自己的資料包時,IP資料包中的資料就是TCP資料包本身。相互通訊的主機中的IP協議層負責傳送和接收IP資料包。每一個IP資料頭中都包括一個位元組的協議識別符號。當TCP協議請求IP協議層傳送一個IP資料包時,IP資料頭中的協議識別符號指明其中的資料包是一個TCP資料包。接收端的IP層則可以使用此協議識別符號來決定將接收到的資料包傳送到那一層,在這裡是TCP協議層。

當應用程式使用TCP/IP通訊時,它們不僅要指明目標計算機的IP地址,也要指明應用程式使用的埠地址。埠地址可以唯一地表示一個應用程式,標準的網路應用程式使用標準的埠地址,例如,web伺服器使用埠80。你可以在/etc/services中檢視已經登記的埠地址。

IP 協議層也可以使用不同的物理介質來傳送IP資料包到其他的IP地址主機。這些介質可以自己新增協議頭。例如乙太網協議層、PPP協議層或者SLIP協議層。乙太網可以同時連線很多個主機,每一個主機上都有一個乙太網的地址。這個地址是唯一的,並且儲存在乙太網卡中。所以在乙太網上傳輸IP資料包時,必須將IP資料包中的IP地址轉換成主機的乙太網卡中的實體地址。Linux系統使用地址解決協議( ARP)來把IP地址翻譯成主機乙太網卡中的實體地址。希望把IP地址翻譯成硬體地址的主機使用廣播地址向網路中的所有節點傳送一個包括IP地址的ARP請求資料包。擁有此IP地址的目的計算機接收到請求以後,返回一個包括其實體地址的ARP應答。ARP協議不僅僅限於乙太網,它還可以用於其他的物理介質,例如FDDI等。那些不能使用ARP的網路裝置可以標記出來,這樣Linux系統就不會試圖使用ARP。系統中也有一個反向的翻譯協議,叫做RARP,用來將主機的實體地址翻譯成IP地址。閘道器可以使用此協議來代表遠端網路中的IP地址回應ARP請求。

BSD 套介面

BSD 套介面是最早的網路通訊的實現,它由一個只處理BSD 套介面的管理軟體支援。其下面是INET套介面層,它管理TCP協議和UDP協議的通訊末端。UDP(User Datagram Protocol)是無連線的協議,而TCP則是一個可靠的端到端協議。當網路中傳送一個UDP資料包時,Linux系統不知道也不關心這些UDP資料包是否安全地到達目的節點。TCP資料包是編號的,同時TCP傳輸的兩端都要確認資料包的正確性。IP協議層是用來實現網間協議的,其中的程式碼要為上一層資料準備IP資料頭,並且要決定如何把接收到的IP資料包傳送到TCP協議層或者UDP協議層。在IP協議層的下方是支援整個Linux 網路系統的網路裝置,例如PPP和乙太網。網路裝置並不完全等同於物理裝置,因為一些網路裝置,例如回饋裝置是完全由軟體實現的。和其他那些使用mknod命令建立的Linux系統的標準裝置不同,網路裝置只有在軟體檢測到和初始化這些裝置時才在系統中出現。當你構建系統核心時,即使系統中有相應的乙太網裝置驅動程式,你也只能看到/dev/eth0。ARP協議在IP協議層和支援ARP翻譯地址的協議之間。
================================
網路應用程式
使用者層
--------------------------------
BSD 核心層
套介面層
|
LNET
套介面層
|
/ ?
TCP UDP
|
IP
|
PPP | SLIP | Ethernet ---&gt ARP

================================
BSD是UNIX系統中通用的網路介面,它不僅支援各種不同的網路型別,而且也是一種內部程式之間的通訊機制。兩個通訊程式都用一個套介面來描述通訊鏈路的兩端。套介面可以認為是一種特殊的管道,但和管道不同的是,套介面對於可以容納的資料的大小沒有限制。Linux支援多種型別的套介面,也叫做套介面定址族,這是因為每種型別的套介面都有自己的定址方法。

Linux的BSD 套介面支援下面的幾種套介面型別:

1. 流式(stream)
這些套介面提供了可靠的雙向順序資料流連線。它們可以保證資料傳輸中的完整性、正確性和單一性。INET定址族中的TCP協議支援這種型別的套介面。
資料流套介面是可靠的雙向連線的通訊資料流。如果你在套介面中以“ 1, 2”的順序放入兩個資料,它們在另一端也會以“1, 2”的順序到達。它們也可以被認為是無錯誤的傳輸。
經常使用的telnet應用程式就是使用資料流套介面的一個例子。使用HTTP的WWW瀏覽器也使用資料流套介面來讀取網頁。事實上,如果你使用telnet 登入到一個WWW站點的8 0埠,然後鍵入“GET 網頁名”,你將可以得到這個HTML頁。資料流套介面使用TCP得到這種高質量的資料傳輸。資料包套介面使用UDP,所以資料包的順序是沒有保障的。資料包是按一種應答的方式進行資料傳輸的。
2. 資料包(Datagram)
這種型別的套介面也可以像流式套介面一樣提供雙向的資料傳輸,但它們不能保證傳輸的資料一定能夠到達目的節點。即使資料能夠到達,也無法保證資料以正確的順序到達以及資料的單一性、正確性。U D P協議支援這種型別的套介面。
3. 原始(Raw)
這種型別的套介面允許程式直接存取下層的協議。
4. 可靠遞送訊息(Reliable Delivered Messages)
這種套介面和資料包套介面一樣,只能保證資料的到達。
5. 順序資料包(Sequenced Packets)
這種套介面和流式套介面相同,除了資料包的大小是固定的。
6. 資料包(Packet)
這不是標準的BSD 套介面型別,而是Linux 中的一種擴充套件。它允許程式直接存取裝置層的資料包。

基本套介面選項

SO_KEEPALIVE

檢測對方主機是否崩潰,避免(伺服器)永遠阻塞於TCP連線的輸入。 設定該選項後,如果2小時內在此套介面的任一方向都沒有資料交換,TCP就自動給對方 發一個保持存活探測分節(keepalive probe)。這是一個對方必須響應的TCP分節.它會導致以下三種情況:
對方接收一切正常:以期望的ACK響應。2小時後,TCP將發出另一個探測分節。
對方已崩潰且已重新啟動:以RST響應。套介面的待處理錯誤被置為ECONNRESET,套接 口本身則被關閉。 對方無任何響應:源自berkeley的TCP傳送另外8個探測分節,相隔75秒一個,試圖得到 一個響應。在發出第一個探測分節11分鐘15秒後若仍無響應就放棄。套介面的待處理錯 誤被置為ETIMEOUT,套介面本身則被關閉。如ICMP錯誤是“host unreachable(主機不 可達)”,說明對方主機並沒有崩潰,但是不可達,這種情況下待處理錯誤被置為EHOSTUNREACH。

SO_RCVBUF和SO_SNDBUF

每個套介面都有一個傳送緩衝區和一個接收緩衝區。 接收緩衝區被TCP和UDP用來將接收到的資料一直儲存到由應用程式來讀。 TCP:TCP通告另一端的視窗大小。 TCP套介面接收緩衝區不可能溢位,因為對方不允許發出超過所通告視窗大小的資料。 這就是TCP的流量控制,如果對方無視視窗大小而發出了超過宙口大小的資料,則接 收方TCP將丟棄它。 UDP:當接收到的資料包裝不進套介面接收緩衝區時,此資料包就被丟棄。UDP是沒有 流量控制的;快的傳送者可以很容易地就淹沒慢的接收者,導致接收方的UDP丟棄資料包。

SO_LINGER

指定函式CLOSE對面相連線的協議如何操作——當由資料殘留在套介面傳送緩衝區時的處理 LINGER結構

struct linger { int l_onoff; // 0=off, nonzero=on int l_linger; //linger time in seconds };

SO_RCVLOWAT 和SO_SNDLOWAT

每個套介面都有一個接收低潮限度和一個傳送低潮限度。它們是函式select使用的, 接收低潮限度是讓select返回“可讀”而在套介面接收緩衝區中必須有的資料總量。 ——對於一個TCP或UDP套介面,此值預設為1。傳送低潮限度是讓select返回“可寫” 而在套介面傳送緩衝區中必須有的可用空間。對於TCP套介面,此值常預設為2048。 對於UDP使用低潮限度, 由於其傳送緩衝區中可用空間的位元組數是從不變化的,只要 UDP套介面傳送緩衝區大小大於套介面的低潮限度,這樣的UDP套介面就總是可寫的。 UDP沒有傳送緩衝區,只有傳送緩衝區的大小

SO_BROADCAST套介面選項

這個選項能夠讓我們使能或者禁止套介面的傳送廣播能力。只能在資料包模式下使用廣播,並且還必須是支援廣播訊息的乙太網等網路上。如果是點對點的鏈路上就無法辦到這一點。
因為使能廣播的功能必須顯式執行。因此可以避免一些UDP程式無意中把廣播地址當作目的地址進行傳送。但是Linux在處理廣播地址的時候,不是由使用者來識別的,在使用者空間所看到的地址格式是沒有差別的。一直到核心才會識別出廣播地址出來,並加以相應的處理。

資料結構

下面我們要討論使用套介面編寫程式可能要用到的資料結構。

首先是套介面描述符。一個套介面描述符只是一個整型的數值: i n t。

第一個資料結構是struct sockaddr,這個資料結構中儲存著套介面的地址資訊。

struct sockaddr {
unsigned short sa_family; /* address family, AF_xxx */
char sa_data[14]; /* 14 bytes of protocol address */
} ;

sa_family 中可以是其他的很多值,但在這裡我們把它賦值為“ AF_INET”。sa_data包括一個目的地址和一個埠地址。

你也可以使用另一個資料結構sockaddr_in,如下所示:

struct sockaddr_in {
short int sin_family; /* Address family */
unsigned short int sin_port; /* Port number */
struct in_addr sin_addr; /* Internet address */
unsigned char sin_zero[8]; /* Same size as struct sockaddr */
} ;

這個資料結構使得使用其中的各個元素更為方便。要注意的是sin_zero應該使用bzero() 或者memset ( )而設定為全0。另外,一個指向sockaddr_in資料結構的指標可以投射到一個指向資料結構sockaddr的指標,反之亦然。

IP地址和如何使用IP地址

有一系列的程式可以使你處理I P地址。

首先,你可以使用inet_addr( )程式把諸如“ 132.241.5.10“形式的I P地址轉化為無符號的整型數。

ina.sin_addrs_addr = inet_addr("132.241.5.10");

如果出錯,inet_addr( )程式將返回- 1。
也可以呼叫inet_ntoa( )把地址轉換成數字和句點的形式:
printf( " % s " , inet_ntoa( ina.sin_addr ) ) ;
這將會列印出I P地址。它返回的是一個指向字串的指標。

socket()

我們使用系統呼叫socket()來獲得檔案描述符:

#include
#include
int socket(int domain, int type, int protocol);

第一個引數domain設定為“AF_INET”。
第二個引數是套介面的型別:SOCK_DGRAM。
第三個引數設定為0。
系統呼叫socket()只返回一個套介面描述符,如果出錯,則返回- 1。

bind()

一旦你有了一個套介面以後,下一步就是把套介面繫結到本地計算機的某一個埠上。但如果你只想使用connect( )則無此必要。
下面是系統呼叫bind( )的使用方法:

#include
#include
int bind(int sockfd, struct sockaddr *my_addr, int addrlen);

第一個引數sockfd 是由socket( )呼叫返回的套介面檔案描述符。
第二個引數my_addr 是指向資料結構sockaddr的指標。資料結構sockaddr中包括了關於你的地址、埠和IP地址的資訊。
第三個引數addrlen可以設定成sizeof(struct sockaddr)。下面是一個例子:

#include
#include
#include
#define MYPORT 3490
main ( )
{
int sockfd;
struct sockaddr_in my_addr; //說明一個sock地址結構
sockfd = socket(AF_INET, SOCK_STREAM, 0); /* 基本的建立UDP socket,最好進行一些檢查 */
my_addr.sin_family = AF_INET; /* 設定協議集,基於internet協議 */
my_addr.sin_port = htons(MYPORT); // 埠號
my_addr. sin_addr.s_addr = inet_addr("132.241.5.10");//將字串轉換成標準的地址格式
bzero(&(my_addr.sin_zero), 8); /* zero the rest of the struct */
/* don't forget your error checking for bind(): */
bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));
//繫結監聽程式到該socket上

如果出錯,bind() 也返回- 1。
如果你使用connect()系統呼叫,那麼你不必知道你使用的埠號。當你呼叫connect()時,它檢查套介面是否已經繫結,如果沒有,它將會分配一個空閒的埠。

sendto() 和recvfrom()

因為資料包套介面並不連線到遠端的主機上,所以在傳送資料包之前,我們必須首先給出目的地址,請看

int sendto(int sockfd, const void *msg, int len, unsigned int flags,
const struct sockaddr *to, int tolen);

除了兩個引數以外,其他的引數和系統呼叫s e n d ( )時相同。引數t o是指向包含目的I P地址和埠號的資料結構s o c k a d d r的指標。引數t o l e n可以設定為sizeof(struct sockaddr)。

系統呼叫sendto( )返回實際傳送的位元組數,如果出錯則返回- 1。
系統呼叫recvfrom( )的使用方法也和r e c v ( )的十分近似:

int recvfrom(int sockfd, void *buf, int len, unsigned int flags
struct sockaddr *from, int *fromlen);

sockfd: 描述字
buff: 指向輸入緩衝器的指標
nbytes: 讀位元組大小
flag: 標誌:0
from :對方協議地址
addrlen: 對方協議地址長度

函式返回值: 讀入資料的長度,可以為0.

引數from是指向本地計算機中包含源I P地址和埠號的資料結構sockaddr的指標。引數fromlen設定為sizeof(struct sockaddr)。

系統呼叫recvfrom ( )返回接收到的位元組數,如果出錯則返回- 1。

close() 和shutdown()

你可以使用close( )呼叫關閉連線的套介面檔案描述符:

close(sockfd) ;

這樣就不能再對此套介面做任何的讀寫操作了。

使用系統呼叫shutdown(),可有更多的控制權。它允許你在某一個方向切斷通訊,或者切斷雙方的通訊:

int shutdown(int sockfd, int how);

第一個引數是你希望切斷通訊的套介面檔案描述符。第二個引數h o w值如下:
0—Further receives are disallowed
1—Further sends are disallowed
2—Further sends and receives are disallowed (like close())
shutdown() 如果成功則返回0,如果失敗則返回- 1。

客戶機/伺服器模式

在網路上大部分的通訊都是在客戶機/伺服器模式下進行的。例如telnet。當你使用telnet連線到遠端主機的埠23時,主機上的一個叫做telnetd的程式就開始執行。它處理所有進入的telnet連線,為你設定登入提示符等。

應當注意的是客戶機/伺服器模式可以使用SOCK_STREAM、SOCK_DGRAM或者任何其他的方式。例如telnet /telnetd、ftp/ftpd和bootp/bootpd。每當你使用ftp時,遠端計算機都在執行一個ftpd為你服務。

一般情況下,一臺機器上只有一個伺服器程式,它透過使用fork( )來處理多個客戶端程式的請求。最基本的處理方法是:伺服器等待連線,使用accept()接受連線,呼叫fork( )生成一個子程式處理連線。

UDP廣播模式

廣播的用途之一是假定伺服器主機在本地子網上,但不知道它的單播IP地址時,對它進行定位,這就是資源發現(resource discovery)。另一用途是當有多個客戶和單個伺服器通訊時,減少區域網上資料流量。下面是幾個以此為目的使用廣播的因特網應用例項。

★ARP(地址解析協議,address Resolution Protocol。ARP是IPv4的一個基本組成部分,而不是一個使用者應用程式。ARP在本地子網上廣播一個請求:“具有IP地址a.b.c.d的系統請表明自己,並告訴我,你的硬體地址。”
★BOOTP(引導協議,Bootstrap Protocol)。客戶假定有一臺伺服器主機在本地子網上。它以廣播地址(通常是255.255.255.255,因為這是客戶還不知道自己的IP地址、子網掩碼或子網的受限廣播地址)為目的地址發出自己的引導請求。
★NTP(網路時間協議,Network Time Protocol)。一種常見的情形是:一個NTP客戶主機可能配置程使用一個或多個伺服器主機的IP地址,其上面的NTP客戶於是以某個頻率(每64秒一次或更長)輪詢這些伺服器。客戶採用基於伺服器返送的時刻和到達伺服器的往返時間的精確演算法更新時鐘。但在支援廣播的區域網上,就不需要採用客戶輪詢伺服器的方法,而代之以伺服器以每64秒一次的頻率向本地子網上的所有客戶廣播當前時刻。這樣便可以減少網路上的資料流量。
★路由後臺程式。routed是最常用的後臺程式。它輸出自己的路由表的方法便是區域網廣播。所有其他連線到這些區域網上的路由器便可以同時接收這些路由通告,而不用每個路由器都必須配置其鄰居路由器的IP地址。這個特性也被區域網上的主機用於偵聽路由通告並相應更新它的路由表(許多人認為這是一種“誤用”)

如果用{netid,subnetid,hostoid}。({網路Id,子網Id,主機ID})表示IPv4地址,那麼有四種型別的廣播地址。我們用-1表示所有位元位均為1的欄位。

1、子網廣播地址:{netid,subnetid,-1}。這類地址編排指定子網上的所有介面。例如,如果我們對B類地址128.7採用8位子網ID,那麼128.7.6.225將是128.7.6的子網上所有介面的子網廣播地址。
路由器通常不轉發這類廣播(TCPv2)圖18.2給出了一個連線到128.7.1和128.7.6兩個子網的路由器。路由器在12.7..1子網上接收到一個目的地址為210.37.6.255(另一個介面的子網廣播地址)的單播IP資料包。路由器通常不向128.7.6子網轉發這個資料包。有些系統具有執行轉發子網廣播資料包的配置選項。

2、 全部子網廣播地址:{netid,-1,-1}。這類廣播地址編排指定網路上的所有子網。如果說這類地址層被用過的話,那麼現在已很少見了。

3、 網路廣播地址:{netid,-1}。這類地址用於不進行子網劃分的網路。但不進行子網劃分的網路現在幾乎不存在了。

4、 受限廣播地址:{-1,-1,-1}或255.255.255.255。路由器從不轉發目的地址為255.255.255.255的IP資料包。

在這四類廣播地址中,子網廣播地址是今天最常見的。但有些老系統仍然傳送目的地址為255.255.255.255的資料包。還有些老系統不理解子網廣播地址,他們將僅發往255.255.255.255的資料包解釋為廣播。

在檢視廣播之前,我們應該已清楚向單播地址傳送UDP資料包的步驟。圖中網路的地址為192.168.0,其中8位用作子網ID,8位用作主機ID。左邊主機的應用程式在一個UDP套介面上呼叫sendto函式,將資料包發往IP地址192.168.0.3、埠1234。UDP層附加一個UDP頭部,並將UDP資料包傳遞到IP層。IP層給它附加一個IPv4頭部,並確定其外出介面。在乙太網的情況下,將呼叫ARP來確定與目的IP地址相應的乙太網地址:08:00:22:03:ff:42。然後,將分組作為乙太網幀傳送出去。乙太網幀的目的地址是上述48位地址。幀型別欄位的值為0800,指示這是一個IPv4分組。IPv6幀型別欄位的值為86dd。



中間主機的乙太網介面看到該幀,並將它的目的乙太網地址與自己的乙太網地址(02:60:8c:2f:4e:13:)進行比較。由於二者不相等,介面便忽略該幀。因此,單播幀不會對這臺主機造成任何額外開銷。右邊主機的乙太網介面也看到該幀。當它將該幀的目的乙太網地址與自己的乙太網地址進行比較時,發現二者相等,介面便讀入整個幀。由於幀型別欄位只為0800,於是將分組放入IP輸入佇列。

當IP層處理該分組時,它首先將目的IP地址進行比較,由於目的地址是其中之一,於是就接受這個分組。

然後,IP層檢查IPv4頭部協議欄位,其值對於UDP為17.於是將IP資料包傳送到UDP層。

UDP層檢查其目的埠(如果其UDP套介面已連線,也可能檢查源埠),將資料包方到相應套介面的接收佇列。如果需要,就喚醒程式,由程式讀取這個新接收的資料包。

這個例子的關鍵點是單播IP資料包只能由目的IP地址指定的主機接收。子網上的其它主機不受任何影響。我們現在考慮一個類似的例子:同樣的子網,但傳送程式傳送的是子網廣播資料包,其地址為192.168.0.255。


當左側的主機傳送資料包時,它注意到目的IP地址是子網廣播地址,於是便將它對映程48位的乙太網地址ff.ff.ff.ff.ff.ff。這使得子網上的每一個乙太網介面都會接收該幀。在圖中右側兩臺執行IPv4的主機都接收該幀。由於乙太網幀型別是0800,兩個主機都將資料包傳遞到IP層。由於目的IP地址匹配二者的廣播地址,並且協議欄位為17(UDP),兩個主機於是都將分組上傳至UDP。

最右邊的主機將UDP資料包傳遞給繫結埠1234的應用程式。接收廣播UDP資料包的應用程式不需要任何特殊的處理,它僅僅建立一個UDP套介面,並將應用的埠號捆綁到其上。(我們假設捆綁的IP地址為INADDR_ANY,這是典型的情況)但是中間的主機沒有任何應用程式繫結UDP埠1234。於是主機的UDP程式碼丟棄這個已收到的資料包。這臺主機禁止傳送埠不可達的ICMP訊息,因為這樣做會產生廣播風暴(broadcast storm):子網上許多主機機會同時產生響應,這將導致網路在幾秒鐘內不可用。在圖中,我們也表示出了左邊主機將輸出資料包又遞送給自己的情況,這是一種廣播屬性:根據定義,廣播要到達子網上的所有主機,包括髮送者自身。

我們還假定傳送應用程式已經繫結要傳送到的埠(1234),因此它將收到它傳送的每個資料包的複製。(但是一般情況下,沒有將程式捆綁資料包所傳送到UDP埠的要求。)

本例也表明了廣播存在地根本問題:子網上所有未參與廣播應用系統的主機也必須完成對資料包的協議處理,直至在UDP層將它丟棄。所有非IP主機(例如與行 Novell IPX 的主機)也必須在鏈路層接收完整的幀,並在該層將它丟棄(假定這些主機不支援幀的幀型別,IPv4分組的幀型別域值為0800)。因此,以高速率產生的IP資料包的應用系統(例如音訊、影片應用系統)會嚴重影響子網上其他主機的執行。

----------------------------------------------------------------------------
UDP回射伺服器程式

//伺服器main主程式
Int main(int argc, char **argv)
{
int sockfd; //定義套接字
struct sockaddr_in servaddr, cliaddr; //IPv4套介面地址定義
sockfd = Socket(AF_INET, SOCK_DGRAM, 0); //建立UDP套接字

bzero(&servaddr, sizeof(servaddr)); //地址結構清零
servaddr.sin_family = AF_INET; //IPv4協議
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//核心指定地址
servaddr.sin_port = htons(SERV_PORT); //服務端監聽埠

/*分配協議地址,繫結埠*/
Bind(sockfd, (SA *) &servaddr, sizeof(servaddr));

/* 回射子程式*/
dg_echo(sockfd, (SA *) &cliaddr, sizeof(cliaddr));
}


void dg_echo(int sockfd, SA *pcliaddr, socklen_t clilen)
{
int n; //讀入位元組數
socklen_t len; //協議地址長度, 沒有這個引數用 clilen也可以
char mesg[MAXLINE];

for ( ; ; ) {
len = clilen;

/* 讀入一行 */
n = Recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &len);

/* 回射到對方套介面 */
Sendto(sockfd, mesg, n, 0, pcliaddr, len);
}

===========================================
UDP回射客戶程式

//客戶 main主程式
int main(int argc, char **argv) //命令列的第二個引數代表伺服器地址
{
int sockfd; //套接字
struct sockaddr_in servaddr; //伺服器地址結構
const int on=1;//設定常量,用於開啟廣播模式

/* 必須在命令列指定伺服器地址*/
if (argc != 2) err_quit("usage: udpcli ");
bzero(&servaddr, sizeof(servaddr)); //地址結構清零
servaddr.sin_family = AF_INET; //IPv4
servaddr.sin_port = htons(SERV_PORT); //9877埠

/*網路位元組序的IP地址*/
Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);

/*建立UPD套介面*/
sockfd = Socket(AF_INET, SOCK_DGRAM, 0);

//設定該介面上的廣播模式
setsockopt(sockfd,SOL_Socket,SO_BROADCAST,&on,sizeof(on));

/*回射客戶端子程式, stdin 為標準輸入:鍵盤*/
dg_cli(stdin, sockfd, (SA *) &servaddr, sizeof(servaddr));

exit(0); //子程式結束後退出程式
}

客戶端傳送回射子程式

void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{
int n; //讀入位元組數
char sendline[MAXLINE], recvline[MAXLINE + 1]; // 1:結束標誌佔用

/* 從鍵盤讀入一行 */
while (Fgets(sendline, MAXLINE, fp) != NULL) { //如果不是^D結束

/* 將讀入行傳送到廣播地址*/
Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);

/*從讀入回射,讀入位元組數為n, 不關心從何處讀入
n = Recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);

recvline[n] = 0; /* recvline字串的結束標誌*/

Fputs(recvline, stdout); //輸出到標準輸出:顯示器

} //while迴圈結束:直到從鍵盤讀入結束符^D為止


驗證收到的響應

void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{
int n; socklen_t len;
char sendline[MAXLINE], recvline[MAXLINE + 1];
struct sockaddr *preply_addr; //對方 (回應)地址指標

preply_addr = Malloc(servlen); //分配地址結構
while (Fgets(sendline, MAXLINE, fp) != NULL) {
Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
len = servlen;
/* 讀入一行,並獲得對方的套介面地址*/
n = Recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr, &len);

/*對方套介面地址長度和指定伺服器地址長度不相同*/
/*或套介面地址結構也不相同時,*/
if (len != servlen || memcmp(pservaddr, preply_addr, len) != 0) {
printf(“reply from %s (ignored) ”, //忽略回射行,並輸出對方地址
Sock_ntop(preply_addr, len) );
continue; //下一輪迴圈
}
recvline[n] = 0; /* null terminate */
Fputs(recvline, stdout);
}
}

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10617542/viewspace-947673/,如需轉載,請註明出處,否則將追究法律責任。

相關文章