三、Java网络通信_16(笔记)

2 篇文章 0 订阅

Java 网络通信

一. Java 网络编程

网络编程是指编写运行在多个设备(计算机)的程序,这些设备都通过网络连接起来

java.net 包中 J2SE 的 API 包含有类和接口,它们提供低层次的通信细 节。你可以直接使用这些类和接口,来专注于解决问题,而不用关注通信 细节。

二. 网络基础知识

a) OSI 分层模型

image-20230817114139226

OSI(Open System Interconnect),即开放式系统互联。 一般都叫 OSI 参考模型,是 ISO(国际标准化组织)组织在 1985 年研究的网络互连模 型。 ISO 为了更好的使网络应用更为普及,推出了 OSI 参考模型。

每一层实现各自的功能和协议,并完成与相邻层的接口通信

某一层的服务就是该层及其下各层的一种能力,它通过接口提供给更高一层。各层所提供的服务与这些服务是怎么实现的无关。

  1. 应用层: OSI 参考模型中最靠近用户的一层,是为计算机用户提供应用接口,也为用户直接提供各种网络服务。

    常见应用层的网络服务协议有:HTTP,HTTPS,FTP,POP3、SMTP 等。

  2. 表示层:表示层提供各种用于应用层数据的编码和转换功能,确保一个系统的应用层发送的数据能被另一个系统的应用层识别。如果必要,该层可提供一种标准表示形式,用于将计算机内部的多种数据格式转换成通信中采用的标准表示形式。数据压缩和加密也是表示层可提供的转换功能之一。

  3. 会话层: 会话层就是负责建立、管理和终止表示层实体之间的通信会话。该层的通信由不同设备中的应用程序之间的服务请求和响应组成。

  4. 传输层: 传输层建立了主机端到端的链接,传输层的作用是为上层协议提供端到端的可靠和透明的数据传输服务,包括处理差错控制和流量控制等问题。该层向高层屏蔽了下层数据通信的细节,使高层用户看到的只是在两个传输实体间的一条主机到主机的、可由用户控制和设定的、可靠的数据通路。我们通常说的,TCP UDP 就是在这一层。端口号既是这里的“端”。

  5. 网络层: 本层通过 IP 寻址来建立两个节点之间的连接,为源端的运输层送来的分组,选择合适的路由和交换节点,正确无误地按照地址传送给目的端的运输层。就是通常说的 IP 层。这一层就是我们经常说的IP 协议层。IP 协议是 Internet 的基础。

  6. 数据链路层:负责在两个相邻节点之间建立、维护和管理数据链路使得数据能够在物理网络上传输

    作用是将比特流(数字信号)转换为数据链路(物理连接),并通过定义一组特定的协议和规则来确保数据的可靠传输和处理。这些协议和规则包括错误检测和纠正、流量控制、拥塞控制等,可以有效地保证数据在物理网络上传输的正确性和可靠性。

  7. 物理层: 实际最终信号的传输是通过物理层实现的。通过物理介质传输比特流。规定了电平、速度和电缆针脚。

    常用设备有(各种物理设备)集线器、中继器、调制解调器、网线、双绞线、同轴电缆。这些都是物理层的传输介质。

b) TCP/IP 分层模型

TCP/IP 协议(传输控制协议/互联网协议)不是简单 的一个协议,而是一组特别的协议,包括:TCP,IP,UDP,ARP 等, 这些被称为子协议。在这些协议中,最重要、最著名的就是 TCP 和 IP。 因此,大部分网络管理员称整个协议族为“TCP/IP”。

i. 链路层

链路层有时也称作数据链路层或网络接口层,通常包括操作系统中 的设备驱动程序和计算机中对应的网络接口卡。**它们一起处理与电 缆(或其他任何传输媒介)的物理接口细节。**把链路层地址和网络层 地址联系起来的协议有 ARP(Address Resolution Protocol,地址 解析协议)和 RARP(Reverse Address Resolution Protocol,逆地址解析协议)。

ii. 网络层

网络层处理分组在网络中的活动,例如分组的选路。在 TCP/IP 协议 族中,网络层协议包括 IP 协议(Internet Protocol,网际协议)、 ICMP 协议(Internet Control Message Protocol,网际控制报文 协议)和 IGMP 协议(Internet Group Management Protocol, 网际组管理协议)。

iii. 传输层

传输层主要为两台主机上的应用程序提供端到端的通信。在 TCP/IP 协议族中,有两个互不相同的传输协议:TCP(Transmission Control Protocol,传输控制协议)和 UDP(User Datagram Protocol,用 户数据报协议)。

iv. 应用层

应用层负责处理特定的应用程序细节。几乎各种不同的 TCP/IP 实现 都会提供下面这些通用的应用程序:Telnet 远程登录、SMTP (Simple Mail Transfer Protocol,简单邮件传输协议)、FTP(File Transfer Protocol,文件传输协议)、HTTP(Hyper Text Transfer Protocol,超文本传输协议)等。

c) TCP/IP 五层协议和 OSI 的七层协议对应关系

image-20230817144149808

d) IP 地址

IP 地址:互联网协议地址(Internet Protocol Address) ,是 IP Address 的缩写.主要为计算机网络相互连接进行通信而设计的协议。
IP 地址被用来给 Internet 上的电脑一个编号。大家日常见到的情况是每台联网的 PC 上都需要有 IP 地址,才能正常通信。我们可以把“个人电脑”比作“一台电话”,那么“IP 地址”就相当于“电话号码”,而 Internet 中的路由器,就相当于电信局的“程控式交换机”。

IP 地址是一个 32 位的二进制数,通常被分割为 4 个“8 位二进制数”(也就是 4 个字节)。IP 地址通常用“点分十进制”表示成(a.b.c.d)的形式,其中,a,b,c,d 都是 0~255 之间的十进制整数。

特殊的网址:

  1. 每一个字节都为 0 的地址(“0.0.0.0”)对应于当前主机;

  2. IP 地址中的每一个字节都为 1 的 IP 地址(“255.255.255.255”)
    是当前子网的广播地址;

  3. IP 地址中凡是以“11110”开头的 E 类 IP 地址都保留用于将来和实
    验使用。

  4. IP 地址中不能以十进制 “127”作为开头,该类地址中数字 127.0.0.1 到 127.255.255.255 用于回路测试,如:127.0.0.1 可以代表本机 IP 地址,用“http://127.0.0.1”就可以测试本机中配 置的 Web 服务器。

  5. 网络 ID 的第一个 8 位组也不能全置为“0”,全“0”表示本地网络。

e) 端口号

端口包括物理端口和逻辑端口。物理端口是用于连接物理设备 之间的接口,逻辑端口是逻辑上用于区分服务的端口。TCP/IP 协议中的端口就是逻辑端口,通过不同的逻辑端口来区分不同的服务。一个 IP 地 址的端口通过 16bit 进行编号,最多可以有 65536 个端口。端口是通过 端口号来标记的,端口号只有整数,范围是从 0 到 65535。

端口有什么用呢?我们知道,一台拥有 IP 地址的主机可以提供许多 服务,比如 Web 服务、FTP 服务、SMTP 服务等,这些服务完全可以通 过 1 个 IP 地址来实现。那么,主机是怎样区分不同的网络服务呢?显然 不能只靠 IP 地址,因为 IP 地址与网络服务的关系是一对多的关系。实 际上是通过“IP 地址+端口号”来区 分不同的服务的。

端口号小于 256 的定义为常用端口,服务器一般都是通过常用端口 号来识别的。任何 TCP/IP 实现所提供的服务都用 1—1023 之间的端口 号,是由 ICANN 来管理的;

