配色: 字号:
TCP连接的三次握手详解
2013-10-31 | 阅:  转:  |  分享 
  
TCP连接的三次握手详解1.连接时三次握手三次握手过程如下图所示:

整个过程可简述为如下五步:①服务器端,通过调用socket、bind、listen等待客户端的连接。②客户端调用socket后,调用connect主动连接,它向服务器发送一个连接请求报文段,该报文段中TCP首部中的同步位SYN(Synchronize)=1,同时选择一个初始序列号seq=J,一般来说,SYN连接请求报文段不携带数据,只包含一个IP头部、TCP头部及可能有的TCP选项信息。但是,它要消耗一个序列号。然后客户端进程进入SYN_SENT(同步已发送)状态。③服务器端进程接收到客户端的SYN连接请求报文段后,它向客户端发送一个确认报文段,该报文段中TCP首部中的同步位SYN和确认位ACK都为1,确认号ack=J+1(表示J报文段已经收到,下次要接收的报文段序号为J+1),同时它也要选择一个初始序列号seq=K,同样该报文段一般也不携带数据,要消耗一个序列号。此时服务器端进程进入SYN_RECV(同步收到)状态。④客户端收到服务端确认报文段后,它再想客户端发送一个确认报文段,该报文段中ACK位为1,确认号ack=K+1,该报文段序列号seq=J+1。TCP标准

规定,该报文段可以携带数据,但若不携带数据则不消耗序列号,即此时,下一个报文段序列号还是J+1,这是客户端进入ESTABLISHLED(已建立连接)状态。⑤服务器端收到客户端的确认后,也进入ESTABLISHLED状态。下图为用WireShark软件抓取的TCP通信过程的数据包。客户端IP地址为:10.3.130.90(从第34帧数据中,我们也可以看出)服务器端IP地址为:10.3.128.39第一条红线上方为建立连接时,三次握手过程。第二条红线下方为关闭连接时,四次握手过程。两条红线中间为数据数据通信的过程,第299帧为客户端向服务器端发送一个报文段,然后第300帧为服务器端对报文段的确认报文段,我们可以看到该报文段的Len为0,所以它是不含数据的。类似的,第823帧为服务器端向客户端发送了一个报文段,然后824帧为客户端对该报文段的确认。

我们看一下第35帧的数据。

我们发现TCP头部中Flags标志中的ACK和Syn位都为1,Seq=0,Ack=1。好,现在看第36帧,是客户端向服务器端发的确认报文段,显然其Len=0,也即它是不携带数据的,此时的Seq=1,根据我们所说的TCP标准,此报文段应该是不会占有该序列号的,也即下次客户端向服务器端发送数据的起始报文段序号还是1。那我们看看第299帧是不是如此。

由上图,我们可以看出,确实Seq=1,并且下一次报文段的Seq=7,大家可能是不是会觉得下一次的报文段Seq=2,其实,我们要理解对Seq序列号的标记是根据传输的数据(真真实实的数据,除去哪些头部的)的字节数决定的,对每一个字节的数据都必须对应一个Seq。那由Seq=1,Nextsequencenumber=7,我们可知这个报文段这次一共传了6个字节的数据。我们看上图下面的TCPsegmentdata发现确实是传了6字节的数据,并且由箭头所指处可知,传的是字符串“123456”,然后由图最上面的Length列可知(由截图最下面中间部分也可知),该报文段的整个大小为60字节,而真实的数据只有6字节,也就是说其他头部信息占了54字节,那以太网(数据链路层)头部、IP头部、TCP头部各占多少呢?好,那我们来看看一个应用程序的数据是怎样由上层向下层封装的。由下图,我们可知以太网首部为14字节,IP首部和TCP首部都为20字节,呵呵,正好54字节,当然,这里这个数字理论上说应该说是最小的情况,各个头部在某些情况下,头部可能会大于上面的值。不信,我们看看第34、35帧的报文段Length都为66,但是真实数据Len=0,所以该报文段

包含应该全部是些首部信息为66字节大于54。由上面的分析可知,第299帧,应该是没有包含4字节的以太网尾部信息的。

好了,对于上面的分析我们告一段落,现在再来讨论一个问题,也是面试的时候,经常会被问到的.问题1:假如第三次客户端向服务器端发送的ACK的丢失,会出现什么情况?问题分析:第三次客户端向服务器端发送的ACK丢失,此时客户端处于ESTABLISHLED状态,但服务器端处于SYN_RECV状态。此时可能会出现两种情况:(1)因为客户端处于ESTABLISHLED状态,所以它认为连接已经建立了可以发送数据了,此时如果它发送数据给服务端,但是由于服务端没有接收到ACK,它还处于SYN_RECV状态,这个时候服务器端接收到客户端发来的数据,将回应RST报文,告之Server端错误。(2)对于Server端,它在一段时间内没有接收到ACK报文,它将重新发送SYN+ACK报文段,以便Client端再次发送ACK报文。问题2:TCP建立连接的时候,为什么非要用三次握手,而只用两次可以吗?问题分析:问题意思就是说,不用第三次客户端发给服务端的ACK确认报文,即当服务器端接收到客户端的SYN报文时,服务器端就进入ESTABLISHLED状态,然后向客户端发

