Socket网络编程(三)——TCP快速入门

概述

  1. TCP是什么
    英语:Transmission Control Protocol,缩写为 TCP
    TCP是传输控制协议;是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793定义
    与UDP一样完成第四层传输层所指定的功能与职责

  2. TCP的机制
    三次握手、四次挥手
    具有校验机制、可靠、数据传输稳定

  3. TCP能做什么
    聊天消息传输、推送
    单人语音、视频聊天等
    几乎UDP能做的都能做,但需要考虑复杂性、性能问题
    限制:无法进行广播,多播等操作

TCP连接可靠性

1. 三次握手过程

20240228-020352-Ph.png
三次握手的过程包括:

第一次握手:客户端发送SYN包,携带自己的序列号。
第二次握手:服务器端回复SYN+ACK包,确认收到客户端的SYN包,并携带自己的序列号。
第三次握手:客户端回复ACK包,确认收到的SYN+ACK包,完成握手过程。

这个过程确保了后续数据的可靠传输和完整性,保障了网络通信的稳定性和可靠性。

2. 四次挥手过程

20240228-020858-ym.png
四次挥手是TCP协议中用于终止连接的过程,这个过程涉及到客户端和服务端之间发送四个数据包。由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。在四次挥手中,首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭。1234

以下是四次挥手的详细步骤:

第一次挥手:客户端发送一个FIN报文,用来关闭客户端到服务器的数据传送。此时客户端进入FIN_WAIT_1状态.
第二次挥手:服务器收到FIN报文后,发送一个ACK报文作为响应,确认序号为收到序号加1。此时服务器进入CLOSE_WAIT状态。
第三次挥手:服务器完成数据发送任务后,发送一个FIN报文,用来关闭服务器到客户端的数据传送。此时服务器进入LAST_ACK状态。
第四次挥手:客户端收到FIN报文后,发送一个ACK报文作为响应,确认序号为收到序号加1。此时客户端进入TIME_WAIT状态。客户端在等待一段时间(通常为2MSL,即最大报文段寿命)后,如果没有收到服务器的任何响应,则进入CLOSE状态。

这个过程确保了双方都能正确关闭连接,避免了数据丢失。

3. 为什么挥手需要四次?

由于 TCP 的半关闭(half-close)特性,TCP 提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力。

任何一方都可以在数据传送结束后发出连接释放的通知,待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候,则发出连接释放通知,对方确认后就完全关闭了TCP连接。
通俗的来说,两次握手就可以释放一端到另一端的 TCP 连接,完全释放连接一共需要四次握手。

可以用下面这个例子理解:

举个例子:A 和 B 打电话,通话即将结束后,A 说 “我没啥要说的了”,B 回答 “我知道了”,于是 A 向 B 的连接释放了。但是 B 可能还会有要说的话,于是 B 可能又巴拉巴拉说了一通,最后 B 说“我说完了”,A 回答“知道了”,于是 B 向 A 的连接释放了,这样整个通话就结束了。

传输可靠性

  1. 排序、顺序发送、顺序组装
  2. 丢弃、超时
  3. 重发机制-定时器

数据传输示意图
20240228-022539-2k.png

TCP核心API

socket():创建—个Socket
bind():绑定一个Socket到一个本地地址和端口上
connect():连接到远程套接字
accept():接受一个新的连接
write():把数据写入到Socket输出流
read():从Socket输入流读取数据

客户端Socket创建流程:
20240228-013916-lO.png

服务端ServerSocket创建流程:
20240228-014016-IO.png

Socket和进程的关系:
20240228-014241-Xq.png

TCP传输初始化配置&建立连接

  1. 初始化服务器TCP链接监听
  2. 初始化客户端发起链接操作
  3. 服务器Socket链接处理

客户端创建Socket建立连接

  1. 创建Socket基本方法和构造函数