客户端只需保证该端口号在本机上是惟一的就可以了。客户端口号 因存在时间很短暂又称临时端口号;大多数 TCP/IP 实现给临时端口号 分配 1024—5000 之间的端口号。大于 5000 的端口号是为其他服务器 预留的。

三. 什么是 URL

a) 统一资源定位符

URL 是对可以从互联网上得到资源的位置和访问方法的一种简洁表示,互联网上的资源文件都有一个唯一的 URL。它由协议、服务器名称(IP 地址)、路径、文件名和请求参数组成。

b) 分类

i. 绝对 URL:显示文件的完整路径,这意味着绝对 URL 本身所在的位置与被引用的实际文件的位置无关

ii. 相对 URL:以包含 URL 本身的文件夹的位置为参考点,描述目标文件夹的位置。

c) Java 中的 URL 类

i. 构造器

image-20230817144747460

ii. 常用方法

image-20230817144922799

iii. 实例演示

/*
引入SneakyThrows注解,使得抛出的异常可以被捕获并处理

创建URL对象

输出URL对象相关信息
URL为:https://www.baidu.com/s?
协议为:https
验证信息:www.baidu.com
文件名及请求参数:/s?
主机名:www.baidu.com
路径:/s?
端口:443
默认端口:80
请求参数:wd=java
定位位置:/s?wd=java
构建请求参数

发送GET请求

构建请求参数
输出响应结果
 */

public class DemoA {
    @SneakyThrows
    public static void main(String[] args) {
        URL url = new URL("https://www.baidu.com/s?wd=java");
        System.out.println("URL 为:" + url.toString());
        System.out.println("协议为:" + url.getProtocol());
        System.out.println("验证信息:" + url.getAuthority());
        System.out.println("文件名及请求参数:" + url.getFile());
        System.out.println("主机名:" + url.getHost());
        System.out.println("路径:" + url.getPath());
        System.out.println("端口:" + url.getPort());
        System.out.println("默认端口:" + url.getDefaultPort());
        System.out.println("请求参数:" + url.getQuery());
        System.out.println("定位位置:" + url.getRef());
    }
}

运行结果:

image-20230817204822387

d) Java 中的 URLConnection 类

i. 常用方法

image-20230817145223887

image-20230817145238101

ii. 实例演示

/*
1、引入SneakyThrows注解,使得抛出的异常可以被捕获并处理

2、创建URL对象

3、创建URLConnection对象

4、判断连接对象是否为HttpURLConnection
如果是HttpURLConnection,则将连接对象赋值给connection变量
如果不是HttpURLConnection,则输出提示信息并直接返回

5、创建InputStreamReader对象

6、创建BufferedReader对象

7、构建StringBuilder对象

8、循环读取输入流中的内容

9、将每次读取到的内容添加到StringBuilder对象中

10、输出StringBuilder对象中的内容

11、关闭输入流和连接对象
 */
public class DemoB {
    @SneakyThrows
    public static void main(String[] args) {
        // 构建请求地址
        URL url = new URL("https://www.baidu.com");
        // 获取URLConnection对象
        URLConnection urlConnection = url.openConnection();
        // 判断连接对象是否为HttpURLConnection
        HttpURLConnection connection = null;
        if (urlConnection instanceof HttpURLConnection) {
            connection = (HttpURLConnection) urlConnection;
        } else {
            System.out.println("请输入 URL 地址");
            return;
        }
        // 创建InputStreamReader对象
        InputStreamReader reader = new InputStreamReader(connection.getInputStream());
        BufferedReader in = new BufferedReader(reader);
        // 创建InputStreamReader对象
        StringBuilder urlString = new StringBuilder();
        // 循环读取输入流中的内容
        String current;
        while ((current = in.readLine()) != null) {
            // 将每次读取到的内容添加到StringBuilder对象中
            urlString.append(current).append("\n");
        }
        // 输出StringBuilder对象中的内容
        System.out.println(urlString);
        // 关闭输入流和连接对象
        in.close();
        connection.disconnect();

    }
}

运行结果:

image-20230817204912941

四. Socket 编程

a) TCP 方式

TCP 是传输控制协议的缩写,它保障了两个应用程序之间的可靠通信。通常用于互联网协议,被称 TCP / IP。

服务器端(TcpServer)

public class TcpServer {

    @SneakyThrows
    public static void main(String[] args) {
        //创建服务器并指定端口号(创建ServerSocket对象,并指定监听的端口号)
        ServerSocket serverSocket = new ServerSocket(9999);
        System.out.println("服务器启动成功");
        //创建服务器的套接字
        Socket socket = serverSocket.accept();
        System.out.println("服务器的套接字创建成功!");

        //模拟服务器发送消息
        //获取服务器的输出流
        OutputStream out = socket.getOutputStream();
        //使用输出流写入“hello,world!”字符串
        out.write("hello,world!".getBytes());

        //创建字符流对象
        Writer writer = new OutputStreamWriter(out);
        //使用字符流写入“hello,world!”字符串
        writer.write("hello,world!");

        //关闭字符流
//        out.close();
        //关闭输出流
        writer.close();
        //关闭套接字
        socket.close();
        //关闭ServerSocket
        serverSocket.close();
    }
}

客户端(TcpClient)

public class TcpClient {
    @SneakyThrows
    public static void main(String[] args) {
        // 创建客户端套接字
        InetAddress address = InetAddress.getByName("127.0.0.1");
        Socket socket = new Socket(address, 9999);
        // 客户端接收服务器发送过来的消息
        InputStream in = socket.getInputStream();
        //创建一个字节数组

        byte[] bytes = new byte[100];
        //使用输入流读取字节数组中的数据
        int len = in.read(bytes);
        //将读取到的字节转换成字符串
        String str = new String(bytes, 0, len);
        //将读取到的字节转换成字符串
        System.out.println("服务器说:" + str);
        //关闭输入流
        in.close();
        //关闭套接字
        socket.close();
    }
}

注:连接成功,服务器的套接字是在客户端创建套接字并且发送请求之后创建的。

运行结果:

image-20230817210120569

b) UDP 方式

UDP 是用户数据报协议的缩写,一个无连接的协议。提供了 应用程序之间要发送的数据的数据包。

/*
1、创建一个UDP服务器类UDPServer,继承自Thread类。
2、在run()方法中,创建一个DatagramSocket对象,监听9999端口。
3、创建一个byte类型的数组data和一个DatagramPacket对象packet。
4、接收客户端发送的数据,即调用DatagramSocket的receive()方法,并将接收到的数据存储在data数组中。
5、将接收到的数据转换为字符串,并打印出来。
6、创建一个UDP客户端类UDPClient,继承自Thread类。
7、在run()方法中,定义一个字符串str,表示要发送的数据。
8、获取本地机器的IP地址。
9、创建一个DatagramSocket对象,用于发送数据。
10、将要发送的数据转换为字节数组,并创建一个DatagramPacket对象。
11、将发送的数据发送到服务器,即调用DatagramSocket的send()方法。
12、在主方法中,创建一个UDPServer线程和一个UDPClient线程,并启动它们。

总结:UDP通信的例子,其中包括一个UDP服务器和一个UDP客户端。在该例子中,
服务器通过创建一个DatagramSocket对象并监听9999端口来接收客户端的数据。
客户端则通过创建一个DatagramSocket对象,并向服务器发送一条Hello消息,
然后等待服务器的回复。在服务器接收到客户端的数据后,将数据转换为字符串并打印出来。
客户端在接收到服务器的回复后,程序运行结束。
 */
public class UDPDemo {
    // 定义主方法
    public static void main(String[] args) {
        // 创建一个UDP服务器线程并启动
        new UDPServer().start();
        // 创建一个UDP客户端线程并启动
        new UDPClient().start();
    }

