我们知道,网络间的通信简单的从应用层来看,就是同一或者不同主机上的进程之间进行信息传送和/或接收。在信息传输的过程中我们需要遵循一定的规则。
举个简单例子,假如我想传送一个信息A给你,那么按照特定的规则我发送的A在传输的过程中实际上是以A++的形式存在的。但是你收到的信息还是A。说明在传输过程中我的信息被“规则化”了以后又被“规则化”回来了。这种“规则”,就是我们平时所说的协议。即,要按照它的格调办事。
本次所讨论的对象TCP协议就是这些规则的一种。也就是说,如果我们在网络上通信时遵循TCP协议的话,就得按照它的格调来。
TCP协议是一个面向连接的协议,无论是在哪一方向另一方发送数据之前,都必须在双方之间建立一条连接
,按照上面所说的,“必须建立连接”就是TCP格调的一部分。
利用TCP协议通信的最简单三个步骤是:
1.双方之间建立一条连接
2.信息传送与接收
3.断开这条连接
注意:本文经常会提到“通信”一词,这里就简单的指进程之间通过网络进行信息交流与传递。
这里有个疑问,是不是建立好连接就能进行通信,通信完成后就关闭或者断开这条连接呢?是的。正常情况下本来就是这么简单方便容易理解记忆加操作!但是,很多时候,情况它不正常。那么不正常指的是什么呢?我们先把问题放在这里,来看一下最正常的情况吧。
在分析这张简单的图之前我们先来看一些概念:
SYN:同步序号,是TCP建立连接时使用的握手信号
ack:接收端给发送端的一种传输类控制字符,表示发来的数据已确认接收无误
FIN:TCP终止时使用的挥手信号
以下,我们就用“服务器”和“客户端”这两个名词来代表我们所讨论的通信两端。
TCP连接时,
服务器端通过socket、bind、listen这三个系统调用函数
来完成接收外来连接的准备阶段。
当客户端需要向服务器发送连接请求之时,要创建好套接字。
两端都准备好了以后就可以完成如图1所示的TCP正常连接了。
就像打电话时双方都需要手机和电话号码一样,TCP连接需要一个套接字对。一个TCP套接字对是一个定义该连接的两个端点的四元组。本地IP地址、本地TCP端口号、外地IP地址、外地TCP端口号。每个套接字对唯一标识一个网络上的一个TCP连接。
标识每个端点的两个值(IP地址和端口号)通常称一个套接字。
图1又叫三次握手,过程如下:
(1)客户端通过调用系统调用函数connect函数发起主动打开,这导致客户端TCP发送一个SYN给服务器端,表示我要连你。
(2)服务器接收到请求以后,发送一个ack告诉客户端说我知道了,同时发送一个SYN给客户端说你已经连在我这里了,准备发数据吧。
(3)客户端收到以后发一个ack给服务器说我知道我能发数据了,发不发看心情。
具体连接的细节我没有介绍,我主要简单介绍一下大致过程以及这其中比较特别的一些情况。
连接好以后双方就可以互相发送和接收数据了,入图3所示
图2又叫四次挥手,如图所示其过程如下:
(1)客户端首先系统调用函数close,(当然也可以是服务器先调用先关闭,这里讲比较常见的情况)主动关闭连接,客户端TCP发送一个FIN给服务器端说我不需要连接了咱们断连吧。
(2)服务器端TCP确认客户端的FIN并且发送ack给客户端说我知道了,知道你不会再发数据给我了。
(3)过了一会儿,服务器调用close关闭客户端在这边连接的那个套接字,同时给客户端发送一个FIN说我把在我上面的连接关了。
(4)客户端收到FIN以后给服务器发送一个ack表示知道自己跟服务器那边没关系了,然后安心的死了。
好了,以上就是TCP连接传送数据最理想最简单的情况,我上面也提到,通常是没有这么简单的,因为它老是发生“意外”,有好的也有不好的,我们把这些“意外”统称为“特殊情况”。
下面,我们来看一下这些情况:####
TCP的半关闭:
先上图:
先注意:
发送一个FIN就表示,我不会再给你发送数据了。
TCP提供了连接的一端在结束它的发送后(发了一个FIN),还能接收来自另一端数据 的能力。直到对方发送过来一个FIN就彻底断开不再(丧失)接收。
我们看着图4中的六个箭头来一步一步分析:
(1)客户端给服务器发送一个FIN,说我想死,不会再给你发数据了。
(2)服务器给客户端发送一个ack,表示我知道你不会再给我发了。
但是,服务器这边还有数据要发给客户端呀,而且刚好客户端现在的状 态是能接收,所以,
(3)服务器端把数据发送给客户端。
(4)客户端就很不矜持的接收了并且发给服务器一个ack,说我收到你的数据啦。
当服务器一看没啥好发的了,你不是想断嘛,那就把你断了吧。
(5)服务器把客户端在它上面的连接断了,发给客户端一个FIN说我已经把你断了不会再给你发了。
(6)客户端收到FIN以后给服务器发送一个ack表示知道自己跟服务器那边没关系了,然后安心的死了。
为了能使用TCP的半关闭特性,编程接口必须为应用程序提供一种方式来说明。
方式就是,图4中客户端在发FIN的时候不再调用close而改调用shutdown,且第二个参数置为1,此时插口的API支持半关闭。
然而,虽然TCP有这种能力但是用得少,一般就是把数据接收完了再发送FIN想死也来得及嘛。不过人家也确实有派的上用场的时候,只是情况比较少而已,应用程序(例图4客户端),一般都是通过调用close来终止两个方向的连接。
RST:表示要求对方重新建立连接
在某些情况下,一端会发送给对方一个RST要求重新连接,然后如果双方本来就没有连接就算了,如果原来已有连接的话就断开自身这端的连接。
我在网上看到一篇博文儿介绍了RST可能出现的几种情况感觉很详细这里贴一个超链吧,感兴趣的可以自己去看:几种TCP连接中出现RST的情况。该文里面有一些小问题,比如里面讲请求超时连接时,应该把“主机27很不友好”改为“主机89很不友好”。
在这里我还是简单根据上述博文的内容提一下几种TCP连接中出现RST的情况:
(1)服务器程序端口未打开而客户端来连接:此时有些操作系统上的主机会回应一个RST
给客户端,而有些操作系统上的主机则不予理会。
(2)请求超时:例如,客户端发送一个SYN给服务器端,服务器端在超时之前没有给客户端回应ack或/和SYN,则客户端会发送一个SYN给服务器端拒绝进一步连接。
(3)提前关闭:例如,你给我发5个数据,我只接三个还有两个不接,然后我关闭连接,给你发一个RST说我不接了,咱重连吧。
(4)在一个已关闭的socket上收到数据,就会给对方发送一个RST。
简单地说就是,不管是不接收连接还是不接收数据,只要我不接收你,我就给你发送一个RST。不管以前有没有连接过,都断了,给我重连。
说了这么多,下面来考虑一种情况检测检测:服务器主机崩溃重启后,会给发送数据过来的客户端发送一个RST,属于那种情况呢?
没错,属于不接收数据的情况。
因为当服务器崩溃后重启时,它的TCP丢失了崩溃前所有的连接信息,TCP连接是安全可靠的,
我都不认识你当然不会接收你的数据,给你一个RST,你重新来连我吧。
其实说到这里下面我就应该上TCP状态转移图
了,那么我就上个图吧。(这是在一篇博文里找的图本来准备附链接但没附,关键是要理解图的含义)
感觉我上面把状态大致过程都说得差不多了,结合到图中就是每发送或接收一次信息就变一个状态。
这里我只对上图中的三个状态加以描述。
(1)同时打开:同时打开就是两端同时执行主动打开,这种可能性极小但是存在。为了处理同时打开TCP还特意设计了一番,对于这种同时打开,TCP建立一条连接而不是两条(其他好多协议组建立两条)。
两端几乎同时发送SYN并进入SYN_SENT状态,当每一端收到SYN时,状态变为SYN_RCVD状态。同时它们都再发送SYN(本来服务器该干的事)并对收到的SYN(本来服务器第二步时客户端第三步时该干的事)进行确认。当双方都收到SYN及相应ACK时,状态都变迁为ESTABLISHED。需要注意的是:
这里的两端每一端都既是服务器又是客户端。
(2)ESTABLISHED:这个状态时表示三次握手已经完成可以传送数据啦。
(3)TIME_WAIT:也称等待2MSL状态。每个具体TCP实现必须选择一个报文段最大生存时间MSL,它是任何报文段(数据)被丢弃前在网络内的最长时间。
其实我一开始特别不理解,为什么要有TIME_WAIT这个状态呢,明明都已经死了啊!那就是没状态了嘛还给它时间等啥,直接到CLOSED就好了啊。
后来,我找到了它存在的两个理由,概述如下:
a.
可靠地实现TCP全双工连接的终止:维护连接一定时间以防最后一个确认ask需要重发。这也就解释了为什么TIME_WAIT 端总是执行主动关闭的那一端。因为可能不得不重传最终那个ack的就是那一端。
b.
允许老的重复报文段在网络中消逝:例,我们关闭一个连接A,过一段时间后在相同的IP地址和端口号之间建立一个新连接B,因为A和B的IP地址和端口号都相同,如果老连接A断开的时间还不到MSL,B连接就能传送数据了,此时假如连接A再传送数据则很有可能会被连接B误当成是自己连接中的数据来传送。为了解决这个问题,TCP将不给TIME_WAIT状态的连接(IP地址和端口号)发起新的连接,等过了TIME_WAIT这个状态后已经过去2MSL时间了,连接中的数据早就在网络中消逝了,此时再发起的新的连接就不会收到老的重复数据了。