在上大学的时候, 我们可能就听说了 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, 2, 0)) <0)
- err_sys ("send normal head failed");
- else
- printf ("send normal head %d\n", ret);
- ptr += 2;
- n = 1;
- if ((ret = send (sockfd, ptr, n, MSG_OOB)) < 0)
- err_sys ("send oob failed");
- else
- printf ("send oob %d\n", ret);
- ptr += n;
- if ((ret = send (sockfd, ptr, 2, 0)) < 0)
- 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) 建立新连接时, 设置连接句柄的信号处理进程 (为当前进程)
1 fcntl (clfd, F_SETOWN, getpid ());
c) 在信号处理器中使用 MSG_OOB 接收带外数据
- int g_fd = 0;
- void on_urg (int signo)
- {
- int ret = 0;
- char buf[BUFLEN] = { 0 };
- ret = recv (g_fd, buf, sizeof (buf), MSG_OOB);
- if (ret> 0)
- buf[ret] = 0;
- 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), 0);
- if (ret> 0)
- buf[ret] = 0;
- 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
1 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), 0);
- if (ret> 0)
- buf[ret] = 0;
- 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), 0);
- if (ret> 0)
- buf[ret] = 0;
- 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 紧急的意义, 没有给予它优先的处理权.
最后, 总结一下 OOB 这个功能.
这么多年来没有遇到 OOB 的处理, 可能本身就说明了大家对它的态度 -- 就是挺鸡肋的一功能,
而且即使真的需要紧急处理了, 1 字节的限制也导致不能传递什么更多的信息, 且本身 OOB 的处理又有些复杂和局限性,
例如使用信号处理器, 如果有多个连接, 我怎么知道是哪个连接上的 OOB?
如果使用 SO_OOBINLINE,OOB 被当作普通数据, 这里面如果有个结构体被生生插入一个 OOB 字节,
而且还没有正确识别出来, 这里面的对齐问题可要了老命了.
所以最后的结论是: OOB 是过时的, 请不要使用它
测试程序 1
测试程序 2
来源: https://www.cnblogs.com/goodcitizen/p/11793274.html