    // 定义UDP服务器类
    static class UDPServer extends Thread {
        // 运行线程的逻辑
        @SneakyThrows
        public void run() {
            // 创建一个UDP服务器的DatagramSocket
            DatagramSocket socket = new DatagramSocket(9999);
            // 定义一个字节数组和一个DatagramPacket对象
            byte[] data = new byte[100];
            DatagramPacket packet = new DatagramPacket(data, data.length);
            // 接收数据
            socket.receive(packet);
            // 将接收到的数据转换成字符串,并打印出来
            String str = new String(data, 0, packet.getLength());
            System.out.println(str);
        }
    }

    // 定义UDP客户端类
    static class UDPClient extends Thread {
        // 运行线程的逻辑
        @SneakyThrows
        public void run() {
            // 定义要发送的数据
            String str = "Hello,UDPServer!";
            // 获取本地机器的IP地址
            InetAddress address = InetAddress.getLocalHost();
            // 创建一个UDP客户端的DatagramSocket
            DatagramSocket socket = new DatagramSocket();
            // 将要发送的数据转换成字节数组
            byte[] data = str.getBytes();
            // 创建一个DatagramPacket对象
            DatagramPacket packet = new DatagramPacket(data, data.length, address, 9999);
            // 发送数据
            socket.send(packet);
        }
    }
}

运行结果

image-20230817215500478

UDP 的通信建立的步骤

​ UDP 客户端首先向被动等待联系的服务器发送一个数据报文。

一个典型的 UDP 客户端要经过下面三步操作:

  1. 创建一个 DatagramSocket 实例,可以有选择地对本地地址和
    端口号进行设置,如果设置了端口号,则客户端会在该端口号上监听从服务器端发送来的数据;
  2. 使用 DatagramSocket 实例的 send()和 receive()方法来发送和接收 DatagramPacket 实例,进行通信;
  3. 通信完成后,调用 DatagramSocket 实例的 close()方法来关闭该套接字。由于 UDP 是无连接的,因此 UDP 服务端不需要等待客户端的请求以建立连接。另外,UDP 服务器为所有通信使用同一套接字,这点与 TCP 服务器不同,TCP 服务器则为每个成功返回的 accept()方法创建一个新的套接字。

一个典型的 UDP 服务端要经过下面三步操作:

  1. 创建一个 DatagramSocket 实例,指定本地端口号,并可以有 选择地指定本地地址,此时,服务器已经准备好从任何客户端接收 数据报文;
  2. 使用 DatagramSocket 实例的 receive()方法接收一个 DatagramPacket 实例,当 receive()方法返回时,数据报文就 包含了客户端的地址,这样就知道了回复信息应该发送到什么地 方;
  3. 使用 DatagramSocket 实例的 send()方法向服务器端返回 DatagramPacket 实例。

c) TCP 和 UDP 的区别

i. 基于连接与无连接

ii. TCP 要求系统资源较多,UDP 较少;

iii. UDP 程序结构较简单

iv. 流模式(TCP)与数据报模式(UDP);

v. TCP 保证数据正确性,UDP 可能丢包

vi. TCP 保证数据顺序,UDP 不保证

在这里插入图片描述

五. BIO、NIO 和 AIO

a) BIO

Java BIO (blocking I/O), 同步阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行 处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以 通过线程池机制改善。

error错误写法

服务器端(BioServer)

public class BioServer {
    @SneakyThrows
    public static void main(String[] args) {
        // 创建 Scanner 对象,用于接收用户输入
        Scanner scanner = new Scanner(System.in);
        // 创建 ServerSocket 对象,监听 9999 端口
        ServerSocket serverSocket = new ServerSocket(9999);
        // 等待客户端连接
        Socket socket = serverSocket.accept();
        // 获取 OutputStream 对象,用于向客户端发送数据
        OutputStream out = socket.getOutputStream();
        // 获取 InputStream 对象,用于从客户端接收数据
        InputStream in = socket.getInputStream();
        // 循环接收客户端响应
        while (true) {
            // 输出提示信息
            System.out.print("Input:");
            // 接收用户输入
            String str = scanner.next();
            // 将用户输入转换为字节数组
            byte[] bytes = str.getBytes();
            // 将字节数组写入 OutputStream 对象
            out.write(bytes);
            // 如果用户输入为 "bye",则退出循环
            if ("bye".equals(str)) {
                break;
            }
        }
        // 循环接收客户端响应
        while (true) {
            // 读取 100 个字节的数据
            byte[] bytes = new byte[100];
            // 从 InputStream 对象中读取数据
            int len = in.read(bytes);
            // 将字节数组转换为字符串
            String str = new String(bytes, 0, len);
            // 输出客户端响应
            System.out.println("客户端说:" + str);
            // 如果客户端响应为 "bye",则退出循环
            if (str.equals("bye")) {
                break;
            }
        }
        // 关闭 OutputStream 对象
        out.close();
        // 关闭 InputStream 对象
        in.close();
        // 关闭 Socket 对象
        socket.close();
        // 关闭 ServerSocket 对象
        serverSocket.close();
    }
} 

客户端( BioClient)

public class BioClient {
    @SneakyThrows
    public static void main(String[] args) {
        // 创建 Scanner 对象,用于接收用户输入
        Scanner scanner = new Scanner(System.in);
        // 创建 Socket 对象,连接服务器
        Socket socket = new Socket("localhost", 9999);
        // 获取 OutputStream 对象,用于向服务器发送数据
        OutputStream out = socket.getOutputStream();
        // 获取 InputStream 对象,用于从服务器接收数据
        InputStream in = socket.getInputStream();
        // 循环接收服务器响应
        while (true) {
            // 输出提示信息
            System.out.print("Input:");
            // 接收用户输入
            String str = scanner.next();
            // 将用户输入转换为字节数组
            byte[] bytes = str.getBytes();
            // 将字节数组写入 OutputStream 对象
            out.write(bytes);
            // 如果用户输入为 "bye",则退出循环
            if ("bye".equals(str)) {
                break;
            }
        }
        // 循环接收服务器响应
        while (true) {
            // 读取 100 个字节的数据
            byte[] bytes = new byte[100];
            // 从 InputStream 对象中读取数据
            int len = in.read(bytes);
            // 将字节数组转换为字符串
            String str = new String(bytes, 0, len);
            // 输出服务器响应
            System.out.println("服务器说:" + str);
            // 如果服务器响应为 "bye",则退出循环
            if (str.equals("bye")) {
                break;
            }
        }
        // 关闭 OutputStream 对象
        out.close();
        // 关闭 InputStream 对象
        in.close();
        // 关闭 Socket 对象
        socket.close();
    }
}
运行结果

image-20230817211914127

出现问题:一个死循环和只有一个线程

解决:I/O流+多线程+socket编程

success正确写法

发送线程

// 发送线程
public class BioSendThread extends Thread {

    // 定义输出流和名称
    private OutputStream out;
    private Scanner scanner;
    private String name;

    // 构造函数,传入输出流和名称
    public BioSendThread(OutputStream out, String name) {
        this.out = out;
        this.name = name;
        scanner = new Scanner(System.in);
    }

    // 运行线程的逻辑
    @SneakyThrows
    public void run() {
        // 循环接收用户输入
        while (true) {
            // 打印提示信息
            System.out.print("Input:");
            // 读取用户输入的内容
            String content = scanner.next();
            // 将当前线程的名称和用户输入的内容拼接起来,并写入输出流
            out.write((name + content).getBytes());
            // 如果用户输入的是bye,则退出循环
            if ("bye".equals(content)) {
                // 关闭输出流
                //out.close();
                // 退出线程
                return;
            }
        }
    }
}

接收线程

// 接收的线程
public class BioResviceThread extends Thread {

    // 定义输入流
    private InputStream in;

    // 构造函数,传入输入流
    public BioResviceThread(InputStream in) {
        this.in = in;
    }

