在上大学的时候,我们可能就听说了OOB(Out Of Band 带外数据,又称紧急数据)这个概念。

当时老师给的解释就是在当前处理的数据流之外的数据,用于紧急的情况。然后就没有然后了……

毕业这么多年了,回想一下,还真是没有接触过OOB的场景,更没有实地发送、接收过OOB。

那么到底该怎样处理OOB呢?OOB在所谓的紧急情况下是否有用呢?下面一一道来。

首先产生OOB是非常简单的,只需要在寻常send的最后一个参数,加入MSG_OOB标志位:

ret = send (sockfd, ptr, n, MSG_OOB);

如果考虑一个完整的测试场景,需要有惯常数据,中间夹带OOB数据,这样才能比较好的测试接收端是否能正确的区分他们,

所以客户端可以写成这样:

     strcpy(buf, "abcdefghijklmn");
char const* ptr = buf;
if ((ret = send (sockfd, ptr, , )) < )
err_sys ("send normal head failed");
else
printf ("send normal head %d\n", ret); ptr += ;
n = ;
if ((ret = send (sockfd, ptr, n, MSG_OOB)) < )
err_sys ("send oob failed");
else
printf ("send oob %d\n", ret); ptr += n;
if ((ret = send (sockfd, ptr, , )) < )
err_sys ("send normal tail failed");
else
printf ("send normal tail %d\n", ret);

算法比较简单,先发送2字节惯常数据,接着1字节OOB,最后2字节惯常数据结尾。

需要注意的是,目前只有TCP支持OOB,UDP没所谓顺序,更没所谓带内带外之分,所以也没有OOB;

另外TCP目前大多数实现只支持1字节OOB,大于1字节的OOB,只有最后一字节会被当为OOB处理,之前的作为普通数据。

然后我们来说一下接收OOB的三种方法:

1. 使用SIGURG信号专门处理OOB

这种方法是将OOB与惯常数据分开处理,具体步骤如下:

a) 进程起始时,建立SIGURG信号处理器

     struct sigaction sa;
sa.sa_handler = on_urg;
sa.sa_flags |= SA_RESTART;
sigemptyset (&sa.sa_mask);
sigaction (SIGURG, &sa, NULL);

b) 建立新连接时,设置连接句柄的信号处理进程(为当前进程)

 fcntl (clfd, F_SETOWN, getpid ()); 

c) 在信号处理器中使用MSG_OOB接收带外数据

 int g_fd = ;
void on_urg (int signo)
{
int ret = ;
char buf[BUFLEN] = { };
ret = recv (g_fd, buf, sizeof (buf), MSG_OOB);
if (ret > )
buf[ret] = ;
else
strcpy (buf, "n/a"); printf ("got urgent data on signal %d, len %d, %s\n", signo, ret, buf); }

d) 惯常数据,可以在主处理流程中使用不带MSG_OOB的recv,像以前那样处理

         ret = recv (clfd, buf, sizeof(buf), );
if (ret > )
buf[ret] = ;
else
strcpy (buf, "n/a"); printf ("recv %d: %s\n", ret, buf);

由于惯常数据的接收,会被OOB打断,因此这里可能需要一个循环,不断接收惯常数据。

下面是方法1的接收输出:

hostname length: 64
get hostname: localhost.localdomain
setup SIGURG for oob data
setown to 31793
got urgent data on signal 23, len 1, c
recv 2: ab
has oob!
recv -1: n/a
recv 2: de
write back 70
recv 2: ab
recv 2: ab
got urgent data on signal 23, len 1, c
has oob!
recv -1: n/a
recv 2: de
write back 70
recv 2: ab
no oob!
got urgent data on signal 23, len 1, c
recv 2: de
write back 70
recv 2: ab
recv 2: ab
got urgent data on signal 23, len 1, c
has oob!
recv -1: n/a
recv 2: de
write back 70
^C

可以看到信号处理器中接收到的总是OOB数据'c',而普通recv只能读到非OOB数据'a''b''d''e'。而且普通数据的接收,会被OOB数据打断成两块,无法一次性读取。

