| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 网络协议 -> 趣谈网络协议(二)传输层 -> 正文阅读 |
|
[网络协议]趣谈网络协议(二)传输层 |
目录 一、UDP协议传输层有两个比较重要的协议,一个是TCP,一个是UDP。 1、TCP和UDP的区别
2、UDP包头3、UDP的特点
4、UDP使用场景(1)需要资源少,网络情况比较好的内网,或者对于丢包不敏感的应用。DHCP就是基于UDP协议的。一般的获取IP地址都是内网请求,而且,一次获取失败还可以继续获取。PXE启动时自动安装操作系统,操作系统镜像下载所使用的TFTP也是基于UDP协议的。 (2)无需一对一沟通,无需建立连接,可以广播的应用。UDP 的不面向连接的功能,可以使得可以承载广播或者多播的协议。DHCP 就是一种广播的形式,就是基于 UDP 协议。对于多播,32位 IP 地址中的 D 类地址(组播地址),可以将包组播给一批机器。当一台机器上的某个进程想监听某个组播地址的时候,需要发送 IGMP 包,所在网络的路由器就能收到这个包,知道有个机器上有个进程在监听这个组播地址。当路由器收到这个组播地址的时候,会将包转发给这台机器,这样就实现了跨路由器的组播。云中网络部分的 VXLAN协议,也是需要用到组播,也是基于 UDP 协议的。 (3)需要处理速度快,时延低,可以容忍少数丢包,但是要求即便网络拥塞,也毫不退缩,一往无前的应用。如果应用本身实现了自己的连接策略、可靠保证和时延要求,也可以使用UDP代替TCP。 5、基于UDP的例子(1)网页或APP的访问 起初访问网页和手机 APP 都是基于 HTTP 协议的。HTTP 协议是基于 TCP 的,建立连接都需要多次交互,对于时延比较大的目前主流的移动互联网来讲,建立一次连接需要的时间会比较长,然而既然是移动中,TCP 可能还会断了重连,也是很耗时的。而且目前的 HTTP 协议,往往采取多个数据通道共享一个连接的情况,这样本来为了加快传输速度,但是 TCP 的严格顺序策略使得哪怕共享通道,前一个不来,后一个和前一个即便没关系,也要等着,时延也会加大。而 QUIC( Quick UDP Internet Connections,快速 UDP 互联网连接)是 Google 提出的一种基于 UDP 改进的通信协议,在应用层上,会自己实现快速建立连接,减少重传时延、自适应拥塞控制。提供了更好的用户互动体验。 (2)流媒体协议 直播协议多采用RTMP,RTMP协议也是基于TCP的。TCP 的严格顺序传输要保证前一个收到了,下一个才能确认,如果前一个收不到,下一个包就算已经收到了,在缓存里面,也需要等着。对于直播来讲,这显然是不合适的,因为老的视频帧丢了其实也就丢了,就算再传过来用户也不在意了,他们要看新的了,如果老是没来就等着,卡顿了,新的也看不了,那就会丢失客户,所以直播,实时性比较比较重要,宁可丢包,也不要卡顿的。另外,对于丢包,其实对于视频播放来讲,有的包可以丢,有的包不能丢,因为视频的连续帧里面,有的帧重要,有的不重要,如果必须要丢包,隔几个帧丢一个,其实看视频的人不会感知,但是如果连续丢帧,就会感知了,因而在网络不好的情况下,应用希望选择性的丢帧。还有就是当网络不好的时候,TCP 协议会主动降低发送速度,这对本来当时就卡的看视频来讲是要命的,应该应用层马上重传,而不是主动让步。因而,很多直播应用,都基于 UDP 实现了自己的视频传输协议。 (3)实时游戏 游戏有一个特点,就是实时性比较高,因而,实时游戏中客户端和服务端要建立长连接,来保证实时传输。但是游戏玩家很多,服务器却不多。由于维护 TCP 连接需要在内核维护一些数据结构,因而一台机器能够支撑的 TCP 连接数目是有限的,然后 UDP 由于是没有连接的,在异步 IO 机制引入之前,常常是应对海量客户端连接的策略。另外还是 TCP 的强顺序问题,对战的游戏,对网络的要求很简单,玩家通过客户端发送给服务器鼠标和键盘行走的位置,服务器会处理每个用户发送过来的所有场景,处理完再返回给客户端,客户端解析响应,渲染最新的场景展示给玩家。如果出现一个数据包丢失,所有事情都需要停下来等待这个数据包重发。客户端会出现等待接收数据,然而玩家并不关心过期的数据,激战中卡 1 秒,等能动了都已经死了。游戏对实时要求较为严格的情况下,采用自定义的可靠 UDP 协议,自定义重传策略,能够把丢包产生的延迟降到最低,尽量减少网络问题对游戏性造成的影响。 (4)IOT物联网 一方面,物联网领域终端资源少,很可能只是个内存非常小的嵌入式系统,而维护 TCP 协议代价太大;另一方面,物联网对实时性要求也很高,而 TCP 还是因为上面的那些原因导致时延大。Google 旗下的 Nest 建立 Thread Group,推出了物联网通信协议 Thread,就是基于 UDP 协议的。 (5)移动通信领域 在 4G 网络里,移动流量上网的数据面对的协议 GTP-U 是基于 UDP 的。因为移动网络协议比较复杂,而 GTP 协议本身就包含复杂的手机上线下线的通信协议。如果基于 TCP,TCP 的机制就显得非常多余。 二、TCP协议1、TCP包头包的序号是为了解决乱序问题,确认序号是为了解决丢包问题。 状态位中,SYN是发起一个连接,ACK是回复,RDT是重新连接,FIN是结束连接等。TCP是面向连接的,因而双方要维护连接的状态,这些带状态位的包发送,会引起双方的状态变更。 窗口大小:TCP要做流量控制,通信双方各声明一个窗口,标识自己的当前的处理能力。 除了流量控制,TCP还会做拥塞控制,控制发送的速度。 2、三次握手、四次挥手三次握手也常称为“请求——应答——应答之应答”,是为了确认通信双方都可发可收,以及沟通包的序号问题。每次连接都要有不同的序号,这个序号的起始序号是随时间变化的,可以看成一个32位的计数器,每4微秒加一,到重复需要4个多小时。 通信双方断开连接是在第四次挥手后再等2MSL。MSL 是 Maximum Segment Lifetime,报文最大生存时间,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。因为 TCP 报文基于是 IP 协议的,而 IP 头中有一个 TTL 域,是 IP 数据报可以经过的最大路由数,每经过一个处理它的路由器此值就减 1,当此值为 0 则数据报将被丢弃,同时发送 ICMP 报文通知源主机。协议规定 MSL 为 2 分钟,实际应用中常用的是 30 秒,1 分钟和 2 分钟等。 断开状态时序图 TCP状态机 3、 顺序问题与丢包问题TCP 协议为了保证顺序性,每一个包都有一个 ID。在建立连接的时候,会商定起始的 ID 是什么,然后按照 ID 一个个发送。为了保证不丢包,对于发送的包都要进行应答,但是这个应答也不是一个一个来的,而是会应答某个之前的 ID,表示都收到了,这种模式称为累计确认或者累计应答(cumulative acknowledgment)。 为了记录所有发送的包和接收的包,TCP 也需要发送端和接收端分别都有缓存来保存这些记录。发送端的缓存里是按照包的 ID 一个个排列,根据处理的情况分成四个部分。第一部分:发送了并且已经确认的。第二部分:发送了并且尚未确认的。第三部分:没有发送,但是已经等待发送的。第四部分:没有发送,并且暂时还不会发送的。 之所以区分三、四部分,是为了做流量控制。在 TCP 里,接收端会给发送端报一个窗口的大小,叫 Advertised window。这个窗口的大小应该等于上面的第二部分加上第三部分,。超过这个窗口的,接收端做不过来,就不能发送了。 确认与重发机制一种方法是超时重试,即对每一个发送了但没有 ACK 的包都设一个定时器,超过一定时间,就重新尝试。超时时间的选择需要评估往返时间,需要 TCP 通过采样 RTT 的时间,然后进行加权平均,算出一个值,而且这个值还是要不断变化的,因为网络状况不断地变化。除了采样 RTT,还要采样 RTT 的波动范围,计算出一个估计的超时时间。由于重传时间是不断变化的,我们称为自适应重传算法(Adaptive Retransmission Algorithm)。每当遇到一次超时重传的时候,都会将下一次超时时间间隔设为先前值的两倍。两次超时,就说明网络环境差,不宜频繁反复发送。超时触发重传存在的问题是,超时周期可能相对较长。有一个可以快速重传的机制,当接收方收到一个序号大于下一个所期望的报文段时,就会检测到数据流中的一个间隔,于是它就会发送冗余的 ACK,仍然 ACK 的是期望接收的报文段。而当客户端收到三个冗余的 ACK 后,就会在定时器过期之前,重传丢失的报文段。还有一种方式称为 SACK (Selective Acknowledgment ),这种方式需要在 TCP 头里加一个 SACK 的东西,可以将缓存的地图发送给发送方。例如可以发送 SACK6、SACK8、SACK9,有了地图,发送方一下子就能看出来是 7 丢了。 流量控制问题如果接收方处理消息太慢,导致缓存中没有空间了,可以通过确认信息修改窗口的大小,甚至可以设置为 0,发送方将暂时停止发送。这样的话,发送方会定时发送窗口探测数据包,看是否有机会调整窗口的大小。当接收方比较慢的时候,要防止低能窗口综合征,别空出一个字节来就赶快告诉发送方,然后马上又填满了,可以当窗口太小的时候,不更新窗口,直到达到一定大小,或者缓冲区一半为空,才更新窗口。这就是我们常说的流量控制。 拥塞控制问题TCP 的拥塞控制主要来避免两种现象,包丢失和超时重传。一旦出现了这些现象。就说明发送速度太快了,要慢一点。 拥塞控制也是通过窗口的大小来控制的,前面的滑动窗口 rwnd 是怕发送方把接收方缓存塞满,而拥塞窗口 cwnd,是怕把网络塞满。这里有一个公式 LastByteSent - LastByteAcked <= min {cwnd, rwnd} ,是拥塞窗口和滑动窗口共同控制发送的速度。那发送方怎么判断网络是不是慢呢?网络上,通道的容量 = 带宽 × 往返延迟。如果我们设置发送窗口,使得发送但未确认的包为为通道的容量,就能够撑满整个管道。 TCP BBR 拥塞算法企图找到一个平衡点,就是通过不断地加快发送速度,将管道填满,但是不要填满中间设备的缓存,因为这样时延会增加,在这个平衡点可以很好的达到高带宽和低时延的平衡。 三、SocketSocket 可以作插口或者插槽讲,在通信之前,双方都要建立一个 Socket。 Socket 编程进行的是端到端的通信,往往意识不到中间经过多少局域网,多少路由器,因而能够设置的参数,也只能是端到端协议之上网络层和传输层的。 在网络层,Socket 函数需要指定是 IPv4 还是 IPv6,分别对应设置为 AF_INET 和 AF_INET6。另外,还要指定是 TCP 还是 UDP。TCP 协议是基于数据流的,所以设置为 SOCK_STREAM,而 UDP 是基于数据报的,因而设置为 SOCK_DGRAM。 1、基于 TCP 协议的 Socket 程序函数调用过程TCP 的服务端要先监听一个端口,一般是先调用 bind 函数,给这个 Socket 赋予一个 IP 地址和端口。当一个网络包来的时候,内核要通过 TCP 头里面的这个端口,来找到应用程序。 当服务端有了 IP 地址和端口号,就可以调用 listen 函数进行监听。在 TCP 的状态图里面,有一个 listen 状态,当调用这个函数之后,服务端就进入了这个状态,这个时候客户端就可以发起连接了。 在内核中,为每个 Socket 维护两个队列。一个是已经建立了连接的队列,这时候连接三次握手已经完毕,处于 established 状态;一个是还没有完全建立连接的队列,这个时候三次握手还没完成,处于 syn_rcvd 的状态。 接下来,服务端调用 accept 函数,拿出一个已经完成的连接进行处理。如果还没有完成,就要等着。 在服务端等待的时候,客户端可以通过 connect 函数发起连接。先在参数中指明要连接的 IP 地址和端口号,然后开始发起三次握手。内核会给客户端分配一个临时的端口。一旦握手成功,服务端的 accept 就会返回另一个 Socket。 监听的 Socket 和真正用来传数据的 Socket 是两个,一个叫作监听 Socket,一个叫作已连接 Socket。连接建立成功之后,双方开始通过 read 和 write 函数来读写数据,就像往一个文件流里面写东西一样。 ?说 TCP 的 Socket 就是一个文件流,是非常准确的。因为,Socket 在 Linux 中就是以文件的形式存在的。除此之外,还存在文件描述符。写入和读出,也是通过文件描述符。 在内核中,Socket 是一个文件,那对应就有文件描述符。每一个进程都有一个数据结构 task_struct,里面指向一个文件描述符数组,来列出这个进程打开的所有文件的文件描述符。文件描述符是一个整数,是这个数组的下标。这个数组中的内容是一个指针,指向内核中所有打开的文件的列表。 既然是一个文件,就会有一个 inode,只不过 Socket 对应的 inode 不像真正的文件系统一样,保存在硬盘上的,而是在内存中的。在这个 inode 中,指向了 Socket 在内核中的 Socket 结构。 在这个结构里面,主要的是两个队列,一个是发送队列,一个是接收队列。在这两个队列里面保存的是一个缓存 sk_buff。缓存里面能够看到完整的包的结构。 数据结构图: 2、基于 UDP 协议的 Socket 程序函数调用过程UDP 是没有连接的,所以不需要三次握手,也就不需要调用 listen 和 connect,但是,UDP 的交互仍然需要 IP 和端口号,因而也需要 bind。UDP 是没有维护连接状态的,因而不需要每对连接建立一组 Socket,而是只要有一个 Socket,就能够和多个客户端通信。也正是因为没有连接状态,每次通信的时候,都调用 sendto 和 recvfrom,都可以传入 IP 地址和端口。 3、服务器如何连接更多的端口?系统会用一个四元组来标识一个 TCP 连接
服务器通常固定在某个本地端口上监听,等待客户端的连接请求。因此,服务端TCP 连接四元组中只有对端 IP和对端的端口,也即客户端IP端口是可变的,因此,最大 TCP 连接数 = 客户端 IP 数×客户端端口数。对 IPv4,客户端的 IP 数最多为 2 的 32 次方,客户端的端口数最多为 2 的 16 次方,也就是服务端单机最大 TCP 连接数,约为 2 的 48 次方。? 当然,服务端最大并发 TCP 连接数远不能达到理论上限。首先主要是文件描述符限制,按照上面的原理,Socket 都是文件,所以首先要通过 ulimit 配置文件描述符的数目;另一个限制是内存,按上面的数据结构,每个 TCP 连接都要占用一定内存,而操作系统是有限的。 在资源有限的情况下,要想接更多的项目,就需要降低每个项目消耗的资源数目。 方式一:多进程这就相当于你是一个代理,在那里监听来的请求。一旦建立了一个连接,就会有一个已连接 Socket,这时候你可以创建一个子进程,然后将基于已连接 Socket 的交互交给这个新的子进程来做。 在 Linux 下,创建子进程使用 fork 函数,在父进程的基础上完全拷贝一个子进程。在 Linux 内核中,会复制文件描述符的列表,也会复制内存空间,还会复制一条记录当前执行到了哪一行程序的进程。复制的时候再调用 fork,复制完毕之后,父进程和子进程都会记录当前刚刚执行完 fork。这两个进程刚复制完的时候,几乎一模一样,只是根据 fork 的返回值来区分到底是父进程,还是子进程。如果返回值是 0,则是子进程;如果返回值是其他的整数,就是父进程。 方式二:多线程在 Linux 下,通过 pthread_create 创建一个线程,也是调用 do_fork。不同的是,虽然新的线程在 task 列表会新创建一项,但是很多资源,例如文件描述符列表、进程空间,还是共享的,只不过多了一个引用而已。 ?新的线程也可以通过已连接 Socket 处理请求,从而达到并发处理的目的。 上面基于进程或者线程模型的,其实还是有问题的。新到来一个 TCP 连接,就需要分配一个进程或者线程。一台机器无法创建很多进程或者线程。有个 C10K,它的意思是一台机器要维护 1 万个连接,就要创建 1 万个进程或者线程,那么操作系统是无法承受的。如果维持 1 亿用户在线需要 10 万台服务器,成本也太高了。 方式三、IO 多路复用,一个线程维护多个 Socket由于 Socket 是文件描述符,因而某个线程盯的所有的 Socket,都放在一个文件描述符集合 fd_set 中,这就是项目进度墙,然后调用 select 函数来监听文件描述符集合是否有变化。一旦有变化,就会依次查看每个文件描述符。那些发生变化的文件描述符在 fd_set 对应的位都设为 1,表示 Socket 可读或者可写,从而可以进行读写操作,然后再调用 select,接着盯着下一轮的变化。 select 函数还是有问题的,因为每次 Socket 所在的文件描述符集合中有 Socket 发生变化的时候,都需要通过轮询的方式,也就是需要将全部项目都过一遍的方式来查看进度,这大大影响了一个项目组能够支撑的最大的项目数量。因而使用 select,能够同时盯的项目数量由 FD_SETSIZE 限制。 方式四、IO多路复用,事件通知epoll函数在内核中的实现不是通过轮询的方式,而是通过注册 callback 函数的方式,当某个文件描述符发送变化的时候,就会主动通知。 ?如图所示,假设进程打开了 Socket m, n, x 等多个文件描述符,现在需要通过 epoll 来监听是否这些 Socket 都有事件发生。其中 epoll_create 创建一个 epoll 对象,也是一个文件,也对应一个文件描述符,同样也对应着打开文件列表中的一项。在这项里面有一个红黑树,在红黑树里,要保存这个 epoll 要监听的所有 Socket。 当 epoll_ctl 添加一个 Socket 的时候,其实是加入这个红黑树,同时红黑树里面的节点指向一个结构,将这个结构挂在被监听的 Socket 的事件列表中。当一个 Socket 来了一个事件的时候,可以从这个列表中得到 epoll 对象,并调用 call ?back 通知它。这种通知方式使得监听的 Socket 数据增加的时候,效率不会大幅度降低,能够同时监听的 Socket 的数目也非常的多了。上限就为系统定义的、进程打开的最大文件描述符个数。因而,epoll 被称为解决 C10K 问题的利器。 |
|
网络协议 最新文章 |
使用Easyswoole 搭建简单的Websoket服务 |
常见的数据通信方式有哪些? |
Openssl 1024bit RSA算法---公私钥获取和处 |
HTTPS协议的密钥交换流程 |
《小白WEB安全入门》03. 漏洞篇 |
HttpRunner4.x 安装与使用 |
2021-07-04 |
手写RPC学习笔记 |
K8S高可用版本部署 |
mySQL计算IP地址范围 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/25 23:43:22- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |