0
点赞
收藏
分享

微信扫一扫

当初我要是这么学习计算机网络就好了「附图文解析」

大漠雪关山月 2022-04-24 阅读 81
网络

文章目录

1.概念篇

交换机: 一些学校,公司搭建的一个网络的时候就会用到交换机。交换机就是把多个主机构成一个网络。

集线器: 上古时期老设备,网线分叉,同一时刻只能有一根网线工作

路由器: 解决集线器同一时刻只能有一根网线工作的弊端,可以使得所有设备都有网络「路由器不仅能组成一个局域网,同时也链接了两个局域网的功能,让局域网之间由交换数据的功能」

局域网: 局部组建的一种私有网络。

广域网: 通过路由器将多个局域网连接起来,在物理范围上组建成很大范围的网络。广域网内部的局域网都属于其子网

局域网和广域网: 没有特定的概念,都是相对而言。「公司的局域网一定比家庭的广域网范围更大」

网络通信: 进行网络数据传输。更详细点是:网络主机中不同进程间机遇网络传输数据

路由器和交换机有什么区别:

IP地址

MAC地址

端口号

有了IP地址,端口,就可以定位到网络中唯一的一个进程。但存在一个问题:网络通信是基于光电信号,高低电平转换为二进制数据01传输的,我们如何知道对方送的什么数据呢?「图片,视屏,文本,音频对应的数据格式,编码方式也都不同」此时就需要有一个 协议 来规定双方发送接收数据饿。

