[apue] 如何处理 tcp 紧急数据(OOB)?
在上大学的时候,我们可能就听说了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是过时的,请不要使用它
[apue] 如何处理 tcp 紧急数据(OOB)?的更多相关文章
- TCP/IP数据包结构具体解释
[关键词] TCP IP 数据包 结构 具体解释 网络 协议 一般来说,网络编程我们仅仅须要调用一些封装好的函数或者组件就能完毕大部分的工作,可是一些特殊的情况下,就须要深入的理解 网络数据包的结构, ...
- TCP/IP 数据包报文格式(IP包、TCP报头、UDP报头)(转)
reference:http://blog.51cto.com/lyhbwwk/2162568 https://blog.csdn.net/wangzhen209 ...
- 以太网,IP,TCP,UDP数据包分析【转】
原文地址:http://www.cnblogs.com/feitian629/archive/2012/11/16/2774065.html 1.ISO开放系统有以下几层: 7 应用层 6 表示层 5 ...
- TCP/IP数据包结构详解
一般来说,网络编程我们只需要调用一些封装好的函数或者组件就能完成大部分的工作,但是一些特殊的情况下,就需要深入的理解网络数据包的结构,以及协议分析.如:网络监控,故障排查等…… IP包是不安全的,但是 ...
- 以太网,IP,TCP,UDP数据包分析(此文言简意赅,一遍看不懂的话,耐心的看个10遍就懂了,感谢作者无私奉献)
1.ISO开放系统有以下几层: 7 应用层 6 表示层 5 会话层 4 传输层 3 网络层 2 数据链路层 1 物理层 2.TCP/IP 网络协议栈分为应用层(Application).传输层(Tra ...
- TCP同步传送数据示例(简洁、清楚)
转自:http://www.2cto.com/kf/201206/134841.html 本例子写了个简单的TCP数据传送功能.没有使用BinaryWriter,BinaryReader,而是使用Ne ...
- TCP同步传送数据示例以及可能出现问题分析
TCP传送数据可以分为同步传送和异步传送,首先这里使用了TCP的同步传送方式,学习了TCP同步传送数据的原理. 同步工作方式是指利用TCP编写的程序执行到监听或者接受数据语句的时候,在未完成当前工作( ...
- Node.js学习之TCP/IP数据通讯
Node.js学习之TCP/IP数据通讯 1.使用net模块实现基于TCP的数据通讯 提供了一个net模块,专用于实现TCP服务器与TCP客户端之间的通信 1.1创建TCP服务器 在Node.js利用 ...
- Indy10 Tcp接收数据问题
在做Delphi开发时,使用Indy组件来做网络通讯是一种比较快捷的方式.今天要说一下indy10中tcp接收数据的问题. 我们在测试时经常使用Wrinteln来发送数据,用Readln来接收数据.用 ...
随机推荐
- sql server编写archive通用模板脚本实现自动分批删除数据
博主做过比较多项目的archive脚本编写,对于这种删除数据的脚本开发,肯定是一开始的话用最简单的一个delete语句,然后由于部分表数据量比较大啊,索引比较多啊,会发现删除数据很慢而且影响系统的正常 ...
- redis-分布式锁-消除竞争条件
因为信号量的设计过程中,获取一个信号量需要执行多个命令组成的流水,这样容易形成竞争条件. 为了消除信号量实现中所有可能出现的竞争条件,构建一个正确的计数信号量,需要在 信号量时,添加带有短暂超时时间的 ...
- Tornado基础学习篇
1.1 Tornado是什么? Tornado是使用Python编写的一个强大的.可扩展的Web服务器.它在处理严峻的网络流量时表现得足够强健,但却在创建和编写时有着足够的轻量级,并能够被用在大量的应 ...
- Kubernetes 系列(四):使用Traefik访问.net core api
一. 准备 本篇的要求是在前三篇的基础上已经搭建好的本地k8s以及部署了Traefik,我们将会使用Traefik Ingress来访问.net core api,比较简单,做个记录,如果还没有搭建k ...
- Openshift yum安装
Openshift yum安装: Yum 安装docker [root@DockerServer openshift]# yum repolist [root@DockerServer openshi ...
- MongoDB 学习笔记之 批处理
批处理: MongoDB批处理方式有2种, 有序插入(有序仍是顺序处理的.发生错误就停止.) 无序插入(无序列表会将操作按类型分组,来提高性能,因此,应确保应用不依赖操作执行顺序.发生错误继续处理剩余 ...
- 合并果子(STL优先队列)
STL优先队列:priority_queue 定义:priority_queue<int>q; 从小到大:priority_queue<int,vector<int>,g ...
- 基于Prometheus和Grafana的监控平台 - 环境搭建
相关概念 微服务中的监控分根据作用领域分为三大类,Logging,Tracing,Metrics. Logging - 用于记录离散的事件.例如,应用程序的调试信息或错误信息.它是我们诊断问题的依据. ...
- 使用Line Pos Info 和 Modern C++ 改进打印日志记录
使用Line Pos Info 和 Modern C++ 改进打印日志记录 使用跟踪值:不管自己是多么的精通,可能仍然使用调试的主要方法之一 printf , TRaCE, outputDebugSt ...
- 后门木马免杀-msfvenom和msf5(evasion)
贴上使用笔记 不多介绍了 很简单的东西 msfvenom各平台生成木马大全: windows:msfvenom -a x86 --platform Windows -p windows/meterpr ...