[原]TCP 带外数据(即紧急模式的发送和接受)

刘生玺 18/09/13 17:36:24

   首先给出OSI 参考模型与TCP/IP协议模型图:

这里写图片描述

1. 概述:

首先,我们需要知道的是数据分为两种,一种是带内数据,一种是带外数据。带内数据就是我们平常传输或者说是口头叫的数据。带外数据就是我们接下来讲的内容。

   许多的传输层都具有带外数据(也称为 经加速数据 )的概念,想法就是连接的某段发生了重要的事情,希望迅速的通知给对端。这里的迅速是指这种通知应该在已经排队了的带内数据之前发送。也就是说,带外数据拥有更高的优先级。带外数据不要求再启动一个连接进行传输,而是使用已有的连接进行传输。

   其中,UDP没有实现带外数据(是个极端哦~)

   TCP中telnet,rlogin,ftp等应用(除了这样的远程非活跃应用之外,几乎很少有使用到带外数据的地方

2. TCP 带外数据

2.0 铺垫 :

我们亲爱的 TCP 就位于传输层,下面还有互联网层(主要是IP)与网卡等 。相信学过网络编程的都知道,对于报文的处理,在经过每一层时都会有添加头部和解析头部的操作。如下所示:

这里写图片描述

在这里,自然,我们先关心一下TCP包的首部

这里写图片描述

首部之后就是用户数据 ,再来一次图:

这里写图片描述

针对标题,我们自然只关心以下两个部分:

  1. 紧急字段URG:告诉系统此报文段中有紧急数据,应尽快传送。当URG=1时。

  2. 紧急指针:指出在本报文段中的紧急数据的最后一个字节的序号,即指出带外数据字节在正常字节流中的位置。

所以当TCP发送带外数据时,他的TCP首部一定是设置了URG标志和紧急指针的 。而紧急指针就是用来指出带外数据字节在正常字节流中的位置的 。

更加具体的可以参考:完整的首部解释,建议阅读

2.1 发送端 :

  1. 紧急数据是插在正常数据流中进行传输的 。

  2. 一个紧急指针只指向一个字节的带外数据的后一个字节位置。比如我们要发送数据1,2,3,4,5,6 ,7,8,如果我们只发送一个字节的带外数据X,那么发送缓冲区就是(1,2,3,4,5,6,7,8,X),紧急指针置为10,X是带外数据字节 。如果我们发送多个字节的带外数据(X,Y,Z),那么发送缓冲区就是(1,2,3,4,5,6,7,8,X,Y,Z),紧急指针指向Z的后面,为12 ,Z 被当作带外数据字节。

  3. 假如由于发送窗口的关系,导致该发送缓冲区中的数据(1,2,3,4,5,6,7,8,X)分为多次或者两次发送。比如:发送窗口是6,那么就分为两个包发送,情况如下:第一个包紧急指针为10,传送六个字节(1,2,3,4,5,6),接收端记下接受的字节数并且发现紧急指针指向的紧急数据没有到达,所以继续等待下一个包,下一个包(紧急指针还是10)发过来 7,8,X ,接收端发现紧急指针指向的紧急数据在这个包里,所以将紧急数据进行处理即可。

  4. 带外字节会被标记为OOB

  5. 即使发送端TCP因流量控制而暂停发送数据(接受缓冲区的套接字接受缓冲区已满,导致其TCP向发送端通告了一个值为0 的窗口),紧急通知照样不伴随任何数据的发送。也就是说:即使数据的流动会因为TCP的流量控制而停止,紧急通知却总是无障碍的发送到对端TCP

2.2 接受端 :

   一旦有一个新的紧急指针到达,不论由紧急指针指向的实际数据字节是否已经到达接受端,以下两个动作都会发生 :

  1. 当接受到一个设置了URG标志的分节时,接受端检查紧急指针,确定它是否指向新的带外数据,比如:前面发送了两个包,只有第一个才会通知接受进程有新的带外数据到达。

  2. 当有新的紧急指针到达时,接受进程被通知到。首先,内核会给接受套接字的属主进程发送SIGURG信号,前提是接受进程调用了 fcntl或者ioctl为这个套接字建立了属主,并且该属主进程为该信号建立了信号处理函数 。

  3. 只有一个OOB标记,如果新的OOB字节在旧的OOB字节之前到达,旧的OOB字节就会被丢弃。

  4. 当由紧急指针指向的实际数据字节到达接受端TCP时,数据字节会有两个存储地区:一个是和普通数据一样的在线留存,另外一个是独立的单字节带外缓冲区,接受进程从这个单字节带外缓冲区读入数据的唯一方法是指定MSG_OOB调用recvrecvfromrecvmsg。如果放在和普通数据一起的带内区域,接受进程就得通过检查该连接的带外标记OOB来获悉何时访问带这个数据字节。两个区域的使用通过套接字选项SO_OOBLINE来使用,默认情况下将带外数据字节放入独立的单字节带外缓冲区内。

2.3 会发生的一些错误:

  1. 如果接受进程请求读入数据(通过MSG_OOB标志),但是对端并没有发送任何带外数据,读入操作将返回EINVAL

  2. 在接受进程已被告知对端发送了一个带外字节(SIGURGselect)的前提下,如果接受进程试图读入该字节,但是该字节尚未到达,读入操作返回EWOULDBLOCK。接受进程此时做的就是从缓冲区中读入数据,腾出空间,以允许对端TCP发送出那个带外字节。

  3. 如果接受进程试图多次读入同一个带外字节,读入操作返回EINVAL

  4. 如果开启了SO_OOBINLINE套接字选项,接受进程如果还是通过MSG_OOB读入带外数据,读入操作将返回EINVAL

2.4 问题与讨论:

(1) 为何不直接将一个字节的紧急数据放在紧急指针哪里呢?

   因为TCP数据包在ip层可能被拆包,成为多个数据段。一个包含紧急数据的数据包被拆成两个数据包,那么这两个包有的tcp头部有相同的紧急指针(和UGR)。如果将紧急数据直接放在紧急指针的内存处,那么将多出一个紧急数据!所以,不该将紧急数据放在TCP头部。

2.5 总结:

带外数据概念实际上时向接收端传送三个不同的信息:

(1)发送端进入紧急模式这个事实。接收进程得以通知这个事实的手段不外乎SIGURG信号或select调用。本通知在发送进程发送带外字节后由发送端TCP立即发送,即使往接收端的任何数据发送因流量控制而停止了,TCP仍然发送本通知。本通知可能导致接收端进入某种特殊处理模式,以处理接收的任何后继数据。
(2)带外字节的位置,也就是它相对于来自发送端的其余数据的发送位置:带外标记。
(3)带外字节的实际值。既然TCP是一个不解释应用进程所发送数据的字节流协议,带外字节就可以是任何8位值。

对于TCP的紧急模式,我们可以认为URG标志时通知(信息1),紧急指针是带外标记(信息2),数据字节是其本身(信息3)。
与这个带外数据概念相关的问题有:

  1. 每个连接只有一个TCP紧急指针;
  2. 每个连接只有一个带外标记;
  3. 每个连接只有一个单字节的带外缓冲区(该缓冲区只有在数据非在线读入时才需考虑)。如果带外数据时在线读入的,那么当心的带外数据到达时,先前的带外字节字节并未丢失,不过他们的标记却因此被新的标记取代而丢失了。

   带外数据的一个常见的用途体现在rlogin程序中。当客户中断运行在服务器主机上的程序时,服务器需要告知客户丢弃所有已在服务器排队的输出,因为已经排队等着从服务器发送到客户的输出最多有一个窗口的大小。服务器向客户发送一个特殊字节,告知后者清刷所有这些输出(在客户看来是输入),这个特殊字节就作为带外数据发送。客户收到由带外数据引发的SIGURG信号后,就从套接字中读入直到碰到带外数据发送。客户收到由带外数据引发的SIGURG信号后,就从套接字中读入直到碰到带外标记,并丢弃到标记之前的所有数据。这种情形下即使服务器相继地快速发送多个带外字节,客户也不受影响,因为客户只是读到最后一个标记为止,并丢弃所有读入的数据。

   总之,带外数据是否有用取决于应用程序使用它的目的。如果目的是告知对端丢弃直到标记处得普通数据,那么丢失一个中间带外字节及其相应的标记不会有什么不良后果。但是如果不丢失带外字节本身很重要,那么必须在线收到这些数据。另外,作为带外数据发送的数据字节应该区别于普通数据,因为当前新的标记到达时,中间的标记将被覆写,从而事实上把带外字节混杂在普通数据之中。举例来说,telnet在客户和服务器之间普通的数据流中发送telnet自己的命令,手段是把值为255的一个字节作为telnet命令的前缀字节。(值为255的单个字节作为数据发送需要2个相继地值为255的字节。)这么做使得telnet能够区分其命令和普通用户数据,不过要求客户进程和服务器进程处理每个数据字节以寻找命令。

作者:liushengxi_root 发表于 2018/09/13 17:36:24 原文链接 https://blog.csdn.net/liushengxi_root/article/details/82563181
阅读:24