TCP带外数据OOB

时间:2021-05-15 03:59:32

传输层协议使用带外数据(out-of-band,OOB)来发送一些重要的数据,如果通信一方有重要的数据需要通知对方时,协议能够将这些数据快速地发送到对方.为了发送这些数据,协议一般不使用与普通数据相同的通道,而是使用另外的通道实现.

OOB数据(TCP)介绍

带外数据即就是优先数据,linux系统的套接字机制支持低层协议发送和接受带外数据.但是TCP协议没有真正意义上的带外数据.为了发送重要协议,TCP提供了一种称为紧急模式(urgent mode)的机制。TCP在报文头中设置URG位,表示进入紧急模式.接收方可以对紧急模式采取特殊的处理。

TCP带外数据OOB
tcp报文格式

很容易看出来,这种方式数据不容易被阻塞,可以通过在我们的服务器端程序里面捕捉SIGURG信号来及时接受数据或者使用带OOB标志的recv函数来接受.使用send和recv函数时,可以指定最后一个参数flags为MSG_OOB来发送接收带外数据。

    send(...,MSG_OOB,);
recv(...,MSG_OOB,);

OOB数据特点

1.OOB数据每次只能是一个字符
2.普通数据使用一般方式接收与发送,OOB数据使用MSG_OOB接收与发送
3.一个数据使用MSG_OOB,则最后一个字符是OOB数据,其他的是非OOB数据
4.OOB数据是优先数据。优先体现在什么地方?
在我看来OOB数据优先体现在同一次发送数据(包含普通数据和OOB数据)时,OOB数据会优先于普通数据到达目标端。下边有实例可以认证这一点。

带外数据发送过程:和普通数据发送过程一样,只是在数据字符串最后一个字符处设置一个标记,目标端接收到字符串,也是存放在一个描述符号中,但是会将指针指向最后一个字符上;接收进程发现字符上有带外数据标记,会发送信号SIGURG(紧急数据),即带外数据的优先级体现在信号的优先。

使用带外数据的实际例子telnet、rlogin、ftp命令,前两个命令会将中止字符作为紧急数据发送到远端,这会使得远端冲洗所有未处理的输入,并且丢弃所有为发送的终端输入,会快速中断一个向屏幕发送大量数据的运行进程;ftp命令使用带外数据来中断一个文件的传输。

在循环发送数据时,带外数据标记可能会被覆盖,导致形成一长串字符,只有最后一个被标记带外数据,其他的被覆盖。

案例:

客户程序send函数设置为MSG_OOB:
服务器接收数据recv两次:
a.不设置为MSG_OOB标志,服务器接收的数据没有最后一个字符;
b.设置为MSG_OOB标志,只会接收到一个字符,也就是带外数据。

oob_server.c
recv MSG_OOB
oob_client.c
send MSG_OOB

对于这个实例,客户端程序发送带外数据,服务器端有两种方式可以接受数据,信号和select。

客户端程序如下:

/**
* oob_client.c
* */
main()
{
int fd;
int r;
struct sockaddr_in dr;

fd=socket(AF_INET,SOCK_STREAM,0);
if(fd==-1) printf("socket err:%m\n"),exit(-1);

dr.sin_family=AF_INET;
dr.sin_port=htons(9999);
dr.sin_addr.s_addr=inet_addr("192.168.245.149");

r=connect(fd,(struct sockaddr*)&dr,sizeof(dr));
if(r==-1) printf("connect err:%m\n"),exit(-1);

r=send(fd,"hello tty",9,MSG_OOB); //发送带外数据,只有最后一个字节被标识为带外数据,其他的为普通数据,并且带外数据会优先于普通数据到达
if(r==-1) printf("send err:%m\n"),exit(-1);
close(fd);
}

1. 使用SIGURG信号处理带外数据

/**
* oob_server_signal.c
**/

void handle(int s)
{
int r;
char data[100];
r=recv(cfd,data,100,MSG_OOB);
if(r>0){
data[r]=0;
printf("接收带外数据:%s\n",data);
}
}
main()
{
/*此处为server建立socket,并bind地址*/
fcntl(cfd,F_SETOWN,getpid()); //将进程创建为套接口的所有者,使得带外数据到达时,内核向进程发送一个SIGURG信号
signal(SIGURG,handle);
while(1)
{
r=recv(cfd,buf,1024,0);
if(r>0) {
buf[r]=0;
printf("普通数据:%s\n",buf);
} else
break;
}
...
}

运行结果:

[ty@tiany I_O]$ ./oob_server_signal 
接收带外数据:y
普通数据:hello tt

由输出可以发现字符串最后一个字符‘y’先被接收到,正是体现的带外数据的紧急特性,也就是TCP的紧急模式。

2. 使用select异常接收带外数据

 int main(int argc, char **argv)
{
/*此处为server建立socket,并bind地址,省略*/
char buf[1024]; //buf缓冲区接收数据(普通数据和带外数据)
fd_set read_fds; //接收普通数据,放入读事件集合中
fd_set exceptions_fds; //接收带外数据,发生异常,放入异常事件集合中

FD_ZERO(&read_fds);
FD_ZERO(&exceptions_fds);

while(1) {
FD_SET(connfd,&read_fds);
FD_SET(connfd,&exceptions_fds);
ret = select(connfd+1,&read_fds,NULL,&exceptions_fds,NULL);

if(FD_ISSET(connfd,&read_fds)){
memset(buf,0x00,sizeof(buf));
ret = recv(connfd,buf,sizeof(buf)-1,0);
}

if(FD_ISSET(connfd,&exceptions_fds)) {
memset(buf,0x00,sizeof(buf));
ret =recv(connfd,buf,sizeof(buf)-1,MSG_OOB);
}
}
...
}

对于select函数的使用参看我上一边博文。
完整代码获取链接:https://github.com/ty92/OOB