    // 运行线程的逻辑
    @SneakyThrows
    public void run() {
        // 定义一个字节数组,用于存储读取到的数据
        byte[] bytes = new byte[100];
        // 循环接收数据
        while (true) {
            // 读取数据的长度
            int len = in.read(bytes);
            // 如果读取到的数据长度为0,则退出循环
            if (len == 0) {
                break;
            }
            // 将读取到的数据转换成字符串,并打印出来
            String str = new String(bytes, 0, len);
            System.out.println(str);
            // 如果字符串为空或者包含bye,则退出循环
            if (str == null || str.contains("bye")) {
                // 关闭输入流
               // in.close();
                // 退出线程
                return;
            }
        }
    }
}

服务器端(BioServer)

public class BioServer {
    // 使用SneakyThrows注解,当发生异常时,不会直接抛出,而是将异常封装成RuntimeException并抛出
    @SneakyThrows
    public static void main(String[] args) {
        // 创建ServerSocket对象,监听本地机器的9999端口
        ServerSocket serverSocket = new ServerSocket(9999);
        // 等待客户端连接
        Socket socket = serverSocket.accept();
        // 获取Socket的输出流和输入流
        OutputStream out = socket.getOutputStream();
        InputStream in = socket.getInputStream();
        // 创建一个新的BioSendThread线程,将输出流传递给它,并启动
        new BioSendThread(out, "服务器:").start();
        // 创建一个新的BioResviceThread线程,将输入流传递给它,并启动
        new BioResviceThread(in).start();
    }
}

客户端(BioClient)

public class BioClient {
    // 使用SneakyThrows注解,当发生异常时,不会直接抛出,而是将异常封装成RuntimeException并抛出
    @SneakyThrows
    public static void main(String[] args) {
        // 创建Socket对象,连接本地机器的9999端口
        Socket socket = new Socket("localhost", 9999);
        // 获取Socket的输出流和输入流
        OutputStream out = socket.getOutputStream();
        InputStream in = socket.getInputStream();
        // 创建一个新的BioSendThread线程,将输出流传递给它,并启动
        new BioSendThread(out, "客户端:").start();
        // 创建一个新的BioResviceThread线程,将输入流传递给它,并启动
        new BioResviceThread(in).start();
        // 让线程睡眠10000毫秒,模拟数据传输的过程
        Thread.sleep(10000);
        // 打印bye
        System.out.println("bye");
    }
}
运行结果

image-20230817214251106

b) NIO

Java NIO (non-blocking I/O),同步非阻塞,服务器实现模式为 一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上, 多路复用器轮询到连接有 I/O 请求时才启动一个线程进行处理。

i. NIO

本质就是避免原始的 TCP 建立连接使用 3 次握手的操作,减 少连接的开销

image-20230817154740439

ii. Buffer-缓冲区

Buffer 是一个对象,它包含一些要写入或者要读取 的数据。在 NIO 类库中加入 Buffer 对象,体现了新库与原 IO 的一个重要的区别。

在面向流的 IO 中,可以将数据直接写入或读取到 Stream 对象中。在 NIO 库中,所有的数据都是用缓冲区处理的(读 写 )。

缓 冲 区 实 质 上 是 一 个 数 组 , 通 常 它 是 一 个 字 节 数 组 (ByteBuffer),也可以使用其他类型的数组,这个数组为缓冲区提 供了数据的访问读写等操作属性,如位置,容量,上限等概念,参考 API 文档。

Buffer 类型:我们最常用的就是 ByteBuffer,实际上每 一种 java 基本类型都对应了一种缓冲区

iii. Channel(管道/通道)

通道就像自来水管道一样,网络数据通过 Channel 读取和写入,通道与流不同之处在于通道是双向的,而流 只是一个方向上移动,而通道可以用于读,写或者二者同时进行,最 关键的是可以与多路复用器结合起来,有多种的状态位,方便多路复 用器去识别。

事实上通道分为两大类,一类是网络读写(SelectableChannel),一类是用于文件操作的(FileChannel)。

iv. Selector(选择器,多路复用器)

多路复用器是 NIO 编程的基础, 非常重要,多路复用器提供选择已经就绪的任务的能力。

简单说,就是 Selector 会不断地轮询注册在其上的通道 (Channel),如果某个通道发生了读写操作,这个通道就处于就绪 状态,会被 Selector 轮询出来,然后通过 SelectionKey 可以取得 就绪的 Channel 集合,从而进行后续的 IO 操作。

Selector 线程就类似一个管理者(Master),管理了成千上万个 管道,然后轮询哪个管道的数据已经准备好,通知 CPU 执行 IO 的 读取或写入操作。

Selector 模式:当 IO 事件(管理)注册到选择器以后,selector 会分配给每个管道一个 key 值,相当于标签。selector 选择器是以 轮询的方式进行查找注册的所有 IO 事件(管道) .

当我们的 IO 事件(管道)准备就绪后,select 就会识别,会通 过 key 值来找到相应的管道,进行相关的数据处理操作(从管道里 读或写数据,写道我们的数据缓冲区中).

创建 NIO 服务器的主要步骤

(1)打开 ServerSocketChannel,监听客户端连接

(2)绑定监听端口,设置连接为非阻塞模式

(3)创建 Reactor 线程,创建多路复用器并启动线程

(4)将 ServerSocketChannel 注册到 Reactor 线程中的 Selector 上,监听 ACCEPT 事件

(5)Selector 轮询准备就绪的 key

(6)Selector 监听到新的客户端接入,处理新的接入请求,完成 TCP 三次握手,简历物理链路

(7)设置客户端链路为非阻塞模式

(8)将新接入的客户端连接注册到 Reactor 线程的 Selector 上,监听读操作,读取客户端发送的网络消息

(9)异步读取客户端消息到缓冲区

(10)对 Buffer 编解码,处理半包消息,将解码成功的消息封装成 Task

(11)将应答消息编码为 Buffer,调用 SocketChannel 的 write 将消息异步发送给客户端

举例子

假设你是一个厨师,你需要为多个客人准备菜肴。如果你使用传统的阻塞IO方式,那么你只能为一个客人准备菜肴,每准备一个客人的菜肴就要等待该客人的订单完成,然后再为下一个客人准备菜肴。

这样,如果有很多客人等待用餐,你的工作效率就会很低。 但是,如果你使用同步非阻塞IO方式,就可以实现多路复用,同时为多个客人准备菜肴。

具体来说,你可以为每个客人创建一个线程,然后将每个线程与一个客人的订单绑定起来。这样,当有客人下单时,你就可以同时为多个客人准备菜肴,而不需要等待某一个客人的订单完成。

这样,你就可以大大提高工作效率,更好地为客人服务。 这就是Java同步非阻塞IO的概念,它可以使得一个线程可以同时处理多个连接,从而提高系统的并发性和吞吐量。

c) AIO

Java AIO(NIO.2) (Asynchronous I/O) , 异步非阻塞,服务器实 现模式为一个有效请求一个线程,客户端的 I/O 请求都是由 OS 先完成 了再通知服务器应用去启动线程进行处理,在 NIO 基础之上引入了异步 通道的概念。

并提供异步文件和异步套接字通道的实现,从而在真正意 义上实现了异步非阻塞,之前我们学过的 NIO 只是非阻塞而非异步。

而 AIO 它不需要通过多路复用器对注册的通道的进行轮训操作即可实现异 步读写,从而简化了 NIO 编程模型。也可以称为 NIO2.0,这种模式才 是真正的属于异步非阻塞的模型。

举例子

假设你是一个图书管理员,你需要为多个读者借阅图书。如果你使用传统的阻塞IO方式,那么你只能为一个读者借阅图书,每借阅一个读者的图书就要等待该读者的借阅信息返回,然后再为下一个读者借阅图书。

