大师兄

加餐 | 模块一思考题解答

今天我会带你把《模块一:互联网和传输层协议》中涉及的课后练习题,逐一讲解,并给出每一讲练习题的解题思路和答案。

02 | 传输层协议 TCP : TCP 为什么握手是 3 次、挥手是 4 次?

问题一台内存在 8G 左右的服务器,可以同时维护多少个连接

解析】连接是内存中的状态对象,从理论上分析,连接本身不太占用内存。不同语言连接对象大小不等,但是通常很小。下面我提供一段 Java 程序,你可以感受一下:

public class Server {
    public static void main(String[] argv) throws IOException {
        var serverSocket = new ServerSocket();
        var addr = new InetSocketAddress(3001);
        serverSocket.bind(addr);
        var list = new LinkedList<>();
        while(true) {
            var client = serverSocket.accept();
            list.add(client);
            System.out.println(list.size());
        }
    }
}
public class Client {
    public static void main(String[] argv) throws IOException, InterruptedException {
        var clients = new LinkedList<>();
        for(int i = 0; i < 1000000; i++) {
            var client = new Socket("127.0.0.1", 3001);
            clients.add(client);
        }
        Thread.sleep(10000000);
    }
}

通过运行上面这段程序,你可以观察到以下这几个现象:

  1. 创建 100W 连接速度不是很快,这说明 TCP 连接创建有成本(3 次握手,都是网络 IO);

  2. jps找到对应的进程的id,在用sudo cat /proc/{进程ID}/status | grep VmHWM可以看到实际的内存占用。按照这种增长趋势,8G 内存空间可以轻轻松松存放 100W 个连接。

但是如果单机建立太多的连接,会报一个Cannot assign requested address的异常,这是因为客户端连接服务端时,操作系统要为每个客户端分配一个端口,上面的程序很快会把端口号用尽。

所以,我们可以得出一个结论:核心的问题是,通信需要缓冲区,通信需要 I/O。这是因为通信占用资源,连接本身占用资源少

另外,我看到的评论区有不少高质量的回答,建议你回到“02 | 传输层协议 TCP : TCP 为什么握手是 3 次、挥手是 4 次?”看一看,作为知识补充。如果看到好的答案,不妨动手点个赞(我给大家一一点了赞)。

03 | TCP 的封包格式: 为什么需要粘包和拆包?

问题有哪些好用的压测工具

压力测试最常见的工具是 Apache Benchmark(简称 AB),在 Linux 下面可以通过包管理器安装 ab:

yum install httpd-tools
// 或
apt-get install apache2-utils

ab 安装好后,可以利用下面这条指令向某个网站发送并发 1000 的 10000 次请求:

ab -n 10000 -p 1000 https://example.com/

ab 是用 C 语言写的,作为一个随手就可以用的工具,它的设计非常简单,是一个单线程的工作模型,因此如果遇到阻塞情况,可能直接导致 ab 工具自己积压崩溃。

所以。这里我给你推荐一个 Java 生态好用的工具“JMeter”,拥有可视化的界面,如下图所示:

图片1.png

这个工具在各个平台上都可以用,比 ab 稳定,有图形化界面,可以配置任意线程数量,还有可视化的图表支持。

04 | TCP 的稳定性 :滑动窗口和流速控制是怎么回事?

问题先不要查资料既然发送方有窗口,那么接收方也需要有窗口吗?

解析】我们一起思考下,接收方收到发送方的每个数据分组(或者称为 TCP Segment),接收方肯定需要缓存。举例来说,如果发送方发送了:1, 2, 3, 4。 那么接收方可能收到的一种情况是:1,4,3。注意,没有收到 2 的原因可能是延迟、丢包等。这个时候,接收方有两种选择。

选择一:什么都不做(这样分组 2 的 ACK 就不会发送给发送方,发送方发现没有收到 2 的 ACK,过一段时间就有可能重发 2,3,4,5)。 当然具体设计还需要探讨,比如不重发整个分组,只重发已发送没有收到 ACK 的分组。

这种方法的缺陷是性能太差,重发了整个分组(或部分)。因此我们可以考虑另一种选择。

选择二:如果重发一个窗口,或部分窗口,问题就不会太大了。虽然增加了网络开销,但是毕竟有进步(1 进步了,不会再重发)。

性能方面最大的开销是等待超时的时间,就是发送方要等到超时时间才重发窗口,这样操作性能太差。因此,TCP 协议有一个快速重传的机制——接收方发现接收到了 1,但是没有接收到 2,那么马上发送 3 个分组 2 的 ACK 给到发送方,这样发送方收到多个 ACK,就知道接收方没有收到 2,于是马上重发 2。

无论是上面哪种方案,接收方也维护一个滑动窗口,是一个不错的选择。接收窗口的状态,可以和发送窗口的状态相互对应了。

05 | UDP 协议:TCP 协议和 UDP 协议的优势和劣势?

问题Moba 类游戏的网络应该用 TCP 还是 UDP

解析】所有在线联机游戏都有件非常重要的事情需要完成,就是确定事件发生的唯一性,这个性质和聊天工具是类似的。听我这么说,是不是有点迷?请听我慢慢道来。

你在王者荣耀中控制后羿释放技能,这是一个事件。同时,王昭君放了大招,这是第二个事件。两个事件一定要有先后顺序吗?答案是当然要有。因为游戏在同一时刻只能有一个状态。

类比一下,多个线程同时操作内存,发生了竞争条件(具体分析可以参见《重学操作系统》专栏关于“线程”的内容),那么是不是意味着,内存在同一时刻有两个状态呢?当然不是,内存同时刻只能有一个状态,所以多个线程的操作必须有先有后

回到 Moba 游戏的问题,每个事件,游戏服务器必须给一个唯一的时序编号,对应后羿的技能和王昭君的技能。所以,在线竞技类游戏,事实上是玩家在不断向服务器竞争一个自增序列号的过程。无论客户端发生怎样的行为,只有竞争到自增 ID 才能进步。也就是说,服务器要尽快响应多个客户端提交的事件,并以最快的速度分配自增序号,然后返回给客户端

所以,Moba 服务端的核心是自增序号的计算和尽量缩减延迟。从这个角度出发,你再来看看,应该用 TCP 协议还是 UDP 协议呢?

虽然TCP 协议有 3 次握手,但是连接上之后,双方就不会再有额外的传输成本,因此创建连接的成本,可以忽略不计。

同时,TCP 协议还提供稳定性支持,不需要自己实现稳定性。如果规模较小的在线竞技类游戏,TCP 完全适用。但是当游戏玩家体量上升后,TCP 协议的头部(数据封包)较大,会增加服务器额外的 I/O 压力。要发送更多的数据,自然有更大的 I/O 压力。从这个角度来看,UDP 就有了用武之地。

总结

本模块我们学习互联网协议群中最重要的两种传输层协议:TCP 协议和 UDP 协议。这两种协议,应该是你以后打交道最多的传输层协议。我认为,除了协议本身,协议的设计者的设计思路,是你更应该重视的事情。

希望通过本次课程的学习,你能够有所收获,将来遇到相关问题,能对应到这一模块所学的知识,比如:

  • 当你既要保证 FIFO,又要提供多处理的数据结构时,可以想到滑动窗口

  • 当你设计请求/响应模型的时,可以想到多路复用

  • 当你为自己的应用选择协议时,可以想到实现可靠性最基本的思路

好的,这一讲就到这里。发现求知的乐趣,我是林䭽,感谢你学习本次课程。 接下来我们将进入“模块二”开始学习网络层协议,下一讲介绍“06 | IPv4 协议:路由和寻址的区别是什么?”再见!