歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> 關於Linux

Linux編程中的帶外數據

定義帶外數據

想 像一下在銀行人們排起隊等待處理他們的帳單。在這個隊伍中每個人最後都會移到前面由出納員進行服務。現在想像一下一個走入銀行,越過整個隊伍,然後用槍抵 住出納員。這個就可以看作為帶 外 數據 。這個強盜越過整個隊伍,是因為這把槍給了他凌駕於眾人的權力。出納員也會集中注意力於這個強盜身上,因為他知道當前 的形勢是很緊急的。

相應的,一個連接的流式套接口上的帶 外 數據 的工作原理也與此類似。通常情況下,數據 由連接的一端流到另一端,並且認為 數據 的所有字節都是精確排序的。晚寫入的字節絕不會早於先寫入的字節到達。然而套接口API概念性的提供了一些實用程序,從而可以使得一串數據 無阻的先於 通常的數據 到達接收端。這就是所謂的發送帶 外 數據 。

從技術上來說,一個TCP 流不可以發送帶 外 數據 。而他所支持的只是一個概念性的緊急數據 ,這些緊急數據作為帶 外 數據 映射到套接口API。 這就帶 來了許多限制,這些我們會在後面進行討論。

盡管我們可以立刻享受到在銀行中越過整個隊伍的利益,但是我們也會認識到使用槍來達到這樣的目的是反社會的行為。一個TCP 流通常希望以完美的隊列來發送數據 字節,那麼亂序的發送數據 就似乎與流的概念相違背。那麼為什麼要提供帶 外 數據 的套接口方法呢?

也 許我們已經意識到了,有時數據 會以一定的方式變得緊急。一個流套接口會有一個大量的數據 隊列等待發送到網絡。在遠程端點,也會有大量已接收的,卻還沒有被 程序讀取的數據 。如果發送客戶端程序由於一些原因需要取消已經寫入服務器的請求,那麼他就需要向服務器緊急發送一個標識取消的請求。如果向遠程服務器發送 取消請求失敗,那麼就會無謂的浪費服務器的資源。

使 用帶 外 數據 的實際程序例子就是telnet,rlogin,ftp命令。前兩個程序會將中止字符作為緊急數據 發送到遠程端。這會允許遠程端沖洗所有未處理 的輸入,並且丟棄所有未發送的終端輸出。這會快速中斷一個向我們屏幕發送大量數據 的運行進程。ftp命令使用帶 外 數據 來中斷一個文件的傳輸。

套接口與帶 外 數據

重新強調套接口接口本身並不是限制因素是很重要的。帶 外 數據 的概念實際上映射到 TCP /IP通信的緊急數據模式。在今天,TCP 流對於網絡是很重要的,而在這一章我們僅專注於帶 外 數據 適應於TCP 緊急數據 的套接口使用。

實現上的變化

很不幸,TCP 的實現在緊急數據 就如何處理上有兩種不同的解釋。這些區別我們將會本章的後面進行詳細的討論。這些不同的解釋是:

TCP 緊急指針的RFC793解釋

TCP 緊急指針的BSD解釋

現 在已經出現了平分的狀態,因為原始的TCP 規格允許兩種解釋。從而,一個"主機需要"的RFC標識正確的解釋。然而,大多數的實現都基於BSD源碼,而在 今天BSD方法還是一個通用的用法。從支持兩種解釋的角度而言,Linux處於分裂的狀態。然而,Linux默認使用BSD解釋。

現在我們稍做停頓,來檢測一個我們Linux系統的當前設置。這決了我們這一章的例子是否可以產生同樣的結果。

$ cat /proc/sys/net/ipv4/tcp_stdurg

0

$

這裡顯示的輸出為0。這表示當前起作的為BSD解釋。如果我們得到其他的輸出結果(例如1),那麼如果我們希望得到也本章的例子相同的結果,我們應將其改為0。

下面列出了tcp_stdurg設置可能的取值。tcp_stdurg值可以在Shell腳本中進行查詢和設置,包括啟動與關閉腳本。

/proc/sys/net/ipv4_stdurg的設置值:

0   BSD解釋(Linux默認)

1   RFC793解釋

如果我們需要將其設置改為0,我們需要root權限,然後輸入下面的命令:

# echo 0 >/proc/sys/net/ipv4/tcp_stdurg

#