这样,如果有很多读者需要借阅图书,你的工作效率就会很低。 但是,如果你使用Java AIO方式,就可以实现多路复用,同时为多个读者借阅图书。

具体来说,你可以为每个读者创建一个线程,然后将每个线程与一个读者的借阅信息绑定起来。这样,当有读者需要借阅图书时,你就可以同时为多个读者借阅图书,而不需要等待某一个读者的借阅信息返回。

这样,你就可以大大提高工作效率,更好地完成借阅图书的任务。 这就是Java AIO的概念,它可以使得一个线程可以同时处理多个连接,从而提高系统的并发性和吞吐量。

d) 联系和区别

i. 都是通过网络通信来实现的功能,比如一会我们写的代码都是在 跟着关为学网络通信 18 TCP/IP 基础上进行的;

ii. BIO 和 NIO 都是同步操作,而 AIO 是异步操作;

iii. BIO 是阻塞情况,NIO 和 AIO 是非阻塞

阻塞和非阻塞

我们使用银行取款业务来说明

阻塞就是银行人多的情况下,你在哪里排队等待,期间不能处理其他事

非阻塞就是银行人多的情况下,抽号坐在哪里等待,期间可以处理其他事

e) 适用场景

i. BIO 方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4 以前的唯一选择,但 程序直观简单易理解。

ii. NIO 方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4 开始支持。

iii. AIO 方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用 OS 参与并发操作,编程比较复杂,JDK7 开始 支持。

六. 聊一聊 HTTP 协议

HTTP 协议是超文本传输协议,属于应用层协议,规定了客户端与服务端传输数据的格式;

它是无状态的,对于前面传送过的信息没有记录;

请求方式有 GET,POST,HEAD,PUT,DELETE 等等,最主要的 get,post 方法;

get 请求:数据会以 URL 的形式传输,对数据大小有一定的限制,安全性比较低 ,用于传输一些比较小, 安全性要求低的数据;

post 请求:数据是通过数据包的形式传输,比较安全,用于传输比较大的,对于安全性要求较高的数据;

——GETPOST
后退按钮/刷新无害数据会被重新提交(浏览器应该告知用 户数据会被重新提交)。
书签可收藏为书签不可收藏为书签
编码类型application/x-www-form-urlencodedapplication/x-www-form-urlencoded 或 multipart/formdata。为二进制数据使用多重编码。
历史参数保留在浏览器历史中。参数不会保存在浏览器历史中。
对数据长度的限制是的。当发送数据时,GET 方法向 URL 添加数据;URL 的长度是受限制的(URL 的最大长度是 2048 个字符)。无限制。
对数据类型的限制只允许 ASCII 字符。没有限制。也允许二进制数据。
安全性与 POST 相比,GET 的安全性较差,因 为所发送的数据是 URL 的一部分。 在发送密码或其他敏感信息时绝不要使用 GET !POST 比 GET 更安全,因为参数不会 被保存在浏览器历史或 web 服务器日 志中。
可见性数据在 URL 中对所有人都是可见的。数据不会显示在 URL 中。
缓存能被缓存不能缓存

HTTP 状态码

状态代码有三位数字组成,第一个数字定义了响应的类别,共分五种类别:

1xx:指示信息–表示请求已接收,继续处理

2xx:成功–表示请求已被成功接收、理解、接受

3xx:重定向–要完成请求必须进行更进一步的操作

4xx:客户端错误–请求有语法错误或请求无法实现

5xx:服务器端错误–服务器未能实现合法的请求

下面提供 HTTP 状态码的完整列表。

1xx(临时响应)

表示临时响应并需要请求者继续执行操作的状态码。

100(继续)请求者应当继续提出请求。

服务器返回此代码表示已收到请求的第一部分,正在等待其余部 分。

101(切换协议)请求者已要求服务器切换协议,服务器已确认并准备切换。

2xx (成功)

表示成功处理了请求的状态码。

200(成功)服务器已成功处理了请求。通常,这表示服务器提供了请求的网页。如果是对您的 robots. txt 文件显示此状态码,则表示 Googlebot 已成功检索到该文件。

201(已创建)请求成功并且服务器创建了新的资源。

202(已接受)服务器已接受请求,但尚未处理。

203(非授权信息)服务器已成功处理了请求,但返回的信息可能来自另一来源。

204(无内容)服务器成功处理了请求,但没有返回任何内容。

205(重置内容)服务器成功处理了请求,但没有返回任何内容。与 204 响应不同,此响应要求请求者 重置文档视图(例如,清除表单内容以输入新内容)。

206(部分内容)服务器成功处理了部分 GET 请求。

3xx (重定向)

要完成请求,需要进一步操作。通常,这些状态码用来重定向。Google 建议您在每次请求中使用重 定向不要超过 5 次。您可以使用网站管理员工具查看一下 Googlebot 在抓取重定向网页时是否遇到问题。 诊断下的网络抓取页列出了由于重定向错误导致 Googlebot 无法抓取的网址。

300(多种选择)针对请求,服务器可执行多种操作。服务器可根据请求者 (user agent) 选择一项操作, 或提供操作列表供请求者选择。

301(永久移动)请求的网页已永久移动到新位置。服务器返回此响应(对 GET 或 HEAD 请求的响应)时, 会自动将请求者转到新位置。您应使用此代码告诉 Googlebot 某个网页或网站已永久移动到新位置。

302(临时移动)服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来响应以后的 请求。此代码与响应 GET 和 HEAD 请求的 301 代码类似,会自动将请求者转到不同的位置,但您不应使 用此代码来告诉 Googlebot 某个网页或网站已经移动,因为 Googlebot 会继续抓取原有位置并编制索 引。

303(查看其他位置)请求者应当对不同的位置使用单独的 GET 请求来检索响应时,服务器返回此代码。 对于除 HEAD 之外的所有请求,服务器会自动转到其他位置。

304(未修改)自从上次请求后,请求的网页未修改过。服务器返回此响应时,不会返回网页内容。 如果网页自请求者上次请求后再也没有更改过,您应将服务器配置为返回此响应(称为 If-Modified-S ince HTTP 标头)。服务器可以告诉搜索引擎的蜘蛛/机器人 自从上次抓取后网页没有变更,进而节省带宽 和开销。

305(使用代理)请求者只能使用代理访问请求的网页。如果服务器返回此响应,还表示请求者应使用代 理。

307(临时重定向)服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来响应以后 的请求。此代码与响应 GET 和 HEAD 请求的 301 代码类似,会自动将请求者转到不同的位置,但您不应 使用此代码来告诉 Googlebot 某个页面或网站已经移动,因为 Googlebot 会继续抓取原有位置并编制 索引。

4xx(请求错误)

这些状态码表示请求可能出错,妨碍了服务器的处理。 400(错误请求)服务器不理解请求的语法。

401(未授权)请求要求身份验证。对于登录后请求的网页,服务器可能返回此响应。

403(禁止)服务器拒绝请求。如果您在 Googlebot 尝试抓取您网站上的有效网页时看到此状态码(您 可以在 Google 网站管理员工具诊断下的网络抓取页面上看到此信息),可能是您的服务器或主机拒绝了 G ooglebot 访问。

404(未找到)服务器找不到请求的网页。例如,对于服务器上不存在的网页经常会返回此代码。 如果您的网站上没有 robots.txt 文件,而您在 Google 网站管理员工具"诊断"标签的 robots.txt 页 上看到此状态码,则这是正确的状态码。但是,如果您有 robots.txt 文件而又看到此状态码,则说明您的 r obots.txt 文件可能命名错误或位于错误的位置(该文件应当位于顶级域,名为 robots.txt)。