private static Socket createSocket() throws IOException {
        /*
        // 无代理模式,等效于空构造函数
        Socket socket = new Socket(Proxy.NO_PROXY);

        // 新建一份具有HTTP代理的套接字,传输数据将通过www.baidu.com:8080端口转发
        Proxy proxy = new Proxy(Proxy.Type.HTTP,
                new InetSocketAddress(Inet4Address.getByName("www.baidu.com"), 8800));
        socket = new Socket(proxy);

        // 新建一个套接字,并且直接链接到本地20000的服务器上
        socket = new Socket("localhost", PORT);

        // 新建一个套接字,并且直接链接到本地20000的服务器上
        socket = new Socket(Inet4Address.getLocalHost(), PORT);

        // 新建一个套接字,并且直接链接到本地20000的服务器上,并且绑定到本地20001端口上
        socket = new Socket("localhost", PORT, Inet4Address.getLocalHost(), LOCAL_PORT);
        socket = new Socket(Inet4Address.getLocalHost(), PORT, Inet4Address.getLocalHost(), LOCAL_PORT);
        */

        Socket socket = new Socket();
        // 绑定到本地20001端口
        socket.bind(new InetSocketAddress(Inet4Address.getLocalHost(), LOCAL_PORT));

        return socket;
    }
  1. 初始化Socket的基本配置
private static void initSocket(Socket socket) throws SocketException {
        // 设置读取超时时间为2秒
        socket.setSoTimeout(2000);

        // 是否复用未完全关闭的Socket地址,对于指定bind操作后的套接字有效
        socket.setReuseAddress(true);

        // 是否开启Nagle算法
        socket.setTcpNoDelay(true);

        // 是否需要在长时无数据响应时发送确认数据(类似心跳包),时间大约为2小时
        socket.setKeepAlive(true);

        // 对于close关闭操作行为进行怎样的处理;默认为false,0
        // false、0:默认情况,关闭时立即返回,底层系统接管输出流,将缓冲区内的数据发送完成
        // true、0:关闭时立即返回,缓冲区数据抛弃,直接发送RST结束命令到对方,并无需经过2MSL等待
        // true、200:关闭时最长阻塞200毫秒,随后按第二情况处理
        socket.setSoLinger(true, 20);

        // 是否让紧急数据内敛,默认false;紧急数据通过 socket.sendUrgentData(1);发送
        socket.setOOBInline(true);

        // 设置接收发送缓冲器大小
        socket.setReceiveBufferSize(64 * 1024 * 1024);
        socket.setSendBufferSize(64 * 1024 * 1024);

        // 设置性能参数:短链接,延迟,带宽的相对重要性
        socket.setPerformancePreferences(1, 1, 0);
    }
  1. 客户端手动连接
public static void main(String[] args) throws IOException {
        Socket socket = createSocket();

        initSocket(socket);

        // 链接到本地20000端口,超时时间3秒,超过则抛出超时异常
        socket.connect(new InetSocketAddress(Inet4Address.getLocalHost(), PORT), 3000);

        System.out.println("已发起服务器连接,并进入后续流程~");
        System.out.println("客户端信息:" + socket.getLocalAddress() + " P:" + socket.getLocalPort());
        System.out.println("服务器信息:" + socket.getInetAddress() + " P:" + socket.getPort());

        try {
            // 发送接收数据
            todo(socket);
        } catch (Exception e) {
            System.out.println("异常关闭");
        }

        // 释放资源
        socket.close();
        System.out.println("客户端已退出~");

    }

服务端创建ServerSocket监听连接

  1. 创建ServerSocket方法
private static ServerSocket createServerSocket() throws IOException {
        // 创建基础的ServerSocket
        ServerSocket serverSocket = new ServerSocket();

        // 绑定到本地端口20000上,并且设置当前可允许等待链接的队列为50个
        //serverSocket = new ServerSocket(PORT);

        // 等效于上面的方案,队列设置为50个
        //serverSocket = new ServerSocket(PORT, 50);

        // 与上面等同
        // serverSocket = new ServerSocket(PORT, 50, Inet4Address.getLocalHost());

        return serverSocket;
    }
  1. 初始化ServerSocket配置
private static void initServerSocket(ServerSocket serverSocket) throws IOException {
        // 是否复用未完全关闭的地址端口
        serverSocket.setReuseAddress(true);

        // 等效Socket#setReceiveBufferSize
        serverSocket.setReceiveBufferSize(64 * 1024 * 1024);

        // 设置serverSocket#accept超时时间
        // serverSocket.setSoTimeout(2000);

        // 设置性能参数:短链接,延迟,带宽的相对重要性
        serverSocket.setPerformancePreferences(1, 1, 1);
    }
  1. 监听服务端固定端口,并且接受客户端建立连接
