并且对于有网络协议工程师之类笔试,几乎是必考的内容.企业对这个问题热情之高,出乎个人意料:-)。有时上午面试前强调这个问题,并重复讲一次,下午几乎每个人都被问到这个问题。linux
所以在这里详细解释一下这两个过程。面试
TCP三次握手编程
所谓三次握手(Three-way Handshake),是指创建一个TCP链接时,须要客户端和服务器总共发送3个包。ubuntu
三次握手的目的是链接服务器指定端口,创建TCP链接,并同步链接双方的序列号和确认号并交换 TCP 窗口大小信息.在socket编程中,客户端执行connect()时。将触发三次握手。
第一次握手:
客户端发送一个TCP的SYN标志位置1的包指明客户打算链接的服务器的端口,以及初始序号X,保存在包头的序列号(Sequence Number)字段里。
第二次握手:
服务器发回确认包(ACK)应答。即SYN标志位和ACK标志位均为1同时,将确认序号(Acknowledgement Number)设置为客户的I S N加1以.即X+1。windows
第三次握手.
客户端再次发送确认包(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攻击是一个典型的DDOS攻击。检测SYN攻击很是的方便,当你在服务器上看到大量的半链接状态时,特别是源IP地址是随机的,基本上能够判定这是一次SYN攻击.在Linux下能够以下命令检测是否被Syn攻击
netstat -n -p TCP | grep SYN_RECV
通常较新的TCP/IP协议栈都对这一过程进行修正来防范Syn攻击,修改tcp协议实现。主要方法有SynAttackProtect保护机制、SYN cookies技术、增长最大半链接和缩短超时时间等.
可是不能彻底防范syn攻击。
TCP 四次挥手
TCP的链接的拆除须要发送四个包,所以称为四次挥手(four-way handshake)。客户端或服务器都可主动发起挥手动做,在socket编程中,任何一方执行close()操做便可产生挥手操做。centos
参见wireshark抓包,实测的抓包结果并无严格按挥手时序。我估计是时间间隔过短形成。注意上面的字段标号地段和发送接收的内容序号,可能有个有错,记不住哪一个了,后头要细看看安全
第二部分:补充tcp链接过程服务器
在TCP/IP协议中,TCP协议提供可靠的链接服务,采用三次握手创建一个链接,如图1所示。cookie
(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 is to provide a “buffering period” between the end of this connection and any subsequent ones. If not for this period, it is possible that packets from 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面向的是报文的服务。
- TCP介绍和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技术,不须要服务器中转。