米鼠商城

多快好省,买软件就上米鼠网

最新项目

人才服务

靠谱的IT人才垂直招聘平台

TCP/IP卷一:70---TCP连接管理之(TCP服务器(TCP端口号、限制本地IP地址、限制外部节点、连接队列))

  • darkalex
  • 5
  • 2020-03-22 01:38
  • 大多数TCP服务器是并发的。当一个新的连接请求到达服务器时,服务器接受该连接,并调用一个新的进程或线程来处理新的客户端。根据不同的操作系统,各种其他的资源也可以被分配来调用新的服务器。我们对多个并行服务器间的TCP交互非常感兴趣,尤其希望了解TCP服务器是如何使用端口号的,以及如何处理多个并发客户端的

一、TCP端口号

  • 可通过观察任何一台TCP服务器来了解TCP是如何处理端口号的。在一台拥有IPv4与 IPv6双协议栈的主机上利用netstat命令观察安全外壳服务器(也称作sshd)。sshd应用程序执行的是安全外壳协议。该协议能够提供可加密认证的远程终端功能。
  • 下面的输出结果来自于没有主动安全外壳连接的系统(除了与服务器相关的输出外,其他所有的输出行都已被删除):
    • -a:该选项能够报告所有的网络节点,包括那些处于侦听状态和未处于侦听状态的节点
    • -n:以点分十进制(或十六进制)数的形式打印IP地址,而不会试图利用DNS将地址转换为一个域名。此外,该选项还会打印数值端口号(例如22),而不是服务名(例如ssh)
    • -t:用于只选择TCP节点

  • 本地地址(这实际意味着本地节点)的输出结果为:::22。这种面向IPv6的方式表示的是全0地址,也被称作通配符地址,并使用端口号220这意味着一个针对22号端口的连接进 人请求(即一个SYN)会被任何本地接口接受。如果主机是多宿主的(此例即是),我们可以为本地IP地址指定一个单一的地址(主机IP地址中的一个地址),并且只有被该接口接收到的连接才能够被接受(参见本节后面的例子)。端口号22是为安全外壳协议预留的知名端口号。其他端口号是由网络编号分配机构(ITNA)来维护的
  • 外部地址的输出结果为:::*,这表示一个通配符地址与端口号(即,一个通配符节点)。 此处由于本地节点处于LISTEN状态,正等待一个连接的到来,因此外部地址与外部端口号尚不知晓。现在我们在主机10.0.0.3上启动一个安全外壳客户端来连接该服务器
  • 下面是从netstat输出的相关行(RECV-Q与Send-Q列只包含零值,为清楚起见,将其删除):

  • 标有端口号“22”的第2行是一个ESTABLISHED连接。本地与外地节点相关的4元组都会填写在这个连接中,其中包括:本地IP地址与端口号,外地IP地址与端口号。本地IP地址与连接请求到达的接口相关(以太网接口通过与IPv4地址映射的IPv6地址::ffff:10.0.0.1进行标识)
  • 处于LISTEN状态的本地节点会独自地运行。它用于为并行服务器接收未来可能出现的请求。当有新的连接请求到达并被接收时,操作系统中的TCP模块创建处于ESTABLISHED 状态的新节点。同样需要注意的是,当连接处于ESTABLISHED状态时,它的端口号仍为22,与LISTEN状态时相同
  • 现在我们从同一个系统( 10.0.0.3 )向服务器发送另一个客户端请求。相关的netstat输出如下:

  • 现在我们有两个处于ESTABLISHED状态的连接。它们从同一个客户端到相同的服务器 端。两条连接的服务器端口号均为220 由于外地端日号不同,因此这并不算是一个TCP错误。这两个外地端口必须是不同的。因为每个安全外壳程序使用的是一个临时端口,而每一 个临时端口在定义时都是主机( 10.0.0.3 )尚未使用的端口
  • 这个例子再次说明TCP依靠4元组多路分解了获得的报文段。目的IP 地址与目的端口号、源IP地址与源端日号,这4元组共同构成了本地与外地节点。TCP协 议不能仅仅根据目的端口号来决定哪一个进程该得到接收的报文段。在所有三个节点中只有 位于端口22的节点会处于LISTEN状态并接收进人的连接请求。处于ESTABLISHED状态的节点不能接收SYN报文段,而处于LISTEN状态的节点则不能接收数据段。例子中主机 的操作系统已经证实了这一点。 (如果不能如此,TCP就会变得非常混乱,从而不能正常地工作)
  • 下面我们发起第三个客户端连接。这条连接来自IP地址169.229.62.97,通过DSL PPPoE链路与服务器10.0.0.1相连。因此这个客户端与服务器不处于同一个以太网。 (为了清 楚起见,下面的输出结果移除了Proto列,只留下了tcp部分)

  • 在这台多宿主的主机上,第三条ESTABLISHED连接的IP地址与PPPoE链路的接口地址(67.125.227.195 )相关联。值得注意的是Send-Q状态的数值并不是0,而是被928字节所代替。这意味着服务器已经在这条连接上发送了928字节的数据但仍然未收到任何确认

