带 外 数据

 定义带 外 数据 

想 像一下在银行人们排起队等待处理他们的帐单。在这个队伍中每个人最后都会移到前面由出纳员进行服务。现在想像一下一个走入银行,越过整个队伍,然后用枪抵 住出纳员。这个就可以看作为  数据 。这个强盗越过整个队伍,是因为这把枪给了他凌驾于众人的权力。出纳员也会集中注意力于这个强盗身上,因为他知道当前 的形势是很紧急的。

相应的,一个连接的流式套接口上的  数据 的工作原理也与此类似。通常情况下,数据 由连接的一端流到另一端,并且认为 数据 的所有字节都是精确排序的。晚写入的字节绝不会早于先写入的字节到达。然而套接口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:

*/

[cpp]  view plain copy print ?
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <string.h>  
  4. #include <errno.h>  
  5. #include <unistd.h>  
  6. #include <signal.h>  
  7. #include <fcntl.h>  
  8. #include <sys/types.h>  
  9. #include <sys/socket.h>  
  10.   
  11. extern void bail(char *on_what);  
  12. extern int BindAccept(char *addr);  
  13.   
  14. static int s = -1;   /* Socket */  
  15.   
  16. /* 
  17. * SIGURG signal handler: 
  18. */  
  19. static void sigurg (int signo)  
  20. {  
  21.    int n;  
  22.    char buf[256];  
  23.   
  24.    n = recv(s,buf,sizeof buf,MSG_OOB);  
  25.    if(n<0)  
  26.        bail("recv(2)");  
  27.   
  28.    buf[n] = 0;  
  29.    printf("URG ''%s'' (%d) /n",buf,n);  
  30.   
  31.    signal(SIGURG ,sigurg );  
  32. }  
  33.   
  34. int main(int argc,char **argv)  
  35. {  
  36.    int z;   /* Status */  
  37.    char buf[256];  
  38.   
  39.    /* 
  40.    * Use a server address from the command 
  41.    * line,if one has been provided. 
  42.    * Otherwise,this program will default 
  43.    * to using the arbitrary address 
  44.    * 127.0.0.1: 
  45.    */  
  46.    s = BindAccept(argc >=2 ?argv[1] :"127.0.0.1:9011");  
  47.   
  48.    /* 
  49.    * Establish owership: 
  50.    */  
  51.    z = fcntl(s,F_SETOWN,getpid());   
  52.    if(z==-1)  
  53.        bail("fcntl(2)");  
  54.   
  55.    /* 
  56.    * Catch SIGURG : 
  57.    */  
  58.    signal(SIGURG ,sigurg );  
  59.   
  60.    for(;;)  
  61.    {  
  62.        z = recv(s,buf,sizeof buf,0);  
  63.        if(z==-1)  
  64.            bail("recv(2)");  
  65.        if(z==0)  
  66.            break;  
  67.        buf[z] = 0;  
  68.   
  69.        printf("recv ''%s'' (%d) /n",buf,z);  
  70.    }  
  71.   
  72.    close(s);  
  73.    return 0;  
  74. }  

然而,在我们将接收程序投入使用之前,我们还需要一个发送程序。

发送带 外 数据 

下面列出的程序演示了一个简短的发送程序,他只可以传输一些小的字符串,然后停止发送     数据  。这个程序为了在接收端管理传送块使用了许多的sleep(3)调用。

[cpp]  view plain copy print ?
  1. /* 
  2. * oobsend.c 
  3. * 
  4. * Example OOB sender: 
  5. */  
  6. #include <stdio.h>  
  7. #include <stdlib.h>  
  8. #include <string.h>  
  9. #include <unistd.h>  
  10. #include <errno.h>  
  11. #include <sys/types.h>  
  12. #include <sys/socket.h>  
  13.   
  14. extern void bail(char *on_what);  
  15. extern int Connect(char *addr);  
  16.   
  17. /* 
  18. * Send in-band data: 
  19. */  
  20. static void iband(int s,char *str)  
  21. {  
  22.    int z;  
  23.   
  24.    z = send(s,str,strlen(str),0);  
  25.    if(z==-1)  
  26.        bail("send(2)");  
  27.   
  28.    printf("ib: ''%s'' (%d) /n",str,z);  
  29. }  
  30.   
  31. /* 
  32. * Send out-of-band data: 
  33. */  
  34. static void oband(int s,char *str)  
  35. {  
  36.    int z;  
  37.   
  38.    z = send(s,str,strlen(str),MSG_OOB);  
  39.    if(z==-1)  
  40.        bail("send(2)");  
  41.   
  42.    printf("OOB ''%s'' (%d)/n",str,z);  
  43. }  
  44.   
  45. int main(int argc,char **argv)  
  46. {  
  47.    int s = -1;  
  48.   
  49.    s = Connect(argc >=2  
  50.            ? argv[1]  
  51.            : "127.0.0.1:9011");  
  52.   
  53.    iband(s,"In the beginning");  
  54.    sleep(1);  
  55.   
  56.    iband(s,"Linus begat Linux,");  
  57.    sleep(1);  
  58.   
  59.    iband(s,"and the Penguins");  
  60.    sleep(1);  
  61.   
  62.    oband(s,"rejoiced");  
  63.    sleep(1);  
  64.   
  65.    iband(s,"exceedingly.");  
  66.    close(s);  
  67.   
  68.    return 0;  
  69. }  

