在上大学的时候,我们可能就听说了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. Spring Boot 梳理 - 4个核心

    Spring Boot 魔法的核心:自动配置.起步依赖.命令行界面.Actuator 自动配置: 不用手动配置JdbcTemplate的Bean 不用手动配置DataSource的Bean Sprin ...

  2. css3练习

    读条的实现1 .div{position: relative;border: 1px solid #111;width: 80px;height: 60px} .div div{width: 20px ...

  3. druid 连接池的配置参数

    介绍 DRUID是阿里巴巴开源平台上一个数据库连接池实现,它结合了C3P0.DBCP.PROXOOL等DB池的优点,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况,可以说是针对监控而生 ...

  4. 【ADO.NET基础-GridView】GridView的编辑、更新、取消、删除以及相关基础操作代码

    代码都是基础操作,后续功能还会更新,如有问题欢迎提出和提问....... 前台代码: <asp:GridView ID=" OnRowDataBound="GridView1 ...

  5. 死磕 java线程系列之线程模型

    问题 (1)线程类型有哪些? (2)线程模型有哪些? (3)各语言使用的是哪种线程模型? 简介 在Java中,我们平时所说的并发编程.多线程.共享资源等概念都是与线程相关的,这里所说的线程实际上应该叫 ...

  6. 博客的第一天:回顾半年前的基础:SQL--基础查询+年月日格式+拼接

    ----------------------2019/6月份 <<必知必会>>书本练习-实践练习--------------------------- ---order by没 ...

  7. Python爬虫:获取JS动态内容

    经过一段时间的python学习,能写出一些爬虫了.但是,遇到js动态加载的网页就犯了难.于是乎谷歌.百度,发现个好介绍http://www.jianshu.com/p/4fe8bb1ea984 主要就 ...

  8. JavaScript系列:高级函数篇

    前言: 本篇主要是介绍 JavaScript使用函数的高级方法,函数是JavaSCript中最有趣的部分,利用Function特性可以编写出很多非常有意思的代码,本篇主要包括:函数回调,高阶函数以及函 ...

  9. 第三方日志库logrus使用

    日志是程序中必不可少的一个环节,由于Go语言内置的日志库功能比较简洁,我们在实际开发中通常会选择使用第三方的日志库来进行开发.本文介绍了logrus这个日志库的基本使用. logrus介绍 Logru ...

  10. MongoDB 学习笔记之 检测存储引擎

    检测存储引擎: db.serverStatus().storageEngine db.serverStatus().wiredTiger (转)WiredTiger测试结果 单纯写的测试结果 结论:W ...