如果对于 Googlebot 抓取的网址看到此状态码(在"诊断"标签的 HTTP 错误页面上),则表示 Google bot 跟随的可能是另一个页面的无效链接(是旧链接或输入有误的链接)。

405(方法禁用)禁用请求中指定的方法。

406(不接受)无法使用请求的内容特性响应请求的网页。

407(需要代理授权)此状态码与 401(未授权)类似,但指定请求者应当授权使用代理。如果服务器返回 此响应,还表示请求者应当使用代理。

408(请求超时)服务器等候请求时发生超时。

409(冲突)服务器在完成请求时发生冲突。服务器必须在响应中包含有关冲突的信息。服务器在响应与 前一个请求相冲突的 PUT 请求时可能会返回此代码,以及两个请求的差异列表。

410(已删除)如果请求的资源已永久删除,服务器就会返回此响应。该代码与 404(未找到)代码类似, 但在资源以前存在而现在不存在的情况下,有时会用来替代 404 代码。如果资源已永久移动,您应使用 3 01 指定资源的新位置。

411(需要有效长度)服务器不接受不含有效内容长度标头字段的请求。

412(未满足前提条件)服务器未满足请求者在请求中设置的其中一个前提条件。

413(请求实体过大)服务器无法处理请求,因为请求实体过大,超出服务器的处理能力。

414(请求的 URI 过长)请求的 URI(通常为网址)过长,服务器无法处理。

415(不支持的媒体类型)请求的格式不受请求页面的支持。

416(请求范围不符合要求)如果页面无法提供请求的范围,则服务器会返回此状态码。

417(未满足期望值)服务器未满足"期望"请求标头字段的要求。

5xx(服务器错误)

这些状态码表示服务器在处理请求时发生内部错误。这些错误可能是服务器本身的错误,而不是请求 出错。

500(服务器内部错误)服务器遇到错误,无法完成请求。 501(尚未实施)服务器不具备完成请求的功能。例如,服务器无法识别请求方法时可能会返回此代码。

502(错误网关)服务器作为网关或代理,从上游服务器收到无效响应。

503(服务不可用)服务器目前无法使用(由于超载或停机维护)。通常,这只是暂时状态。

504(网关超时)服务器作为网关或代理,但是没有及时从上游服务器收到请求。

505(HTTP 版本不受支持)服务器不支持请求中所用的 HTTP 协议版本。

七. 聊一聊 TCP 协议

TCP 协议是面向连接的、可靠的传输层协议,规定了数据在网络中是如何传输的。

image-20230817161143668

三次握手

1.第一次握手:建立连接。客户端发送连接请求报文段,将 SYN 位置为 1,Sequence Numb er 为 x;然后,客户端进入 SYN_SEND 状态,等待服务器的确认;

2.第二次握手:服务器收到 SYN 报文段。服务器收到客户端的 SYN 报文段,需要对这个 SYN 报文段进行确认,设置 Acknowledgment Number 为 x+1(Sequence Number+1);同时,自 己自己还要发送 SYN 请求信息,将 SYN 位置为 1,Sequence Number 为 y;服务器端将上述所有信息放到一个报文段(即 SYN+ACK 报文段)中,一并发送给客户端,此时服务器进入 SYN_ RECV 状态;

3.第三次握手:客户端收到服务器的 SYN+ACK 报文段。然后将 Acknowledgment Number 设置为 y+1,向服务器发送 ACK 报文段,这个报文段发送完毕以后,客户端和服务器端都进入 ESTABLISHED 状态,完成 TCP 三次握手。

四次挥手

1.第一次分手:主机 1(可以使客户端,也可以是服务器端),设置 Sequence Number 和 Ackn owledgment Number,向主机 2 发送一个 FIN 报文段;此时,主机 1 进入 FIN_WAIT_1 状态; 这表示主机 1 没有数据要发送给主机 2 了;

2.第二次分手:主机 2 收到了主机 1 发送的 FIN 报文段,向主机 1 回一个 ACK 报文段,Ackn owledgment Number 为 Sequence Number 加 1;主机 1 进入 FIN_WAIT_2 状态;主机 2 告 诉主机 1,我也没有数据要发送了,可以进行关闭连接了;

3.第三次分手:主机 2 向主机 1 发送 FIN 报文段,请求关闭连接,同时主机 2 进入 CLOSE_W AIT 状态;

4.第四次分手:主机 1 收到主机 2 发送的 FIN 报文段,向主机 2 发送 ACK 报文段,然后主机 1 进入 TIME_WAIT 状态;主机 2 收到主机 1 的 ACK 报文段以后,就关闭连接;此时,主机 1 等待 2MSL 后依然没有收到回复,则证明 Server 端已正常关闭,那好,主机 1 也可以关闭连接了。

八. 常见问题

1、UDP 和 TCP 区别

image-20230817162613695

2、说一下 Java 的 Socket 编程

  1. 是一种通信协议,它允许两台计算机通过网络连接进行通信。
  2. 使用TCP/IP协议,它提供了一种面向连接的、可靠的、基于流的数据传输方式。
  3. 包括客户端和服务器端两个部分,客户端发送请求,服务器端接收请求并进行处理。
  4. 可以用于开发各种网络应用程序,例如聊天室、文件传输、在线游戏等。

3、nginx 反向代理

正向代理(Forward Proxy):一般情况下,如果没有特别说明,代理技术默认说的是正向代理技术。

关于正向代理的概念如下:正向代理(forward)是一个位于客户端【用户 A】和原始服务器(origin server)【服 务器 B】之间的服务器【代理服务器 Z】,为了从原始服务器取得内容,用户 A 向代理服务器 Z 发送一个请 求并指定目标(服务器 B),然后代理服务器 Z 向服务器 B 转交请求并将获得的内容返回给客户端。客户端 必须要进行一些特别的设置才能使用正向代理。

从上面的概念中,可以看出,所谓的正向代理就是代理服务器替代访问方【用户 A】去访问目标服务器【服务器 B】。

正向代理的作用:

  1. 访问本无法访问的服务器 B
  2. 加速访问服务器 B
  3. Cache 作用
  4. 客户端访问授权

反向代理正好与正向代理相反,对于客户端而言代理服务器就像是原始服务器,并且客户端不需要进行任何特 别的设置。客户端向反向代理的命名空间(name-space)中的内容发送普通请求,接着反向代理将判断向何处(原 始服务器)转交请求,并将获得的内容返回给客户端。

使用反向代理服务器的作用:

  1. 保护和隐藏原始资源服务器

  2. 负载均衡(当反向代理服务器不止一个的时候,我们甚至可以把它们做成集群,当更多的用户访问资源服 务器 B 的时候,让不同的代理服务器 Z(x)去应答不同的用户,然后发送不同用户需要的资源。)

Nginx (engine x) 是一个高性能的 HTTP 和反向代理 web 服务器,同时也提供了 IMAP/POP3/SMTP 服务。

4、说说你对 get 和 post 请求,并且说说它们之间的区别?

image-20230817163115730

5、TCP 协议在哪一层?IP 协议在那一层?HTTP 在哪一层?

TCP协议是传输控制协议,它属于网络层。

IP协议是网际协议,它属于网络层。

HTTP协议是超文本传输协议,它属于应用层。

6、讲一下 TCP 的连接和释放连接。(三次握手四次挥手)

TCP连接和释放连接的过程可以简单概括为三次握手和四次挥手。

三次握手:

  1. 客户端向服务器发送连接请求报文,其中包含一个 SYN 字符和一个随机序列号。
  2. 服务器收到客户端的连接请求报文后,回复一个 ACK 报文,其中包含一个 SYN 字符和一个自己的随机序列号,同时也向客户端发送一个连接确认报文。
  3. 客户端收到服务器的连接确认报文后,向服务器发送一个 ACK 报文,其中包含一个 SYN 字符和一个自己的随机序列号,表示连接已经建立。