2.使用SO_OOBINLINE标志位将OOB作为惯常数据处理

这种方法是将OOB数据当作惯常数据接收,在接收前通过判断哪些是普通数据哪些是OOB数据,具体步骤如下:

a) 新连接建立时,设置套接字选项SO_OOBINLINE

 setsockopt (fd, SOL_SOCKET, SO_OOBINLINE, &oil, sizeof (oil));

b) 在接收数据前,先判断下一个字节是否为OOB,如果是,则接收1字节OOB数据(注意不使用MSG_OOB标志)

         if (sockatmark (clfd))
{
printf ("has oob!\n");
ret = recv (clfd, buf, sizeof(buf), );
if (ret > )
buf[ret] = ;
else
strcpy (buf, "n/a"); printf ("recv %d: %s\n", ret, buf);
}
else
printf ("no oob!\n");

这里sockatmark当下个字节为OOB时返回1,否则返回0。

c) 如果不是,按惯常数据接收

         ret = recv (clfd, buf, sizeof(buf), );
if (ret > )
buf[ret] = ;
else
strcpy (buf, "n/a"); printf ("recv %d: %s\n", ret, buf);

同理,由于惯常数据会被OOB打断,上述代码总是可以正确的分离OOB与普通数据。

下面是方法2的接收输出:

hostname length: 64
get hostname: localhost.localdomain
setown to 31883
recv 2: ab
no oob!
recv 3: cde
write back 70
recv 2: ab
has oob!
recv 1: c
recv 2: de
write back 70
recv 2: ab
has oob!
recv 1: c
recv 2: de
write back 70
recv 2: ab
no oob!
recv 3: cde
write back 70
recv 2: ab
has oob!
recv 1: c
recv 2: de
write back 70
^C

可以看出,有时候OOB数据不能被正常的识别,会被当作普通数据处理掉。而且这种方式也不能体现OOB紧急的意义,没有给予它优先的处理权。

3.使用 select/epoll 多路事件分离

这种方法是利用select或epoll,将OOB数据作为exception事件与普通数据的read事件相分离,这里以select为例:

a) 建立 select 事件处理循环

     for (;;) {
// must set it in every loop.
memcpy (&rdds, &cltds, sizeof (cltds));
memcpy (&exds, &cltds, sizeof (cltds));
FD_SET(sockfd, &rdds);
ret = select (FD_SIZE+, &rdds, NULL, &exds, NULL);
……
}

b) 建立连接时,将连接fd加入待监听fd_set

             if (FD_ISSET(clfd, &rdds))
{
if (clfd == sockfd)
{
// the acceptor
printf ("poll accept in\n");
clfd = accept (sockfd, NULL, NULL);
if (clfd < ) {
printf ("accept error: %d, %s\n", errno, strerror (errno));
exit ();
} print_sockopt (clfd, "new accepted client");
// remember it
FD_SET(clfd, &cltds);
printf ("add %d to client set\n", clfd);
}
else
{
……
}
}

c) 连接上有数据到达时,如果是read事件,使用recv接收数据

             if (FD_ISSET(clfd, &rdds))
{
if (clfd == sockfd)
{
……
}
else
{
// the normal client
printf ("poll read in\n");
ret = recv (clfd, buf, sizeof(buf), );
if (ret > )
buf[ret] = ;
else
sprintf (buf, "errno %d", errno); printf ("recv %d from %d: %s\n", ret, clfd, buf);
if (ret <= ) {
FD_CLR(clfd, &cltds);
printf ("remove %d from client set\n", clfd);
}
}
}

d) 如果是exception事件,使用recv(..,MSG_OOB)接收带外数据

             if (FD_ISSET(clfd, &exds))
{
// the oob from normal client
printf ("poll exception in\n");
if (sockatmark (clfd))
{
printf ("has oob!\n");
ret = recv (clfd, buf, , MSG_OOB);
if (ret > )
buf[ret] = ;
else
sprintf (buf, "errno %d", errno); printf ("recv %d from %d on urgent: %s\n", ret, clfd, buf);
if (ret > ) {
// let clfd cleared in sig_cld
do_uptime (clfd);
}
else
{
FD_CLR(clfd, &cltds);
printf ("remove %d from client set\n", clfd);
}
}
else
printf ("no oob!\n");
}