编译程序:
$ 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所发生的事件队列为:

数据 放入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 信号处理器,这样他就会在紧急数据 到达时报告。这就会允许我们观察许多事件。

[cpp]  view plain copy print ?
  1. <span style="font-size:18px;"><strong>/* 
  2. * oobinline.c 
  3. * 
  4. * OOB inline receiver: 
  5. */  
  6. #include <stdio.h>  
  7. #include <stdlib.h>  
  8. #include <string.h>  
  9. #include <unistd.h>  
  10. #include <errno.h>  
  11. #include <signal.h>  
  12. #include <fcntl.h>  
  13. #include <sys/ioctl.h>  
  14. #include <sys/types.h>  
  15. #include <sys/socket.h>  
  16.   
  17. extern void bail(char *on_what);  
  18. extern int BindAccept(char *addr);  
  19.   
  20. /* 
  21. * SIGURG signal handler: 
  22. */  
  23. static void sigurg (int signo)  
  24. {  
  25.    write(1,"[SIGURG ]/n",9);  
  26.    signal(SIGURG ,sigurg );  
  27. }  
  28.   
  29. /* 
  30. * Emulate the IEEE Std 1003.1g 
  31. * standard function sockatmark(3): 
  32. */  
  33. static int Sockatmark(int s)  
  34. {  
  35.    int z;  
  36.    int flag;  
  37.   
  38.    z = ioctl(s,SIOCATMARK,&flag);  
  39.    if( z == -1 )  
  40.        return -1;  
  41.    return flag ? 1 : 0;  
  42. }  
  43.   
  44. int main(int argc,char **argv)  
  45. {  
  46.    int z;       /* Status */  
  47.    int s;       /* Socket */  
  48.    int oobinline= 1;   /* oob inline */  
  49.    char buf[256];  
  50.   
  51.    /* 
  52.    * use a server address from the command 
  53.    * line,if one has been provided. 
  54.    * otherwise,this program will default 
  55.    * to using the arbitrary address 
  56.    * 127.0.0.1; 
  57.    */  
  58.    s = BindAccept(argc >= 2  
  59.            ? argv[1]  
  60.            : "127.0.0.1:9011");  
  61.   
  62.    /* 
  63.    * Establish ownership: 
  64.    */  
  65.    z = fcntl(s,F_SETOWN,getpid());  
  66.    if(z==-1)  
  67.        bail("fcntl(2)");  
  68.   
  69.    /* 
  70.    * Catch SIGURG : 
  71.    */  
  72.    signal(SIGURG ,sigurg );  
  73.   
  74.    /* 
  75.    * Receive the OOB data inline: 
  76.    */  
  77.    z = setsockopt(s,  
  78.            SOL_SOCKET,  
  79.            SO_OOBINLINE,  
  80.            &oobinline,  
  81.            sizeof oobinline);  
  82.   
  83.    if(z==-1)  
  84.        bail("setsockopt(2)");  
  85.   
  86.    for(;;)  
  87.    {  
  88.        printf("/n[%s]/n",  
  89.                Sockatmark(s)  
  90.                ? "AT MARK"  
  91.                : " NO MARK");  
  92.   
  93.        z = recv(s,buf,sizeof buf,0);  
  94.        if(z==-1)  
  95.            bail("recv(2)");  
  96.        if(z==0)  
  97.            break;  
  98.        buf[z]=0;  
  99.   
  100.        printf("rcv ''%s''(%d)/n",  
  101.                buf,z);  
  102.    }  
  103.   
  104.    close(s);  
  105.    return 0;  
  106. }  
  107. </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函数来处理  数据 。

你可能感兴趣的:(带 外 数据)