在上大学的时候,我们可能就听说了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

最新文章

  1. Atitit.异步编程技术原理与实践attilax总结
  2. HDU 1005 F(Contest #1)
  3. java考核完的心得
  4. mac 命令行批量删除.svn[转]
  5. (一)观察者模式-C++实现
  6. PHP操作cookie函数:setcookie()与setrawcookie()
  7. 为什么Laravel是最成功的PHP框架?
  8. 『零行代码』解决键盘遮挡问题(iOS)
  9. Jdbc初体验
  10. Django:之ORM、CMS和二维码生成
  11. poj3468树状数组的区间更新,区间求和
  12. Ubuntu忘记root密码怎么办?
  13. 【Spark篇】---Spark故障解决(troubleshooting)
  14. Mac电脑C语言开发的入门帖
  15. linux下初始化mysql时报错
  16. LODOP在页面不同位置输出页眉页脚
  17. [contest 781] 9.6
  18. python测试开发django-24.表单提交之get请求
  19. 利用GPU改善程序性能的一点心得
  20. rem手机端适配

热门文章

  1. freemarker常用属性
  2. JQuery 数组按指定长度分组
  3. MongoDB 基础教程CURD帮助类
  4. 品Spring:对@Autowired和@Value注解的处理方法
  5. Android_布局
  6. .NET斗鱼直播弹幕客户端(下)
  7. Mac 10.14 安装抓包工具Fiddler
  8. BBEdit 13.0 for Mac 打开大文件不吃力
  9. Kali Linux开启ssh服务设置自启
  10. 生成函数(TBC)