進行雙重檢測總是很明知的,所以在改變以後再列出其值來確定改變是否為內核所接受。我們也可以在上面的例子中使用cat命令來顯示0值。

編寫帶 外 數據

一個write調用將會寫入一個我們已習慣的帶 內數據 。相應的,必須使用一個新的函數來寫入帶 外 數據 。為了這個目的,在這裡列出send函數地原型:

#include <sys/types.h>

#include <sys/socket.h>

int send(int s, const void *msg, int len, unsigned int flags);

這個函數需要四個參數,分別為:

1 要寫入的套接口s

2 存放要寫入的消息的緩沖地址msg

3 消息長度(len)

4 發送選項flags

send函數與write函數相類似,所不同的只是提供了額外的flags參數。這是實際的部分。send函數返回寫入的字節數,如果發生錯誤則會返回-1,檢測errno可以得到錯誤原因。

要發送帶 外 數據 ,與write調用相似,使用前三個參數。如果我們為flags參數指定了C語言宏MSG_OOB,則數據 是作為帶 外 數據 發送的,而不是通常的帶 內數據 ,如下面的例子代碼:

char buf[64]; /* Data */

int len;      /* Bytes */

int s;        /* Socket */

. . .

send(s,buf,len,MSG_OOB);

如果所提供的flags參數沒有MSG_OOB位,那麼數據 是作為通常數據 寫入的。這就允許我們使用同一個函數同時發送帶 內數據 與帶 外 數據 。我們只需要簡單的在程序控制中改變flags參數值來達到這個目的。

讀取帶 外 數據

帶 外 數據 可以用兩種不同的方法進行讀取:

單獨讀取帶 外 數據

與帶 內數據 混合讀取

為了與通常數據 流分開單獨讀取帶 外 數據 ,我們需要使用recv函數。如果我們猜想recv函數與read函數相似,只是有一個額外的flags參數,那麼我們的猜想是正確的。這個函數的原型如下:

#include <sys/types.h>

#include <sys/socket.h>

int recv(int s, void *buf, int len, unsigned int flags);

recv函數接受四參數,分別為:

1 要從中接收數據 的套接口s(帶 內數據 或帶 外 數據 )

2 要放置所接收的數據 的緩沖區地址buf

3 接收緩沖區的最大長度

4 調用所需的flags參數

正如我們所看到的,recv函數是與send函數調用相對應的函數。為要接收帶 外 數據 ,在flags參數中指定C宏MSG_OOB。沒有MSG_OOB標志位,recv函數所接收的為通常的帶 內數據 ,就像通常的read調用一樣。

recv函數返回所接收到的字節數,如果出錯則返回-1,檢測errno可以得到錯誤原因。

下面的代碼例子演示了如何讀取帶 外 數據 :

char buf[128];   /* Buffer */

int n;      /* No. of bytes */

int s;             /* Socket */

int len;         /* Max bytes */

. . .

n = recv(s,buf,len,MSG_OOB);

盡管指出帶 外 數據 可以與通常數據 相混合還為時尚早,但是我們會在後面進行相關的討論。

理解SIGURG 信號

當帶 外 數所到在時,接收進程需要收到通知。如果需要與通常數據 流分開讀取時更是如此。這樣做的一個方法就是當帶 外 數據 到達時,使Linux內核向我們的進程發送一個SIGURG 信號。

使用SIGURG 信號通知需要兩個先決條件:

我們必須擁有套接口

我們必須為SIGURG 創建一個信號處理器

要接收SIGURG 信號,我們的進程必須為套接口的所有者。要建立這樣的擁有關系,我們可以使用fcntl函數。其函數原型如下:

#include <unistd.h>

#include <fcntl.h>

int fcntl(int fd, int cmd, long arg);

函數參數如下:

1 要在其上執行控制函數的文件描述符fd(或是套接口)

2 要執行的控制函數cmd

3 要設置的值arg

函數的返回值依賴於fcntl所執行的控制函數。對於課外閱讀感興趣的讀者,fcntl的Linux man手冊頁詳細的描述了cmd的F_SETOWN操作。

要將我們的進程創建為套接口的所有者,接收程序需要使用下面的代碼:

int z; /* Status */

int s; /* Socket */

z = fcntl(s,F_SETOWN,getpid());

if ( z == -1 ) {

perror("fcntl(2)");

exit(1);

}

