带外数据

http://blog.csdn.net/xinjixjz/article/details/7483487

 定义带 外 数据 

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

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

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

/*
* 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;
}


现在编译这个程序:

                                         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函数来处理带 外 数据 。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值