送SYN+ACK报文,当客户端接收到该报文时,客户端也进入ESTABLISHLED状态。然后两种就可以发送数据了。咋一看,这样似乎没有问题,但是假如服务端向客户端发送的SYN+ACK报文丢失,会出现什么情况?此时Server端进入ESTABLISHLED状态,值得注意的是,进入这个状态,代表该端认为连接建立成功,它就会分配资源,以为接下来数据传输做准备。对于Client端,由于它没有接收到ACK,它会以为上次发送的SYN报文丢失了,会再次重发SYN报文,此时,Server端又会分配资源,造成资源的浪费。但是我并不认为这个理由能站住脚,因为我觉得Server端在再次接收相同的Client端的SYN时,它完全可以根据报文的头部知道该SYN为上次同一Client端发送过来的,它可以不重新分配资源呀,它就会想,我这边对你的连接已经建立成功了呀,你为什么还发SYN来呢,那肯定是因为我刚刚发给你的SYN+ACK报文丢失了,那我重新给你发一次吧,由于它知道是同一个Client端发来的,它完全可以不重新分配资源呀。我看网上还有一种解释,如下:具体可参考链接:http://blog.chinaunix.net/uid-20665047-id-3137792.html

我觉得这种解释也不是完全靠谱,的确,如果第二次的Server端发送给Client端的SYN+ACK在不断lost的情况下,Client端会不断给Server重传SYN,但是就上我上面说的,它完全可以忽略它,不重新分配资源,只是不断给Client端发送SYN+ACK确认。上面说,此时Server进入ESTABLISHLED状态,它会认为连接建立成功,向Client端发数据,但对于Client来说,它处于SYN_SENT状态,并且不知道服务端的序列号,将会忽略S发来的数据,而S超时后,会由重新发送数据。我觉得,正如问题1一样,当服务器端向客户端发送数据时,客户端处于SYN_SENT状态,它完全也可以向S端发送一个RST报文,告诉S端,我这么连接建立错误了,你不要在发送数据来了呀。我觉得还是谢希仁老头的那本《计算机网络》P228页讲解的比较有道理。现在,我们假设一种情况,假如Client端第一次发送的一个SYN到处乱跑或在某个结点停留了很久很久,此时Client端等了半天都没有等到Server端的ACK,它以为第一次发过去的SYN已经lost了,于是发再重发SYN,这次的SYN很快很顺利的到达了Server端,然后Server端发了SYN+ACK给Client端,Client端也接收到了,与是两者连接建立成功了,开始发数据了,

噼噼啪啪,一会数据发送完了,两个都关闭连接会话,但是,第一次本以为已经lost了的SYN报文,突然又传到Server端了,这个时候Server端以为Client端又要来跟他连接了,它接收到了SYN后,向Client端发SYN+ACK报文,并进入ESTABLISHLED状态,等待Client端发数据过来了(假设Server不会主动第一次给Client发数据),问题是,Client端已经关闭了,它不会接收到Server端发过去的SYN+ACK,更不会给Server端发数据了,于是Server端把一切资源都准备好阻塞的等待Client端发数据过来了,然后它就这样一直等下去,等下去,直到永远。谢老爷的《计算机网络》的下载地址:http://pan.baidu.com/share/link?shareid=2466780766&uk=4213349809问题3:为什么TCP通信中Server端要用bind()函数,而客户端不用bind()函数?问题分析:首先解释一下bind()函数的作用,它的作用就是给一个socket绑定一个IP地址

和端口号,对于Server端的socket它也要一个IP地址,但是大多数情况下,Server端的socket绑定的IP地址为INADDR_ANY,它是什么意思呢?现在假设一台Server有三个网卡分配对于A、B、C三个IP地址,假如我们绑定的A地址,就是说我们的Server的IP的地址为A,当Client端连接B、C地址,它是连接不成功的。但是,如果我们的Server绑定的IP地址绑定为INADDR_ANY,则Client端连接三者中的任何一个都是可以成功的。但是,我们知道我们编程中,Client端一般都不会调用bind()函数,如果Client端只有一个网卡,那OS内核肯定会把这仅有的一个IP地址给它,问题是如果,Client端有多个网卡时,OS将到底选择哪个IP给Client端的socket呢?《UNIX网络编程第1卷》(下载地址)P85页有说明:TCP客户端通常不把IP地址绑定到它的socket上。当连接套接口时(调用connect函数时),内核将根据所用外出网络接口来选择源IP地址,而所用外出接口则取决于到达服务器所需的路径。

当然,通常可以不绑定,如果你如果执意要绑定的话,那也是可以的。但是自己指定可能会引起问题,首先你还得必须先知道你Client端的IP地址,还有你指定的端口,可能已经被占用的了,而有内核指定的话,你啥都不用管,它肯定会指定一个没被占用的,通常来说,内核会指定一个和Server端一样的端口号(不要以为Server端使用的端口号必须和Client端使用的端口号要相同,要相同的是服务端使用的端口号和Client端中要连接的服务器的地址中的端口)。注意:对于有连接的Client端,是调用connect()函数时,内核绑定IP地址和端口的。由于无连接时,是不会调用connect函数的,所以要调用bind()函数。也即:有连接:Server需调用bind()函数,Client端可以不用。无连接:Server端、Client端都要调用bind()函数。

本文使用的网络数据抓图软件WireShark:下载地址本文博客地址:http://blog.csdn.net/xgmiao

献花(0)
+1
(本文系xgmiao2012首藏)