飞创网站建设,手机怎样做网站图解,沃尔玛跨境电商平台,wordpress 重复点赞目录 1. 回顾端口号2. UDP协议2.1 理解报头2.2 UDP的特点2.3 UDP的缓冲区及注意事项 3. TCP协议3.1 报头3.2 流量控制2.3 数据发送模式3.4 捎带应答3.5 URG 紧急指针3.6 PSH3.7 RES 1. 回顾端口号 在 TCP/IP 协议中#xff0c;用 “源IP”#xff0c; “源端口号”… 目录 1. 回顾端口号2. UDP协议2.1 理解报头2.2 UDP的特点2.3 UDP的缓冲区及注意事项 3. TCP协议3.1 报头3.2 流量控制2.3 数据发送模式3.4 捎带应答3.5 URG 紧急指针3.6 PSH3.7 RES 1. 回顾端口号 在 TCP/IP 协议中用 “源IP” “源端口号” “目的IP”“目的端口号”“协议号” 这样一个五元组来标识一个通信(可以通过 netstat -n 查看)。 端口号范围划分 0 - 1023知名端口号HTTPFTPSSH等这些广为使用的应用层协议他们的端口号都是固定的。 ssh(22)ftp(21)telnet(23)http(80)https(443) 1024 - 65535操作系统动态分配的端口号。客户端程序的端口号就是由操作系统从这个范围分配的。 cat /etc/services看到知名端口号。 两个问题详细见 网络基础二 一个进程是否可以bind多个端口号? ------ 可以。一个端口号是否可以被多个进程bind? ------ 不可以。
2. UDP协议 以UDP为例当我们在客户端的应用层通过 sendto 系统调用将数据发送到服务端站在网络协议栈的角度看待该数据实际上是先被拷贝到传输层(传输层属于OS)而这部分数据就是 UDP 中看到的数据应用层的报文经由传输层时OS会封装一层传输层的报头即 UDP 报头。 分装报头时发送端会随机形成一个源端口并填充目的端口号。其中的 UDP 长度代表的是整个 UDP 报文的长度检验和则是检验整个报文在传输过程中是否出错。 因此我们过去在 udp_socket 编程时客户端需要在发送请求的同时填充服务端的端口号以及定义端口号时我们采用的类型是 uint16_t这一切都是源于 UDP 的报头决定的 而因为网络通信时报文在协议栈中自顶向下是要分装的最后再自底向上解包分用。因此我们需要解决 报头和有效载荷的分离问题固定报头长度。有效载荷是如何向上交付的问题绑定目的端口号。UDP 是面向数据报的如何保证接收到的报文是完整的 ------ 通过报头长度确定如果报头长度小于8字节报头都有问题整个报文直接丢弃大于8字节UDP长度 - 报头长度 数据长度。
2.1 理解报头 我们在各种教科书上看到的报头都形如上图但是报头究竟是个什么东西呢 一个描述报头的结构体所谓报头就是一个结构化的数据。 struct udphdr {uint16_t src_port, dst_port;uint16_t udp_len, checksum;
}但是一个服务器是同时对接多个客户端的因此服务器内是会存在大量的报文的因此操作系统内可能积攒大量的报文可能还没有向应用层进行交付那么操作系统就需要对这些 UDP/TCP 报文管理起来。 内核中存在一个 sk_buff 结构体其中有 *data、*tail 两个字段的指针指向同一位置并且在内核中定义一段缓冲区用户保存接收到的报文数据。假设应用层通过 sendto 将 “hello” 发送到传输层那么在拷贝到传输层时*data 指针就向前移动 5 个字节用于存储上层发送的数据。接着还需要定义一个 udphdr 报头对象*data 指针继续向前偏移 8 个字节用于存储报头数据。这就是 UDP 封装。 而 UDP 层可能积攒了很多的报文数据只需要通过 struct sk_buff *next 字段将报文以链表的形式组织起来。这样一来对报文的管理就转变为对链表的增删查改。 更重要的是不同端的主机通信采用的也是网络协议栈同样也有传输层。什么是协议双方都需要按照同样的规则、同样的标准来通信。因此在不同端无论收发信息采用的都是 udphdr 这样的描述结构体双方使用同样的数据类型来解释收到的 UDP 报头的 8 个字节。因此协议就是结构化数据协议就是一种约定。
2.2 UDP的特点 无连接知道对端的 IP 和端口号就直接进行传输不需要建立连接。 不可靠没有确认机制没有重传机制如果因为网络故障该段无法发到对方UDP协议层也不会给应用层返回任何错误信息。 面向数据报不能够灵活的控制读写数据的次数和数量在向上层交付时UDP 清楚的报文的有效载荷是多少个字节。 应用层交给UDP多长的报文UDP原样发送既不会拆分也不会合并。 例如用UDP传输100个字节的数据如果发送端调用一次 sendto发送100个字节那么接收端也必须调用对应的一次 recvfrom接收100个字节而不能循环调用10次 recvfrom每次接收10个字节。 UDP的 socket 既能读也能写这个概念叫做全双工。
2.3 UDP的缓冲区及注意事项 UDP没有真正意义上的发送缓冲区调用 sendto 会直接交给内核由内核将数据传给网络层协议进行后续的传输动作。 UDP具有接收缓冲区但是这个接收缓冲区不能保证收到的 UDP 报的顺序和发送 UDP 报的顺序一致即可能先发的后收到。如果缓冲区满了再到达的UDP数据就会被丢弃。 我们注意到UDP 协议首部中有一个16位的最大长度也就是说一个 UDP 能传输的数据最大长度是64K(包含UDP首部)。 然而 64K 在当今的互联网环境下是一个非常小的数字如果我们需要传输的数据超过64K就需要在应用层手动的分包然后多次发送并在接收端手动拼接。
3. TCP协议
3.1 报头 同样的TCP 报头在内核中也是一种结构化的字段。 4 位首部长度取值范围 [ 0000 1111 ] [ 0 , 15 ] [00001111] [0, 15] [00001111][0,15]单位是 4个字节即 4 位首部长度实际的取值范围为 [ 0 , 15 ] ∗ 4 [ 0 60 ] [0, 15] * 4 [060] [0,15]∗4[060]也就意味着选项字段的大小最多为 40 40 40 字节。 假设 TCP 报头的长度为 20 20 20 字节那么首部长度即为 x ∗ 4 20 x * 4 20 x∗420 x 5 x 5 x5 即 x 0101 x 0101 x0101 有了首部长度 x x x默认先读取前 20 20 20 字节提取首部长度字段如果 x 20 x 20 x20即代表报头为 20 20 20 字节如果 x 20 x 20 x20 x − 20 x - 20 x−20 选项字段长度剩下的就是数据长度。这样一来即解决了报头和有效载荷的分离问题。 与 UDP 协议同理当应用层将数据通过 sendto 传递给传输层后传输层申请 sk_buff 缓冲区接着将数据从用户空间拷贝到 sk_buff 缓冲区再为 TCP 报文构建协议报头并填充字段最后将报头添加到 sk_buff 缓冲区中。至此完成对 TCP 报文的封装。 6位标志位后面详讲 URG: 紧急指针是否有效ACK确认号是否有效即应答报文设置的标志位PSH提示接收端应用程序立刻从 TCP 缓冲区把数据读走RST对方要求重新建立连接我们把携带 RST 标识的称为复位报文段SYN请求建立连接我们把携带 SYN标识的称为同步报文段用于三次握手建立连接FIN通知对方本端要关闭了我们称携带 FIN 标识的为结束报文段四次挥手关闭连接
3.2 流量控制
与 UDP 不同的是TCP 拥有独立的 发送和接收 缓冲区。并且单独的客户端既可以同时发送和接收因此 TCP 是全双工通信。 假设 TCP 通信的双方一方发的特别快另一方处理数据比较慢但是发送方仍一直持续的发送那么接收缓冲区就会被写满。但是发送方对于接收缓冲区已经被写满并不知情所以继续在发送数据但是数据又存不下了于是报文被直接丢弃了 丢弃后虽然 TCP 后续可能对该报文进行重传。但是可能这份报文可能经过无数个路由器转发了无数次报文好不容易到才到达目的地花费这么大的代价结果被丢弃了这种做法是不合理的 面对上述情况本质问题就是发送方无法得知接收方缓冲区的情况。如果接受方来不及接收报文了发送方 放缓 或 暂停发送问题就能够得到解决而这种解决方法称为 流量控制。 在实际 tcp_socket 通信时我们并没有遇到这类问题因为上面的工作已经由发送方的 TCP 完成了用户并不需要关心由 TCP 全权负责这也是为什么 TCP 称为 传输控制协议在实际的网络通信中用户层调用 sendto 将数据传递到传输层后在上层我们就认为数据已经发送出去了而实际上可能数据还存储在本地操作系统的缓冲区内还未进行发送。 TCP 为了保证传输的可靠性协议要求 TCP 的接收方必须在收到报文后进行确认向客户端进行响应反馈 即 确认应答机制。 需要注意的是假设发送的数据为 h e l l o hello hello真正发送的时候可不仅仅只有数据而是将数据封装在报文内将整个 TCP 报文发送给对方。同样的接收方在给发送方进行应答时也是发送的一个 TCP 报文同时在报头中设置了 标志位ACK标志位置 1 1 1。 16 位窗口大小 记录发送方接收缓冲区的剩余空间大小。 那么如何进行流量控制呢 ------ 发送方能够得知接收方的接收能力。 而接收缓冲区接收能力的强弱以剩余的空间大小为衡量。因此当接收方在向发送方进行应答时在应答的报头中的 窗口大小字段填充自己接收缓冲区的剩余空间大小那么发送方就能够清楚的进行传输控制了 即窗口大小是用于流量控制的字段 上述一切关于流量控制的方式、细节全部由 TCP 通信的双方的操作系统协商完成 当接收缓冲区被写满发送方也就停止发送但是上层用户可不关心上层用户还是继续向传输层写写写那当发送缓冲区也被写满了呢 这就是典型的生产消费模型消费者不消费了生产者就进行要阻塞即进程就要进入阻塞状态其本质就是进程在等待 OS 进行发送。进程是一个执行流OS 也是一个执行流这就是两个执行流在进行同步 即便 TCP 是传输具有可靠性的但也无法保证 100% 可靠的网络通信。 假设 C 端给 S 端发送第一条信息但是在这条信息发送出去到 S 端进行应答之前这段时间对于 C 端它是无法确认这次的报文对方是否收到。 当 S 端给 C 端应答第一条信息后那么就轮到 S 端无法确认它发出去的报文对方是否收到。那如果要做到 S 端也能够确认自己的报文对方收到了那就需要 C 端继续发送一次应答那这次应答的报文对于 C 端它又无法确认对方是否收到因为还没收到对方的应答。 所以这个是一个死循环永远无法确认最新的一条信息对方是否收到了也即最新的一条消息的可靠性其实是无法保证的。但是可以保证除了最后一条信息之前的所有信息的可靠性如上图所示① 和 ② 都是收到了应答因此除了最新的 ③ 信息其它都能够保证可靠性。
2.3 数据发送模式 像我们上面介绍的通信方式C 端给 S 端发送数据 S 端需要给 C 端进行应答反之同理。这些都是串行发送的通信效率比较慢。 这是 TCP 发送数据常见的方式将发送时间进行重叠并发的发送多份报文并且规定每个报文都需要进行应答保证可靠性的同时提高效率。 由于网络的实际情况错综复杂因此无法保证接收顺序与发送顺序一致那么就可能导致数据乱序的问题可能一份数据拆分为多个报文进行发送而乱序是不可靠的一种表现因此在同时发送多份报文的情况下需要保证报文按序到达。 即报头内的 32 位序号给报文携带编号。接收方可以根据报头中序号对接收到的报文进行排序即可保证报文按序到达。 还有一个问题是C 端发了 4 份报文S 端是需要给 C 端应答 4 次的那么 S 端的哪一次应答是对 C 端的哪一次报文做的应答呢如果不能解决这个问题那么当发送方发了 10 个报文最终只收到了 9 个应答那发送方就无法得知自己发送的 10 个报文里面到底是哪个报文丢了导致也无法进行后续补发报文的操作。 因此报头中还有另一个字段 ----- 32位确认序号用于记录历史上发送方发送的哪些报文已经被接收方所接收由接收方在应答报头中填充对应收到报文的序号值 1。 确认序号之前的所有报文已经被接收方收到。例如确认序号301代表的含义是序号300 200 100 的报文都已经被 S 端收到了。之所以这样设计的原因是不管是数据还是应答在网络传输中都有可能丢失假设 S 端前三次报文都收到了但是这三次应答都丢失了最后一份报文的应答 401 没有丢失那么 C 端在接收到 S 端的 401 应答时即可以得知 4 份报文 S 端都接收到了。换言之这样设计可以允许少量的应答丢失。
3.4 捎带应答 看似一切完美在应答的时候明明可以继续用 32 位序号来保障应答的可靠性为啥还要多设计出一个32位确认序号呢 要清楚我们至始至终为了方便理解一直都是以单向通信为例。但实际上的通信是双向的即 C 端 在向 S 端发数据的同时 S 端 也在给 C 端发数据。 那么 S 端 就需要在应答 C 端的同时发送自己的数据。如果只有一个 32 位序号那么应答 和 发数据 这两件事就需要分开来完成即 S 端需要向 C 端发送两次报文。而有了 32 位确认序号后这两件事可以同时完成即发送数据的同时顺带给 C 端应答了这就叫做捎带应答 实际通信时大部分报文都既是数据又是对历史报文的应答确认 总结TCP 在保证可靠性的同时还会进行各种提高效率的设定数据并发发送、捎带应答只不过这些工作都有 TCP(内核) 自主完成用户并不知情。
3.5 URG 紧急指针 标志位用于标识不同的报文类型。 服务器是同时面对多个客户端的即需要同时处理来自多个客户端的 TCP 报文而这些报文可能包含连接建立、连接断开、正常数据传输或异常数据等类型。那么服务器就必须具备识别不同报文类型的能力才能根据报文类型执行相应的处理动作。 我们上面说过发送方可能同时一次性发送一批报文并且这批报文的数据是有顺序的那么就必须保证这批报文按序到达而按序到达的本质就是队列先发先收。但不妨有时候会有一些紧急任务那么在按序到达的规则下即便是紧急任务也要排队但所谓紧急任务肯定是需要尽快处理、提前被处理的。 因此面对这种情况URG 标志位置 1表明该报文是紧急报文。 16位紧急指针记录紧急数据在有效载荷中的偏移量。 收到报文先查看标志位如果 U R G 1 URG 1 URG1那么根据紧急指针查看紧急数据紧急数据只有一个字节。 紧急任务的场景假设发送方上传一批数据但是上传过程中发现上传错了而此时接收方的缓冲区已经积攒了大量的报文还没被处理这种情况就可以发送紧急指针示意对方立即终止或暂停上传行为通过一些固定的标志信息反馈如果直接关闭连接那么接收方需要把缓冲区的数据全部读取完才能得知发送方已经关闭连接。 再者例如当发现服务器运行速度比较慢时通过给服务器发送紧急指针进行服务检测。 在发送 send 和 接收 recv 的第三个参数 flag就会提供一些标志位例如 MSG_OOB 就表示紧急数据。 ssize_t recv(int sockfd, void *buf, size_t len, int flags);MSG_OOBThis flag requests receipt of out-of-band data that would not be received in the normal data stream.3.6 PSH 在讲流量控制的时候我们讲过当接受发给发送方应答的同时报头中的窗口大小会记录接收方缓冲区剩余空间大小以便发送方进行传输控制。 假设接收方上层 http 处理数据比较耗时导致接收缓冲区被写满随后接收方在应答报文中将窗口大小设置为 0告知对方无法接收更多数据。一段时间后接收方的上层处理了一些数据缓冲区腾出一些空间但此时的发送方并不知情还在傻傻的阻塞等待。 因此为了避免这种情况发送方会定期发送空闲时间较长的询问报文这种方式效率较低因为它依赖超时来触发询问然后等待接收方的应答。另一方面当接收方的缓冲区的剩余空间由 0 0 0 变为 ! 0 !0 !0 时接收方也会主动向发送方进行窗口大小的更新报文。 这里所谓的询问报文也是完整的 TCP 报头同时将 PSH 标志位置 1。即 PSH告知对方尽快将数据向上层交付。 但 PSH 不仅仅应用于询问报文中当数据需要被尽快交付时也可以使用。最典型的案例就是xshell 远程连接远端服务器时为了保证更好的用户体验xshell 尽可能的让用户输入的每一条指令携带 PSH 标志位让上层的 ssh 尽快交付解释。
3.7 RES 我们一直在说通信但建立 TCP 通信的前提是建立连接而建立连接就需要三次握手。 虽然 TCP 是保证可靠性的但不代表三次握手就必须是成功的也可能是失败的保证的不是数据 100% 到达目的地而是数据到达目的地了 或者 数据传输异常了TCP 需要知道。 三个报文的前两个报文丢失都不影响假设客户端连接建立请求的报文丢失了服务端没有收到服务端知情那么也就不会给应答。客户端收不到服务端的应答也就同样知情报文丢了要么是自己的要么是对方的应答报文那么客户端就会进行报文重发。而假设服务端的报文丢了虽然服务端自己不知情但是客户端没收到客户端是知情的它就不会给服务端发送应答报文那么服务端收不到应答报文服务端也知情了。 最麻烦的是最后一次 ACK假设最后一次客户端的 ACK 丢了但客户端并不知情。站在客户端的角度客户端认为最后一次 ACK 报文发送出去即连接建立成功而服务端需要等到接收了报文才认为连接建立成功。这种情况就会导致双端连接建立认知不一致的问题。 因为客户端认为连接建立好了所以它就开始向服务端发送数据服务端收到数据后脑袋大大的问号因为它认为三次握手还没完成于是服务端就向客户端推送一个报文并在报头中设置 RST 标志位。客户端收到 RST 标志位的报文后就重置连接重新开始三次握手。 因此三次握手的最后一次是无法保证可靠性的即三次握手不是一定成功的但可以通过 RST 标志位做容错处理。 同样的 RST 也不仅仅应用于三次握手。例如当双方通信的过程中客户端网络中断连接被释放了。随后客户端恢复了网络环境但服务端并不知道于是继续向客户端发送数据。这也是双端连接建立认知不一致的情况。客户端恢复网络后它认为连接还没建立但服务器不知情依旧认为连接还在。这种情况客户端同样可以向服务器发送 RST 重置连接请求。