一、TCP报文
上图为TCP报文的格式,可以看到TCP头部占20个字节,其中红色圆圈中每一项占一位,表示TCP报文的类型,置1表示该项有效。
SYN表示建立连接。
FIN表示关闭连接。
ACK表示响应、确认。
PSH表示带有PUSH标志的数据传输。
RST表示连接重置或拒绝连接请求。
URG表示紧急指针有效。
其中,ACK是可能与SYN或FIN或PSH同时置1的。
二、TCP(传输控制协议)是面向连接的,可靠的,字节流服务。
1、面向连接
面向连接即双方通信前要先建立连接,通信结束双方要关闭连接,而UDP却不用。
建立TCP连接需要三次握手:客户端调用connect()向服务器建立连接(发送SYN包),服务器收到后确认客户端的连接请求并向客户端建立连接(发送ACK+SYN包),客户端收到后确认服务器的连接请求(发送ACK包)。
关闭TCP连接需要四次挥手:本端调用closesocket()关闭套接字(发送FIN包),对端收到后确认这个关闭(发送ACK包),对端调用closesocket()关闭套接字(发送FIN包),本端收到后确认这个关闭(发送ACK包)。
2、可靠性和滑动窗口
TCP的可靠性主要体现在:
①、报文发送方会等待接收方对于数据报的ACK回复确认,超时的话会重新发送。
②、报文接收方会对数据报文进行校验,出错则不对报文进行确认,使发送方重新发送。
③、IP协议并不能保证数据到达的次序,而UDP也没有对数据帧进行乱序处理,所以UDP同样不保证数据顺序。而TCP协议则对乱序进行处理以保证数据按照顺序到达:每个报文都有一个序号和确认号,序号是当前数据包的第一个数据字节在发送数据流中的偏移量,确认号则为希望接收的下一个数据字节的序号。如果当前接收的数据序号和期望接收的序号不同的话即出现乱序,TCP会将乱序的报文先在缓存中保存起来,以备后用。如果这个乱序的报文实在是“乱”的很厉害则不会处理该报文,也不会发送对应的ACK。
TCP的可靠性通过“滑动窗口”机制来实现,从TCP首部可以看到滑动窗口的大小用16位来保存,故其最大为2^16-1即65535个字节,另外在TCP的选项字段中还包含了一个TCP窗口扩大因子,窗口扩大因子用来扩大TCP窗口,可把原来16bit的窗口,扩大为31bit。
滑动窗口可以分为发送窗口和接收窗口,因为TCP是双工的协议,所以TCP会话的双方都各自维护一个“发送窗口”和一个“接收窗口”。“接收窗口”的大小取决于应用、系统、硬件的限制,“发送窗口”是根据连接时对端告知的其“接收窗口”大小来构造的。而且本端"发送窗口"的大小会随着对端"接收窗口"的大小而改变。
对于TCP会话的发送方,任何时候在其发送缓存内的数据都可以分为4类,“已经发送并得到对端ACK的”,“已经发送但还未收到对端ACK的”,“未发送且对端允许发送的”,“未发送但对端不允许发送”。其中“已经发送但还未收到对端ACK的”和“未发送但对端允许发送的”这两部分数据称之为发送窗口。对于TCP的接收方,在某一时刻在它的接收缓存内存在3种。“已接收”,“未接收准备接收”,“未接收但还未准备接收”(由于ACK直接由TCP协议栈回复,默认无应用延迟,不存在“已接收未回复ACK”)。其中“未接收准备接收”称之为接收窗口。
(以下参考和转载自:TCP 滑动窗口(发送窗口和接收窗口),https://my.oschina.net/xinxingegeya/blog/485650)
如下图,中间的两部分称为发送窗口:
而当收到接收方新的ACK对于发送窗口中后续字节的确认时,窗口滑动,如下图,当收到ACK=36时窗口滑动:
滑动窗口的传输可靠性是建立在“确认重传”基础上的:发送窗口只有收到对端对于本段发送窗口内字节的ACK确认,才会移动发送窗口的左边界,接收窗口只有在前面所有的段都确认了的情况下才会移动左边界。当在前面还有字节未接收但收到后面字节的情况下,窗口不会移动,并不对后续字节确认,以此确保对端会对这些数据重传。
3、流量控制
TCP协议会根据情况自动改变滑窗大小,以实现流量控制,它主要是通过控制本端TCP接收窗口大小来对对端的发送窗口流量限制:
应用程序在需要时,比如对端发送数据太快的时候,TCP协议栈会缩小TCP的接收窗口,然后TCP协议栈在下个段发送时包含新的窗口大小通知给对端,对端按通知的窗口来改变发送窗口,以此达到减缓发送速率的目的。主要是接收方传递信息给发送方,使其不要发送数据太快,是一种端到端的控制。主要的方式就是返回的ACK中会包含自己的接收窗口的大小,并且利用大小来控制发送方的数据发送。
(以下参考和转载自 一站式学习Wireshark,http://blog.jobbole.com/71925/)
如下图,客户端向服务器发送数据,服务器接收窗口大小是5000字节。客户端发送了2500字节后服务器缓冲区中可用大小还剩2500字节,紧接着客户端又发送了2000字节,从而缓冲区只剩500字节的可用大小。这时服务器向客户端发送确认信息,对缓存中数据进行处理并清空缓存。以上过程重复进行,客户端又发送3000字节和1000字节,服务器缓存减少至1000字节,客户端再次确认数据并处理缓存中内容。
如果客户端发送数据太快或服务器端接收数据太慢的话则可能会造成丢包和数据损坏,所以种情况的话TCP会减小接收窗口的大小,从而控制发送方发送行为。当接收窗口减小后,通过返回给发送端的ACK报文的TCP头窗口大小值来告知发送端减小其发送窗口。如下图:服务器初始窗口大小为5000字节。客户端发送2000字节,之后又发送了2000字节,缓冲区中只有1000字节可用。服务器意识到缓冲区正在快速填满,它知道如果数据继续以此速率传输,很快会有报文丢失。为了防止报文丢失,服务器发送确认信息给客户端,更新窗口大小为1000字节。结果,客户端减少数据发送,服务器以可以接受的速率处理缓存内容,即保持数据流以稳定的速率传输。当服务器能够更加快速的处理报文时,它会发送一个较大窗口的ACK报文。
某些特殊情况下,服务器可能由于内存不足,处理能力不够,或其他原因导致无法再处理从客户端发送的数据。当这种情况发生时,服务器会发送窗口为0的报文。当客户端接收到此报文时,它会暂停所有数据传输,但会保持与服务器的连接以传输探测(keep-alive)报文。探测报文在客户端以稳定间隙发送,以查看服务器接收窗口状态。一旦服务器能够再次处理数据,将会返回非零值窗口大小,传输会恢复。
4、拥塞控制
TCP的“拥塞控制”是为了防止过多的数据注入网络中形成网络堵塞。它通过使用几种算法来实现,如慢开始、拥塞避免、快重传和快恢复,TCP会在合适的情况选择合适的算法来对数据传输进行控制以实现“拥塞控制”。
5、Nagle算法
Nagle算法通过减少小的封包的传送量来提高TCP/IP的效能,比如某个应用连续发送1字节的数据,而TCP包头就占用40字节,所以导致了很大的浪费,而且这些小的封包如果在短时间内大量发送的话会造成拥塞,拥塞会造成重传,重传可能会引发更严重的拥塞,导致网络吞吐量下降。Nagle算法伪代码如下:
if (有新数据要发送)
{
if (可发送的数据>=MSS and 窗口大小>=MSS)
{
立即发送完整MSS大小的segment
}
else
{
if (尚有未被对方ACK的数据)
在下一個确认(ACK)封包收到前,将数据排入缓冲区队列
else
立即发送数据
}
}
在TCP编程中我们可以通过setsockopt()的TCP_NODELAY选项来禁止Nagle算法,然而只有某些特殊的应用才必须禁用Nagle算法。大部分应用程序禁用Nagle是因为他们用一系列小规模的写操作来发送逻辑上相关联的数据造成的发送性能问题,如果有一种操作能够将这些数据合并起来一起发送出去那就不必禁用Nagle算法——那就是使用writev()函数(windows下WSASend()函数),它可以完成一次发送多个缓冲区中的数据来进行集中发送。
转载参考出处:TCP 滑动窗口,https://my.oschina.net/xinxingegeya/blog/485650
一站式学习Wireshark,http://blog.jobbole.com/71925/
http://www.cnblogs.com/woaiyy/p/3554182.html