transmission control protocol 传输控制协议
基于字节流的通信协议
数据在传输前要建立连接,传输完毕要断开连接
客户端在收发数据前要使用connnect函数和服务器建立连接
建立连接的目的是保证ip地址、端口等正确无误,为数据的传输开辟通道
tcp建立连接时腰传输三个数据包,俗称三次握手
1)当客户端调用connect函数后,tcp协议会组建一个数据包,并设置syn标志位
表示数据包是用来建立同步连接的,同时生成一个随机数字1000,
填充 序号seq字段,表示该数据包的序号,完成这些工作
开始向服务器发送数据包,客户端就进入syn-send状态
2)服务器端收到数据包,检测到已经设置了syn标志位,就知道客户端
发来的建立连接的请求包,服务器端也会组建一个数据包
并设置syn和ack标志位,syn表示该数据包是用来建立连接的
ack用来确认收到了刚才客户端发来的数据包
服务器生成一个随机数2000,填充序列号seq
2000 和客户端数据包没有关系
服务器将客户端数据包序列号加1,并用这个数据自填充a
确认号ack字段
服务器将数据包发出,进入syn-recv状态
3)客户端收到数据包,检测到已经设置了syn和ack标志位
就知道这就是服务器发来的 确认包。
客户端会检测确认号ack字段,看他的值是否为1000+1,
如果是就说明连接建立成功
客户端将数据包发出,进入establelised状态,表示连接已经建立成功
4)服务器收到数据包,检测已经设置了ack标志位,就知道是客户端发来的、
确认包,服务器会检测确认号ack字段,看他的值是否是2000—+1
如果是表明建立连接成功,服务器进入estableised状态
说明:三次握手的关键是要确认对方收到自己的数据包
就是通过确认号ack实现的,计算机会记录下自己
发送的数据包序列号seq,待接收到对方的数据包后,检测
确认号ack = seq + 1是否成立,如果成立说明对方正确接收到了自己的数据包
二:tcp数据的传输过程
ack号的增量=传输数据的字节数,假设每次ack
号不加传输的字节数,这样虽然可以确认数据包的传输,
但是无法明确100字节全部传输正确还是丢失了一部分
ack = seq + 传递字节数 + 1;
最后加一是为了告诉对方要传递的seq号
上图表示通过数据包想主机b发送100字节的数据
单中间发生了错误,主机b未收到。经过一段时间后,主机A
仍未收到ack确认,因此尝试重传数据。
为了完成数据包的重传,Tcp套接字每次发送数据包时都会启动定时器
如果在一定时间内没有收到目标机器的回传ack包
那么定时器超时,数据包会重传。
重传超时时间( rto, retransmission time out)
这个值他打了会导致不必要的等待时间,太小了会导致不必要的重传
RTT( round trip time ):往返延时,在计算机网络中他是一个重要的性能指标
表示从发送端发送数据开始,到发送端收到来自接收端的确认,总共经历的延时
重传次数
tcp包的重传次数根据系统设置的不同而不同,有些重传三次,又得要求
很高的系统,会不断重传丢失的数据包
三:tcp四次握手断开连接
建立连接非常重要,他是数据正确传输的前提
断开连接同样重要,他让计算机释放不再使用的资源
如果连接不能正常断开,不仅会造成数据传输错误
还会导致套接字不能关闭,持续占用资源,如果并发量高
服务器压力堪忧
建立连接后,客户端和服务器都处于establised状态
这时,客户端发起断开连接的请求
1)客户端调用close()函数后,向服务器发送fin数据包,进入fin_wait_1状态
fin是finish的缩写,表示完成任务需要断开连接
2)服务器接收到数据包后,检测到设置了fin标志位,
知道要断开连接,于是想客户端发送 确认包 ,进入cclose_wait状态
注意:服务器接收到请求后,并不是立即断开连接,而是先向客户端发哦送确认包
告诉他我知道了,我需要准备一下,才能断开连接
3)客户端收到确认包 后,进入fin_wait_2状态,等待服务器准备完毕再次发送数据包
4)等待片刻后,服务器准备完毕,可以断开连接,于是再主动
向客户端发送fin包, 告诉他我准备好了,断开连接吧,
然后进入last_ack状态
5)客户端接收到服务器的fin包后,再向服务器发送ack包
告诉他你断开连接吧,然后进入time_wait状态
6)服务器接收到客户端的ack包后,就断开连接,关闭套接字,进入closed状态
关于time_wait状态的说明
客户端最后一次发送ack包后进入time_wait状态,而不是直接
进入closed状态,这是为什么呢
tcp是面向连接的传输方式,必须保证数据能够正确到达目标机器,不能
丢失或出错,而网络是不稳定的,随时可能会毁坏数据,所以
机器A每次向B发送数据包后,都要求机器B确认,回传ack包
告诉机器A才能知道数据传送成功了,如果机器B没有回传
机器A会重新发送,知道机器B回传ack包
客户端最后一次向服务器回传ack时,有可能会因为网络问题导致服务器收不到
服务器会再次发送fin包,如果这是客户端完全管理了连接,那么服务器无论如何也收不到ack包了,
所以客户端需要等待片刻,确认对方收到ack包后,才能进入closed状态
那么,要等多久呢?
数据包在网络中是有生存时间的,超过这个时间还未到达目标主机
就会被丢弃,并通知源主机。这称为报文最大生存时间
time_wait要等待2个报文最大生存时间,ACK包到达服务器需要最大报文生存时间
服务器重传也需要msl时间,如果2msl后还未收到服务器重传的ifn包,
就说明服务器已经收到ack包
四:优雅的断开连接--shutdown()
调用close() /closesocket函数意味着完全断开连接,既不能发送数据也不能接收数据
这种生硬的方式
上图演示了两台正在进行双向通信的主机
主机A发送完数据后,单方面调用close()、closesocket断开连接
之后主机A , B 都不能再接受对方传输的数据
实际上,是完全无法调用与数据收发有关的函数
一般情况下这不会有问题,单有些特殊时刻,需要只断开一条数据传输通道
而保留另一条。
使用shutdown函数可以达到这个目的
int shutdown( int sock, int howto ) //linux
int shutdown( SOCKET s, int howto ) //windows
sock为需要断开的套接字,howto为断开方式
SHUT_RD:断开输入流
SHUT_WR:断开输出流
close() /closesocket() /shutdown的区别
确切额说,close()、closesocket用来关闭套接字,将套接字描述符从内存
中清除,之后再也不能使用该套接字,
shutdown用来关闭连接,而不是套接字,不管调用多少次,
套接字依然存在,知道调用close
默认情况下,close()/close()会立即向网络中发送FIN包,
不管输出缓冲区中是否还有数据,而shutdown会等输出缓冲区的数据传输完毕再发送
FIN包,也就意味着,调用close将丢失缓冲区中的数据,