定義帶外數據
想 像一下在銀行人們排起隊等待處理他們的帳單。在這個隊伍中每個人最後都會移到前面由出納員進行服務。現在想像一下一個走入銀行,越過整個隊伍,然後用槍抵 住出納員。這個就可以看作為帶 外 數據 。這個強盜越過整個隊伍,是因為這把槍給了他凌駕於眾人的權力。出納員也會集中注意力於這個強盜身上,因為他知道當前 的形勢是很緊急的。
相應的,一個連接的流式套接口上的帶 外 數據 的工作原理也與此類似。通常情況下,數據 由連接的一端流到另一端,並且認為 數據 的所有字節都是精確排序的。晚寫入的字節絕不會早於先寫入的字節到達。然而套接口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函數來處理帶 外 數據 。