四次挥手:

  1. 客户端向服务器发送一个 FIN 报文,表示要关闭连接。
  2. 服务器收到客户端的 FIN 报文后,向客户端发送一个 ACK 报文,表示已经收到 FIN 报文。
  3. 服务器向客户端发送一个 FIN 报文,表示要关闭连接。
  4. 客户端收到服务器的 FIN 报文后,向服务器发送一个 ACK 报文,表示已经收到 FIN 报文。

完成上述过程后,TCP连接就会正式关闭。

举例子:

假设 Alice 和 Bob 在一个聊天室里,他们想要通过 TCP 协议进行聊天。

三次握手:

  1. Alice 向 Bob 发送一个请求建立连接的消息,表示想要和 Bob 聊天。
  2. Bob 收到 Alice 的请求建立连接的消息后,回复一个确认连接的消息,表示同意和 Alice 聊天。
  3. Alice 收到 Bob 的确认连接的消息后,再次发送一个确认连接的消息,表示连接已经建立。

四次挥手:

  1. Alice 向 Bob 发送一个消息,表示要结束聊天。
  2. Bob 收到 Alice 的消息后,向 Alice 发送一个确认消息,表示已经收到 Alice 的消息。
  3. Bob 向 Alice 发送一个消息,表示也要结束聊天。
  4. Alice 收到 Bob 的消息后,向 Bob 发送一个确认消息,表示已经收到 Bob 的消息。

完成上述过程后,Alice 和 Bob 之间的聊天就会正式结束。

7、 TCP 有哪些应用场景

巧记:

TCP 协议传输安全,应用场景多种多样。

HTTP、HTTPS 数据传输,FTP、SFTP 文件传输。

SMTP 发送邮件,MySQL、Oracle 接收请求,

QQ、微信传消息,大多数游戏实时通信。

三次握手建立连接,四次挥手断开连接。

可靠传输保证数据安全,实时性强保证及时。

TCP 协议功不可没,网络传输必不可少。

TCP 协议是一种面向连接的可靠的传输层协议,因此它适用于许多应用场景,包括但不限于:

  1. 网页浏览:HTTP、HTTPS 等协议在传输数据时都是基于 TCP 协议的。
  2. 文件传输:FTP、SFTP 等协议在传输文件时都是基于 TCP 协议的。
  3. 邮件发送:SMTP 协议在发送邮件时是基于 TCP 协议的。
  4. 数据库访问:MySQL、Oracle 等数据库在接收客户端请求时是基于 TCP 协议的。
  5. 聊天工具:QQ、微信等聊天工具在传输消息时都是基于 TCP 协议的。
  6. 游戏通信:大多数游戏在进行实时通信时都是基于 TCP 协议的。

总之,TCP 协议在各种需要可靠传输的场景中都得到了广泛的应用。

image-20230817194505038

8、 TCP 为什么可靠

TCP 协议是一种可靠的传输层协议,它可以确保数据在客户端和服务器之间传输的可靠性。这是因为 TCP 协议采用了以下几种机制来保证数据的可靠传输:

  1. 连接建立:TCP 协议采用三次握手的方式来建立连接,确保客户端和服务器之间的连接是可靠的。
  2. 数据分段:TCP 协议将数据分成多个小段进行传输,每个小段都有一个序号,这样可以保证数据的完整性。
  3. 数据校验:TCP 协议采用校验和的方式来检验数据的完整性,如果数据在传输过程中出现错误,可以及时发现并重新传输。
  4. 流量控制:TCP 协议可以通过滑动窗口机制来控制数据的流量,避免数据传输过快导致服务器无法处理。
  5. 拥塞控制:TCP 协议可以通过算法来控制数据的发送速度,避免因为数据传输过快导致网络拥塞。

正是因为这些机制的存在,TCP 协议才能够保证数据在客户端和服务器之间的可靠传输。

举例子

假设你正在通过网络向你的朋友发送一份文件,这份文件有很多页,TCP 协议会将这些页面分成多个小段,每个小段都有一个数字序号,然后将这些小段发送给你的朋友。在发送的过程中,TCP 协议会确保每个小段都能够正确地发送和接收,如果有任何小段出现问题,TCP 协议会要求重新发送,直到所有小段都被正确地发送和接收。

这就像是你在邮寄一本书,你会把书拆分成多个小册子,每个小册子都有一个数字序号,然后把这些小册子装进一个大信封里,再把大信封交给邮递员。在这个过程中,你会确保每个小册子都是完整的,数字序号是正确的,这样邮递员才能够把这些小册子正确地送到你的朋友手中。TCP 协议就像是一个邮递员,它会确保数据被正确地发送和接收,保证数据的可靠性。

9、 TCP 的连接建立为什么需要三次握手(即进行了两次确认)

这主要是为了防止已经失效的连接请求报文段突然又传给了服务器,因而会产生错误。

假如现在 Client 端向 Server 端发送请求连接,而这个报文段却在网络结点中长时间滞留了,在连接释放以后 Client 发送的这个报文段才到达对方,此时这个报文段就是一个已经失效的连接请求报文段。

但 Server 收到这个报文段后并不知其失效,以为是 Client 又发送的一次请求,就会向 Client 发送同意建立连 接的确认报文段。 但问题是 Client 并没有给 Server 发送请求,因此也会对这个确认报文段不予理睬,也不会发送数据。

但 Server 会以为连接建立了,并会一直处于等待状态,等待对方的数据,在这样的情况下,Server 的一些资源 就会浪费了。

举例子

假设你正在通过网络向你的朋友发送一份文件,你需要先和你的朋友建立一个可靠的连接,这样你才能够将文件发送给他。

在建立连接的过程中,你会向你的朋友发送一个连接请求报文,你的朋友会接收这个报文,并向你发送一个连接确认报文。你会接收这个报文,并向你的朋友发送一个连接确认报文。

这样,你和你的朋友之间就建立了一个可靠的连接。 进行三次握手的原因是为了确保你和你的朋友之间的连接是可靠的。

在建立连接的过程中,你和你的朋友之间需要交换一些信息,例如你的 IP 地址、你的朋友的端口号等。

如果你和你的朋友之间没有交换这些信息,就无法保证连接是可靠的。 进行三次握手的过程中,你和你的朋友之间交换了三个报文,这样就可以确保你和你的朋友之间的连接是可靠的。

10、 讲一下浏览器从接收到一个 URL 到最后展示出页面,经历了哪些过程。

1、在浏览器地址栏中输入 URL

2、DNS 域名解析,获得域名相对应的 IP 地址。

当我们在浏览器输入如同 www.baidu.com 的时候,其实这些网站的名字都是微克简化人们的记忆来命名 的 ,计算机其实并不认识这个东西。浏览器只认识 IP 地址,所以当输入域名地址的时候,浏览器首先会从浏览 器缓存中是否存在相应的域名、IP 对应关系,如果有则向这个 IP 地址发送请求,如果没有则向系统缓存–路由 缓存中查找,如果都没有,再去 DNS 服务器中找 IP。

3、浏览器向服务器发起 TCP 连接,与浏览器建立 TCP 三次握手

4、建立连接成功后,浏览器向服务器发送 HTTP 请求,请求数据包。HTTP 请求是由三部分组成:请求行、请求 报头和请求正文。

5、服务器处理收到的请求(可以看成是向 Servlet|Action 等控制层发送内容,CGI 接收内容后会执行相应操作)

6、返回相应结果至浏览器(会有相应状态码返回,例如 404)

7、关闭 TCP 连接

8、浏览器解析渲染页面

浏览器在收到 HTML、CSS、JS 文件后,就需要进行渲染。

