合肥网站制作哪家好,开发企业门户网站,找产品代理去哪个网站,彩票系统开发搭建彩票网站服务器安全怎么做文章目录 1. TCP协议1.1 TCP协议段格式1.2 确认应答(ACK)机制1.3 16位窗口大小1.4 6位标志位1.4.1 TCP三次握手 1.5 确认应答(ACK)机制1.6 超时重传机制1.7 连接管理机制1.7.1 理解TIME_WAIT状态1.7.2 理解 CLOSE_WAIT 状态 1. TCP协议
TCP全称为传输控制协议#xff0c;意思… 文章目录 1. TCP协议1.1 TCP协议段格式1.2 确认应答(ACK)机制1.3 16位窗口大小1.4 6位标志位1.4.1 TCP三次握手 1.5 确认应答(ACK)机制1.6 超时重传机制1.7 连接管理机制1.7.1 理解TIME_WAIT状态1.7.2 理解 CLOSE_WAIT 状态 1. TCP协议
TCP全称为传输控制协议意思是要对数据的传输进行一个详细的控制。
1.1 TCP协议段格式 TCP的报头是20个字节一共5行每行4个字节。这是标准长度如果报头中含有选项那么报头的长度就是变长的。
那么我们该如何去解包呢 因为TCP的报头是变长的所以我们首先要看的是4位首部长度。它的意思是除数据外其它的总长度。因为是4个bit位所以最大是1111并且它的单位是4字节所以最大长度是4*1560字节也就是说TCP报头最大60字节。而我们得知标准长度是20个字节那么4位首部长度是5也就是0101它的取值范围就是0101~1111。
当我们要解包时先提取20字节在这20字节找到4位首部长度假如是30那么就30-2010得出选项的大小再把选项提取出来。
1.2 确认应答(ACK)机制
首先我们要谈一个问题可靠性。
那么什么是不可靠的呢 丢包、乱序、校验失败等等。
怎么确定一个报文是丢了还是没丢 当我们给对方发送消息的时候怎么确定对方有没有收到呢如果对方给我们应答吃了吃的是炸鸡。就说明对方收到了我发送的消息。 那么对方又怎么确定我有没有收到呢我们再给对方回应。 可能大家此时就会发现一个结论在长距离交互的时候永远有一条最新的数据是没有应答的。
如果发送的消息有对应的应答就一定没有丢否则是不确定的。
还有一个问题当我们传多个报文时怎么确定哪个回应对应哪个报头呢 这里我们就需要使用32位序号和32位确认序号。 当我们发送数据的时候在32位序号中添加数字在响应数据时在32位确认序号中添加。
32位确认序号代表的是对方前面已经发送完整报文的数据量。 举个例子比如客户端给服务器发送了12356这几个报文那么我们的确认序号就应该是4说明4前面的序号报文都接受成功了应该再从4开始重新发送。
那么为什么需要两种序号呢 因为TCP协议是全双工的我在给你发消息的同时我也可以收消息。如果客户端想给你发消息的同时再接受消息呢反之服务端也是如此。
所以客户端根据它的序号和对方的确认序号保证客户端发送数据的可靠性服务端根据它的序号和客户端的确认序号保证服务器发送的数据可靠性。
总而言之序号是让对方确认的确认序号是对方让我确认的。
1.3 16位窗口大小
首先我们要知道TCP协议是具有发送缓冲区和接受缓冲区的。 我们在调用writeread等系统接口时其实就是把数据拷贝到缓冲区或者从缓冲区拷贝数据到应用层。
从进程地址空间看就是从内核空间拷贝到用户空间的栈或者堆上。
当互相通信的时候TCP的过程如下图所示 每个发送缓冲区和接受缓冲区都是一对所以TCP通信的时候是全双工的。
我们在应用层用系统调用接口把数据拷贝到内核缓冲区里所以数据什么时候发发多少出错了怎么办要不要添加提高效率的策略都是由OS内的TCP自主决定的。所以叫做传输控制协议。
既然是缓冲区就说明缓冲区是有大小的。那么如果发送数据太快缓冲区来不及接受或者发送的太慢效率太低。这些问题该怎么办呢 为了解决这个问题我们需要让客户端知道服务器的接受能力它的接受能力就是接受缓冲区剩余空间的大小。
那么客户端怎么知道服务器接受能力的大小 发送都会有应答应答里包含TCP报头报头里有16位窗口大小来表示接受能力的属性字段。我们就可以根据对方的窗口大小来设置发送速度这种策略叫做流量控制。
如果我们是第一次发送消息我们怎么确定对方的接受能力呢 因为是16位最大是64K所以我们不能超过这个大小。
1.4 6位标志位 在前面初步讲解了3次握手和4次挥手初步了解三次握手
在服务端收到的报文有的是为了建立链接的有的是正常传输数据有的是断开链接。这说明报文是有区别的。
SYN: 请求建立连接SYN需要被设置成1我们把携带SYN标识的称为同步报文段。 客户端需要给服务端发送一个报头即使没有有效载荷我们也要发一个SYN为1的TCP报头过去。
FIN: 通知对方本端要关闭了。我们称携带FIN标识的为结束报文段。
SYN和FIN不应该同时设置。
ACK: 确认标记位表示该报文是对历史报文的确认一般在大部分正式通信的信号下ACK都是1。 比如客户端给服务器发送一个报文服务端给客户端响应返回这个响应返回中ACK就会设置成1说明上一条报文发送成功然后可以提取确认序列号。
PSH: 提示接收端应用程序立刻从TCP缓冲区把数据读走。 当客户端给服务端发送数据时服务器是用read来从接受缓冲区里读取的。如果读取条件不满足read会被阻塞。这是进程主动地去轮询检测的。
当数据准备好的时候我们不想使用系统默认的读取情况而是想主动通知服务端立即去读取我们可以使用PSH标记位。
URG: 紧急指针标记位。 我们知道报文在发送的时候是可能乱序到达的。 那么如何做到让我们的报文进行按序到达呢 主机每次发送数据时TCP就给每个数据包分配一个序列号并且在一个特定的时间内等待接收主机对分配的这个序列号进行确认如果发送主机在一个特定时间内没有收到接收主机的确认则发送主机会重传此数据包。接收主机利用序列号对接收的数据进行确认以便检测对方发送的数据是否有丢失或者乱序等接收主机一旦收到已经顺序化的数据它就将这些数据按正确的顺序重组成数据流并传递到高层进行处理。
数据在TCP中是有序到达的话但是如果有一些数据优先级更高但序号较后我们想紧急处理此数据。我们是按照优先级还是序号呢 我们可以设置TCP中的URG来让优先级更高的数据先处理。
不是所有的数据都是紧急数据我们该如何找到紧急数据呢 这里就需要根据16位紧急指针的偏移量了。但是只能读取一个字节。
1.4.1 TCP三次握手 这里的线画的是斜线原因是考虑到时间的关系。 并且我们要知道大量的连接OS就要管理这些连接就需要先描述后组织。
下面我们要讲解一下为什么是3次握手 三次握手前两次都有应答第三次没有应答。所以说3次握手不一定成功。
为什么不是一次 如果一次握手成功那么它非常容易受到攻击。如果一台主机不断的给我们服务器发送SYN链接服务器需要创建相关数据结构来管理那么它可能创建假的SYN来把服务器的资源占满让正常的SYN不能链接。
为什么不是二次 如果客户端将服务器的回复丢弃那么效果就和一次是一样的了。
为什么是三次 三次握手最后一次是由服务端来确定的所以最后握手如果没有成功影响的是客户端不是服务端。最后一次握手服务器不一定能收到但是客户端是发送出去了它需要维护相关数据结构。以奇数次握手来将花费的成本嫁接到客户端。并且客户端发送一次接受一次服务器发送一次接受一次以最小成本验证全双工。
如果客户端发出最后一次ACK但是报文丢了服务端没有收到那么客户端认为建立成功服务端认为建立没有成功下面会是什么情况呢 那么客户端就会给服务端发送数据如何服务端就会给客户端发出报头报头里的RST就会设置成1。 RST: 对方要求重新建立连接我们把携带RST标识的称为复位报文段。
1.5 确认应答(ACK)机制 TCP将每个字节的数据都进行了编号即为序列号。 每一个ACK都带有对应的确认序列号意思是告诉发送者我已经收到了哪些数据下一次你从哪里开始发。
1.6 超时重传机制 主机A发送数据给B之后可能因为网络拥堵等原因数据无法到达主机B如果主机A在一个特定时间间隔内没有收到B发来的确认应答就会进行重发。 但是主机A未收到B发来的确认应答也可能是因为ACK丢失了。因此主机B会收到很多重复数据那么TCP协议需要能够识别出那些包是重复的包并且把重复的丢弃掉。这时候我们可以利用前面提到的序列号就可以很容易做到去重的效果。
那么丢包要在特定的时间内重传如果超时的时间如何确定? 最理想的情况下找到一个最小的时间保证确认应答一定能在这个时间内返回。但是这个时间的长短随着网络环境的不同是有差异的。如果超时时间设的太长会影响整体的重传效率。如果超时时间设的太短 有可能会频繁发送重复的包。
TCP为了保证无论在任何环境下都能比较高性能的通信因此会动态计算这个最大超时时间 Linux中(BSD Unix和Windows也是如此)超时以500ms为一个单位进行控制每次判定超时重发的超时时间都是500ms的整数倍。如果重发一次之后仍然得不到应答等待 2 *500ms 后再进行重传。如果仍然得不到应答等待 4 *500ms 进行重传 依次类推以指数形式递增。累计到一定的重传次数TCP认为网络或者对端主机出现异常强制关闭连接。
1.7 连接管理机制
在正常情况下TCP要经过三次握手建立连接四次挥手断开连接 在某些特殊情况如果客户端和服务端想同时断开连接那么在第一次ACK时可以把FIN加上这样就是3次挥手。
如果我们服务器不accept3次握手也能成功accept只是把已经成功建立的连接拿上来。
服务端状态转化 [CLOSED - LISTEN] 服务器端调用listen后进入LISTEN状态等待客户端连接。
[LISTEN - SYN_RCVD] 一旦监听到连接请求(同步报文段)就将该连接放入内核等待队列中并向客户端发送SYN确认报文。
[SYN_RCVD - ESTABLISHED] 服务端一旦收到客户端的确认报文就进入ESTABLISHED状态可以进行读写数据了。
[ESTABLISHED - CLOSE_WAIT] 当客户端主动关闭连接(调用close)服务器会收到结束报文段服务器返回确认报文段并进入CLOSE_WAIT。
[CLOSE_WAIT - LAST_ACK] 进入CLOSE_WAIT后说明服务器准备关闭连接(需要处理完之前的数据)当服务器真正调用close关闭连接时会向客户端发送FIN此时服务器进入LAST_ACK状态等待最后一个ACK到来(这个ACK是客户端确认收到了FIN)。
[LAST_ACK - CLOSED] 服务器收到了对FIN的ACK彻底关闭连接。
客户端状态转化 [CLOSED - SYN_SENT] 客户端调用connect发送同步报文段。
[SYN_SENT - ESTABLISHED] connect调用成功则进入ESTABLISHED状态开始读写数据。
[ESTABLISHED - FIN_WAIT_1] 客户端主动调用close时向服务器发送结束报文段同时进入FIN_WAIT_1。
[FIN_WAIT_1 - FIN_WAIT_2] 客户端收到服务器对结束报文段的确认则进入FIN_WAIT_2开始等待服务器的结束报文段。
[FIN_WAIT_2 - TIME_WAIT] 客户端收到服务器发来的结束报文段进入TIME_WAIT并发出LAST_ACK。
[TIME_WAIT - CLOSED] 客户端要等待一个2MSL(Max Segment Life, 报文最大生存时间)的时间才会进入CLOSED状态。
1.7.1 理解TIME_WAIT状态
这里是以客户端为例如果是服务端也是一样的。谁先断开连接谁先进入TIME_WAIT状态当客户端发送最后一次ACK之后客户端理论上可以关闭连接了但是需要等一段时间才会进入CLOSED状态。
为什么TIME_WAIT状态需要等一段时间才会结束 原因是在第四次挥手时发送的ACK可能会丢失那么对方可能会进行FIN重传机制。如果在一段时间内没有收到对方的FIN就认为对方收到了ACK。并且如果当正常数据发送的时候同时发送FIN那么等一段时间是为了让历史数据尽可能的被双方收到。
那么TIME_WAIT状态需要等待的时间是多长呢 TCP协议规定主动关闭连接的一方要处于TIME_ WAIT状态等待两个MSL的时间后才能回到CLOSED状态。 MSL在RFC1122中规定为两分钟但是各操作系统的实现不同 在Centos7上默认配置的值是60s。可以通过 cat /proc/sys/net/ipv4/tcp_fin_timeout 查看msl的值。
为什么是TIME_WAIT的时间是2MSL MSL是TCP报文的最大生存时间因此TIME_WAIT持续存在2MSL的话就能保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失(否则服务器立刻重启可能会收到来自上一个进程的迟到的数据但是这种数据很可能是错误的)。 同时也是在理论上保证最后一个报文可靠到达(假设最后一个ACK丢失那么服务器会再重发一个FIN。这时虽然客户端的进程不在了但是TCP连接还在仍然可以重发LAST_ACK)。
如果是服务端先断开连接进入TIME_WAIT状态后我们就不能启动服务端了(./server 8080)它的错误码是2(绑定失败)。 这是因为虽然server的应用程序终止了但TCP协议层的连接并没有完全断开因此不能再次监听同样的server端口。
在server的TCP连接没有完全断开之前不允许重新监听某些情况下可能是不合理的 服务器需要处理非常大量的客户端的连接(每个连接的生存时间可能很短但是每秒都有很大数量的客户端来请求)。这个时候如果由服务器端主动关闭连接(比如某些客户端不活跃就需要被服务器端主动清理掉)就会产 生大量TIME_WAIT连接。 由于我们的请求量很大就可能导致TIME_WAIT的连接数很多每个连接都会占用一个通信五元组(源ip、源端口、目的ip、目的端口、协议)。其中服务器的ip和端口和协议是固定的如果新来的客户端连接的ip和端口号和TIME_WAIT占用的链接重复了就会出现问题。
解决TIME_WAIT状态引起的bind失败的方法使用setsockopt()设置socket描述符的选项SO_REUSEADDR为1表示允许创建端口号相同但IP地址不同的多个socket描述符。 函数功能设置套接字描述符的属性。
sockfd要设置的套接字描述符。 level是被设置的选项的级别如果想要在套接字级别上设置选项就必须把level设置为 SOL_SOCKET。 optname指定准备设置的选项optname可以有哪些取值这取决于level。 optval指向某个变量的指针该变量是要设置新值的缓冲区。可以是一个结构体也可以是普通变量。 optlenoptval缓冲区的长度。
代码如下 // 1. 创建socketlistenSock_ socket(PF_INET, SOCK_STREAM, 0);if (listenSock_ 0){exit(1);}int opt 1;setsockopt(listenSock_, SOL_SOCKET, SO_REUSEADDR, opt, sizeof(opt));// 2. bind绑定// 2.1 填充服务器信息1.7.2 理解 CLOSE_WAIT 状态
如果客户端断开连接服务端没有close那么服务端就会进入CLOSE_WAIT 状态。 小结: 对于服务器上出现大量的 CLOSE_WAIT 状态原因就是服务器没有正确的关闭 socket导致四次挥手没有正确完成。这是一个BUG只需要加上对应的 close 即可解决问题。