二、限制本地IP地址

  • 本节将介绍当服务器不能借助通配符处理某个本地IP地址而将其设置为一个特殊的本地地址时发生的情况。如果我们将sock程序当作服务器运行并为其提供一个特殊的IP地址,那么该地址将成为监听端的本地IP地址。例如:

  • 这样就限制了服务器只能使用到达本地IPv4地址10.0.0.1的连接。下面netstat的输出结果 反映了这一点:

  • 在上述例子中特别有趣的是我们的sock程序只与本地IPv4地址10.0.0.1绑定,所以 netstat的输出结果与之前不同。在之前的例子中,通配符地址与端口号标识了两种版本的IP地址。在这种情况下,我们绑定了一个特殊的地址、端口或地址族(只适用IPv4)。如果我 们从本地网络连接这台服务器,比如从主机10.0.0.3,那么正常工作的记录情况如下:

  • 如果我们从一台目的地址不是10.0.0.1 (甚至包括本地地址127.0.0.1 )的主机连接服务器,连接请求将不会被TCP模块接收。如果我们查看tcpdump,SYN会引发一个RST报文段,如下图所示

  • 服务器的应用程序将不会察觉到连接请求一因为拒绝接收的操作是由操作系统的TCP模块根据该应用程序指定的本地IP地址与SYN报文段中包含的目的地址做出的。我们发现限制本地IP地址的能力是相当严格的

三、限制外部节点

  • 根据前面UDP的文章介绍,一台UDP服务器不仅能够指定本地IP地址与本地端口号,还能 够指定外部IP地址与外部端口号。 [RFCO793]所介绍的TCP的抽象接口函数允许一台服务 器为一个完全指定的外部节点(等待一个特定的客户端以发起一个主动打开)或者一个未被指定的外部节点(等待任何客户端)执行被动打开
  • 不幸的是,普通的伯克利套接字API没有提供实现这一点的方法。服务器不必指定客户端的节点,而是等待连接的到来,然后检查客户端的IP地址与端口号。下表概括了TCP 服务器能够建立的三种类型的地址绑定

  • 在上述例子中,local_port是服务器被分配的端口号,而Iocal_IP必须是一个应用于本地系统的单播IP地址。上表中三行的排列顺序显示了当收到一个连接请求时TCP模块决定选择哪一个节点的次序。最明确的绑定(第1行,如果支持的话)将会被首先尝试,而 最不明确的绑定(最后一行,所有的IP地址都用通配符表示)将会被最后尝试。对于同时 支持IPv4与IPv6双协议栈的系统,它的端口号空间可能会出现混合的情况。从本质上说,这意味着如果服务器使用IPv6地址绑定了一个端口号,那么也就将该端口号应用于了IPv4 地址

