陕西seo优化,安卓优化大师官方版本下载,中国互联网四大门户,做机械设计图纸找什么网站TCP协议
TCP通信时序 下图是一次TCP通讯的时序图。TCP连接建立断开。包含大家熟知的三次握手和四次握手。 TCP通讯时序
在这个例子中#xff0c;首先客户端主动发起连接、发送请求#xff0c;然后服务器端响应请求#xff0c;然后客户端主动关闭连接。 两条竖线表示通讯的…TCP协议
TCP通信时序 下图是一次TCP通讯的时序图。TCP连接建立断开。包含大家熟知的三次握手和四次握手。 TCP通讯时序
在这个例子中首先客户端主动发起连接、发送请求然后服务器端响应请求然后客户端主动关闭连接。 两条竖线表示通讯的两端从上到下表示时间的先后顺序 注意数据从一端传到网络的另一端也需要时间所以图中的箭头都是斜的。 双方发送的段按时间顺序编号为1-10各段中的主要信息在箭头上标出例如段2的箭头上标着SYN, 8000(0), ACK1001, 表示该段中的SYN位置132位序号是8000该段不携带有效载荷数据字节数为0ACK位置132位确认序号是1001带有一个mssMaximum Segment Size最大报文长度选项值为1024。
建立连接三次握手的过程
1. 客户端发送一个带SYN标志的TCP报文到服务器。这是三次握手过程中的段1。
客户端发出段1SYN位表示连接请求。序号是1000这个序号在网络通讯中用作临时的地址每发一个数据字节这个序号要加1这样在接收端可以根据序号排出数据包的正确顺序也可以发现丢包的情况另外规定SYN位和FIN位也要占一个序号这次虽然没发数据但是由于发了SYN位因此下次再发送应该用序号1001。mss表示最大段尺寸如果一个段太大封装成帧后超过了链路层的最大帧长度就必须在IP层分片为了避免这种情况客户端声明自己的最大段尺寸建议服务器端发来的段不要超过这个长度。
2 . 服务器端回应客户端是三次握手中的第2个报文段同时带ACK标志和SYN标志。它表示对刚才客户端SYN的回应同时又发送SYN给客户端询问客户端是否准备好进行数据通讯。、
服务器发出段2也带有SYN位同时置ACK位表示确认确认序号是1001表示“我接收到序号1000及其以前所有的段请你下次发送序号为1001的段”也就是应答了客户端的连接请求同时也给客户端发出一个连接请求同时声明最大尺寸为1024。
客户必须再次回应服务器端一个ACK报文这是报文段3。 . 客户端发出段3对服务器的连接请求进行应答确认序号是8001。在这个过程中客户端和服务器分别给对方发了连接请求也应答了对方的连接请求其中服务器的请求和应答在一个段中发出因此一共有三个段用于建立连接称为“三方握手three-way-handshake”。在建立连接的同时双方协商了一些信息例如双方发送序号的初始值、最大段尺寸等。
在TCP通讯中如果一方收到另一方发来的段读出其中的目的端口号发现本机并没有任何进程使用这个端口就会应答一个包含RST位的段给另一方。例如服务器并没有任何进程使用8080端口我们却用telnet客户端去连接它服务器收到客户端发来的SYN段就会应答一个RST段客户端的telnet程序收到RST段后报告错误Connection refused $ telnet 192.168.0.200 8080 Trying 192.168.0.200… telnet: Unable to connect to remote host: Connection refused 数据传输的过程 客户端发出段4包含从序号1001开始的20个字节数据。 服务器发出段5确认序号为1021对序号为1001-1020的数据表示确认收到同时请求发送序号1021开始的数据 服务器在应答的同时也向客户端发送从序号8001开始的10个字节数据这称为piggyback。 客户端发出段6对服务器发来的序号为8001-8010的数据表示确认收到请求发送序号8011开始的数据。 在数据传输过程中ACK和确认序号是非常重要的应用程序交给TCP协议发送的数据会暂存在TCP层的发送缓冲区中 发出数据包给对方之后只有收到对方应答的ACK段才知道该数据包确实发到了对方可以从发送缓冲区中释放掉了 如果因为网络故障丢失了数据包或者丢失了对方发回的ACK段经过等待超时后TCP协议自动将发送缓冲区中的数据包重发。
关闭连接四次握手的过程
由于TCP连接是全双工的因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭而另一方执行被动关闭。
客户端发出段7FIN位表示关闭连接的请求。服务器发出段8应答客户端的关闭连接请求。服务器发出段9其中也包含FIN位向客户端发送关闭连接请求。客户端发出段10应答服务器的关闭连接请求。 建立连接的过程是三方握手而关闭连接通常需要4个段**服务器的应答和关闭连接请求通常不合并在一个段中因为有连接半关闭的情况**这种情况下客户端关闭连接之后就不能再发送数据给服务器了但是服务器还可以发送数据给客户端直到服务器也关闭连接为止。
滑动窗口 (TCP流量控制)
介绍UDP时我们描述了这样的问题如果发送端发送的速度较快接收端接收到数据后处理的速度较慢而接收缓冲区的大小是固定的就会丢失数据。TCP协议通过“滑动窗口Sliding Window”机制解决这一问题。看下图的通讯过程 滑动窗口
发送端发起连接声明最大段尺寸是1460初始序号是0窗口大小是4K表示“我的接收缓冲区还有4K字节空闲你发的数据不要超过4K”。接收端应答连接请求声明最大段尺寸是1024初始序号是8000窗口大小是6K。发送端应答三方握手结束。发送端发出段4-9每个段带1K的数据发送端根据窗口大小知道接收端的缓冲区满了因此停止发送数据。接收端的应用程序提走2K数据接收缓冲区又有了2K空闲接收端发出段10在应答已收到6K数据的同时声明窗口大小为2K。接收端的应用程序又提走2K数据接收缓冲区有4K空闲接收端发出段11重新声明窗口大小为4K。发送端发出段12-13每个段带2K数据段13同时还包含FIN位。接收端应答接收到的2K数据6145-8192再加上FIN位占一个序号8193因此应答序号是8194连接处于半关闭状态接收端同时声明窗口大小为2K。接收端的应用程序提走2K数据接收端重新声明窗口大小为4K。接收端的应用程序提走剩下的2K数据接收缓冲区全空接收端重新声明窗口大小为6K。接收端的应用程序在提走全部数据后决定关闭连接发出段17包含FIN位发送端应答连接完全关闭。
上图在接收端用小方块表示1K数据实心的小方块表示已接收到的数据虚线框表示接收缓冲区因此套在虚线框中的空心小方块表示窗口大小从图中可以看出随着应用程序提走数据虚线框是向右滑动的因此称为滑动窗口。
从这个例子还可以看出发送端是**一K一K地发送数据而接收端的应用程序可以两K两K地提走数据当然也有可能一次提走3K或6K数据或者一次只提走几个字节的数据。**也就是说应用程序所看到的数据是一个整体或说是一个流stream在底层通讯中这些数据可能被拆成很多数据包来发送但是一个数据包有多少字节对应用程序是不可见的因此TCP协议是面向流的协议。而UDP是面向消息的协议每个UDP段都是一条消息应用程序必须以消息为单位提取数据不能一次提取任意字节的数据这一点和TCP是很不同的。
TCP状态转换
这个图N多人都知道它排除和定位网络或系统故障时大有帮助但是怎样牢牢地将这张图刻在脑中呢那么你就一定要对这张图的每一个状态及转换的过程有深刻的认识不能只停留在一知半解之中。下面对这张图的11种状态详细解析一下以便加强记忆不过在这之前先回顾一下TCP建立连接的三次握手过程以及 关闭连接的四次握手过程。 TCP状态转换图
CLOSED表示初始状态。
LISTEN该状态表示服务器端的某个SOCKET处于监听状态可以接受连接。
SYN_SENT这个状态与SYN_RCVD遥相呼应当客户端SOCKET执行CONNECT连接时它首先发送SYN报文随即进入到了SYN_SENT状态并等待服务端的发送三次握手中的第2个报文。SYN_SENT状态表示客户端已发送SYN报文。
SYN_RCVD: 该状态表示接收到SYN报文在正常情况下这个状态是服务器端的SOCKET在建立TCP连接时的三次握手会话过程中的一个中间状态很短暂。此种状态时当收到客户端的ACK报文后会进入到ESTABLISHED状态。 ESTABLISHED表示连接已经建立。
FIN_WAIT_1: FIN_WAIT_1和FIN_WAIT_2状态的真正含义都是表示等待对方的FIN报文。区别是
FIN_WAIT_1状态是当socket在ESTABLISHED状态时想主动关闭连接向对方发送了FIN报文
此时该socket进入到FIN_WAIT_1状态。FIN_WAIT_2状态是当对方回应ACK后该socket进入到FIN_WAIT_2状态正常情况下对方应马上回应ACK报文
所以FIN_WAIT_1状态一般较难见到而FIN_WAIT_2状态可用netstat看到。FIN_WAIT_2主动关闭链接的一方发出FIN收到ACK以后进入该状态。
称之为半连接或半关闭状态。该状态下的socket只能接收数据不能发。TIME_WAIT: 表示收到了对方的FIN报文并发送出了ACK报文等2MSL后即可回到CLOSED可用状态。如果FIN_WAIT_1状态下收到对方同时带 FIN标志和ACK标志的报文时可以直接进入到TIME_WAIT状态而无须经过FIN_WAIT_2状态。
CLOSING: 这种状态较特殊属于一种较罕见的状态。正常情况下当你发送FIN报文后按理来说是应该先收到或同时收到对方的 ACK报文再收到对方的FIN报文。但是CLOSING状态表示你发送FIN报文后并没有收到对方的ACK报文反而却也收到了对方的FIN报文。什么情况下会出现此种情况呢如果双方几乎在同时close一个SOCKET的话那么就出现了双方同时发送FIN报文的情况也即会出现CLOSING状态表示双方都正在关闭SOCKET连接。
CLOSE_WAIT: 此种状态表示在等待关闭。当对方关闭一个SOCKET后发送FIN报文给自己系统会回应一个ACK报文给对方此时则进入到CLOSE_WAIT状态。接下来呢察看是否还有数据发送给对方如果没有可以 close这个SOCKET发送FIN报文给对方即关闭连接。所以在CLOSE_WAIT状态下需要关闭连接。
LAST_ACK: 该状态是被动关闭一方在发送FIN报文后最后等待对方的ACK报文。当收到ACK报文后即可以进入到CLOSED可用状态。
半关闭
当TCP链接中A发送FIN请求关闭B端回应ACK后A端进入FIN_WAIT_2状态B没有立即发送FIN给A时A方处在半链接状态此时A可以接收B发送的数据但是A已不能再向B发送数据。 从程序的角度可以使用API来控制实现半连接状态。
#include sys/socket.h
int shutdown(int sockfd, int how);
sockfd: 需要关闭的socket的描述符
how: 允许为shutdown操作选择以下几种方式:SHUT_RD(0) 关闭sockfd上的读功能此选项将不允许sockfd进行读操作。该套接字不再接受数据任何当前在套接字接受缓冲区的数据将被无声的丢弃掉。SHUT_WR(1): 关闭sockfd的写功能此选项将不允许sockfd进行写操作。进程不能在对此套接字发出写操作。SHUT_RDWR(2): 关闭sockfd的读写功能。相当于调用shutdown两次首先是以SHUT_RD,然后以SHUT_WR。使用close中止一个连接但它只是减少描述符的引用计数并不直接关闭连接只有当描述符的引用计数为0时才关闭连接。 shutdown不考虑描述符的引用计数直接关闭描述符。也可选择中止一个方向的连接只中止读或只中止写。 注意: 如果有多个进程共享一个套接字close每被调用一次计数减1直到计数为0时也就是所用进程都调用了close套接字将被释放。 在多进程中如果一个进程调用了shutdown(sfd, SHUT_RDWR)后其它的进程将无法进行通信。但如果一个进程close(sfd)将不会影响到其它进程。 2MSL 2MSL (Maximum Segment Lifetime) TIME_WAIT状态的存在有两个理由 1让4次握手关闭流程更加可靠4次握手的最后一个ACK是是由主动关闭方发送出去的若这个ACK丢失被动关闭方会再次发一个FIN过来。若主动关闭方能够保持一个2MSL的TIME_WAIT状态则有更大的机会让丢失的ACK被再次发送出去。 2防止lost duplicate对后续新建正常链接的传输造成破坏。lost uplicate在实际的网络中非常常见经常是由于路由器产生故障路径无法收敛导致一个packet在路由器ABC之间做类似死循环的跳转。IP头部有个TTL限制了一个包在网络中的最大跳数因此这个包有两种命运要么最后TTL变为0在网络中消失要么TTL在变为0之前路由器路径收敛它凭借剩余的TTL跳数终于到达目的地。但非常可惜的是TCP通过超时重传机制在早些时候发送了一个跟它一模一样的包并先于它达到了目的地因此它的命运也就注定被TCP协议栈抛弃。
另外一个概念叫做incarnation connection指跟上次的socket pair一摸一样的新连接叫做incarnation of previous connection。lost uplicate加上incarnation connection则会对我们的传输造成致命的错误。 TCP是流式的所有包到达的顺序是不一致的依靠序列号由TCP协议栈做顺序的拼接假设一个incarnation connection这时收到的seq1000, 来了一个lost duplicate为seq1000len1000, 则TCP认为这个lost duplicate合法并存放入了receive buffer导致传输出现错误。通过一个2MSL TIME_WAIT状态确保所有的lost duplicate都会消失掉避免对新连接造成错误。 该状态为什么设计在主动关闭这一方
1发最后ACK的是主动关闭一方。 2只要有一方保持TIME_WAIT状态就能起到避免incarnation connection在2MSL内的重新建立不需要两方都有。
如何正确对待2MSL TIME_WAIT? RFC要求socket pair在处于TIME_WAIT时不能再起一个incarnation connection。但绝大部分TCP实现强加了更为严格的限制。在2MSL等待期间socket中使用的本地端口在默认情况下不能再被使用。 若A 10.234.5.5 : 1234和B 10.55.55.60 : 6666建立了连接A主动关闭那么在A端只要port为1234无论对方的port和ip是什么都不允许再起服务。这甚至比RFC限制更为严格RFC仅仅是要求socket pair不一致而实现当中只要这个port处于TIME_WAIT就不允许起连接。这个限制对主动打开方来说是无所谓的因为一般用的是临时端口但对于被动打开方一般是server就悲剧了因为server一般是熟知端口。比如http一般端口是80不可能允许这个服务在2MSL内不能起来。 解决方案是给服务器的socket设置SO_REUSEADDR选项这样的话就算熟知端口处于TIME_WAIT状态在这个端口上依旧可以将服务启动。当然虽然有了SO_REUSEADDR选项但sockt pair这个限制依旧存在。比如上面的例子A通过SO_REUSEADDR选项依旧在1234端口上起了监听但这时我们若是从B通过6666端口去连它TCP协议会告诉我们连接失败原因为Address already in use. RFC 793中规定MSL为2分钟实际应用中常用的是30秒1分钟和2分钟等。 RFC (Request For Comments)是一系列以编号排定的文件。收集了有关因特网相关资讯以及UNIX和因特网社群的软件文件。
程序设计中的问题
做一个测试首先启动server然后启动client用Ctrl-C终止server马上再运行server运行结果 itcast$ ./server bind error: Address already in use 这是因为虽然server的应用程序终止了但TCP协议层的连接并没有完全断开因此不能再次监听同样的server端口。我们用netstat命令查看一下
itcast$ netstat -apn |grep 6666
tcp 1 0 192.168.1.11:38103 192.168.1.11:6666 CLOSE_WAIT 3525/client
tcp 0 0 192.168.1.11:6666 192.168.1.11:38103 FIN_WAIT2 - server终止时socket描述符会自动关闭并发FIN段给clientclient收到FIN后处于CLOSE_WAIT状态但是client并没有终止也没有关闭socket描述符因此不会发FIN给server因此server的TCP连接处于FIN_WAIT2状态。 现在用Ctrl-C把client也终止掉再观察现象
itcast$ netstat -apn |grep 6666
tcp 0 0 192.168.1.11:6666 192.168.1.11:38104 TIME_WAIT -
itcast$ ./server
bind error: Address already in useclient终止时自动关闭socket描述符server的TCP连接收到client发的FIN段后处于TIME_WAIT状态。TCP协议规定主动关闭连接的一方要处于TIME_WAIT状态等待两个MSLmaximum segment lifetime的时间后才能回到CLOSED状态因为我们先Ctrl-C终止了server所以server是主动关闭连接的一方在TIME_WAIT期间仍然不能再次监听同样的server端口。
MSL在RFC 1122中规定为两分钟但是各操作系统的实现不同在Linux上一般经过半分钟后就可以再次启动server了。为什么要规定TIME_WAIT的时间
端口复用
在server的TCP连接没有完全断开之前不允许重新监听是不合理的。因为TCP连接没有完全断开指的是connfd127.0.0.1:6666没有完全断开而我们重新监听的是lis-tenfd0.0.0.0:6666虽然是占用同一个端口但IP地址不同connfd对应的是与某个客户端通讯的一个具体的IP地址而listenfd对应的是wildcard address。解决这个问题的方法是使用setsockopt()设置socket描述符的选项SO_REUSEADDR为1表示允许创建端口号相同但IP地址不同的多个socket描述符。 在server代码的socket()和bind()调用之间插入如下代码 int opt 1;setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, opt, sizeof(opt));TCP异常断开
心跳检测机制
在TCP网络通信中经常会出现客户端和服务器之间的非正常断开需要实时检测查询链接状态。常用的解决方法就是在程序中加入心跳机制。 Heart-Beat线程 这个是最常用的简单方法。在接收和发送数据时个人设计一个守护进程(线程)定时发送Heart-Beat包客户端/服务器收到该小包后立刻返回相应的包即可检测对方是否实时在线。 该方法的好处是通用但缺点就是会改变现有的通讯协议大家一般都是使用业务层心跳来处理主要是灵活可控。 UNIX网络编程不推荐使用SO_KEEPALIVE来做心跳检测还是在业务层以心跳包做检测比较好也方便控制。
设置TCP属性
SO_KEEPALIVE 保持连接检测对方主机是否崩溃避免服务器永远阻塞于TCP连接的输入。设置该选项后如果2小时内在此套接口的任一方向都没有数据交换TCP就自动给对方发一个保持存活探测分节(keepalive probe)。这是一个对方必须响应的TCP分节.它会导致以下三种情况对方接收一切正常以期望的ACK响应。
2小时后TCP将发出另一个探测分节。对方已崩溃且已重新启动以RST响应。 套接口的待处理错误被置为ECONNRESET套接 口本身则被关闭。 对方无任何响应源自berkeley的TCP发送另外8个探测分节相隔75秒一个试图得到一个响应。 在发出第一个探测分节11分钟 15秒后若仍无响应就放弃。 套接口的待处理错误被置为ETIMEOUT套接口本身则被关闭。 如ICMP错误是“host unreachable(主机不可达)”说明对方主机并没有崩溃但是不可达这种情况下待处理错误被置为EHOSTUNREACH。
根据上面的介绍我们可以知道对端以一种非优雅的方式断开连接的时候我们可以设置SO_KEEPALIVE属性使得我们在2小时以后发现对方的TCP连接是否依然存在。
keepAlive 1;
setsockopt(listenfd, SOL_SOCKET, SO_KEEPALIVE, (void*)keepAlive, sizeof(keepAlive));如果我们不能接受如此之长的等待时间从TCP-Keepalive-HOWTO上可以知道一共有两种方式可以设置一种是修改内核关于网络方面的 配置参数另外一种就是SOL_TCP字段的TCP_KEEPIDLE TCP_KEEPINTVL TCP_KEEPCNT三个选项。 The tcp_keepidle parameter specifies the interval of inactivity that causes TCP to generate a KEEPALIVE transmission for an application that requests them. tcp_keepidle defaults to 14400 (two hours). /*开始首次KeepAlive探测前的TCP空闭时间 */ The tcp_keepintvl parameter specifies the interval between the nine retriesthat are attempted if a KEEPALIVE transmission is not acknowledged. tcp_keep ntvldefaults to 150 (75 seconds). /* 两次KeepAlive探测间的时间间隔 */ The tcp_keepcnt option specifies the maximum number of keepalive probes tobe sent. The value of TCP_KEEPCNT is an integer value between 1 and n, where n s the value of the systemwide tcp_keepcnt parameter. /* 判定断开前的KeepAlive探测次数*/ int keepIdle 1000; int keepInterval 10; int keepCount 10; Setsockopt(listenfd, SOL_TCP, TCP_KEEPIDLE, (void *)keepIdle, sizeof(keepIdle)); Setsockopt(listenfd, SOL_TCP,TCP_KEEPINTVL, (void *)keepInterval, sizeof(keepInterval)); Setsockopt(listenfd,SOL_TCP, TCP_KEEPCNT, (void *)keepCount, sizeof(keepCount));
SO_KEEPALIVE设置空闲2小时才发送一个“保持存活探测分节”不能保证实时检测。对于判断网络断开时间太长对于需要及时响应的程序不太适应。
当然也可以修改时间间隔参数但是会影响到所有打开此选项的套接口关联了完成端口的socket可能会忽略掉该套接字选项。