此时,仍可使用sockatmark来判断是否为OOB数据,另外,如果在连接建立时设定了OOB_INLINE标志位,则此处应使用不带MSG_OOB的recv接收数据,

因为OOB数据已经被当作惯常数据来处理了,此处与方法2是一致的。

下面是方法3的输出:

setup handler for SIGCHLD ok
hostname length: 64
get hostname: localhost.localdomain
got event 1
poll accept in
add 4 to client set
got event 2
poll read in
recv 2 from 4: ab
poll exception in
has oob!
recv 1 from 4 on urgent: c
start worker process 4511
goto serve next client..
got event 1
poll read in
recv 2 from 4: de
got event 1
poll accept in
add 5 to client set
got event 2
poll read in
recv 2 from 5: ab
poll exception in
has oob!
recv 1 from 5 on urgent: c
start worker process 4513
goto serve next client..
got event 1
poll read in
recv 2 from 5: de
got event 1
poll accept in
add 6 to client set
got event 2
poll read in
recv 2 from 6: ab
poll exception in
has oob!
recv 1 from 6 on urgent: c
start worker process 4516
goto serve next client..
got event 1
poll read in
recv 2 from 6: de
SIGCHLD received
wait child 4511 return 0
find clfd 4 for that pid
remove 4 from client set
interrupted by signal, some child process done ?
SIGCHLD received
wait child 4513 return 0
find clfd 5 for that pid
remove 5 from client set
interrupted by signal, some child process done ?
SIGCHLD received
wait child 4516 return 0
find clfd 6 for that pid
remove 6 from client set
interrupted by signal, some child process done ?
^C

需要注意的是,在某些场景下,OOB会被识别为惯常数据,此时exception事件在处理时将得不到OOB数据,不过这有一定的随机性,不是每次都能复现。

最后,总结一下OOB这个功能。

这么多年来没有遇到OOB的处理,可能本身就说明了大家对它的态度——就是挺鸡肋的一功能,

而且即使真的需要紧急处理了,1字节的限制也导致不能传递什么更多的信息,且本身OOB的处理又有些复杂和局限性,

例如使用信号处理器,如果有多个连接,我怎么知道是哪个连接上的OOB?

如果使用SO_OOBINLINE,OOB被当作普通数据,这里面如果有个结构体被生生插入一个OOB字节,

而且还没有正确识别出来,这里面的对齐问题可要了老命了。

所以最后的结论是:OOB是过时的,请不要使用它

测试程序1

测试程序2

测试程序3