F_SETOWN操作會使得fcntl函數成功時返回0,失敗時返回-1。

另外一個先決條件是程序必須准備好接收SIGURG 信號,這是通過為信號創建一個信號處理器來做到的。我們很快就會看到這樣的一個例子。

接收SIGURG 信號

移開了這些煩瑣的工作以後,現在我們可以來探索有趣的帶 外 數據 的概念了。下面所列的程序代碼就是我們用來接收數據 和當帶 外 數據 到達時處理帶 外 數據 的程序。他設計使用BSD解釋來處理帶 外 數據 ,而這也正是Linux的默認情況。

/*

* oobrec.c

*

* Example OOB receiver:

*/

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <errno.h>  
#include <unistd.h>  
#include <signal.h>  
#include <fcntl.h>  
#include <sys/types.h>  
#include <sys/socket.h>  
      
extern void bail(char *on_what);  
extern int BindAccept(char *addr);  
      
static int s = -1;   /* Socket */
      
/* 
* SIGURG signal handler: 
*/
static void sigurg (int signo)  
{  
   int n;  
   char buf[256];  
      
   n = recv(s,buf,sizeof buf,MSG_OOB);  
   if(n<0)  
       bail("recv(2)");  
      
   buf[n] = 0;  
   printf("URG ''%s'' (%d) /n",buf,n);  
      
   signal(SIGURG ,sigurg );  
}  
      
int main(int argc,char **argv)  
{  
   int z;   /* Status */
   char buf[256];  
      
   /* 
   * 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.1: 
   */
   s = BindAccept(argc >=2 ?argv[1] :"127.0.0.1:9011");  
      
   /* 
   * Establish owership: 
   */
   z = fcntl(s,F_SETOWN,getpid());   
   if(z==-1)  
       bail("fcntl(2)");  
      
   /* 
   * Catch SIGURG : 
   */
   signal(SIGURG ,sigurg );  
      
   for(;;)  
   {  
       z = recv(s,buf,sizeof buf,0);  
       if(z==-1)  
           bail("recv(2)");  
       if(z==0)  
           break;  
       buf[z] = 0;  
      
       printf("recv ''%s'' (%d) /n",buf,z);  
   }  
      
   close(s);  
   return 0;  
}

然而,在我們將接收程序投入使用之前,我們還需要一個發送程序。

發送帶外 數據

下面列出的程序演示了一個簡短的發送程序,他只可以傳輸一些小的字符串,然後停止發送帶 外 數據 。這個程序為了在接收端管理傳送塊使用了許多的sleep(3)調用。

/* 
* oobsend.c 
* 
* Example OOB sender: 
*/
#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <errno.h>  
#include <sys/types.h>  
#include <sys/socket.h>  
      
extern void bail(char *on_what);  
extern int Connect(char *addr);  
      
/* 
* Send in-band data: 
*/
static void iband(int s,char *str)  
{  
   int z;  
      
   z = send(s,str,strlen(str),0);  
   if(z==-1)  
       bail("send(2)");  
      
   printf("ib: ''%s'' (%d) /n",str,z);  
}  
      
/* 
* Send out-of-band data: 
*/
static void oband(int s,char *str)  
{  
   int z;  
      
   z = send(s,str,strlen(str),MSG_OOB);  
   if(z==-1)  
       bail("send(2)");  
      
   printf("OOB ''%s'' (%d)/n",str,z);  
}  
      
int main(int argc,char **argv)  
{  
   int s = -1;  
      
   s = Connect(argc >=2  
           ? argv[1]  
           : "127.0.0.1:9011");  
      
   iband(s,"In the beginning");  
   sleep(1);  
      
   iband(s,"Linus begat Linux,");  
   sleep(1);  
      
   iband(s,"and the Penguins");  
   sleep(1);  
      
   oband(s,"rejoiced");  
   sleep(1);  
      
   iband(s,"exceedingly.");  
   close(s);  
      
   return 0;  
}

編譯程序:

$ make oobrecv oobsend

gcc -c -D_GNU_SOURCE -Wall -Wreturn-type -g oobrecv.c

gcc -c -D_GNU_SOURCE -Wall -Wreturn-type -g mkaddr.c

gcc -c -D_GNU_SOURCE -Wall -Wreturn-type -g bindacpt.c

gcc oobrecv.o mkaddr.o bindacpt.o -o oobrecv