四、进入连接队列

  • 一个并行的服务器会为每一个客户端分配一个新的进程或线程。这样负责侦听的服务器能够始终准备着处理下一个到来的连接请求。这是使用并行服务器的根本原因。然而,在侦听服务器正创建一个新进程时,或者在操作系统忙于运行其他高优先级的进程时,甚至更糟的是在服务器正在被伪造的连接请求(这些伪造的建立连接请求是不被允许的)攻击时,多个连接请求可能会到达。在这些情况下,TCP应当如何处理呢?
  • 为了充分探讨这个问题,我们首先必须认识到,在被用于应用程序之前新的连接可能会处于下述两个状态:
    • 一种是连接尚未完成但是已经接收到SYN(也就是处于SYN_RCVD状态)
    • 另一种是连接已经完成了三次握手并且处于ESTABLISHED状态,但还未被应用程序接受
  • 因此在内部操作系统通常会使用两个不同的连接队列分别对应上述两种不同的情况

队列大小的控制规则:

  • 应用程序通过限制这些队列的大小来进行控制。传统上,使用伯克利套接字API应用程序只能间接地控制这两个队列的大小总和。在现代的Linux内核中,这种行为已更改为第二种状况下的连接数目(ESTABLISHED状态的连接)。因此,应用程序能够限制完全形成的等待处理的连接数目
  • 在Linux中,将会适用以下规则:

  • 当一个连接请求到达(即,SYN报文段),将会检查系统范围的参数net.ipv4.tcp_ max_syn_backlog(默认为1000)。如果处于SYN_RCVD状态的连接数目超过了这一阀值,进入的连接将会被拒绝

  • 每一个处于侦听状态下的节点都拥有一个固定长度的连接队列。其中的连接已经被TCP完全接受(即三次握手已经完成),但未被应用程序接受。应用程序会对这一队列做出限制,通常称为未完成连接(backlog)。backlog的数目必须在0与一个系统指定的最大值之间。 该最大值称为net.core.somaxcorm,默认值为128(包含)
  • 需要记住的是backlog的数值指出了一个侦听节点中排队连接的最大数目,所有这些连接已经被TCP接受并等待应用程序接受。无论对系统所允许的已经建立连接的最大数目,还是对一个并行服务器所能同时处理的客户端数目,backlog都不会造成影响

  • 如果侦听节点的队列中仍然有空间分配给新的连接,TCP模块会应答SYN并完成连接。直到接收到三次握手中的第3个报文段之后,与侦听节点相关的应用程序才会知道新的连接。当客户端的主动打开操作顺利完成之后,客户端可能会认为服务器已经准备好接收数据,然而服务器上的应用程序此时可能还未收到关于新连接的通知。如果这种情况发生,服务器的TCP模块将会把到来的数据存人队列中

  • 如果队列中已没有足够的空间分配给新的连接,TCP将会延迟对SYN做出响应,从而给应用程序一个跟上节奏的机会。Linux在这一方面有着独特的行为一它坚持在能力允许的范围内不忽略进入的连接。如果系统控制变量net.ipv4.tcp_abort_on一已被设定,新进入的连接会被重置报文段重新置位

队列溢出

  • 在队列溢出的情况下,发送重置报文段通常是不可取的,而且默认情况下这项功能也不会打开。客户端会尝试与服务器联系,如果它在交换SYN期间接收到一个重置报文段,那么它可能会错误地认为没有服务器存在(而不是认为有一台服务器存在并且十分繁忙)。太忙实际上是一种“软”的或者暂时的错误,而不是一种硬性的错误。正常情况下,当队列已满,应用程序或操作系统会十分繁忙,此时应当阻止应用程序再去服务那些进入的连接。上述状况可能会在短时间内得到改善。然而,如果一台服务器的TCP使用重置报文段进行回复,那么客户端将会放弃主动打开的操作(这与服务器没有启动时所看到的情况是类似的)。在不发送重置报文段的情况下,如果一台侦听的服务器始终无法抽出时间来接受那些已经被 TCP接受却超出队列保存上限的连接,那么根据正常的TCP机制,客户端的主动打开操作将会最终超时。在Linux中,连接的客户端将会明显地放缓一段时间一它们既不会超时也不会重置
  • 借助我们的sock程序,大家会看到当进人连接队列溢出后会发生的情况。我们利用一 个新的选项(-o)来调用程序,并且告诉它在创建完侦听节点后暂停,直到接收到任何连接 请求。如果之后我们在暂停期间再调用多个客户端,服务器的接收连接队列将会被填满,因此可以借助tcpdump检查所发生的一切

  • -ql选项将侦听节点的backlog数值设置为1。 -O30000选项使程序在接收任何客户端连接之前先休眠30000秒(基本上是一个很长的时间,大约8小时)。如果我们现在不断地尝 试连接这台服务器,最早的4个连接将会被立即完成。此后两个连接需要9秒才能完成。其 他操作系统在处理这种情况时会有明显的不同。例如在Solaris 8和FreeBSD 4.7中,两个连 接会被立即处理而第3个连接将会超时,而随后的连接也将超时
  • 下图显示了用一台Linux客户端连接一台FreeBSD服务器的tcpdump输出结果。FreeBSD服务器上运行着符合上文参数设定的sock程序(在TCP连接建立时,即三次握手 完成时,已经用黑体标记了客户端的端口号)。

  • TCP接受的第1个客户端连接请求来自端口2461 (报文段1 - 3)。第2个客户端的连 接请求来自端口2462,也被TCP接受(报文段4 - 6)。服务器的应用程序仍处于睡眠状态,不能够接受任何连接。所有的工作都是由操作系统的TCP模块来完成的。由于三次握手过 程均已完成,这两个客户端都从主动打开成功地返回。 我们尝试开启第3个客户端,它的SYN报文段如上图中的第7个报文段所示(端口号2463),但由于侦听节点的队列已满,服务器端的TCP忽略了该SYN报文段。客户端根据二进制指数退避机制重新传输了它的SYN报文段,如上图中的第8 - 12个报文 段所示。在FreeBSD与Solaris系统中,TCP会在队列满后忽略进人的SYN报文段
  • 据前文所述,如果侦听者的队列有足够的空间,TCP将会接受一个进人的连接请求(即 一个SYN报文段),而不会让应用程序去识别这条连接来自何方(源IP地址与源端口号)。 这一点并不是TCP协议所要求的,而是通用的实现技术(即伯克利套接字的工作方式)。如果采用替代伯克利套接字API的方法(例如TLI/XTI),能够为应用程序提供一种了解何时有 连接到达的方法,并允许它们选择是否接受到达的连接请求。TLI只是在理论上提供这种能力,但未能完全付诸实践,因此伯克利套接字能够更有效地为TCP接日提供支持
  • 在借助伯克利套接字实现的TCP中,当应用程序被告知一条连接已经到达时,TCP的 三次握手过程已经完成。上述行为也意味着一个TCP服务器无法让一个客户端的主动打开操作失败。当一个新的客户端连接传达至服务器应用程序时,TCP的三次握手过程已经结束,而且客户端的主动打开操作已经成功完成。如果此后服务器查看了客户端的IP地址与端口号,并且决定不向该客户端提供服务,那它只能关闭(发送一个FIN)或者重置这条连接(发送一个RST)。无论处于上述哪一种情况,客户端在完成主动打开操作后都会认为一切正常, 甚至已经向服务器发出了请求。因此,需要其他传输层协议来为应用程序提供区分连接到达 与接受的功能(即OSI模型的传输层),但不是TCP


这里给大家推荐一个在线软件复杂项交易平台:米鼠网 https://www.misuland.com

米鼠网自成立以来一直专注于从事软件项目人才招聘软件商城等,始终秉承“专业的服务,易用的产品”的经营理念,以“提供高品质的服务、满足客户的需求、携手共创双赢”为企业目标,为中国境内企业提供国际化、专业化、个性化、的软件项目解决方案,我司拥有一流的项目经理团队,具备过硬的软件项目设计和实施能力,为全国不同行业客户提供优质的产品和服务,得到了客户的广泛赞誉。



如有侵权请联系邮箱(service@misuland.com)

猜你喜欢

评论留言