private static final int PORT = 20000;

    public static void main(String[] args) throws IOException {
        ServerSocket server = createServerSocket();

        initServerSocket(server);

        // 绑定到本地端口上
        server.bind(new InetSocketAddress(Inet4Address.getLocalHost(), PORT), 50);


        System.out.println("服务器准备就绪~");
        System.out.println("服务器信息:" + server.getInetAddress() + " P:" + server.getLocalPort());


        // 等待客户端连接
        for (; ; ) {
            // 得到客户端
            Socket client = server.accept();
            // 客户端构建异步线程
            ClientHandler clientHandler = new ClientHandler(client);
            // 启动线程
            clientHandler.start();
        }
    }

ServerSocket 和 Socket的关系

serverSocket 和 Socket 都是 Java 中用于网络通信的类,但它们有不同的作用。

Socket 是用于建立连接的类,它可以让客户端和服务器之间相互通信。

ServerSocket 是用于监听连接请求的类,它在服务器端等待客户端的连接请求,并在连接成功后与客户端建立对应的 Socket 连接。

具体工作原理可以简单描述为:

当客户端与服务器建立连接时,客户端通过创建 Socket 对象实现,服务器端则通过创建 ServerSocket 对象实现。
客户端向服务器发送连接请求,请求中包含了要连接的服务器地址和端口号。
ServerSocket 接收到连接请求后,会生成一个 Socket 对象与客户端连接,并返回此连接对应的 Socket 对象。
客户端和服务器之间即可通过这个连接进行通信。

总之,ServerSocket 是用于监听连接请求的类,而 Socket 则是用于实现连接并进行通信的类。

Socket基本数据类型传输

基础类型数据传输

  • byte、 char、 short
  • boolean、 int、 long
  • float、 double、 string

客户端数据传输

  1. 客户端使用Socket输出流将客户端的数据传输出去
  2. 建立连接之后将各种数据类型的数据转换成byte字节数组,然后通过ByteBuffer工具统一放入字节流
    (这里使用到了一个ByteBuffer工具,这是一个字节缓冲区,后面会详细介绍)
  3. 最后通过Socket的输出流把数据发送给服务端

代码如下:

private static void todo(Socket client) throws IOException {
        // 得到Socket输出流
        OutputStream outputStream = client.getOutputStream();


        // 得到Socket输入流
        InputStream inputStream = client.getInputStream();
        byte[] buffer = new byte[256];
        ByteBuffer byteBuffer = ByteBuffer.wrap(buffer);

        // byte
        byteBuffer.put((byte) 126);

        // char
        char c = 'a';
        byteBuffer.putChar(c);

        // int
        int i = 2323123;
        byteBuffer.putInt(i);

        // bool
        boolean b = true;
        byteBuffer.put(b ? (byte) 1 : (byte) 0);

        // Long
        long l = 298789739;
        byteBuffer.putLong(l);


        // float
        float f = 12.345f;
        byteBuffer.putFloat(f);


        // double
        double d = 13.31241248782973;
        byteBuffer.putDouble(d);

        // String
        String str = "Hello你好!";
        byteBuffer.put(str.getBytes());

        // 发送到服务器
        outputStream.write(buffer, 0, byteBuffer.position() + 1);

        // 接收服务器返回
        int read = inputStream.read(buffer);
        System.out.println("收到数量:" + read);

        // 资源释放
        outputStream.close();
        inputStream.close();
    }

服务端数据接收

  1. 获取监听到连接的客户端Socket套接字流
  2. 将流读取出来,转换成ByteBuffer
  3. 最后根据不同的数据类型将ByteBuffer中的数据读取并且打印出来