浏览器是一个边解析边渲染的过程。首先浏览器解析 HTML 文件构建 DOM 树,然后解析 CSS 文件构 建渲染树,等到渲染树构建完成后,浏览器开始布局渲染树并将其绘制到屏幕上。这个过程比较复杂,涉及到 两个概念:reflow(回流)和 repain(重绘)。

DOM 节点中的各个元素是以盒模型的形式存在,这些都需要浏 览器去计算其位置和大小等,这个过程称为 reflow;当盒模型的位置、大小以及其他属性,如颜色、字体等确 定下来之后,浏览器便开始绘制内容,这个过程称为 repain。页面在首次加载时必然会经历 reflow 和 repain。 reflow 和 repain 过程是非常消耗性能的,尤其是在移动设备上,它破坏用户体验,有时会造成页面卡顿。所以 我们应该尽可能的减少 reflow 和 repain。

JS 的解析是由浏览器中的 JS 解析引擎完成的。

浏览器在解析过程中,如果遇到请求外部资源时,如图像、JS 等。浏览器将重复 1-6 过程下载该资源。 请求过程是异步的,并不会影响 HTML 文档进行加载,但是当文档加载过程中遇到 JS 文件,HTML 文档会挂 起渲染过程,不仅要等到文档中 JS 文件加载完毕还要等待解析执行完毕,才会继续 HTML 的渲染过程。原因是 因为 JS 有可能修改 DOM 结构,这就意味着 JS 执行完成前,后续所有资源的下载是没有必要的,这就是 JS 阻 塞后续资源下载的根本原因。CSS 文件的加载不影响 JS 文件的加载,但是却会影响 JS 文件的执行。JS 代码执 行前浏览器必须保证 CSS 文件已经下载并加载完毕。

11、http 和 https 的区别

HTTP:是互联网上应用最为广泛的一种网络协议,是一个客户端和服务器端请求和应答的标准(TCP),用于从 WWW 服务器传输超文本到本地浏览器的传输协议,它可以使浏览器更加高效,使网络传输减少。

HTTPS:是以安全为目标的 HTTP 通道,简单讲是 HTTP 的安全版,即 HTTP 下加入 SSL 层,HTTPS 的安全 基础是 SSL,因此加密的详细内容就需要 SSL。作用主要分为两种: 建立一个信息安全通道,来保证数据传输的 安全以及就是确认网站的真实性。

1、https 协议需要到 ca 申请证书,一般免费证书较少,因而需要一定费用。

2、http 是超文本传输协议,信息是明文传输,https 则是具有安全性的 ssl 加密传输协议。

3、http 和 https 使用的是完全不同的连接方式,用的端口也不一样,前者是 80,后者是 443。

4、http 的连接很简单,是无状态的;HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、身份认证的网 络协议,比 http 协议安全。

HTTPHTTPS
不安全的协议,数据传输过程中不加密,存在被窃取或篡改的风险安全的协议,数据传输过程中采用加密技术,可以保证数据的安全性,还可以防止网站被伪造,保证网站的真实性
适用于不涉及敏感信息的网站适用于涉及到敏感信息的网站,例如网上购物、网上银行等
网站的真实性无法保证网站的真实性无法保证
不需要额外的安全措施需要额外的安全措施,例如证书认证、加密密钥管理等

12、 http 的请求有哪些,状态码 502 和 504 有什么区别

image-20230817200141912

注:在网络协议中,上游服务器指的是发送数据的服务器。例如,在 HTTP 协议中,如果客户端向服务器发送了一个请求,那么服务器就是上游服务器。如果服务器向客户端发送了一个响应,那么客户端就是上游服务器。 在网络协议中,上游服务器和下游服务器是相对的概念。上游服务器指的是发送数据的服务器,下游服务器指的是接收数据的服务器。例如,在 HTTP 协议中,如果客户端向服务器发送了一个请求,那么客户端就是下游服务器。如果服务器向客户端发送了一个响应,那么客户端就是上游服务器。

13、 http1.1 和 1.0 的区别

HTTP1.1 和 HTTP1.0 是两个版本的 HTTP 协议,它们的主要区别在于以下几个方面:

  1. 支持持久连接:HTTP1.1 支持持久连接,可以在一个连接中处理多个请求和响应,而 HTTP1.0 不支持持久连接,每个请求和响应都需要新建一个连接。
  2. 支持多线程:HTTP1.1 支持多线程,可以同时处理多个请求和响应,而 HTTP1.0 不支持多线程,只能逐个处理请求和响应。
  3. 支持缓存:HTTP1.1 支持缓存,可以利用缓存来提高服务器和客户端的性能,而 HTTP1.0 不支持缓存。
  4. 支持压缩:HTTP1.1 支持压缩,可以将请求和响应的数据进行压缩,减少传输的数据量,而 HTTP1.0 不支持压缩。
  5. 支持更多的请求方法:HTTP1.1 支持更多的请求方法,例如 PUT、POST、DELETE 等,而 HTTP1.0 只支持 GET 和 POST 请求方法。 总之,HTTP1.1 相比 HTTP1.0 在性能、功能、可扩展性等方面都有所提升。

14、 304 状态码有什么含义?

状态码 304 表示客户端发送的请求没有被修改,服务器可以使用缓存中的资源。

还可以防止网站被伪造,保证网站的真实性 |
| 适用于不涉及敏感信息的网站 | 适用于涉及到敏感信息的网站,例如网上购物、网上银行等 |
| 网站的真实性无法保证 | 网站的真实性无法保证 |
| 不需要额外的安全措施 | 需要额外的安全措施,例如证书认证、加密密钥管理等 |

12、 http 的请求有哪些,状态码 502 和 504 有什么区别
在这里插入图片描述

注:在网络协议中,上游服务器指的是发送数据的服务器。例如,在 HTTP 协议中,如果客户端向服务器发送了一个请求,那么服务器就是上游服务器。如果服务器向客户端发送了一个响应,那么客户端就是上游服务器。 在网络协议中,上游服务器和下游服务器是相对的概念。上游服务器指的是发送数据的服务器,下游服务器指的是接收数据的服务器。例如,在 HTTP 协议中,如果客户端向服务器发送了一个请求,那么客户端就是下游服务器。如果服务器向客户端发送了一个响应,那么客户端就是上游服务器。

13、 http1.1 和 1.0 的区别

HTTP1.1 和 HTTP1.0 是两个版本的 HTTP 协议,它们的主要区别在于以下几个方面:

  1. 支持持久连接:HTTP1.1 支持持久连接,可以在一个连接中处理多个请求和响应,而 HTTP1.0 不支持持久连接,每个请求和响应都需要新建一个连接。
  2. 支持多线程:HTTP1.1 支持多线程,可以同时处理多个请求和响应,而 HTTP1.0 不支持多线程,只能逐个处理请求和响应。
  3. 支持缓存:HTTP1.1 支持缓存,可以利用缓存来提高服务器和客户端的性能,而 HTTP1.0 不支持缓存。
  4. 支持压缩:HTTP1.1 支持压缩,可以将请求和响应的数据进行压缩,减少传输的数据量,而 HTTP1.0 不支持压缩。
  5. 支持更多的请求方法:HTTP1.1 支持更多的请求方法,例如 PUT、POST、DELETE 等,而 HTTP1.0 只支持 GET 和 POST 请求方法。 总之,HTTP1.1 相比 HTTP1.0 在性能、功能、可扩展性等方面都有所提升。

14、 304 状态码有什么含义?

状态码 304 表示客户端发送的请求没有被修改,服务器可以使用缓存中的资源。

这个状态码通常在客户端发送的请求中包含了 “If-Modified-Since” 或 “If-None-Match” 头,服务器会根据这些头来判断客户端请求的资源是否已经修改,如果没有修改,就会返回状态码 304,表示客户端可以使用缓存中的资源。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值