gcc -c -D_GNU_SOURCE -Wall -Wreturn-type -g oobsend.c

gcc oobsend.o mkaddr.o bindacpt.o -o oobsend

$

在編譯完成以後,我們得到兩個可執行程序:

oobrecv 是接收程序(一個服務器)

oobsend 是發送程序(一個客戶端)

現在我們已經准備好來調用這兩個程序了。

測試oobrecv與oobsend程序

最好是在兩個不同的終端會話上運行這兩個程序。使用兩個不同的xterm窗口,或是兩個不同的終端會話。首先在第一個終端會話中啟動接收程序:

$ ./oobrecv

如果我們希望指定我們的以太網地址而不是使用默認的回環地址,那麼這兩個程序都接收一個地址與端口號對。例如,下面的將會工作在一個NIC卡地址為192.168.0.1的系統上:

$ ./oobrecv 192.168.0.1:9023

這會啟動服務器在192.168.0.1的9023端口上監聽。然而,為了演示,我們可以不指定參數來運行這個程序。

現在在第二個終端會話中啟動發送程序:

$ ./oobsend

ib: ''In the beginning'' (16)

ib: ''Linus begat Linux,'' (18)

ib: ''and the Penguins'' (16)

OOB ''rejoiced'' (8)

ib: ''exceedingly.'' (12)

$

以ib:開始的行表明寫入的帶 內數據 。以OOB開始的行表明''rejoiced''是作為帶 外 數據 寫入套接口的。

如果我們可以同時觀察兩個終端,我們就會發現接收程序報告數據 稍晚於發送程序發送數據 。其會話輸出類似於下面的樣子:

$ ./oobrecv

rcv ''In the beginning'' (16)

rcv ''Linus begat Linux,'' (18)

rcv ''and the Penguins'' (16)

URG ''d'' (1)

rcv ''rejoice'' (7)

rcv ''exceedingly.'' (12)

$

在這個終端會話中顯示的以rcv開始的行表明接收到的通常的帶 內數據 。以URG開始的行表明接收到SIGURG 信號,並且信號處理程序被調用。在信號處理器中,緊急數據 被讀取並報告。我們應注意到一個很奇怪的事情--只有d字節被作為帶 外 數據 接收。為什麼是這樣?

理解緊急指針

在這一章前面,套接口接口提供了一個通常的網絡接口。這就包括他如何處理帶 外 數據 。然而,緊急數據的TCP 實現卻達不到帶 外 數據 所包含的通常概念。 盡管整個字符串''rejoiced''使用send作為帶 外 數據 發送,但是在接收端我們可以觀察到下列內容:

只有d字符作為帶 外 數據 被接收

d字符在其余的''rejoice''之前到達

d字符在''rejoice''之前被接收的事實確實演示了d字符更為緊急的事實。他表明字節順序已經被一個緊急元素所打亂。

理解TCP 緊急模式

只有一個字節被作為帶 外 數據 被接收的事實與一個TCP 協議概念到一個套接口概念的映射有關。TCP 緊急模式被映射到更為通常的帶 外 數據 的套接口概念。

TCP 協議本身並不提供帶 外 數據 程序。最接近於這個套接方式的概念就是通信的TCP 緊急模式。為了使得我們理解緊急模式是如何工作,在這裡有必要進行一些TCP 協議的討論。

當設置了MSG_OOB位使用send套接口接口函數時,數據 寫入了TCP 的外行隊列,並且建立了一個緊急指針。這個指針的確切位置是由我們在前面所說的tcp_stdurg來決定的。 下表列出回顧了我們前面所說的兩種解釋,並且表明了緊急指針的位置:

值       解釋       緊急指針

0       BSD解釋       緊急字節之後

1       RFC793解釋   緊急字節之前

下圖顯示了send調用在將字符串''rejoiced''排列為帶 外 數據 返回之後,TCP 發送緩沖區的可視化情況。盡管我們並不對BSD解釋感興趣,但是在這個圖中同時顯示了兩種解釋的情況。

對於BSD解釋,使用MSG_OOB標志調用send所發生的事件隊列為:

1 數據 放入TCP 的外行隊列(在這種情況為空TCP 緩沖區的開始處)

2 開啟TCP 緊急模式(一個TCP URG位設置為真)

3 計算緊急指針,指向輸入外行TCP 隊列的最後一個字節之後。