认识协议

  • 时许:时间实现顺序的详细说明「打电话的时候,男生发起,聊天…,然后由女生挂断」

  • 知名协议的默认端口

    服务器也可以使用「1024,65535」范围内的端口来定义知名端口

    五元组

    协议分层

    OSI七层模型

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cLSeC3Hy-1650795615953)(/Users/cxf/Desktop/MarkDown/images/计算机网络体系结构.png)]

    每一层都呼叫它的下一层来完成需求

    功能
    应用层应用程序间沟通,如简单的电子邮件传输SMTP,文件传输FTP,网络远程访问Telnet「网络编程主要在应用层,拿到数据之后你要干啥…」
    传输层两台主机之间的数据传输。如TCP,UDP「端到端:消费者和商家只关注某个快递是不是收到了」
    网络层管理和路由选择。在IP中识别主机,并通过路由表的方式规划处两台主机之间数据传输路线「点到点:快递公司,怎样运输才高效」
    数据链路层设备之间数据帧的传送和识别「帧同步,冲突检测,差错校验」
    物理层光电信号传输方式

    网络设备所在分层

    封装和分用

    • 不同的协议层对数据包有不同的称谓。传输层:段「segment」;网络层:数据报「datagram」;链路层:帧「frame」
    • 应用层数据通过协议栈发送出去的时候,每层协议都要加一个首部「header」,称为封装「Encapsulation」
    • 首部:包含首部有多长,载荷多大,上层协议…
    • 数据封装成帧后发到传输介质上,到达目的主机后每层都会剥掉首部,根据首部中上层协议字段,将数据交给对应的上层处理

    数据封装图

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x3Xrg8MM-1650795615954)(/Users/cxf/Desktop/MarkDown/images/数据封装.png)]

    数据分用图

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oDoUNxIy-1650795615955)(/Users/cxf/Desktop/MarkDown/images/数据分用.png)]

    封装和分用4不仅仅存在于发送方和接收方,中间设备「路由器/交换机」也会针对数据进行封装和分用

    通常情况下:

    路由器:只是封装分用到网络层就结束

    2. 网络编程套接字

    TCPUDP
    有链接「打电话」无连接「发微信」
    可靠传输「叮叮已读」不可靠传输「叮叮未读」
    面向字节流面向数据报
    全双工全双工

    全双工: 一个socket既可以用来发送也可以用来接收

    半双工: 只能用来发送或者只能用来接收

    2.1 UDP

    DatagramSocket() 构造方法

    方法含义
    DatagramSocket()创建一个套接字对象,绑定一个随机端口
    DatagramSocket(int port)创建一个套接字对象,绑定一个指定端口

    DatagramSocket() 方法

    方法含义
    void receive(DatagramPacket p)从此套接字p只接收数据,如果没有收到数据就阻塞等待
    void send(DatagramPacket p)从此套接字p只接发数据,如果没有发送数据就阻塞等待
    void close()关闭此数据报套接字

    DatagramPacket() 构造方法

    方法含义
    DatagramPacket(byte[] buf, int length)DatagramPacket把接收指定长度length的数据保存在字节数组buf中
    DatagramPacket(byte[] buf, int length, SocketAddress address)DatagramPacket把长度length的字节数组buf数据发送到address

    DatagramPacket() 方法

    方法含义
    InetAddress getAddress()从接受的数据报中获取发送端 IP地址;从发送的数据报中获取接收端 IP地址
    int getPort()从接受的数据报中获取发送端 端口;从发送的数据报中获取接收端 端口
    byte[] getData()获取数据报中的数据

    构造UDP发送数据报的时候,需要传入SocketAddress,该对象可以使用 InetSocketAddress 来创建

    InetSocketAddress

    方法含义
    InetSocketAddress(InetAddress, int port)创建一个 Socket 对象,包含 IP地址端口

    服务端

    package net;
    
    import java.io.IOException;
    import java.net.DatagramPacket;
    import java.net.DatagramSocket;
    import java.net.SocketException;
    
    public class UdpEchoServer {
        private DatagramSocket socket = null;
    
        // 此处指定的端口就是服务器自己的端口,ip 并没有指定,相当于市 0.0.0.0(绑定到当前主机的所有网卡上)
        public UdpEchoServer(int port) throws SocketException {
            this.socket = new DatagramSocket(port);
        }
    
        public void start() throws IOException {
            while (true) {
                // 1.读取客户端发来的请求,客户端发来请求之前这里的receive是阻塞的
                DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
                socket.receive(requestPacket);
                // 把收到的数据进行提取
                String request = new String(requestPacket.getData(), 0, requestPacket.getLength());
                String response = process(request);
                // 2.处理请求
                DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length, requestPacket.getSocketAddress());
                // 3.出列结果返回给客户端
                socket.send(responsePacket);
                System.out.printf("[%s, %d]req:%s; resp:%s\n", requestPacket.getAddress(), requestPacket.getPort(), request, response);
            }
        }
    
        public String process(String request) {
            return request;
        }
    
        public static void main(String[] args) throws IOException {
            UdpEchoServer server = new UdpEchoServer(9090);
            server.start();
        }
    }
    

    客户端

    package net;
    
    import java.io.IOException;
    import java.net.*;
    import java.util.Scanner;
    
    public class UdpEchoClient {
        private DatagramSocket socket = null;
        private String serverIP;
        private int serverPort;
    
        /*
        此处指定的 ip 和 port 是服务器的 ip 和 port
        客户端是不需要指定自己的 ip 和 端口
        客户端 ip 就是本机 ip,客户端的端口就是操作系统自动分配
         */
        public UdpEchoClient(String ip, int port) throws SocketException {
            this.serverIP = ip;
            this.serverPort = port;
            /*
            此处构造这个对象的时候不需要填参数了:绑定这个指定的端口(客户端是无需绑定端口的,端口系统给的)
            前面记录的服务器 ip 和 port 是为了后面发送数据给服务器的准备工作
             */
            socket = new DatagramSocket();
        }
    
        public void start() throws IOException {
            Scanner scanner = new Scanner(System.in);
            while (true) {
                // 1. 从控制台读取用户输入
                System.out.print("-> ");
                String request = scanner.nextLine();
                // 2. 把数据构成 UDP 数据报,发送给服务器
                if (request.equals("exit")) {
                    break;
                }
                DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length, InetAddress.getByName(serverIP), serverPort);
                socket.send(requestPacket);
                // 3. 从服务器读取响应数据
                DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);
                socket.receive(responsePacket);
                // 4. 把响应数据进行解析并显示
                String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
                System.out.printf("req:%s; resp:%s\n", request, response);
            }
        }
    
        public static void main(String[] args) throws IOException {
            UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090);
            client.start();
        }
    }
    
    

    带有 “翻译功能” 的服务端

    package net;
    
    import java.io.IOException;
    import java.net.SocketException;
    import java.util.HashMap;
    
    public class UdpDictServer extends UdpEchoServer {
        private HashMap<String, String> dict = new HashMap<>();
    
        public UdpDictServer(int port) throws SocketException {
            super(port);
            dict.put("cat", "小猫");
            dict.put("dog", "小狗");
            dict.put("pig", "小猪");
            dict.put("fuck", "卧槽");
        }
    
        @Override
        public String process(String req) {
            return dict.getOrDefault(req, "没有找到翻译");
        }
    
        public static void main(String[] args) throws IOException {
            UdpDictServer server = new UdpDictServer(9090);
            server.start();
        }
    }
    
    

    2.2 TCP

    客户端

    package net;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.io.PrintWriter;
    import java.net.Socket;
    import java.util.Scanner;
    
    // 和UDP类似,只是多了个连接过程
    public class TcpEchoClient {
        private Socket socket = null;
    
        public TcpEchoClient(String serverIP, int serverPort) throws IOException {
            // 客户端何时和服务器建立连接:在实例化 Socket 的时候
            this.socket = new Socket(serverIP, serverPort);
        }
    
        public void start() {
            System.out.println("启动客户端");
            try (InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream()) {
                Scanner scanner = new Scanner(System.in);
                Scanner respScanner = new Scanner(inputStream);
                while (true) {
                    // 1. 从控制台读取用户输入
                    System.out.print("-> ");
                    String request = scanner.nextLine();
                    // 2. 把用户输入的数据,构造请求,发送给服务器
                    PrintWriter writer = new PrintWriter(outputStream);
                    writer.println(request);
                    writer.flush();
                    // 3. 从服务器读取响应
                    String response = respScanner.nextLine();
                    // 4. 把响应习显示出来
                    System.out.printf("req:%s, resp:%s\n", request, response);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        public static void main(String[] args) throws IOException {
            TcpEchoClient client = new TcpEchoClient("127.0.0.1", 9090);
            client.start();
        }
    }
    
    

    服务端

    package net;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.io.PrintWriter;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.Scanner;
    
    public class TcpEchoServer {
        private ServerSocket serverSocket = null;
    
        public TcpEchoServer(int port) throws IOException {
            this.serverSocket = new ServerSocket(port);
        }
    
        public void start() throws IOException {
            System.out.println("服务器启动!");
            while (true) {
                // 需要建立好连接,再进行数据通信
                Socket clientSocket = serverSocket.accept();
                // 和客户端进行通信了,通过这个方法来处理整个的连接过程
                processConnection(clientSocket);
            }
        }
    
        private void processConnection(Socket clientSocket){
            System.out.printf("[%s:%d] 客户端建立连接\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
            /*
            需要和客户端进行通信,和文件操作的字节流一模一样
            通过 socket 对象拿到 输入流 对象,对这个 输入流 就相当于从网课读数据
            通过 socket 对象拿到 输出流 对象,对这个 输出流 就相当于往网卡写数据
             */
            try (InputStream inputStream = clientSocket.getInputStream(); OutputStream outputStream = clientSocket.getOutputStream()) {
                Scanner scanner = new Scanner(inputStream);
                while (true) {
                    // 1.根据请求并解析
                    if (!scanner.hasNext()) {
                        System.out.printf("[%s:%d] 客户端退出链接\n", clientSocket.getInetAddress(), clientSocket.getPort());
                        break;
                    }
                    String request = scanner.nextLine();
                    // 2.根据请求计算响应
                    String response = process(request);
                    // 3.把响应写入到客户端
                    PrintWriter writer = new PrintWriter(outputStream);
                    writer.println(response);
                    // 为了保证写入的数据能够及时返回给客户端,手动加上一个刷新缓冲区的操作
                    writer.flush();
                    System.out.printf("[%s:%d]req:%s, resp:%s\n", clientSocket.getInetAddress(), clientSocket.getPort(), request, response);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 此处的 clientSocket 的关闭是非常有必要的
                try {
                    clientSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public String process(String request) {
            return request;
        }
    
        public static void main(String[] args) throws IOException {
            TcpEchoServer server = new TcpEchoServer(9090);
            server.start();
        }
    }
    
    

    翻译功能的客户端

    package net;
    
    import java.io.IOException;
    import java.util.HashMap;
    
    public class TcpDictEchoServer extends TcpEchoServer {
        private HashMap<String, String> dict = new HashMap<>();
    
        public TcpDictEchoServer(int port) throws IOException {
            super(port);
            dict.put("cat", "小猫");
            dict.put("dog", "小狗");
            dict.put("pig", "小猪");
            dict.put("fuck", "卧槽");
        }
    
        @Override
        public String process(String request) {
            return dict.getOrDefault(request, "翻译失败");
        }
    
        public static void main(String[] args) throws IOException {
            TcpDictEchoServer server = new TcpDictEchoServer(9090);
            server.start();
        }
    }
    
    

    利用多线程解决普通客户端一次只能链接一个用户的BUG

    package net;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.io.PrintWriter;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.Scanner;
    
    public class TcpThreadEchoServer {
        private ServerSocket serverSocket = null;
    
        public TcpThreadEchoServer(int port) throws IOException {
            this.serverSocket = new ServerSocket(port);
        }
    
        public void start() throws IOException {
            System.out.println("服务器启动!");
            while (true) {
                // 需要建立好连接,再进行数据通信
                Socket clientSocket = serverSocket.accept();
                // 和客户端进行通信了,通过这个方法来处理整个的连接过程
                //「改动这里,把每次建立好的链接创建一个新的线程来处理」
                Thread t = new Thread(() -> {
                    processConnection(clientSocket);
                });
                t.start();
            }
        }
    
        private void processConnection(Socket clientSocket){
            System.out.printf("[%s:%d] 客户端建立连接\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
            /*
            需要和客户端进行通信,和文件操作的字节流一模一样
            通过 socket 对象拿到 输入流 对象,对这个 输入流 就相当于从网课读数据
            通过 socket 对象拿到 输出流 对象,对这个 输出流 就相当于往网卡写数据
             */
            try (InputStream inputStream = clientSocket.getInputStream(); OutputStream outputStream = clientSocket.getOutputStream()) {
                Scanner scanner = new Scanner(inputStream);
                while (true) {
                    // 1.根据请求并解析
                    if (!scanner.hasNext()) {
                        System.out.printf("[%s:%d] 客户端退出链接\n", clientSocket.getInetAddress(), clientSocket.getPort());
                        break;
                    }
                    String request = scanner.nextLine();
                    // 2.根据请求计算响应
                    String response = process(request);
                    // 3.把响应写入到客户端
                    PrintWriter writer = new PrintWriter(outputStream);
                    writer.println(response);
                    // 为了保证写入的数据能够及时返回给客户端,手动加上一个刷新缓冲区的操作
                    writer.flush();
                    System.out.printf("[%s:%d]req:%s, resp:%s\n", clientSocket.getInetAddress(), clientSocket.getPort(), request, response);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 此处的 clientSocket 的关闭是非常有必要的
                try {
                    clientSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        private String process(String request) {
            return request;
        }
    
        public static void main(String[] args) throws IOException {
            TcpThreadEchoServer server = new TcpThreadEchoServer(9090);
            server.start();
        }
    }
    
    

    利用线程池进行优化

    package net;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.io.PrintWriter;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.Scanner;
    import java.util.concurrent.Executor;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class TcpThreadPoolEchoServer {
        private ServerSocket serverSocket = null;
    
        public TcpThreadPoolEchoServer(int port) throws IOException {
            this.serverSocket = new ServerSocket(port);
        }
    
        public void start() throws IOException {
            System.out.println("服务器启动!");
            ExecutorService pool = Executors.newCachedThreadPool();
            while (true) {
                // 需要建立好连接,再进行数据通信
                Socket clientSocket = serverSocket.accept();
                // 和客户端进行通信了,通过这个方法来处理整个的连接过程
                //「把 processConnection 作为一个任务,交给线程池处理」
                pool.submit(() -> {
                    processConnection(clientSocket);
                });
            }
        }
    
        private void processConnection(Socket clientSocket) {
            System.out.printf("[%s:%d] 客户端建立连接\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
            /*
            需要和客户端进行通信,和文件操作的字节流一模一样
            通过 socket 对象拿到 输入流 对象,对这个 输入流 就相当于从网课读数据
            通过 socket 对象拿到 输出流 对象,对这个 输出流 就相当于往网卡写数据
             */
            try (InputStream inputStream = clientSocket.getInputStream(); OutputStream outputStream = clientSocket.getOutputStream()) {
                Scanner scanner = new Scanner(inputStream);
                while (true) {
                    // 1.根据请求并解析
                    if (!scanner.hasNext()) {
                        System.out.printf("[%s:%d] 客户端退出链接\n", clientSocket.getInetAddress(), clientSocket.getPort());
                        break;
                    }
                    String request = scanner.nextLine();
                    // 2.根据请求计算响应
                    String response = process(request);
                    // 3.把响应写入到客户端
                    PrintWriter writer = new PrintWriter(outputStream);
                    writer.println(response);
                    // 为了保证写入的数据能够及时返回给客户端,手动加上一个刷新缓冲区的操作
                    writer.flush();
                    System.out.printf("[%s:%d]req:%s, resp:%s\n", clientSocket.getInetAddress(), clientSocket.getPort(), request, response);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 此处的 clientSocket 的关闭是非常有必要的
                try {
                    clientSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        private String process(String request) {
            return request;
        }
    
        public static void main(String[] args) throws IOException {
            TcpThreadPoolEchoServer server = new TcpThreadPoolEchoServer(9090);
            server.start();
        }
    }
    
    

    3. 理论知识「八股文」

    3.1 应用层

    3.1.2 DNS

    DNS其实就是一个域名解析作用「比如https:www.baidu.com对应的IP地址是180.101.49.41」

    这样的点分十进制IP大家很难记住于是就起了个朗朗上口的 www.baidu.com 方便人们记忆,www.baidu.com就是一个域名对应的IP地址是180.101.49.41。把 IP 解析成对应域名 www.baidu.com 就是域名解析

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qtv8U99q-1650795615955)(/Users/cxf/Desktop/MarkDown/images/hosts.png)]

    3.1.3 浏览器中输入url后按下回车发生了什么

    3.1.4 NAT 技术

    我们也知道目前IPV4数量已经不够用,虽然IPV6在我国正在大力推崇中,但是目前的主力军依旧是IPV4+NAT

    3.1.4.2 NAT IP转换过程

    NAT IP转换过程

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xLmdVoWH-1650795615956)(/Users/cxf/Desktop/MarkDown/images/NAT技术.jpeg)]

    3.1.4.3 NAPT

    问题来了:子网中多个主机「PCA/B/C」访问同一个外网IP「163.22.120.9」,服务器响应数据却发现目的IP都是相同的「202.244.173.37」,该如何区分子网内部的设备呢?

    IP+Port

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tiyH2TEN-1650795615956)(/Users/cxf/Desktop/MarkDown/images/NAPT技术.jpeg)]

    3.1.4.4 NAT 技术缺陷

    • 外界无法访问内部「162.221.120.9无法访问子网内的10.0.0.10/11/12这些设备,也就是为何你的电脑无法访问我电脑上的127.0.0.1的原因」
    • 路由表的维护,创建,销毁也是有一定的开销
    • 通信过程中一旦有NAT设备异常,内网的设备连接都会断开

    3.1.4.4 NAT 和 代理服务器

    **NAT:**路由器大多都具备NAT功能,完成子网内部设备和外界的沟通

    **代理服务器:**看起来和NAT挺像,客户端像代理服务器发送一个请求,代理服务器把请求发送给真正要访问的服务器;服务器返回结果给代理服务器,代理服务器在返回给客户端

    NAT和代理服务器区别

    NAT代理服务器
    应用解决的IP不足翻墙: 广域网中的代理;负载均衡: 局域网中的代理
    底层实现工作在网络层,实现IP地址的更换工作在应用层
    适用范围局域网的出口部署局域网,广域网都可以使用甚至跨网
    部署防火墙,路由器等硬件设备上部署在服务器上的一个软件程序

    代理服务器又分为正向代理和反向代理

    3.2 传输层

    3.2.1 UDP

    3.2.1.1 UDP首部格式「使用注意」

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fkxL9Dcd-1650795615956)(/Users/cxf/Desktop/MarkDown/images/UDP报头.png)]

    64k对于当下是否满足呢?

    2字节=16位=216=65535byte=65535/1024=64k

    UDP首部有一个16位的最大数据长度,也就是说一次UDP传输最多有64K「包含首部」,传输数据超过64K,则我们需要在应用层进行手动分包,多次发送并在接收端手动拼装。

    五层模型中:程序员最关注的是应用层

    3.2.1.2 UDP 校验和

    用来验证数据是否正确的一种手段「不能保证数据100%正确,但是校验和如果不正确则数据100%不正确」

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2tkd2Tg6-1650795615957)(/Users/cxf/Desktop/MarkDown/images/校验和.png)]

    发送方和接收方利用同样的算法计算校验和

    发送方式sum1,接受方是sum2.如果中途出现数据变动,则校验和大概率不同

    3.2.1.3 UDP特点

    • 无链接:知道对方的 IP,port,但不需要建立连接就可以实现传输数据
    • 不可靠:没有确认应答机制、重传机制,如果出现网络状况,UDP的传输层也不会给应用层任何错误信息
    • 面向数据报:不能灵活控制数据报读写数据的大小和次数

    3.2.1.4 面向数据报

    应用层发送给UDP多长的数据,UDP原样不变、发送给网络层多长数据。既不拆分也不合并。

    用UDP发送 1000 字节数据

    3.2.1.5 UDP缓冲区

    • UDP发送方没有真正的缓冲区:调用 send,内核会把数据交给 网络层协议,进行后续传输。
    • UDP接收方有真正的缓冲区:但是这个缓冲区不能保证收到的数据报的发送和接收的顺序一致,可能会出现错乱。如果缓冲区满了,则会丢掉后续的UDP数据报。

    3.2.1.6 基于UDP层的协议

    • NFS:网络文件系统
    • TFTP:简单文件传输协议
    • BOOTP:启动协议「无盘启动」
    • DHCP:动态网络IP分配协议
    • DNS:域名解析协议

    3.2.2 TCP

    3.2.2.1 TCP首部格式

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zeiarjmC-1650795615957)(/Users/cxf/Desktop/MarkDown/images/TCP首部.png)]

    3.2.2.2 确认应答

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B53CdzPi-1650795615958)(/Users/cxf/Desktop/MarkDown/images/确认应答.png)]

    每次客户端发送数据给服务器都会SYN请求服务器建立连接,服务器收到响应都会ACK给客户端确认应答

    3.2.2.3 超时重传

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xw37fkLU-1650795615958)(/Users/cxf/Desktop/MarkDown/images/SYN丢失.png)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1MCrT1ql-1650795615959)(/Users/cxf/Desktop/MarkDown/images/ACK丢失.png)]

    发送数据丢失的两种情况:

    1. 客户端SYN丢失
    2. 服务端ACK丢失

    处理丢包问题:

    对于服务端发送给客户端的数据丢了,服务端可以重发一次而不会对客户端有影响;但是客户端发送给服务端的数据丢失了,会进行大量的重发,服务端如何处理重复消息呢?

    处理服务端数据重复

    有了以上的 确认应答,超时重传应该可以保证TCP的数据万无一失了吧?但最糟糕的问题来了:如果对于客户端和服务端任何一方而言,重传数据也丢失了该怎么办呢?

    处理重传数据丢失问题

    我们有了尝试一定次数和时间间隔来解决丢包难题,次数我们很容易规定,可以假设超过16次就认为传输失败,可以关闭连接。但是这个 重传时间间隔 该如何确定呢?

    确定重传时间间隔

    因此,TCP为了保证在任何环境下都能保持较高性能的通信效率,因此会动态计算这个 超市时间

    如果累积到一定次数之后就会认为当前网络环境已经无法恢复,就会强制关闭连接

    3.2.2.4 连接管理「面试问的最多」

    三次握手

    1. 三次握手的原始连接

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2V3dLAEN-1650795615959)(/Users/cxf/Desktop/MarkDown/images/三次握手的原始连接.png)]

    2. 三次握手后的连接优化

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Iw4R7IWe-1650795615960)(/Users/cxf/Desktop/MarkDown/images/三次握手后的连接优化.png)]

    四次挥手

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X0QaPjiM-1650795615961)(/Users/cxf/Desktop/MarkDown/images/四次挥手.png)]

    对于建立连接来说,中间的两次ACK+SYN可以合二为一,断开连接也可以合二为一吗?

    当客户端触发 FIN 之后,服务器只要收到 FIN 就会立马返回 ACK「内核完成的」

    当服务器的代码中运行到 socket.close() 操作的时候,就会触发 FIN

    这两个操作在不同的时机,中间有一定的间隔。

    两个重要的状态

    CLOSE_WAIT

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4zvbW5yV-1650795615962)(/Users/cxf/Desktop/MarkDown/images/CLOSE_WAIT.png)]

    TIME_WAIT

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T2UjyyTX-1650795615963)(/Users/cxf/Desktop/MarkDown/images/TIME_WAIT.jpeg)]

    如果 A 收到了 FIN 之后,立即发送 ACK,并且释放链接「变成CLOSE状态」,此时就会出现无法处理重传 FIN 的 ACK 情况,此时就僵硬了。

    等一段时间之后,确保当前 FIN 不被重传了,然后才真的释放链接。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IPcpXSgR-1650795615963)(/Users/cxf/Desktop/MarkDown/images/无法重传.png)]

    TIME_WAIT等待的时间叫做 2MSL「MSL就是网络上两点之间传输消耗的最大时间」

    3.2.2.5 滑动窗口

    TCP最原始的发送数据:发一个,确认一个这样的机制。等到ACK之后才能发送下一个数据,这样的话后续的数据大量会阻塞等待。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ywEBsGia-1650795615965)(/Users/cxf/Desktop/MarkDown/images/确认应答.png)]

    滑动窗口就是为了解决这个问题,减少等待ACK时间「其实就是将多段等待时间重叠在一起了」

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ssE24Jzl-1650795615966)(/Users/cxf/Desktop/MarkDown/images/滑动窗口.png)]

    假设一次发送长度为N的数据,然后等待一波ACK,此时这里的N就称为“窗口大小”

    N越大,传输的速度越高,但是N也不能无限大,如果N无限大,此时确认应答就没有意义了,可靠性就形同虚设了

    • 上图的窗口大小就是 3000字节「3个字段」
    • 发送前 3 个字段的时候无需等待,直接发送
    • 发送第四个字段的时候等待需要阻塞等待第一个ACK「下一个是1001」才能继续发送,依此类推
    • 操作系统内核为了维护这个滑动窗口,需要开辟 发送缓冲区 来记录当前数据还有哪些没有应答;只有确认应答过的数据才能从缓冲区删除掉

    遇到丢包问题怎么办?

    丢包问题分为两种,一种是确认应答ACk丢了,一种是数据丢了。我们需要分开讨论分析。

    ACK丢了

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jrnH4WYq-1650795615966)(/Users/cxf/Desktop/MarkDown/images/丢ACK.png)]

    数据丢了

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ixxXi3Mk-1650795615967)(/Users/cxf/Desktop/MarkDown/images/丢数据.png)]

    这样就构成了 滑动窗口下的快重传

    3.2.2.6 流量控制

    也是在保证可靠性,对滑动窗口进行了制约。滑动窗口越大,就认为传输速率越高。但也并不是越大越好,接收方顶不住消息之后,额外发出的数据大概率是要丢包的,就会触发超时重传机制。所以一定是最合适的才是最好的。发送方和接收方速率理论上匹配最好。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LVbqMskF-1650795615967)(/Users/cxf/Desktop/MarkDown/images/流量控制.png)]

    如何衡量接收方的处理数据的速度

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xwvgMrcV-1650795615968)(/Users/cxf/Desktop/MarkDown/images/窗口大小.png)]

    在考虑一个极端情况:如果发送接收方缓冲区满了,发送方就不再发送数据

    这个窗口只能存放65535个字节吗?

    3.2.2.7 拥塞控制

    和流量控制差不多,都是用来限制发送方传输速率的机制。防止发的太快处理不了。

    • 流量控制是根据接收方的处理速率来进行衡量的
    • 拥塞控制是根据发送方到接收方这一些列通信链路的处理速率来衡量的。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6pDdR6O3-1650795615968)(/Users/cxf/Desktop/MarkDown/images/拥塞控制.png)]

    拥塞控制如何控制拥塞窗口的?

    拥塞控制和流量控制都能影响滑动窗口,到底谁起决定作用

    拥塞窗口的变化规律

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4FpVmrxn-1650795615969)(/Users/cxf/Desktop/MarkDown/images/拥塞窗口变化.png)]

    1. 慢启动「慢开始」:刚开始的时候先慢点传输
    2. 指数增长:指数函数又称为爆炸函数,可以短时间内把窗口大小给加到最大值
    3. 线性规律增长:当指数增长到一定程度「超过设定的阈值24」,就会变为线性增长以此进行拥塞避免
    4. 当遇到了网络阻塞,导致丢包。立即就让窗口大小回到一个最初的很小值,同时修改下一次的阈值为刚才的阈值一半「24/2=12」
    5. 然后再重复刚才的步骤…

    3.2.2.8 延迟应答

    提升传输效率,考虑是否能子啊保证可靠性的前提下继续把滑动窗口调大一点「流量控制的延伸」

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F4IKkiAx-1650795615969)(/Users/cxf/Desktop/MarkDown/images/延迟应答.png)]

    流程简述

    • 假设接收方窗口大小为3M「3000字节」,如果接受了某次收到了2.5M数据就,如果立即返回,发送给发送方的窗口的大小为0.5M
    • 但实际情况是可能处理数据速度很快,不到50ms就把2.5M数据处理掉了,这种情况下接收端还远远没有达到自己的极限,因此可以把窗口调大一些
    • 接收端等待一会儿再应答,比如过200ms再返回。就会返回一个3M的窗口大小。

    记住:窗口越大,网络传输速率就越大,但是一定要在保证可靠性的前提下才可以调大窗口

    那么所有的包都可以延迟应答吗?

    3.2.2.9 捎带应答

    在延迟应答的基础之上做了延伸

    最典型的就是:一问一答

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VTaWrojp-1650795615970)(/Users/cxf/Desktop/MarkDown/images/捎带应答_一问一答.png)]

    1. B收到一个TCP请求,就会立即返回一个ACK「内核」
    2. 应用程序根据请求,计算响应,把响应构造好之后,返回给浏览器

    TCP的四次挥手有没有可能变为三次挥手?

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LluVPP5h-1650795615971)(/Users/cxf/Desktop/MarkDown/images/TCP的四次挥手有没有可能变为三次挥手.png)]

    B发给A的ACK是在内核中完成的、FIN是应用程序代码调用 close() 完成的,这俩操作看似是不同时机,但是如果有了捎带应答机制结果就不一样了。如果恰好触发了捎带应答,则会是 ACK+FIN 合二为一发送过去,此时的话就会是三次挥手

    3.2.2.10 面向字节流

    创建一个 socket 的同时内核就会创建一个 接收/发送 缓冲区

    发送数据

    • 调用 write 写的时候,数据先会被发送到 发送缓冲区
    • 如果发送的数据过长,就被拆分成很多段小的数据包;如果发送的数据过短,就会等待发送缓冲区数据长度差不多了一并发送

    接收数据

    • 接收数据的时候,网卡驱动程序先从内核中接受缓冲区读取数据
    • 然后调用 read 拿 接收缓冲区 的数据

    由于有缓冲区的存在,TCP程序的读和写不是一一对应

    • 写100字节:可以一次 write(new byte[1000]). 也可以循环 1000次 write(new byte[1])
    • 读100字节:可以一次 read(new byte[1000]). 也可以循环 1000次 read(new byte[1])

    3.2.2.11 沾包问题

    多个TCP数据报到达的时候,如果不显示的约定应用层数据的包和包之间边界,就很容易对数据产生混淆

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V5PLiC4J-1650795615971)(/Users/cxf/Desktop/MarkDown/images/沾包问题.png)]

    解决方案:给每个数据包结尾片接一个特殊符号表示结束

    一个简单协议就是用 ; 来分隔

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MFmunJyj-1650795615972)(/Users/cxf/Desktop/MarkDown/images/沾包问题解决.png)]

    在HTTP「应用层协议」中如何解决沾包问题呢?

    不带body带head的GET请求

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uyTCsZrY-1650795615973)(/Users/cxf/Desktop/MarkDown/images/HTTP_GET请求.png)]

    不带head带body的POST请求

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ISlcdx2Z-1650795615973)(/Users/cxf/Desktop/MarkDown/images/HTTP_POST请求.png)]

    POST /index.html/HTTP/1.1\nHOST127.0.0.1:8080\nContent-Type:text/html\nContent-Length:3277\n\…

    3.2.2.12 TCP异常情况

    建立好通信的双方,在通信过程中突然有一方遇到了突发状况。

    1.进程终止

    A,B其中某个进程突然终止「崩溃或者被强制关闭」

    2.机器重启

    按照操作系统既定的流程重启

    3.断电/断网

    这个情况才算 偷袭成功

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D6GXY5BY-1650795615973)(/Users/cxf/Desktop/MarkDown/images/TCP异常偷袭成功.png)]

    接收方掉电

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wkJ2uaD0-1650795615974)(/Users/cxf/Desktop/MarkDown/images/接收方掉电.png)]

    发送方掉电

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XS6nTigw-1650795615975)(/Users/cxf/Desktop/MarkDown/images/发送方掉电.png)]

    3.2.2.13 TCP小结

    优先保证可靠性,再进一步提高效率

    **可靠性:**确认应答,超时重传,连接管理,流量控制,拥塞控制,TCP异常情况

    **效率:**滑动窗口,延迟应答,捎带应答

    **编码注意事项:**沾包问题

    3.2.2.14 基于TCP层的协议

    HTTP,HTTPS,SSH,FTP,SMTP,Telnet

    3.2.3 TCP与UDP对比

    TCP优势:可靠性

    **UDP优势:**效率更高

    经典面试题:如何用UDP保证可靠传输

    「抄TCP的作业」

    • 引入序列号,保证数据的顺序
    • 引入确认应答,保证收到数据
    • 引入超时重传,保证收到数据

    3.3 网络层

    3.3.1 IP协议

    网络层里面最核心的协议叫做 IP协议,分为两个版本:IPV4 和 IPV6

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MQHSUM3L-1650795615976)(/Users/cxf/Desktop/MarkDown/images/IPV4.jpeg)]

    • 4位版本号:对于IPV4来说就是4

    • 4位首部长度:类似于TCP。IP协议包头也是变长的,单位是4字节。4bit最大是15,所以IP头部最大长度就是4*15=60字节。

    • 服务类型:3位优先权已被弃用,1位保留字必须为0,4位TOS字段分别代表:最小延时,最大吞吐量,最高可靠性,最小成本「对于SSH/Telnet这样的程序最小延迟比较用重要;对于FTP,最大吞吐量比较重要」。这4个特性互斥的,用的时候对应的比特位设置为1,其余必须为0

    • 16位总长度:IP数据报整体占多少个字节

      • 16位–>64K,难道说一个 IP 数据包最大只能表示 64K 吗?「是,又不完全是」
      • 因为在IP里,协议内部实现了数据报的拆分「当超过64K,IP协议就会自动的对这个大的包进行拆分,拆成多个小的包,保证每个小的包不会超过64K」
    • 16位标识,3位标志,13位片偏移都是为了拆分

      • 16位唯一的标识主机发送的报文。如果IP报文在数据链路层被分片了,那么每一个片里面的这个id都都是相同的
      • 3位标志:第1位保留;第二位:值为1则是禁止分片;值为0则是允许分片;第3位:只有最后一个分片值为1、其余均为0、用来设置结束标志
      • 13位片偏移:是分片相对于原始IP报文开始处的偏移. 其实就是在表 示当前分片在原报文中处在哪个位置。实际偏移的字节数是这个值 * 8 得到的。因此,除了最后一个报文之外, 其他报文的长度必须是8的整数倍(否则报文就不连续)
    • 8位生存时间:数据报到达目的地的最大跳数。一般是64,每经历一次转发TTL就会-1,直到0了还没有收到,那么就丢弃。主要为了防止循环路由出现

    • 8位协议:表示传输层使用的哪个协议,TCP/UDP 会有不同的值,为了在分用的时候能够让网络层把数据提交给正确的传输层协议来处理

    • 16位首部校验和:使用CRC来校验头部是否损坏

    • 32位源地址:发送端IP地址

    • 32位目的地址:接收段IP地址

    16位表示,3位标志,13位片偏移如何拆分64K的

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ITW1nqKy-1650795615978)(/Users/cxf/Desktop/MarkDown/images/拆分64K.png)]

    生存时间

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3e4Dv0fv-1650795615979)(/Users/cxf/Desktop/MarkDown/images/生存时间.png)]

    3.3.4 网段划分

    IP地址分为两部分,网络号和主机号

    • 网络号:保证两个相连接的网段具有不同的身份标识
    • 主机号:同一网段内,主机具有相同的网络号,但是必须有不同的主机号
    • 不同的子网其实就是把网络号相同的主机连接在一起
    • 如果在子网中新增一台主机,则这台主机的网络号和子网中网络号相同,但是主机号不能和子网中其它主机号相同

    通过合理的设置网络号和主机号,就可以保证网络中的主机IP地址不会重复

    本机网络详情

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1g0NBkez-1650795615979)(/Users/cxf/Desktop/MarkDown/images/本机网络详情.png)]

    假设有一个IP地址:191.100.0.0,子网掩码为:255.255.128.0来划分子网

    • 191.100.0.0
    • 255.255.128.0

    B类子网掩码本来是255.255.0.0,所以此子网掩码网络号向主机号借了一位即17位,因此可以划分21个子网,但实际使用0个「去掉全0全1」,这个网段可以容纳215个主机

    • 网络号为:16位网络号+16位主机号

    计算方式

    十进制二进制
    IP地址180.210.242.13110110100.11010010.11110010.10000011
    子网掩码255.255.248.011111111.11111111.11111000.00000000
    网络号180.210.240.010110100.11010010.11110000.00000000
    主机号0.0.2.13100000000.00000000.00000010.10000011

    几个特殊的IP地址

    • 如果主机号为0:网络号
    • 如果主机号为1:通常表示的是这个局域网的 网关「局域网的出入口,通常也就是路由器的LAN口IP」
    • 如果主机号全1:广播这个IP,往这个IP上发送数据,局域网中的所有设备都能收到

    子网内部的一些设备

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ShugZMDF-1650795615980)(/Users/cxf/Desktop/MarkDown/images/网段划分.jpeg)]

    那么问题来了,手动管理子网内的IP地址是非常麻烦的

    3.3.5 IP地址的数量限制

    IPV4协议,是使用4个字节来表示IP地址,表示的地址个数只能是42亿9千万

    如何应对IP不够用

    1. 动态分配IP地址,一个设备连了网就分配IP;不联网就不分配IP
    2. NAT机制:把IP分成内网和外网IP两种。要求外网IP必须是唯一的,内网IP在不同局域网中可以重复。如果局域网内部的设备想上网,在数据报经过带有外网IP的路由器的时候就会自动的使用这个路由器的外网IP来代表这个局域网的设备「本质上是一个一大堆局域网里的设备,共同使用一个外网IP」

    NAT机制图

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HeZDTAQI-1650795615981)(/Users/cxf/Desktop/MarkDown/images/NAT机制图.png)]

    设备1和设备2如果出现了端口重复该怎么办?

    NAT机制缺陷

    真正解决IP地址不够用的技术:IPV6

    为何当下还是IPV4+NAT呢?

    3.3.6 私有IP地址和公网IP地址

    特殊的IP

    • 主机号全为0:当前局域网

    • 主机号全为1(125):广播IP

    • 以127开头的IP「环回」

    • 内网IP是不要求唯一的「不同网段中会出现相同的IP地址」

    • 除了10,172.16-172.31,192.168是私网IP以外,其余全是外网IP「外网IP要求是唯一的」

    3.3.7 路由

    路由选择也就是规划一条通信传输的路径

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zXZx24ml-1650795615982)(/Users/cxf/Desktop/MarkDown/images/路由选择.png)]

    简约的查找流程

    路由器如何认识目的IP的呢?这就用到了所谓的路由表的概念了。

    路由表

    3.4 数据链路层

    主要负责相邻的两个节点之间的通信

    3.4.1 认识以太网

    3.4.1.1 以太网帧格式

    以太网:并非是一个真正的网络,而是一种技术规范「既包含了数据链路层也涵盖物理层:网络的拓扑结构,访问控制方式,传输速率…」

    以太网中的网线必须使用双绞线「相同设备使用交叉线;不同设备使用直通线」

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zcak4R1H-1650795615982)(/Users/cxf/Desktop/MarkDown/images/以太网帧格式.jpeg)]

    3.4.1.2 认识MAC地址

    MAC地址是6字节,表示范围较于4字节的IPV4多了6w倍「所以才可以财大气粗的给每个硬件在出厂的时候写死一个MAC地址」

    3.4.2 对比理解MAC地址和IP地址

    当数据包到达局域网之后,根据IP+Port可以直接放送给目标主机的目标应用程序。好奇的朋友可能会问:为什么有了IP地址还要发明一个MAC地址呢?

    举个例子「重复了又好像没重复…」:

    高考发送录取通知书的时候,邮政小哥会提前打个电话通知李华,因为邮件地址填写的是光明小区,收件人是李华。李华满怀激动地下楼后等待快递小哥的到来,快递小哥为了验证身份会问到 “李华童鞋,你的准考证号是多少?”,因为李华这个名字全国都会重复,所以报了不会重复的准考证号,于是李华报了自己的证件号 “123”,于是快递小哥又说道 “好,准考证123号童鞋来拿你的录取通知书”。于是李华拿了小葵花大学的录取通知书离开了。

    整个过程感觉重复了但又没有没重复的感觉…

    来下面的解析:

    随着互联网的发展,路由也变得越来越复杂和困难了,于是聪明的人类发明了子网,把互联网分成很多个子网。在路由的时候,路由器就可以把其它子网看成一个整体。对于目的地还在其它其它其它的子网时候,路由器只负责把数据报发送到子网内部即可。然后在其子网内部完成剩余的数据报发送工作。这样做可以做到路径选择上接近最优而不是最优解。不过还是利大于弊,所以被采用了。

    和MAC地址不同的是,IP地址和地域相关「类似于邮政编号,根据不同地区划分不同的邮政编码」。对于同一个子网内部的设备IP,它们的前缀都是相同的。现在路由器只记录每个子网的位置,就知道设备在哪个子网上了,这样大大的节约了路由器的存储空间

    既然IP不能缺掉,那么这个MAC地址又显得多余,能不能去掉呢?

    对比发现:

    • IP地址主要是用来表示转发过程中的起点和终点
    • MAC地址主要是用来表示任意一次转发过程中的起点和终点

    3.4.3 认识MTU

    MTU「最大传输单元:Maximum Transfer Unit」相当于对数据帧的限制,这个限制是数据链路层对下一层的物理层的管理也影响上一层网络层的协议。

    • MTU范围 [46, 1500] 闭区间上
    • 最大值1500被称为MTU,不同网络类型有不同的MTU
    • 不同数据链路层MTU标准不同
    • 如果一个数据包从以太网上到达了数据链路层,若数据包超过MTU,这个数据就被 分片处理

    3.4.3.1 MTU对IP协议的影响

    如果IP数据报超过了1500字节,就无法被封装到一个以太网数据帧中,这个时候就会触发IP的分包操作「IP的分包一般不是因为报头中的64限制了数据报整体的大小,大概率是因为数据链路层以太网帧的MTU限制来分的」

    以下是MTU对IP报如何分片的

    1. 先把一个大的IP数据包拆分成多个小包,并打标签
    2. 由于IP格式中16位标识的存在,被分片的小包都会具有相同的标识身份
    3. IP格式中3位标志在对每个小包贴标签「第一位保留位,第二位设置为0、表示允许分片,第3位,如果是最后一个小包则设置为1,其余全是0」
    4. 接受方收到数据后再将这些小包按顺序组合在一起,完成拼装后在一起 发送给传输层
    5. 中途某个小包出现问题,接收方就会重组失败,网络层也没有传输层超时重传机制,所以不会重新传输数据

    3.4.3.1 MTU对UDP协议的影响

    以下是MTU对UDP的分片

    1. 一旦UDP携带的数据超过1472(1500-IP首部长度20-UDP首部长度8)就会在网络层分成多个数据包
    2. 这个IP数据包中有任意一个丢失,都会引起接受端网络重组失败「意味着:如果UDP被分片,网络出现状况的概率大大增加」

    3.4.3.1 MTU对TCP协议的影响

    TCP的数据报也不能无限大,主要还是受限于MTU。TCP单个数据报的最大长度是MSS「Maximum Segment Size」

    好奇的童鞋有可能会问:为什么TCP没有数据长度限制呢?那UDP有吗?

    以下是MTU对TCP的分片

    1. MSS协商:TCP在建立连接的时候,通信双方会进行协商使用谁的MSS「理想MSS:最理想的情况下就是MSS刚好达到IP不被分片处理的最大长度」
    2. 告知对方MSS:双方在发送SYN的时候会在报头写入自己能支持的MSS值
    3. 选取较小MSS:取双方最小值的MSS
    4. 存储MSS值:MSS的值就在TCP40字节的变长选项中

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JUsc1juK-1650795615983)(/Users/cxf/Desktop/MarkDown/images/MSS和MTU关系.png)]

    3.4.4 ARP协议

    这个了解即可,ARP协议其实并非是一个单纯的数据链路层协议,而是作用在数据链路层和网络层之间的协议

    3.4.4.1 ARP协议的作用

    用来简历IP地址和MAC地址之间的映射关系

    • 在网络通信是,发送端知道接收端的 IP+端口,却不知道接收端的 硬件地址「MAC地址」
    • 站在接收端的角度来看:数据包先是被网卡驱动程序接收再去处理上层「网络层,传输层这些」协议,如果发现数据包的硬件地址和本机地址不符,则会直接丢掉
    • 因此在通信前,还需要或读接收端的MAC地址

    3.4.4.2 ARP协议工作流程

    先获取MAC地址

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uA3lmbwH-1650795615984)(/Users/cxf/Desktop/MarkDown/images/ARP请求获取MAC地址.png)]

    再来看一下使用ARP发送数据过程

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PUuaNe8g-1650795615985)(/Users/cxf/Desktop/MarkDown/images/使用ARP协议发送数据.jpeg)]

    3.5 总结

    应用层

    • 应用层的作用:满足我们日常使用的网络程序
    • DNS解析
    • NAT技术结合应用程序访问外界IP

    传输层

    • 传输层的作用:负责端到端的数据传输
    • 由端口号区分应用程序
    • UDP协议及特点
    • TCP协议的可靠性「两个状态的转化CLOSE,TIME_WAIT」
    • TCP安全性:确认应答,连接管理,超时重传,流量控制,拥塞控制,TCP异常机制
    • TCP的效率:滑动窗口,捎带应答,延迟应答
    • TCP面向字节流,沾包问题的解决方案
    • 基于UDP抄TCP作业实现可靠传输
    • MTU对IP,TCP,UDP影响

    网络层

    • 网络层的作用:负责端到端过程中每个点到点的数据传输
    • IP地址,MAC地址
    • IP协议格式
    • 网段划分
    • IP数量不足的解决办法
    • IP数据包地址路由的选择过程「如何跨网段送达目的地」
    • IP数据包分片原因
    • NAT设备工作原理

    数据链路层

    • 数据链路层的作用:两个设备之间的数据传输
    • 以太网的理解
    • 以太网帧格式
    • MAC地址
    • ARP协议
    • MTU初识
    举报

    相关推荐

    0 条评论