相对于SOCKET开发者,TCP创建过程和链接折除过程是由TCP/IP协议栈⾃动创建的.因此开发者并不需要控制这个过程.但是对于理解TCP底层运作机制,相当有帮助.
⽽且对于有⽹络协议⼯程师之类笔试,⼏乎是必考的内容.企业对这个问题热情之⾼,出乎我的意料:-)。有时上午⾯试前强调这个问题,并重复讲⼀次,下午⼏乎每⼀个⼈都被问到这个问题。因此在这⾥详细解释⼀下这两个过程。TCP三次握⼿
所谓三次握⼿(Three-way Handshake),是指建⽴⼀个TCP连接时,需要客户端和服务器总共发送3个包。
三次握⼿的⽬的是连接服务器指定端⼝,建⽴TCP连接,并同步连接双⽅的序列号和确认号并交换 TCP 窗⼝⼤⼩信息.在socket编程中,客户端执⾏connect()时。将触发三次握⼿。
第⼀次握⼿:
客户端发送⼀个TCP的SYN标志位置1的包指明客户打算连接的服务器的端⼝,以及初始序号X,保存在包头的序列号(Sequence Number)字段⾥。
第⼆次握⼿:
服务器发回确认包(ACK)应答。即SYN标志位和ACK标志位均为1同时,将确认序号(Acknowledgement Number)设置为客户的I S N加1以.即X+1。
第三次握⼿.
客户端再次发送确认包(ACK) SYN标志位为0,ACK标志位为1.并且把服务器发来ACK的序号字段+1,放在确定字段中发送给对⽅.并且在数据段放写ISN的+1
SYN攻击
在三次握⼿过程中,服务器发送SYN-ACK之后,收到客户端的ACK之前的TCP连接称为半连接(half-open connect).此时服务器处于Syn_RECV状态.当收到ACK后,服务器转⼊ESTABLISHED状态.
Syn攻击就是 攻击客户端 在短时间内伪造⼤量不存在的IP地址,向服务器不断地发送syn包,服务器回复确认包,并等待客户的确认,由于源地址是不存在的,服务器需要不断的重发直 ⾄超时,这些伪造的SYN包将长时间占⽤未连接队列,正常的SYN请求被丢弃,⽬标系统运⾏缓慢,严重者引起⽹络堵塞甚⾄系统瘫痪。
Syn攻击是⼀个典型的攻击。检测SYN攻击⾮常的⽅便,当你在服务器上看到⼤量的半连接状态时,特别是源IP地址是随机的,基本上可以断定这是⼀次SYN攻击.在下可以如下命令检测是否被Syn攻击netstat -n -p TCP | grep SYN_RECV
⼀般较新的TCP/IP协议栈都对这⼀过程进⾏修正来防范Syn攻击,修改tcp协议实现。主要⽅法有SynAttackProtect保护机制、SYN cookies技术、增加最⼤半连接和缩短超时时间等.但是不能完全防范syn攻击。TCP 四次挥⼿
TCP的连接的拆除需要发送四个包,因此称为四次挥⼿(four-way handshake)。客户端或服务器均可主动发起挥⼿动作,在socket编程中,任何⼀⽅执⾏close()操作即可产⽣挥⼿操作。
参见wireshark抓包,实测的抓包结果并没有严格按挥⼿时序。我估计是时间间隔太短造成。注意上⾯的字段标号地段和发送接收的内容序号,可能有个有错,记不住哪个了,后头要细看看
第⼆部分:补充tcp连接过程
在TCP/IP协议中,TCP协议提供可靠的连接服务,采⽤三次握⼿建⽴⼀个连接,如图1所⽰。
(1) 第⼀次握⼿:建⽴连接时,客户端A发送SYN包(SYN=j)到服务器B,并进⼊SYN_SEND状态,等待服务器B确认。
(2) 第⼆次握⼿:服务器B收到SYN包,必须确认客户A的SYN(ACK=j+1),同时⾃⼰也发送⼀个SYN包(SYN=k),即SYN+ACK包,此时服务器B进⼊SYN_RECV状态。
(3) 第三次握⼿:客户端A收到服务器B的SYN+ACK包,向服务器B发送确认包ACK(ACK=k+1),此包发送完毕,客户端A和服务器B进⼊ESTABLISHED状态,完成三次握⼿。完成三次握⼿,客户端与服务器开始传送数据。
图1 TCP三次握⼿建⽴连接
由于TCP连接是全双⼯的,因此每个⽅向都必须单独进⾏关闭。这个原则是当⼀⽅完成它的数据发送任务后就能发送⼀个FIN来终⽌这个⽅
向的连接。收到⼀个 FIN只意味着这⼀⽅向上没有数据流动,⼀个TCP连接在收到⼀个FIN后仍能发送数据。⾸先进⾏关闭的⼀⽅将执⾏主动关闭,⽽另⼀⽅执⾏被动关闭。
(1)客户端A发送⼀个FIN,⽤来关闭客户A到服务器B的数据传送(报⽂段4)。
(2)服务器B收到这个FIN,它发回⼀个ACK,确认序号为收到的序号加1(报⽂段5)。和SYN⼀样,⼀个FIN将占⽤⼀个序号。(3)服务器B关闭与客户端A的连接,发送⼀个FIN给客户端A(报⽂段6)。(4)客户端A发回ACK报⽂确认,并将确认序号设置为收到序号加1(报⽂段7)。TCP采⽤四次挥⼿关闭连接如图2所⽰。
图2 TCP四次挥⼿关闭连接
1.为什么建⽴连接协议是三次握⼿,⽽关闭连接却是四次握⼿呢?
这是因为服务端的LISTEN状态下的SOCKET当收到SYN报⽂的连接请求后,它可以把ACK和SYN(ACK起应答作⽤,⽽SYN起同步作⽤)放在⼀个报⽂⾥来发送。但关闭连接时,当收到对⽅的FIN报⽂通知时,它仅仅表⽰对⽅没有数据发送给你了;但未必你所有的数据都全部发送给对⽅了,所以你可能未必会马上会关闭SOCKET,也即你可能还需要发送⼀些数据给对⽅之后,再发送FIN报⽂给对⽅来表⽰你同意现在可以关闭连接了,所以它这⾥的ACK报⽂和FIN报⽂多数情况下都是分开发送的。
2.为什么TIME_WAIT状态还需要等2MSL后才能返回到CLOSED状态?这个问题可以参考《unix ⽹络编程》(第三版,2.7 TIME_WAIT状态)。TIME_WAIT状态由两个存在的理由。(1)可靠的实现TCP全双⼯链接的终⽌。
这是因为虽然双⽅都同意关闭连接了,⽽且握⼿的4个报⽂也都协调和发送完毕,按理可以直接回到CLOSED状态(就好⽐从SYN_SEND状态到ESTABLISH状态那样);但是因为我们必须要假想⽹络是不可靠的,你⽆法保证你最后发送的ACK报⽂会⼀定被对⽅收到,因此对⽅处于LAST_ACK状态下的SOCKET可能会因为超时未收到ACK报⽂,⽽重发FIN报⽂,所以这个TIME_WAIT状态的作⽤就是⽤来重发可能丢失的ACK报⽂。
(2)允许⽼的重复的分节在⽹络中消逝。
假 设在12.106.32.254的1500端⼝和206.168.1.112.219的21端⼝之间有⼀个TCP连接。我们关闭这个链接,过⼀段时间后在 相同的IP地址和端⼝建⽴另⼀个连接。后⼀个链接成为前⼀个的化⾝。因为它们的IP地址和端⼝号都相同。TCP必须防⽌来⾃某⼀个连接的⽼的重复分组在连 接已经终⽌后再现,从⽽被误解成属于同⼀链接的某⼀个某⼀个新的化⾝。为做到这⼀点,TCP将不给处于TIME_WAIT状态的链接发起新的化⾝。既然 TIME_WAIT状态的持续时间是MSL的2倍,这就⾜以让某个⽅向上的分组最多存活msl秒即被丢弃,另⼀个⽅向上的应答最多存活msl秒也被丢弃。 通过实施这个规则,我们就能保证每成功建⽴⼀个TCP连接时。来⾃该链接先前化⾝的重复分组都已经在⽹络中消逝了。
3. 为什么不能⽤两次握⼿进⾏连接?
我们知道,3次握⼿完成两个重要的功能,既要双⽅做好发送数据的准备⼯作(双⽅都知道彼此已准备好),也要允许双⽅就初始序列号进⾏协商,这个序列号在握⼿过程中被发送和确认。
现在把三次握⼿改成仅需要两次握⼿,死锁是可能发⽣的。作为例⼦,考虑计算机S和C之间的通信,假定C给S发送⼀个连接请求分组,S收到了这个分组,并发 送了确认应答分组。按照两次握⼿的协定,S认为连接已经成功地建⽴了,可以开始发送数据分组。可是,C在S的应答分组在传输中被丢失的情况下,将不知道S 是否已准备好,不知道S建⽴什么样的序列号,C甚⾄怀疑S是否收到⾃⼰的连接请求分组。在这种情况下,C认为连接还未建⽴成功,将忽略S发来的任何数据分 组,只等待连接确认应答分组。⽽S在发出的分组超时后,重复发送同样的分组。这样就形成了死锁。 补充:
a. 默认情况下(不改变socket选项),当你调⽤close( or closesocket,以下说close不再重复)时,如果发送缓冲中还有数据,TCP会继续把数据发送完。
b. 发送了FIN只是表⽰这端不能继续发送数据(应⽤层不能再调⽤send发送),但是还可以接收数据。
c. 应⽤层如何知道对端关闭?通常,在最简单的阻塞模型中,当你调⽤recv时,如果返回0,则表⽰对端关闭。在这个时候通常的做法就是也调⽤close,那么TCP层就发送FIN,继续完成四次握⼿。如果你不调⽤close,那么对端就会处于FIN_WAIT_2状态,⽽本端则会处于CLOSE_WAIT状态。这个可以写代码试试。
d. 在很多时候,TCP连接的断开都会由TCP层⾃动进⾏,例如你CTRL+C终⽌你的程序,TCP连接依然会正常关闭,你可以写代码试试。
插曲:
特别的TIME_WAIT状态:
从以上TCP连接关闭的状态转换图可以看出,主动关闭的⼀⽅在发送完对对⽅FIN报⽂的确认(ACK)报⽂后,会进⼊TIME_WAIT状态。TIME_WAIT状态也称为2MSL状态。
什么是2MSL?MSL即Maximum Segment Lifetime,也就是报⽂最⼤⽣存时间,引⽤《TCP/IP详解》中的话:“它(MSL)是任何报⽂段被丢弃前在⽹络内的最长时间。”那么,2MSL也就是这个时间的2倍。其实我觉得没必要把这个MSL的确切含义搞明⽩,你所需要明⽩的是,当TCP连接完成四个报⽂段的交换时,主动关闭的⼀⽅将继续等待⼀定时间(2-4分钟),即使两端的应⽤程序结束。你可以写代码试试,然后⽤setstat查看下。
为什么需要2MSL?根据《TCP/IP详解》和《The TCP/IP Guide》中的说法,有两个原因:
其⼀,保证发送的ACK会成功发送到对⽅,如何保证?我觉得可能是通过超时计时器发送。这个就很难⽤代码演⽰了。
其⼆,报⽂可能会被混淆,意思是说,其他时候的连接可能会被当作本次的连接。直接引⽤《The TCP/IP Guide》的说法:The second isto provide a “buffering period” between the end of this connection and any subsequent ones. If not for this period, it is possible that packetsfrom different connections could be mixed, creating confusion.
TIME_WAIT状态所带来的影响:
当某个连接的⼀端处于TIME_WAIT状态时,该连接将不能再被使⽤。事实上,对于我们⽐较有现实意义的是,这个端⼝将不能再被使⽤。某个端⼝处于TIME_WAIT状态(其实应该是这个连接)时,这意味着这个TCP连接并没有断开(完全断开),那么,如果你bind这个端⼝,就会失败。对于服务器⽽⾔,如果服务器突然crash掉了,那么它将⽆法再2MSL内重新启动,因为bind会失败。解决这个问题的⼀个⽅法就是设置socket的SO_REUSEADDR选项。这个选项意味着你可以重⽤⼀个地址。
对于TIME_WAIT的插曲:
当建⽴⼀个TCP连接时,服务器端会继续⽤原有端⼝监听,同时⽤这个端⼝与客户端通信。⽽客户端默认情况下会使⽤⼀个随机端⼝与服务器端的监听端⼝通信。有时候,为了服务器端的安全性,我们需要对客户端进⾏验证,即限定某个IP某个特定端⼝的客户端。客户端可以使⽤bind来使⽤特定的端⼝。对于服务器端,当设置了SO_REUSEADDR选项时,它可以在2MSL内启动并listen成功。但是对于客户端,当使
⽤bind并设置SO_REUSEADDR时,如果在2MSL内启动,虽然bind会成功,但是在windows平台上connect会失败。⽽在linux上则不存在这个问题。(我的实验平台:winxp, ubuntu7.10)
要解决windows平台的这个问题,可以设置SO_LINGER选项。SO_LINGER选项决定调⽤close时TCP的⾏为。SO_LINGER涉及到linger结构体,如果设置结构体中l_onoff为⾮0,l_linger为0,那么调⽤close时TCP连接会⽴刻断开,TCP不会将发送缓冲中未发送的数据发送,⽽是⽴即发送⼀个RST报⽂给对⽅,这个时候TCP连接就不会进⼊TIME_WAIT状态。如你所见,这样做虽然解决了问题,但是并不安全。通过以上⽅式设置SO_LINGER状态,等同于设置SO_DONTLINGER状态。
断开连接时的意外:
这个算不上断开连接时的意外,当TCP连接发⽣⼀些物理上的意外情况时,例如⽹线断开,linux上的TCP实现会依然认为该连接有效,⽽windows则会在⼀定时间后返回错误信息。这似乎可以通过设置SO_KEEPALIVE选项来解决,不过不知道这个选项是否对于所有平台都有效。
第三部分:常见⾯试题
TCP协议和UDP协议的区别是什么
TCP协议是有连接的,有连接的意思是开始传输实际数据之前TCP的客户端和服务器端必须通过三次握⼿建⽴连接,会话结束之后也要结束连接。⽽UDP是⽆连接的
TCP协议保证数据按序发送,按序到达,提供超时重传来保证可靠性,但是UDP不保证按序到达,甚⾄不保证到达,只是努⼒交付,即便是按序发送的序列,也不保证按序送到。
TCP协议所需资源多,TCP⾸部需20个字节(不算可选项),UDP⾸部字段只需8个字节。TCP有流量控制和拥塞控制,UDP没有,⽹络拥堵不会影响发送端的发送速率TCP是⼀对⼀的连接,⽽UDP则可以⽀持⼀对⼀,多对多,⼀对多的通信。TCP⾯向的是字节流的服务,UDP⾯向的是报⽂的服务。和UDP介绍
请详细介绍⼀下TCP协议建⽴连接和终⽌连接的过程?
助于理解的两幅图():
建⽴连接:三次握⼿
关闭连接:四次挥⼿
三次握⼿建⽴连接时,发送⽅再次发送确认的必要性?
主 要是为了防⽌已失效的连接请求报⽂段突然⼜传到了B,因⽽产⽣错误。假定出现⼀种异常情况,即A发出的第⼀个连接请求报⽂段并没有丢失,⽽是在某些⽹络结 点长时间滞留了,⼀直延迟到连接释放以后的某个时间才到达B,本来这是⼀个早已失效的报⽂段。但B收到此失效的连接请求报⽂段后,就误认为是A⼜发出⼀次 新的连接请求,于是就向A发出确认报⽂段,同意建⽴连接。假定不采⽤三次握⼿,那么只要B发出确认,新的连接就建⽴了,这样⼀直等待A发来数据,B的许多 资源就这样⽩⽩浪费了。
四次挥⼿释放连接时,等待2MSL的意义?
第 ⼀,为了保证A发送的最有⼀个ACK报⽂段能够到达B。这个ACK报⽂段有可能丢失,因⽽使处在LAST-ACK状态的B收不到对已发送的FIN和ACK 报⽂段的确认。B会超时重传这个FIN和ACK报⽂段,⽽A就能在2MSL时间内收到这个重传的ACK+FIN报⽂段。接着A重传⼀次确认。
第⼆,就是防⽌上⾯提到的已失效的连接请求报⽂段出现在本连接中,A在发送完最有⼀个ACK报⽂段后,再经过2MSL,就可以使本连接持续的时间内所产⽣的所有报⽂段都从⽹络中消失。
常见的应⽤中有哪些是应⽤TCP协议的,哪些⼜是应⽤UDP协议的,为什么它们被如此设计?
以下应⽤⼀般或必须⽤udp实现?
多播的信息⼀定要⽤udp实现,因为tcp只⽀持⼀对⼀通信。
如果⼀个应⽤场景中⼤多是简短的信息,适合⽤udp实现,因为udp是基于报⽂段的,它直接对上层应⽤的数据封装成报⽂段,然后丢在⽹络中,如果信息量太⼤,会在链路层中被分⽚,影响传输效率。
如果⼀个应⽤场景重性能甚于重完整性和安全性,那么适合于udp,⽐如多媒体应⽤,缺⼀两帧不影响⽤户体验,但是需要流媒体到达的速度快,因此⽐较适合⽤udp如果要求快速响应,那么udp听起来⽐较合适
如果⼜要利⽤udp的快速响应优点,⼜想可靠传输,那么只能考上层应⽤⾃⼰制定规则了。常见的使⽤udp的例⼦:ICQ,QQ的聊天模块。以qq为例的⼀个说明(转载⾃)
登陆采⽤TCP协议和HTTP协议,你和好友之间发送消息,主要采⽤UDP协议,内⽹传⽂件采⽤了P2P技术。总来的说:
1.登陆过程,客户端client 采⽤TCP协议向服务器server发送信息,HTTP协议下载信息。登陆之后,会有⼀个TCP连接来保持在线状态。 2.和好友发消息,客户端client采⽤UDP协议,但是需要通过服务器转发。腾讯为了确保传输消息的可靠,采⽤上层协议来保证可靠传输。如果消息发送失败,客户端会提⽰消息发送失败,并可重新发送。
3.如果是在内⽹⾥⾯的两个客户端传⽂件,QQ采⽤的是P2P技术,不需要服务器中转。
因篇幅问题不能全部显示,请点此查看更多更全内容