在例子程序oobsend.c中,send調用之後跟隨著了一個sleep調用。這個動作會使得Linux內核執行下列操作:

1 發送目前為止在TCP 緩沖區中已經排隊的數據 ,而不是等待更多的數據 。

2 現在由TCP 協議所創建的數據 包頭設置了URG位。這就表明使用TCP 緊急模式(這是因為設置了MSG_OOB位來調用send函數)

3 計算一個TCP 緊急指針並且放在數據 包頭中。在這個例子中(tcp_stdurg=0),這個指針指向已排隊的帶 外 數據 的最後一個字節之後。

4 包含URG位,緊急指針以及所有等待發送的數據 包的數據 包頭現在作為一個物理數據 包發送到網絡接口設備。

執行完這些步驟之後,數據 包立刻加帶 傳遞到網絡的接收主機。這個數據 在遠程端被接收,概念上如下圖所示。

當一個URG位被設置為真的數據 包被接收到時,Linux內核會使用信號SIGURG 通知擁有這個套接品的進程。之所以這樣做,是因為數據 包包含一個緊急指針(這也就是為什麼要在TCP 頭設置URG位的原因)。

程 序oobrecv.c,一旦處理SIGURG 信號,就會設置MSG_OOB標志,通過recv調用來讀取帶 外 數據 。這會使得Linux內核只返回帶 外 數 據 。因為TCP 並不會記錄帶 外 數據 的起始位置,套接口API只會返回數據 包內緊急指針之前的一個字節(假設tcp_stdurg=0)。 相應的,在我們的 例子中,只有字節d作為帶 外 數據 返回。任何帶 內數據 的讀取隊列會讀取其余的''rejoice''字節,以及緊急字節之後的數據 (如果存在)。

盡管帶 外 數據 並不是在信號處理函數中讀取,只會讀取''rejoice''字節以及非緊急數據 序列。d字節會被阻止在通常的帶 內數據 中返回,是因為他已被標識為帶 外 數據 。

tcp_stdurg=1的緊急模式

空 間並不允許我們詳細討論這種情況,但是一些小的評論還是值得的。當tcp_stdurg=1時,通常會發生一件奇怪的事情,通常會進入緊急模式,而其相應 的緊急指針也會被接收,但是卻並不會讀取相應的緊急數據 。如果緊急指針正如位於數據 包中最後一個數據 字節之後,那麼也許在其後就會再接收到任何數據 字節。 緊急數據也 許會在其後的一個數據 包中。正是因為這個原因,當使用這種模式時,當收到SIGURG 信號時,設置了MSG_OOB位的recv調用並不需要必須為TCP 返回一個帶 外 數據 。

要處理緊急數據 不可得的情況,我們必須執行下面的操作(記住,這適用於tcp_stdurg=1的情況):

1 在一個標記中記錄SIGURG 事件(也就是一個名為urg_mode=1的變量)。

2 由我們的信號處理器中返回。

3 繼續讀取我們程序中的帶 內數據 。

4 當urg_mode的值為真時,試著使用設置了MSG_OOB標記位的recv函數來讀取帶 外 數據 。

5 如果步驟4得到數據 ,那麼設置urg_mode=0,並且返回通常的處理。重復步驟3。

6 如果步驟4沒有得到任何帶 外 數據 ,將urg_mode設置為真繼續處理。重復步驟3。

再一次,必須強調我們也許不會為Linux代碼執行這些步驟,除非Linux改變了方向。Linux默認使用BSD(tcp_stdurg=0)緊急數據 模式,而這是較為容易處理的。

接收內聯帶 外 數據

在前面,我們已經談到,也可以在通常的帶 內數據 混合接收帶 外 數據 。有時用這樣的方式來處理會更為方便。要為一個特定的套接口打開這種操作模式,我們必須設置SO_OOBINLINE套接口選項:

例如

int z;                   /* Status */

int s;                   /* Socket */

int oobinline =1;      /* TRUE */

z = setsockopt(s,

SOL_SOCKET,        /* Level */

SO_OOBINLINE,      /* Option */

&oobinline,       /* ptr to value */

sizeof oobinline); /* Size of value */

警告

如果我們為一個套接口打開了這個選項,我們就不可以使用設置了MSG_OOB標記位的recv函數。如果我們這樣做了,我們就會返回一個錯誤,而變量errno會被設置為EINVAL。