/**
     * 客户端消息处理
     */
    private static class ClientHandler extends Thread {
        private Socket socket;

        ClientHandler(Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            super.run();
            System.out.println("新客户端连接:" + socket.getInetAddress() +
                    " P:" + socket.getPort());

            try {
                // 得到套接字流
                OutputStream outputStream = socket.getOutputStream();
                InputStream inputStream = socket.getInputStream();

                byte[] buffer = new byte[256];
                int readCount = inputStream.read(buffer);
                ByteBuffer byteBuffer = ByteBuffer.wrap(buffer, 0, readCount);

                // byte
                byte be = byteBuffer.get();

                // char
                char c = byteBuffer.getChar();

                // int
                int i = byteBuffer.getInt();

                // bool
                boolean b = byteBuffer.get() == 1;

                // Long
                long l = byteBuffer.getLong();

                // float
                float f = byteBuffer.getFloat();

                // double
                double d = byteBuffer.getDouble();

                // String
                int pos = byteBuffer.position();
                String str = new String(buffer, pos, readCount - pos - 1);

                System.out.println("收到数量:" + readCount + " 数据:"
                        + be + "\n"
                        + c + "\n"
                        + i + "\n"
                        + b + "\n"
                        + l + "\n"
                        + f + "\n"
                        + d + "\n"
                        + str + "\n");

                outputStream.write(buffer, 0, readCount);
                outputStream.close();
                inputStream.close();

            } catch (Exception e) {
                System.out.println("连接异常断开");
            } finally {
                // 连接关闭
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            System.out.println("客户端已退出:" + socket.getInetAddress() +
                    " P:" + socket.getPort());

        }
    }

TCP连接客户端、服务端完整代码

服务端代码:

package cn.kt.SocketDemoL4;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Inet4Address;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.ByteBuffer;

public class Server {
    private static final int PORT = 20000;

    public static void main(String[] args) throws IOException {
        ServerSocket server = createServerSocket();

        initServerSocket(server);

        // 绑定到本地端口上
        server.bind(new InetSocketAddress(Inet4Address.getLocalHost(), PORT), 50);


        System.out.println("服务器准备就绪~");
        System.out.println("服务器信息:" + server.getInetAddress() + " P:" + server.getLocalPort());


        // 等待客户端连接
        for (; ; ) {
            // 得到客户端
            Socket client = server.accept();
            // 客户端构建异步线程
            ClientHandler clientHandler = new ClientHandler(client);
            // 启动线程
            clientHandler.start();
        }
    }

    private static ServerSocket createServerSocket() throws IOException {
        // 创建基础的ServerSocket
        ServerSocket serverSocket = new ServerSocket();

        // 绑定到本地端口20000上,并且设置当前可允许等待链接的队列为50个
        //serverSocket = new ServerSocket(PORT);

        // 等效于上面的方案,队列设置为50个
        //serverSocket = new ServerSocket(PORT, 50);

        // 与上面等同
        // serverSocket = new ServerSocket(PORT, 50, Inet4Address.getLocalHost());

        return serverSocket;
    }

    private static void initServerSocket(ServerSocket serverSocket) throws IOException {
        // 是否复用未完全关闭的地址端口
        serverSocket.setReuseAddress(true);

        // 等效Socket#setReceiveBufferSize
        serverSocket.setReceiveBufferSize(64 * 1024 * 1024);

        // 设置serverSocket#accept超时时间
        // serverSocket.setSoTimeout(2000);

        // 设置性能参数:短链接,延迟,带宽的相对重要性
        serverSocket.setPerformancePreferences(1, 1, 1);
    }

    /**
     * 客户端消息处理
     */
    private static class ClientHandler extends Thread {
        private Socket socket;

        ClientHandler(Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            super.run();
            System.out.println("新客户端连接:" + socket.getInetAddress() +
                    " P:" + socket.getPort());

            try {
                // 得到套接字流
                OutputStream outputStream = socket.getOutputStream();
                InputStream inputStream = socket.getInputStream();

                byte[] buffer = new byte[256];
                int readCount = inputStream.read(buffer);
                ByteBuffer byteBuffer = ByteBuffer.wrap(buffer, 0, readCount);

                // byte
                byte be = byteBuffer.get();

                // char
                char c = byteBuffer.getChar();

                // int
                int i = byteBuffer.getInt();

                // bool
                boolean b = byteBuffer.get() == 1;

                // Long
                long l = byteBuffer.getLong();

                // float
                float f = byteBuffer.getFloat();

                // double
                double d = byteBuffer.getDouble();

                // String
                int pos = byteBuffer.position();
                String str = new String(buffer, pos, readCount - pos - 1);

                System.out.println("收到数量:" + readCount + " 数据:"
                        + be + "\n"
                        + c + "\n"
                        + i + "\n"
                        + b + "\n"
                        + l + "\n"
                        + f + "\n"
                        + d + "\n"
                        + str + "\n");

                outputStream.write(buffer, 0, readCount);
                outputStream.close();
                inputStream.close();

            } catch (Exception e) {
                System.out.println("连接异常断开");
            } finally {
                // 连接关闭
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            System.out.println("客户端已退出:" + socket.getInetAddress() +
                    " P:" + socket.getPort());

        }
    }
}

客户端代码

package cn.kt.SocketDemoL4;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Inet4Address;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;
import java.nio.ByteBuffer;

public class Client {
    private static final int PORT = 20000;
    private static final int LOCAL_PORT = 20001;

    public static void main(String[] args) throws IOException {
        Socket socket = createSocket();

        initSocket(socket);

        // 链接到本地20000端口,超时时间3秒,超过则抛出超时异常
        socket.connect(new InetSocketAddress(Inet4Address.getLocalHost(), PORT), 3000);

        System.out.println("已发起服务器连接,并进入后续流程~");
        System.out.println("客户端信息:" + socket.getLocalAddress() + " P:" + socket.getLocalPort());
        System.out.println("服务器信息:" + socket.getInetAddress() + " P:" + socket.getPort());

        try {
            // 发送接收数据
            todo(socket);
        } catch (Exception e) {
            System.out.println("异常关闭");
        }

        // 释放资源
        socket.close();
        System.out.println("客户端已退出~");

    }

    private static Socket createSocket() throws IOException {
        /*
        // 无代理模式,等效于空构造函数
        Socket socket = new Socket(Proxy.NO_PROXY);

        // 新建一份具有HTTP代理的套接字,传输数据将通过www.baidu.com:8080端口转发
        Proxy proxy = new Proxy(Proxy.Type.HTTP,
                new InetSocketAddress(Inet4Address.getByName("www.baidu.com"), 8800));
        socket = new Socket(proxy);

        // 新建一个套接字,并且直接链接到本地20000的服务器上
        socket = new Socket("localhost", PORT);

        // 新建一个套接字,并且直接链接到本地20000的服务器上
        socket = new Socket(Inet4Address.getLocalHost(), PORT);

        // 新建一个套接字,并且直接链接到本地20000的服务器上,并且绑定到本地20001端口上
        socket = new Socket("localhost", PORT, Inet4Address.getLocalHost(), LOCAL_PORT);
        socket = new Socket(Inet4Address.getLocalHost(), PORT, Inet4Address.getLocalHost(), LOCAL_PORT);
        */

        Socket socket = new Socket();
        // 绑定到本地20001端口
        socket.bind(new InetSocketAddress(Inet4Address.getLocalHost(), LOCAL_PORT));

        return socket;
    }

    private static void initSocket(Socket socket) throws SocketException {
        // 设置读取超时时间为2秒
        socket.setSoTimeout(2000);

        // 是否复用未完全关闭的Socket地址,对于指定bind操作后的套接字有效
        socket.setReuseAddress(true);

        // 是否开启Nagle算法
        socket.setTcpNoDelay(true);

        // 是否需要在长时无数据响应时发送确认数据(类似心跳包),时间大约为2小时
        socket.setKeepAlive(true);

        // 对于close关闭操作行为进行怎样的处理;默认为false,0
        // false、0:默认情况,关闭时立即返回,底层系统接管输出流,将缓冲区内的数据发送完成
        // true、0:关闭时立即返回,缓冲区数据抛弃,直接发送RST结束命令到对方,并无需经过2MSL等待
        // true、200:关闭时最长阻塞200毫秒,随后按第二情况处理
        socket.setSoLinger(true, 20);

        // 是否让紧急数据内敛,默认false;紧急数据通过 socket.sendUrgentData(1);发送
        socket.setOOBInline(true);

        // 设置接收发送缓冲器大小
        socket.setReceiveBufferSize(64 * 1024 * 1024);
        socket.setSendBufferSize(64 * 1024 * 1024);

        // 设置性能参数:短链接,延迟,带宽的相对重要性
        socket.setPerformancePreferences(1, 1, 0);
    }

    private static void todo(Socket client) throws IOException {
        // 得到Socket输出流
        OutputStream outputStream = client.getOutputStream();


        // 得到Socket输入流
        InputStream inputStream = client.getInputStream();
        byte[] buffer = new byte[256];
        ByteBuffer byteBuffer = ByteBuffer.wrap(buffer);

        // byte
        byteBuffer.put((byte) 126);

        // char
        char c = 'a';
        byteBuffer.putChar(c);

        // int
        int i = 2323123;
        byteBuffer.putInt(i);

        // bool
        boolean b = true;
        byteBuffer.put(b ? (byte) 1 : (byte) 0);

        // Long
        long l = 298789739;
        byteBuffer.putLong(l);


        // float
        float f = 12.345f;
        byteBuffer.putFloat(f);


        // double
        double d = 13.31241248782973;
        byteBuffer.putDouble(d);

        // String
        String str = "Hello你好!";
        byteBuffer.put(str.getBytes());

        // 发送到服务器
        outputStream.write(buffer, 0, byteBuffer.position() + 1);

        // 接收服务器返回
        int read = inputStream.read(buffer);
        System.out.println("收到数量:" + read);

        // 资源释放
        outputStream.close();
        inputStream.close();
    }
}

执行结果

  1. 运行服务端
    20240228-151715-ZN.png

  2. 运行客户端
    20240228-151741-r9.png

  3. 服务端接收到消息并输出

20240228-151758-6Y.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/415951.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

阿里云A10推理qwen

硬件配置 vCPU:32核 内存:188 GiB 宽带:5 Mbps GPU:NVIDIA A10 24Gcuda 安装 wget https://developer.download.nvidia.com/compute/cuda/12.1.0/local_installers/cuda-repo-rhel7-12-1-local-12.1.0_530.30.02-1.x86_64.rpm s…

php docx,pptx,excel表格上传阿里云,腾讯云存储后截取第一页生成缩略图

php把word转图片的方法:首先给服务器安装libreoffice;然后使用exec函数来调用命令行操作;最后通过“exec(“soffice --headless --invisible…””方法把word转图片即可。 服务器环境:centos7 *集成环境:宝塔 我们开始给服务器安装libreoffice 直接执行下面的代码就可以…

【Kafka系列 06】Kafka Producer源码解析

温馨提示:本文基于 Kafka 2.3.1 版本。 一、Kafka Producer 原理图 生产者的 API 使用还是比较简单,创建一个 ProducerRecord 对象(这个对象包含目标主题和要发送的内容,当然还可以指定键以及分区),然后调…

数据库之ACID

一、ACID **原子性(Atomicity):**即事务是不可分割的最小工作单元,事务内的操作要么全做,要么全不做,不能只做一部分; 一致性(Consistency):在事务执行前数据…

微服务API网关---APISIX

最近在做微服务调研,看到了apisix这个网关,于是进行了初步了解一下。 微服务是指,将大型应用分解成多个独立的组件,其中每个组件都各自的负责对应项目。 系统的架构大致经历了:单体应用架构–> SOA架构 -->微服务…

DTD、XML阐述、XML的两种文档类型约束和DTD的使用

目录 ​编辑 一、DTD 什么是DTD? 为什么要使用 DTD? 内部 DTD 声明 具有内部 DTD 的 XML 文档 外部 DTD 声明 引用外部 DTD 的 XML 文档 二、XML 什么是XML? XML 不执行任何操作 XML 和 HTML 之间的区别 XML 不使用预定义的标记…

Mallox勒索病毒的最新威胁:如何恢复您的数据?

引言: 在当今数字化时代,网络安全威胁层出不穷,而勒索软件(Ransomware)是其中最为恶劣的一种形式之一。而.Mallox勒索病毒则是近期备受关注的一种勒索软件,其深受全球各地用户的困扰。那么,让我…

postman测试接口

1、postman测试接口 (1)首先安装postman 下载地址:Download Postman | Get Started for Free 选择对应版本下载,然后安装即可 (2)使用postman发送请求 比如以下这个请求例子: 使用postman发…

Qt CMake 国际化相关配置

文章目录 更新ts文件发布ts文件 本来用qmake使用pro文件很简单的一件事,结果用cmake折腾了半天。 何必呢~ 参考:QT6.3 CMake 多语言切换 这是我的 cmake_minimum_required(VERSION 3.16)project(testQml3_6 VERSION 0.1 LANGUAGES CXX)set(CMAKE_AUTO…

mini-spring|关于Bean对象作用域以及FactoryBean的实现和使用

需求 FactoryBean 直接配置FactoryBean 获取FactoryBean中的Bean对象 FactoryBean的getObject方法通过反射获取Bean对象 由此省去对实体Dao类的定义 解决方法 对外提供一个可以二次从 FactoryBean 的 getObject 方法中获取对象的功能即可 整体架构 整个的实现过程包括了两部…

Python matplotlib

目录 1、安装 matplotlib 2、绘制折线图 修改标签文字和线条粗细 校正图形 3、绘制散点图 绘制单点 绘制一系列点 自动计算数据 删除数据点的轮廓 自定义颜色 使用颜色映射 自动保存图表 4、随机漫步 创建 RandomWalk() 类 选择方向 绘制随机漫步图 给点着色 …

Groovy - 大数据共享搜索配置

数据共享搜索列中配置了搜索列,相应的数据共享接口中也需要支持根据配置的字段搜索,配置实体时,支持搜索的入参code必须是searchKeys,且接口应该是需要支持分页(入参必须是 current、pageSize)的。current …

【Excel PDF 系列】iText 库直接实现表格 PDF

你知道的越多,你不知道的越多 点赞再看,养成习惯 如果您有疑问或者见解,欢迎指教: 企鹅:869192208 文章目录 前言生成表格 PDF 效果引入 pom 配置代码实现定义 CreateExcelToPdfModel 对象主方法 前言 最近遇到生成 E…

QEMU之内存虚拟化

内存虚拟化方案 最直观的方案,将QEMU进程的虚拟地址空间的一部分作为虚拟机的物理地址。但该方案有一个问题: 在物理机上,CPU对内存的访问在保护模式下是通过分段分页实现的,在该模式下,CPU访问时使用的是虚拟地址&am…

9 款顶级 iPhone 系统修复软件,可修复各种 iPhone 软件问题

iOS的封闭性和纯粹性仍然无法让iPhone免受潜在风险的影响。iPhone 存在常见问题,包括iPhone/iPad 卡住 Apple 徽标、iOS 更新无法充电问题、iPhone 耳机问题等等。 通常,在这种情况下,您的 iPhone 数据可能无法访问,甚至面临很大…

LeetCode 刷题 [C++] 第148题.排序链表

题目描述 给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。 题目分析 根据题意,可以使用归并排序来对链表进行排序。归并排序是基于分治的思想,比较容易实现的就是自顶向下的递归方式来实现。 先找出链表的中点&#x…

【系统分析师】-软件工程

1、信息系统的生命周期 1、四阶段划分 立项阶段:企业全局、形成概念、需求分析。包含【系统分析师】-系统规划-CSDN博客开发阶段:总体规划--系统分析--设计--实施--验收运维阶段:通过验收、移交之后消亡阶段:更新改造、功能扩展…

【MySQL】深入解析 Buffer Pool 缓冲池

文章目录 1、前置知识1.1、Buffer Pool介绍1.2、后台线程1.2.1、Master Thread1.2.2、IO Thread1.2.3、Purge Thread1.2.4、Page Cleaner Thread 1.3、重做日志缓冲池 2、Buffer Pool 组成2.1、数据页2.2、索引页2.3、插入缓冲2.4、锁空间2.5、数据字典2.6、自适应哈希索引 3、…

数据库JSON类型到映射JAVA上

Mysql存放JSON数据如何映射JAVA实体类 概述:最近写在写SKU模块中,需要表中字段存放JSON类型数据,mybatis-plus在查询的时候如何跟JSON类型所匹配呢?再次记录一下。 直接上代码,后面有解释到底如何映射上的。 Mysql表…

MySql-多表设计-一对一

目录 一对一 一对一 一对一关系表在实际开发中应用起来比较简单,通常是用来做单表的拆分,也就是将一张大表拆分成两张小表,将大表中的一些基础字段放在一张表当中,将其他的字段放在另外一张表当中,以此来提高数据的操…