在网络协议栈中, 目前只有tcp提供了一种面向连接的可靠性数据传输。 而可靠性,无非就是保证,我发给你的,你一定要收到。确保中间的通信过程中,不会丢失数据和乱序。 在TCP保证可靠性数据传输的实现来看, 超时重传、序列号及数据的应答 这三个特征 就是实现可靠性的最基本保证,而对于tcp窗口大小等等设置,也是保证可靠性的一个方面。所有的目的只为一个,保证传输数据的完整性。 为了解决传输线路的不稳定性造成数据包的丢失情况,tcp 使用了发送方超时重传和接收方数据应答的策略。概括而言,就是一种“状态协议“,保证通信双方数据收发的一致性。
tcp协议
源端口:表明发送端所使用的端口号,用于目标主机回应。
目的端口:表明要连接的目标主机的端口号。
序号:
表明发送的数据包的顺序,一般为上次发送包中的顺序号+1。
若该数据包是整个TCP连接中的第一个包(SYN包),则该值是随机生成的。
确认号:
表明本端TCP已经接收到的数据,其值表示期待对端发送的下一个字节的序号。
实际上告诉对方,在这个序号减1以前的字节已正确接收。
若该数据包是整个TCP连接中的第一个包(SYN包),则确认号一般为0。
数据偏移:
表示以32位(4字节)为单位的TCP分组头的总长度(首部长度),用于确定用户数据区的起始位置。
在没有可变内容的情况下,TCP头部的大小为20字节,对应该值为5。
标志位:
紧急标志位(URG):开启时表明此数据包处于紧急状态应该优先处理
确认标志位(ACK):开启时表明确认号有效,否则忽略确认号
推送标志位(PSH):开启时表明应该尽快交付给应用进程,而不必等到缓存区填满才推送,比如 telnet 的场景
复位标志位(RST):开启时表明TCP连接出现连接出现错误,数据包非法拒绝连接
同步标志位(SYN):开启时表明连接建立的标志
终止标志位(FIN):开启时表明释放一个连接
窗口大小:表明期望接受到的数据包字节数,用于拥塞控制。
校验和:实现对TCP报文头以及数据区进行校验。
紧急指针:在紧急状态下(URG打开),指出窗口中紧急数据的位置(末端)。
选项(可变):用于支持一些特殊的变量,比如最大分组长度(MSS)。
填充:用于保证可变选项为32 bit的整数倍。
备注:一般情况下TCP 头部为20字节,加上20字节的 IP头部,一个数据包至少包含40字节的头部
tcp状态机
下面简析一下 TCP的各个状态。
TCP_CLOSE:关闭状态, 一个新建的TCP socket 会处于该状态。
TCP_LISTEN: 监听状态,一般服务器端套接字在调用Listen系统调用后即处于该状态。
TCP_SYN_SENT:同步信号已经发送状态,这个状态一般是指客户端发送SYN(建立连接的同步)数据包后所处的状态(tcp三次握手的第一个包)。在接收到远端服务器端的应答后,即从该状态进入TCP_ESTABLISHED状态。
TCP_SYN_RECEIVED:同步信号已经接受状态,服务器端在接受到远端客户端SYN数据包后,进行相应的处理(创建通信套接字等),然后发送应答数据包(tcp三次握手的第二个包),并将新创建的通信套接字状态设置为TCP_SYN_RECEIVED,在接受到客户端的应答后,即进入TCP_ESTABLISED状态。
TCP_ESTABLISED:建立连接状态,这是双方进行正常通信所处的状态。
TCP_FIN_WAIT_1:本地发送FIN(用于结束连接的)数据包后即可进入该状态,等待对方的应答。一般一端发送完其所要发送的数据后,即可发送FIN数据包,此时发送通道被关闭,但仍可继续接受远端发送的数据包。在接受到远端发送的对于FIN数据包的应答后,将进入TCP_FIN_WAIT_2状态。
TCP_FIN_WAIT_2:进入该状态表示本地已经接受到远端发送的对于本地之前发送的FIN数据包的应答。进入该状态后,本地仍然可以继续接受远端发送给本地的数据包。在接受到远端发送的FIN数据包后(表示远端也已经发送完数据),本地将发送一个应答数据包,并进入TCP_TIME_WAIT状态。TCP_TIME_WAIT状态存在的时间被称为2MSL时间,这一方面是为避免本地发送的应答数据包丢失,另一方面避免一个新创建的套接字接收到旧套接字中遗留的数据包。
TCP_TIME_WAIT:该转状态呗称为2MSL等待状态。如果在此期间接收到远端发送的FIN数据包,则表示之前在TCP_FIN_WAIT_2状态发送的ACK应答数据包在传输中丢失或者长时间被延迟,从而造成了远端重新发送了FIN数据包,此时重复ACK应答数据包。一旦2MSL时间到期,则将进入TCP_CLOSED状态,即完成关闭操作。
TCP_CLOSE_WAIT:该状态存在于后关闭的一端。当接收到远端发送的FIN数据包后,本地发送一个ACK应答数据包,并将该套接字状态从TCP_ESTABLISED设置为TCP_CLOSE_WAIT。本地可以继续向远端发送数据包,在发送完所有的数据后,本地将发送一个FIN数据包关闭本地发送通道,并将状态设置为TCP_LAST_ACK状态,等待远端对FIN数据包的应答数据包。
TCP_CLOSING:如果通信双方同时发送FIN数据包,则同时进行关闭操作,则双方将同时进入TCP_CLOSING状态。具体的,本地发送一个FIN数据包以结束本地数据包发送,如果在等待应答期间,接收到远端发送的FIN数据包,则本地将状态设置为TCP_CLOSING状态。在接收到应答后,再继续装入到TCP_CLOSE_WAIT状态。
TCP_LAST_ACK:作为后关闭的一方,在发送FIN数据包后,即进入TCP_LAST_ACK状态。此时等待远端发送应答数据包,在接收到应答数据包后,即完成关闭操作,进入TCP_CLOSE状态。
关于tcp中time_wait状态的4个问题
1.time_wait状态是什么
简单来说:time_wait状态是四次挥手中server向client发送FIN终止连接后进入的状态。下图为tcp四次挥手过程
能够看到time_wait状态存在于client收到serverFin并返回ack包时的状态
当处于time_wait状态时,我们无法创建新的连接,由于port被占用。
2.为什么会有time_wait状态
time_wait存在的原因有两点
1.可靠的终止TCP连接。
可靠的终止TCP连接,若处于time_wait的client发送给server确认报文段丢失的话,server将在此又一次发送FIN报文段,那么client必须处于一个可接收的状态就是time_wait而不是close状态。
2.保证让迟来的TCP报文段有足够的时间被识别并丢弃。
保证迟来的TCP报文段有足够的时间被识别并丢弃,linux 中一个TCPport不能打开两次或两次以上。当client处于time_wait状态时我们将无法使用此port建立新连接,假设不存在time_wait状态,新连接可能会收到旧连接的数据。
time_wait持续的时间是2MSL,保证旧的数据能够丢弃。由于网络中的数据最大存在MSL(maxinum segment lifetime)
3.哪一方会有time_wait状态
time_wait状态是一般有client的状态。 并且会占用port,有时产生在server端,由于server主动断开连接或者发生异常
4.怎样避免time_wait状态占用资源
假设是client,我们一般不用操心,由于client一般选用暂时port。再次创建连接会新分配一个port。
除非指定client使用某port,只是一般不须要这么做。
假设是server主动关闭连接后异常终止。则由于它总是使用用一个知名serverport号,所以连接的time_wait状态将导致它不能重新启动。只是我们能够通过socket的选项SO_REUSEADDR来强制进程马上使用处于time_wait状态的连接占用的port。
通过socksetopt设置后,即使sock处于time_wait状态,与之绑定的socket地址也能够马上被重用。
此外也能够通过改动内核參数/proc/sys/net/ipv4/tcp_tw/recycle来高速回收被关闭的socket,从而是tcp连接根本不进入time_wait状态,进而同意应用程序马上重用本地的socket地址。
tcp攻击
所谓SYN 洪泛攻击,就是利用SYNACK 报文的时候,服务器会为客户端请求分配缓存,那么黑客(攻击者),就可以使用一批虚假的ip向服务器大量地发建立TCP 连接的请求,服务器为这些虚假ip分配了缓存后,处在SYN_RCVD状态,存放在半连接队列中;另外,服务器发送的请求又不可能得到回复(ip都是假的,能回复就有鬼了),只能不断地重发请求,直到达到设定的时间/次数后,才会关闭。
服务器不断为这些半开连接分配资源(但从未使用),导致服务器的连接资源被消耗殆尽,不过所幸,我们可以使用SYN Cookie进行有效地防御。
所谓的SYN Cookie防御系统,与前面接收到SYN 报文就分配缓存不同,此时暂不分配资源;同时利用SYN 报文的源和目的地IP和端口,以及服务器存储的一个秘密数,使用它们进行散列,得到server_isn,然后附着在SYNACK 报文中发送给客户端,接下来就是对ACK 报文进行判断,如果其返回的ack字段正好等于server_isn + 1,说明这是一个合法的ACK,那么服务器才会为其生成一个具有套接字的全开的连接。