注意

如果我們覺得有用,也可以使用SIGURG 信號。這是通過一個使用F_SETOWN的fcntl函數來建立了。

確定緊急指針

無論我們是否正在使用內聯數據 的方式進行接收,當我們接收到當前數據 流中的數指針時,我們都可以自由的使用一個函數來通知我們。這可以通過正確的參數來調用ioctl(2)來確定。

例如

#include <sys/ioctl.h>

. . .

int z;     /* Status */

int s;     /* Socket */

int flag; /* True when at mark */

z = ioctl(s, SIOCATMARK,&flag);

if ( z == -1 )

abort();         /* Error */

if ( flag != 0 )

puts("At Mark");

else

puts("Not at mark.");

現在我們已經了解了前面所介紹地功能,下面我們將使用一個修改的oobrecv程序來演示接收內聯數據 ,並且在接收到數據 時測試緊急數據 標記。

使用內聯帶 外 數據

下面演示的是一個新版本的oobinline.c程序,他會同時接收帶 內數據 與帶 外 數據 。同時他包含一個經過修改的SIGURG 信號處理器,這樣他就會在緊急數據 到達時報告。這就會允許我們觀察許多事件。

<span style="font-size:18px;"><strong>/* 
* oobinline.c 
* 
* OOB inline receiver: 
*/
#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <errno.h>  
#include <signal.h>  
#include <fcntl.h>  
#include <sys/ioctl.h>  
#include <sys/types.h>  
#include <sys/socket.h>  
      
extern void bail(char *on_what);  
extern int BindAccept(char *addr);  
      
/* 
* SIGURG signal handler: 
*/
static void sigurg (int signo)  
{  
   write(1,"[SIGURG ]/n",9);  
   signal(SIGURG ,sigurg );  
}  
      
/* 
* Emulate the IEEE Std 1003.1g 
* standard function sockatmark(3): 
*/
static int Sockatmark(int s)  
{  
   int z;  
   int flag;  
      
   z = ioctl(s,SIOCATMARK,&flag);  
   if( z == -1 )  
       return -1;  
   return flag ? 1 : 0;  
}  
      
int main(int argc,char **argv)  
{  
   int z;       /* Status */
   int s;       /* Socket */
   int oobinline= 1;   /* oob inline */
   char buf[256];  
      
   /* 
   * 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.1; 
   */
   s = BindAccept(argc >= 2  
           ? argv[1]  
           : "127.0.0.1:9011");  
      
   /* 
   * Establish ownership: 
   */
   z = fcntl(s,F_SETOWN,getpid());  
   if(z==-1)  
       bail("fcntl(2)");  
      
   /* 
   * Catch SIGURG : 
   */
   signal(SIGURG ,sigurg );  
      
   /* 
   * Receive the OOB data inline: 
   */
   z = setsockopt(s,  
           SOL_SOCKET,  
           SO_OOBINLINE,  
           &oobinline,  
           sizeof oobinline);  
      
   if(z==-1)  
       bail("setsockopt(2)");  
      
   for(;;)  
   {  
       printf("/n[%s]/n",  
               Sockatmark(s)  
               ? "AT MARK"
               : " NO MARK");  
      
       z = recv(s,buf,sizeof buf,0);  
       if(z==-1)  
           bail("recv(2)");  
       if(z==0)  
           break;  
       buf[z]=0;  
      
       printf("rcv ''%s''(%d)/n",  
               buf,z);  
   }  
      
   close(s);  
   return 0;  
}  
</strong></span>

現在編譯這個程序:

                                    M

$ make oobinline

gcc -c -D_GNU_SOURCE -Wall -Wreturn-type -g oobinline.c

                                     FL

gcc oobinline.o mkaddr.o bindacpt.o -o oobinline

$

執行下列步驟來進行測試:

1 在第一個終端會話中,啟動oobinline程序。

2 在第二個終端會話中,啟動我們前面所用的oobsend程序。

發送程序的終端會話輸出如下所示:

$ ./oobsend

ib: ''In the beginning'' (16)

ib: ''Linus begat Linux,'' (18)

ib: ''and the Penguins'' (16)

OOB ''rejoiced'' (8)

ib: ''exceedingly.'' (12)

$

這個終端會話與前面的例子相同。然而,接收終端會話的輸出如下所示:

$ ./oobinline

[No Mark]

rev In the beginning (16)

[No Mark]

rev ''Linus begat Linux, (18)

[No Mark]

rev ''and the Penguins'' (16)

[No Mark]

[SIGURG ]

rev ''rejoice'' (7)

[AT MARK]

rev ''d'' (1)

[No Mark]

rev ''exceedingly.'' (12)

[No Mark]

$

注意,當接收字符串''rejoiced''時,與前面的例子相似也會啟動SIGURG 信號。然而注意,標記在直到先讀取''rejoice''字節才到達。然後達到標記,並且接收到額外的內聯字節(d)。在這裡需要注意幾點:

與沒有使用內聯緊急數據 讀取時一樣,SIGURG 信號要盡早到達。

帶 內數據 必須在讀取帶 外 數據 之前順序讀取。

盡管所傳送的數據 包作為一個整體包含整個''rejoiced''字符串,而recv函數會在緊急數據 字節所處的位置停止(接收在d字節處停止)。

接下來需要調用recv函數讀取緊急數據 。對於TCP ,在這個例子中只是一個單一字節。

通常,數據 由一個流式套接口中讀取,而不必指定信息邊界。然而,我們會發現,當緊急數據 由內聯讀取時,確實形成了一個邊界。讀取會在緊急數據 處停止。如果不是這樣,我們就會很容易讀過標記。

緊急指針的限制

到現在為止,我們已經演示了TCP 確實只提供了一個帶 外 數據 字節。這是因為他是使用協議的TCP 緊急模式特性來實現的。

我們很容易會認為TCP 緊急模式及其緊急指針應使得他可以標記緊急數據 的邊界。然而,實際上並不是這樣的,因為緊接著的帶 外 數據 的發送會覆蓋接收者原始的緊急數據 標記,而這個標記也許還沒有進行處理。

如果我們修改oobsend.c程序就可以進行演示。移除所有的sleep(3)函數調用,在oband(s,"rejoiced")之後插入一個oband(s,"very")調用。主程序看起來如下所示:

int
main(int argc,char **argv) {
    int s = -1;      /* Socket */
    s = Connect(argc >= 2
        ? argv[1]
        : "127.0.0.1:9011");
    iband(s,"In the beginning");
    iband(s,"Linus begat Linux,");
    iband(s,"and the Penguins");
    oband(s,"rejoiced");
    oband(s,"very");
    iband(s,"exceedingly.");
    close(s);
    return 0;
}

當再一次運行這個測試時,在一個快速的系統上,我們會收到如下的結果:

$ ./oobinline
[No Mark]
rcv ''In the beginning'' (16)
[No Mark]
rcv ''Linus begat Linux,'' (18)
[No Mark]
[SIGURG ]
rcv ''and the Penguinsrejoicedver'' (27)
[AT MARK]
rcv ''yexceedingly.'' (13)
[No Mark]

在這裡需要的注意的幾點:

只接收到一個SIGURG 信號。

只有一個緊急數據 標記,盡管在發送端寫入了兩個帶 外 數據 。

在字符串''yexceedingly''中第一個y是一個帶 外 數據 字節。接下來的字節只是簡單的隨後發送的帶 內數據 字節。

前面的測試依賴於sleep(3)所提供的到物理數據 控制集合的延遲。

正 如我們前面的注意所指出的,我們的結果與許會與我們例子輸出略有不同。當由一個低速的486系統向一個快速的Pentium III 系統發送時會顯示出更多的不同。當由一個快速的CPU向一個慢速的CPU發送時會觀察到另外一個接收模式,其他的因素會決定數據 包如何進行分割。

使用select(2)處理帶 外 數據

在這一章還有一些空間來討論這個特殊的話題,但只是一些簡單的建議看起來也許會更合適。

對於select函數調用,帶 外 數據 的概念是一個異常。我們也許可以記起第11章,"並發客戶端服務器",select會阻塞,直到下面的一個或是多個事件發生:

一個讀事件(要讀取的數據 已到達)

一個寫事件(數據 現在可以寫入)

一個異常(帶 外 數據 到達)

我們的程序可以在select調用中捕獲這個異常。然後,可以在必要時使用設置了MSG_OOB標記位的recv函數來處理帶 外 數據 。

Copyright © Linux教程網 All Rights Reserved