TCP带外数据
传输层协议使用带外数据(out-of-band,OOB)来发送一些重要的数据,如果通信一方有重要的数据需要通知对方时,协议能够将这些数据快速地发送到对方.为了发送这些数据,协议一般不使用与普通数据相同的通道,而是使用另外的通道.linux系统的套接字机制支持低层协议发送和接受带外数据.但是TCP协议没有真正意义上的带外数据.为了发送重要协议,TCP提供了一种称为紧急模式(urgentmode)的机制.TCP协议在数据段中设置URG位,表示进入紧急模式.接收方可以对紧急模式采取特殊的处理.很容易看出来,这种方式数据不容易被阻塞,可以通过在我们的服务器端程序里面捕捉SIGURG信号来及时接受数据或者使用带OOB标志的recv函数来接受.
定义带外数据
想 像一下在银行人们排起队等待处理他们的帐单。在这个队伍中每个人最后都会移到前面由出纳员进行服务。现在想像一下一个走入银行,越过整个队伍,然后用枪抵 住出纳员。这个就可以看作为带外数据。这个强盗越过整个队伍,是因为这把枪给了他凌驾于众人的权力。出纳员也会集中注意力于这个强盗身上,因为他知道当前 的形势是很紧急的。
相应的,一个连接的流式套接口上的带外数据的工作原理也与此类似。通常情况下,数据由连接的一端流到另一端,并且认为 数据的所有字节都是精确排序的。晚写入的字节绝不会早于先写入的字节到达。然而套接口API概念性的提供了一些实用程序,从而可以使得一串数据无阻的先于 通常的数据到达接收端。这就是所谓的发送带外数据。
从技术上来说,一个TCP流不可以发送带外数据。而他所支持的只是一个概念性的紧急数据,这些紧急数据作为带外数据映射到套接口API。这就带来了许多限制,这些我们会在后面进行讨论。
尽管我们可以立刻享受到在银行中越过整个队伍的利益,但是我们也会认识到使用枪来达到这样的目的是反社会的行为。一个TCP流通常希望以完美的队列来发送数据字节,那么乱序的发送数据就似乎与流的概念相违背。那么为什么要提供带外数据的套接口方法呢?
也 许我们已经意识到了,有时数据会以一定的方式变得紧急。一个流套接口会有一个大量的数据队列等待发送到网络。在远程端点,也会有大量已接收的,却还没有被 程序读取的数据。如果发送客户端程序由于一些原因需要取消已经写入服务器的请求,那么他就需要向服务器紧急发送一个标识取消的请求。如果向远程服务器发送 取消请求失败,那么就会无谓的浪费服务器的资源。
使 用带外数据的实际程序例子就是telnet,rlogin,ftp命令。前两个程序会将中止字符作为紧急数据发送到远程端。这会允许远程端冲洗所有未处理 的输入,并且丢弃所有未发送的终端输出。这会快速中断一个向我们屏幕发送大量数据的运行进程。ftp命令使用带外数据来中断一个文件的传输。
套接口与带外数据
重新强调套接口接口本身并不是限制因素是很重要的。带外数据的概念实际上映射到 TCP/IP通信的紧急数据模式。在今天,TCP流对于网络是很重要的,而在这一章我们仅专注于带外数据适应于TCP紧急数据的套接口使用。
实现上的变化
很不幸,TCP的实现在紧急数据就如何处理上有两种不同的解释。这些区别我们将会本章的后面进行详细的讨论。这些不同的解释是:
TCP紧急指针的RFC793解释
TCP紧急指针的BSD解释
现 在已经出现了平分的状态,因为原始的TCP规格允许两种解释。从而,一个"主机需要"的RFC标识正确的解释。然而,大多数的实现都基于BSD源码,而在 今天BSD方法还是一个通用的用法。从支持两种解释的角度而言,Linux处于分裂的状态。然而,Linux默认使用BSD解释。
现在我们稍做停顿,来检测一个我们Linux系统的当前设置。这决了我们这一章的例子是否可以产生同样的结果。
$ cat /proc/sys/net/ipv4/tcp_stdurg
0
$
这里显示的输出为0。这表示当前起作的为BSD解释。如果我们得到其他的输出结果(例如1),那么如果我们希望得到也本章的例子相同的结果,我们应将其改为0。
下面列出了tcp_stdurg设置可能的取值。tcp_stdurg值可以在Shell脚本中进行查询和设置,包括启动与关闭脚本。
/proc/sys/net/ipv4_stdurg的设置值:
0 BSD解释(Linux默认)
1 RFC793解释
如果我们需要将其设置改为0,我们需要root权限,然后输入下面的命令:
# echo 0 >/proc/sys/net/ipv4/tcp_stdurg
#
进行双重检测总是很明知的,所以在改变以后再列出其值来确定改变是否为内核所接受。我们也可以在上面的例子中使用cat命令来显示0值。
编写带外数据
一个write调用将会写入一个我们已习惯的带内数据。相应的,必须使用一个新的函数来写入带外数据。为了这个目的,在这里列出send函数地原型:
#include <sys/types.h>
#include <sys/socket.h>
int send(int s, const void *msg, int len, unsigned int flags);
这个函数需要四个参数,分别为:
1 要写入的套接口s
2 存放要写入的消息的缓冲地址msg
3 消息长度(len)
4 发送选项flags
send函数与write函数相类似,所不同的只是提供了额外的flags参数。这是实际的部分。send函数返回写入的字节数,如果发生错误则会返回-1,检测errno可以得到错误原因。
要发送带外数据,与write调用相似,使用前三个参数。如果我们为flags参数指定了C语言宏MSG_OOB,则数据是作为带外数据发送的,而不是通常的带内数据,如下面的例子代码:
char buf[64]; /* Data */
int len; /* Bytes */
int s; /* Socket */
. . .
send(s,buf,len,MSG_OOB);
如果所提供的flags参数没有MSG_OOB位,那么数据是作为通常数据写入的。这就允许我们使用同一个函数同时发送带内数据与带外数据。我们只需要简单的在程序控制中改变flags参数值来达到这个目的。
读取带外数据
带外数据可以用两种不同的方法进行读取:
单独读取带外数据
与带内数据混合读取
为了与通常数据流分开单独读取带外数据,我们需要使用recv函数。如果我们猜想recv函数与read函数相似,只是有一个额外的flags参数,那么我们的猜想是正确的。这个函数的原型如下:
#include <sys/types.h>
#include <sys/socket.h>
int recv(int s, void *buf, int len, unsigned int flags);
recv函数接受四参数,分别为:
1 要从中接收数据的套接口s(带内数据或带外数据)
2 要放置所接收的数据的缓冲区地址buf
3 接收缓冲区的最大长度
4 调用所需的flags参数
正如我们所看到的,recv函数是与send函数调用相对应的函数。为要接收带外数据,在flags参数中指定C宏MSG_OOB。没有MSG_OOB标志位,recv函数所接收的为通常的带内数据,就像通常的read调用一样。
recv函数返回所接收到的字节数,如果出错则返回-1,检测errno可以得到错误原因。
下面的代码例子演示了如何读取带外数据:
char buf[128]; /* Buffer */
int n; /* No. of bytes */
int s; /* Socket */
int len; /* Max bytes */
. . .
n = recv(s,buf,len,MSG_OOB);
尽管指出带外数据可以与通常数据相混合还为时尚早,但是我们会在后面进行相关的讨论。
理解SIGURG信号
当带外数所到在时,接收进程需要收到通知。如果需要与通常数据流分开读取时更是如此。这样做的一个方法就是当带外数据到达时,使Linux内核向我们的进程发送一个SIGURG信号。
使用SIGURG信号通知需要两个先决条件:
我们必须拥有套接口
我们必须为SIGURG创建一个信号处理器
要接收SIGURG信号,我们的进程必须为套接口的所有者。要建立这样的拥有关系,我们可以使用fcntl函数。其函数原型如下:
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, long arg);
函数参数如下:
1 要在其上执行控制函数的文件描述符fd(或是套接口)
2 要执行的控制函数cmd
3 要设置的值arg
函数的返回值依赖于fcntl所执行的控制函数。对于课外阅读感兴趣的读者,fcntl的Linux man手册页详细的描述了cmd的F_SETOWN操作。
要将我们的进程创建为套接口的所有者,接收程序需要使用下面的代码:
int z; /* Status */
int s; /* Socket */
z = fcntl(s,F_SETOWN,getpid());
if ( z == -1 ) {
perror("fcntl(2)");
exit(1);
}
F_SETOWN操作会使得fcntl函数成功时返回0,失败时返回-1。
另外一个先决条件是程序必须准备好接收SIGURG信号,这是通过为信号创建一个信号处理器来做到的。我们很快就会看到这样的一个例子。
接收SIGURG信号
移开了这些烦琐的工作以后,现在我们可以来探索有趣的带外数据的概念了。下面所列的程序代码就是我们用来接收数据和当带外数据到达时处理带外数据的程序。他设计使用BSD解释来处理带外数据,而这也正是Linux的默认情况。
/*
* oobrec.c
*
* Example OOB receiver:
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
extern void bail(char *on_what);
extern int BindAccept(char *addr);
static int s = -1; /* Socket */
/*
* SIGURG signal handler:
*/
static void sigurg(int signo)
{
int n;
char buf[256];
n = recv(s,buf,sizeof buf,MSG_OOB);
if(n<0)
bail("recv(2)");
buf[n] = 0;
printf("URG ''%s'' (%d) \n",buf,n);
signal(SIGURG,sigurg);
}
int main(int argc,char **argv)
{
int z; /* Status */
char buf[256];
/*
* Use a server address from the command
* line,if one has been provided.
* Otherwise,this program will default
* to using the arbitrary address
* 127.0.0.1:
*/
s = BindAccept(argc >=2 ?argv[1] :"127.0.0.1:9011");
/*
* Establish owership:
*/
z = fcntl(s,F_SETOWN,getpid());
if(z==-1)
bail("fcntl(2)");
/*
* Catch SIGURG:
*/
signal(SIGURG,sigurg);
for(;;)
{
z = recv(s,buf,sizeof buf,0);
if(z==-1)
bail("recv(2)");
if(z==0)
break;
buf[z] = 0;
printf("recv ''%s'' (%d) \n",buf,z);
}
close(s);
return 0;
}
然而,在我们将接收程序投入使用之前,我们还需要一个发送程序。
发送带外数据
下面列出的程序演示了一个简短的发送程序,他只可以传输一些小的字符串,然后停止发送带外数据。这个程序为了在接收端管理传送块使用了许多的sleep(3)调用。
/*
* oobsend.c
*
* Example OOB sender:
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
extern void bail(char *on_what);
extern int Connect(char *addr);
/*
* Send in-band data:
*/
static void iband(int s,char *str)
{
int z;
z = send(s,str,strlen(str),0);
if(z==-1)
bail("send(2)");
printf("ib: ''%s'' (%d) \n",str,z);
}
/*
* Send out-of-band data:
*/
static void oband(int s,char *str)
{
int z;
z = send(s,str,strlen(str),MSG_OOB);
if(z==-1)
bail("send(2)");
printf("OOB ''%s'' (%d)\n",str,z);
}
int main(int argc,char **argv)
{
int s = -1;
s = Connect(argc >=2
? argv[1]
: "127.0.0.1:9011");
iband(s,"In the beginning");
sleep(1);
iband(s,"Linus begat Linux,");
sleep(1);
iband(s,"and the Penguins");
sleep(1);
oband(s,"rejoiced");
sleep(1);
iband(s,"exceedingly.");
close(s);
return 0;
}
编译程序:
$ make oobrecv oobsend
gcc -c -D_GNU_SOURCE -Wall -Wreturn-type -g oobrecv.c
gcc -c -D_GNU_SOURCE -Wall -Wreturn-type -g mkaddr.c
gcc -c -D_GNU_SOURCE -Wall -Wreturn-type -g bindacpt.c
gcc oobrecv.o mkaddr.o bindacpt.o -o oobrecv
gcc -c -D_GNU_SOURCE -Wall -Wreturn-type -g oobsend.c
gcc oobsend.o mkaddr.o bindacpt.o -o oobsend
$
在编译完成以后,我们得到两个可执行程序:
oobrecv 是接收程序(一个服务器)
oobsend 是发送程序(一个客户端)
现在我们已经准备好来调用这两个程序了。
测试oobrecv与oobsend程序
最好是在两个不同的终端会话上运行这两个程序。使用两个不同的xterm窗口,或是两个不同的终端会话。首先在第一个终端会话中启动接收程序:
$ ./oobrecv
如果我们希望指定我们的以太网地址而不是使用默认的回环地址,那么这两个程序都接收一个地址与端口号对。例如,下面的将会工作在一个NIC卡地址为192.168.0.1的系统上:
$ ./oobrecv 192.168.0.1:9023
这会启动服务器在192.168.0.1的9023端口上监听。然而,为了演示,我们可以不指定参数来运行这个程序。
现在在第二个终端会话中启动发送程序:
$ ./oobsend
ib: ''In the beginning'' (16)
ib: ''Linus begat Linux,'' (18)
ib: ''and the Penguins'' (16)
OOB ''rejoiced'' (8)
ib: ''exceedingly.'' (12)
$
以ib:开始的行表明写入的带内数据。以OOB开始的行表明''rejoiced''是作为带外数据写入套接口的。
如果我们可以同时观察两个终端,我们就会发现接收程序报告数据稍晚于发送程序发送数据。其会话输出类似于下面的样子:
$ ./oobrecv
rcv ''In the beginning'' (16)
rcv ''Linus begat Linux,'' (18)
rcv ''and the Penguins'' (16)
URG ''d'' (1)
rcv ''rejoice'' (7)
rcv ''exceedingly.'' (12)
$
在这个终端会话中显示的以rcv开始的行表明接收到的通常的带内数据。以URG开始的行表明接收到SIGURG信号,并且信号处理程序被调用。在信号处理器中,紧急数据被读取并报告。我们应注意到一个很奇怪的事情--只有d字节被作为带外数据接收。为什么是这样?
TCP带外数据的更多相关文章
- UNIX网络编程——TCP带外数据小结
带外数据概念实际上时向接收端传送三个不同的信息:(1)发送端进入紧急模式这个事实.接收进程得以通知这个事实的手段不外乎SIGURG信号或select调用.本通知在发送进程发送带外字节后由发送端TCP立 ...
- TCP带外数据读写
#include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include &l ...
- 网络IPC:套接字之带外数据
带外数据(Out-of-band data)是一些通信协议所支持的可选特征,允许更高优先级的数据比普通数据优先传输.即使传输队列已经有数据,带外数据先行传输.TCP支持带外数据,但是UDP不支持.套接 ...
- UNIX网络编程——带外数据
许多传输层有带外数据的概念,它有时也称为经加速数据.其想法是一个连接的某端发生了重要的事情,而且该端希望迅速通告其对端.这里"迅速"意味着这种通知应该在已排队等待发送的任何&quo ...
- UNIX网络编程——带外数据小结
TCP没有真正的带外数据,不过提供紧急模式和紧急指针.一旦发送端进入紧急模式,紧急指针就出现在发送到对端的分节中的TCP首部中.连接的对端收取该指针是在告知接收进程发送端已经进入紧急模式,而且该指针指 ...
- 利用DNS实现SQL注入带外查询(OOB)
根据用于数据检索的传输信道,SQLi可分为三个独立的类别:inference(经典SQL注入),inband(盲注.推理注入.带内注入)和out-of-band 一.什么是OOB out-of-ban ...
- tcp ESTABLISHED 接收数据
tcp_rcv_established函数的工作原理是把数据包的处理分为2类:fast path和slow path,其含义显而易见.这样分类的目的当然是加快数据包的处理,因为在正常情况下,数据包是按 ...
- TCP传输小数据包效率问题(译自MSDN)
TCP传输小数据包效率问题(译自MSDN) http://www.ftpff.com/blog/?q=node/16 摘要:当使用TCP传输小型数据包时,程序的设计是相当重要的.如果在设计方案中不对T ...
- ipmitool+python应用处理大量带外地址
ipmitool 是一种可用在 linux 系统下的命令行方式的 ipmi 平台管理工具,它支持 ipmi 1.5 规范(最新的规范为 ipmi 2.0),通过它可以实现获取传感器的信息.显示系统日志 ...
随机推荐
- BZOJ2453维护队列&&BZOJ2120数颜色
2016-05-28 11:20:22 共同的思路: 维护某种颜色上一次在哪里出现pre,可以知道当pre<询问的l时更新答案 块内按照pre排序 修改的时候重新O(n)扫一遍,如果和之前的不一 ...
- Linux 的cp命令
Linux 的cp命令 功能: 复制文件或目录说明: cp指令用于复制文件或目录,如同时指定两个以上的文件或目录,且最后的目的地是一个已经存在的目录,则它会把前面指定的所有文件或目录复制到此目录中.若 ...
- 通过jquery获取ul中第一个li的属性
当加载列表时,默认希望选中第一条.top_menu 为ul的ID 通过 $("#top_menu li:first") 就可以获取到 ul下第一个li标签.然后就可以利用 例如 修 ...
- 自己编写php框架(一)
这两天一直在琢磨如何做一个php模板,将前台html页面和php代码分割开来.虽然现在市面上有各种各样的php模板,但还是想自己编写一个体验一下. 之前一直写到了将替换完php代码的含有php代码的h ...
- 理解CSS3 transform中的Matrix(矩阵)
一.哥,我被你吓住了 打架的时候会被块头大的吓住,学习的时候会被奇怪名字吓住(如“拉普拉斯不等式”).这与情感化设计本质一致:界面设计好会让人觉得这个软件好用! 所以,当看到上面“Matrix(矩阵) ...
- HTML静态网页 Window.document对象
一.找到元素: docunment.getElementById("id"):根据id找,最多找一个: var a =docunment.getElementById(&qu ...
- Jquery各个版本的区别
一: 一般原则是越新越好,jQuery版本是在不断进步和发展的,最新版是当时最高技术水平,也是最先进的技术理念. 但个人的角度来看.是最新版本x.x.0的上一版本最好.比如说1.10.0版,上一版本是 ...
- Nginx localtion匹配规则
mark:2016年05月25日13:20:54 (存手打,拒绝转载) 一.location分为 普通location 和 正则location 只有带有 "~" 或者" ...
- zabbix_agent 步骤
Zabbix server 做好了,只要在安装一个zabbix-agent(监控端就可以啦) groupadd zabbix useradd -g zabbix zabbix 下载一个客户端的安装包: ...
- $.getJSON JSONP的新坑
神坑1:返回的内容必须是正规的json数据.如 { "firstName": "Bill", "lastName": "Gates ...