[apue] 如何处理 tcp 紧急数据(OOB)?的更多相关文章

  1. TCP/IP数据包结构具体解释

    [关键词] TCP IP 数据包 结构 具体解释 网络 协议 一般来说,网络编程我们仅仅须要调用一些封装好的函数或者组件就能完毕大部分的工作,可是一些特殊的情况下,就须要深入的理解 网络数据包的结构, ...

  2. TCP/IP 数据包报文格式(IP包、TCP报头、UDP报头)(转)

    reference:http://blog.51cto.com/lyhbwwk/2162568                    https://blog.csdn.net/wangzhen209 ...

  3. 以太网,IP,TCP,UDP数据包分析【转】

    原文地址:http://www.cnblogs.com/feitian629/archive/2012/11/16/2774065.html 1.ISO开放系统有以下几层: 7 应用层 6 表示层 5 ...

  4. TCP/IP数据包结构详解

    一般来说,网络编程我们只需要调用一些封装好的函数或者组件就能完成大部分的工作,但是一些特殊的情况下,就需要深入的理解网络数据包的结构,以及协议分析.如:网络监控,故障排查等…… IP包是不安全的,但是 ...

  5. 以太网,IP,TCP,UDP数据包分析(此文言简意赅,一遍看不懂的话,耐心的看个10遍就懂了,感谢作者无私奉献)

    1.ISO开放系统有以下几层: 7 应用层 6 表示层 5 会话层 4 传输层 3 网络层 2 数据链路层 1 物理层 2.TCP/IP 网络协议栈分为应用层(Application).传输层(Tra ...

  6. TCP同步传送数据示例(简洁、清楚)

    转自:http://www.2cto.com/kf/201206/134841.html 本例子写了个简单的TCP数据传送功能.没有使用BinaryWriter,BinaryReader,而是使用Ne ...

  7. TCP同步传送数据示例以及可能出现问题分析

    TCP传送数据可以分为同步传送和异步传送,首先这里使用了TCP的同步传送方式,学习了TCP同步传送数据的原理. 同步工作方式是指利用TCP编写的程序执行到监听或者接受数据语句的时候,在未完成当前工作( ...

  8. Node.js学习之TCP/IP数据通讯

    Node.js学习之TCP/IP数据通讯 1.使用net模块实现基于TCP的数据通讯 提供了一个net模块,专用于实现TCP服务器与TCP客户端之间的通信 1.1创建TCP服务器 在Node.js利用 ...

  9. Indy10 Tcp接收数据问题

    在做Delphi开发时,使用Indy组件来做网络通讯是一种比较快捷的方式.今天要说一下indy10中tcp接收数据的问题. 我们在测试时经常使用Wrinteln来发送数据,用Readln来接收数据.用 ...

随机推荐

  1. redis分布式锁-WATCH锁(废弃)

    使用watch构建锁的代码结构.(最着负载不断增加,系统完成一次加锁操作,重试次数不断加大) pine=conn.pineline while pine.watch if xxx pine.unwat ...

  2. 死磕 java同步系列之mysql分布式锁

    问题 (1)什么是分布式锁? (2)为什么需要分布式锁? (3)mysql如何实现分布式锁? (4)mysql分布式锁的优点和缺点? 简介 随着并发量的不断增加,单机的服务迟早要向多节点或者微服务进化 ...

  3. Spring Boot 简介与入门(2.1.6版)

    Spring Boot 2.1.6 版       与时俱进是每一个程序员都应该有的意识,当一个Java程序员在当代遍布的时候,你就该意识到能多学点什么.可观的是后端的框架是稳定的,它们能够维持更久的 ...

  4. springboot 2.1.3 + mybatis + druid配置多数据源

    在一些大型的项目中,通常会选择多数据库来满足一些业务需求,此处讲解使用springboot.mybatis和druid来配置多数据源 1.依赖配置 pom文件引入相关依赖 <dependency ...

  5. 给定一个公式字符串用java进行拆解并计算结果

    需求很简单,给定一个字符串形式的公式规则,用java代码进行拆解,并能计算出结果. ♦考虑字符串中数字格式[整数.小数点] ♦考虑字符串中运算符[+-*/()] ♦考虑空格.运算规则[被0除] 以下是 ...

  6. 【ADO.NET基础-Regidter】简单的账户注册界面和源代码(可用于简单面试基础学习用)

    在阅读时如有问题或者建议,欢迎指出和提问,我也是初学者......... 前台代码: <!DOCTYPE html> <html xmlns="http://www.w3. ...

  7. 12-z-index

    z-index 这个东西非常简单,它有四大特性,每个特性你记住了,页面布局就不会出现找不到盒子的情况. z-index 值表示谁压着谁,数值大的压盖住数值小的, 只有定位了的元素,才能有z-index ...

  8. xpath语法分享

    # xpath语法: ## 使用方式: 使用//获取整个页面当中的元素,然后写标签名,然后再写谓词进行提取.比如: ``` //div[@class='abc'] ``` ## 需要注意的知识点: 1 ...

  9. python openpyxl内存不主动释放 ——关闭Excel工作簿后内存依旧(MemoryError)

    在openpyxl对Excel读写操作过程中,发现内存没有马上释放,如果得多次读取大文件,内存爪机,后续代码就无法运行. 尝试:各种wb.save()或者with open等途径无法解决. 发现:因为 ...

  10. Docker 学习笔记之 核心概念

    Docker核心概念: Docker Daemon Docker Container Docker Registry Docker Client 通过rest API 和Docker Daemon进程 ...