TCP流套接字编程

news2024/9/22 21:33:42

ServerSocket API

ServerSocket 是创建TCP服务端Socket的API。

ServerSocket 构造方法:
 

ServerSocket 方法:
 

Socket API

Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket。不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的.

Socket 构造方法:


 

Socket 方法:


 

 

TCP中的长短连接

TCP发送数据时,需要先建立连接,什么时候关闭连接就决定是短连接还是长连接:
短连接:每次接收到数据并返回响应后,都关闭连接,即是短连接。也就是说,短连接只能一次收发数据。
长连接:不关闭连接,一直保持连接状态,双方不停的收发数据,即是长连接。也就是说,长连接可以多次收发数据。
对比以上长短连接,两者区别如下:

  • 建立连接、关闭连接的耗时:短连接每次请求、响应都需要建立连接,关闭连接;而长连接只需要第一次建立连接,之后的请求、响应都可以直接传输。相对来说建立连接,关闭连接也是要耗时的,长连接效率更高。
  • 主动发送请求不同:短连接一般是客户端主动向服务端发送请求;而长连接可以是客户端主动发送请求,也可以是服务端主动发。
  • 两者的使用场景有不同:短连接适用于客户端请求频率不高的场景,如浏览网页等。长连接适用于客户端与服务端通信频繁的场景,如聊天室,实时游戏等。

扩展了解:
基于BIO(同步阻塞IO)的长连接会一直占用系统资源。对于并发要求很高的服务端系统来说,这样的消耗是不能承受的。

由于每个连接都需要不停的阻塞等待接收数据,所以每个连接都会在一个线程中运行。
一次阻塞等待对应着一次请求、响应,不停处理也就是长连接的特性:一直不关闭连接,不停的处理请求。

实际应用时,服务端一般是基于NIO(即同步非阻塞IO)来实现长连接,性能可以极大的提升。
 

示例一:一发一收(短连接)

以下为一个客户端一次数据发送,和服务端多次数据接收(一次发送一次接收,可以接收多次),即只有客户端请求,但没有服务端响应的示例:

TCP服务端

package net1;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

public class TcpServer {
    private static final int port = 8888;

    public static void main(String[] args) throws IOException {
        ServerSocket server = new ServerSocket(port);
        while (true) {
            System.out.println("------------------------------------");
            System.out.println("等待客户端建立TCP连接...");
            Socket client = server.accept();
            System.out.println("客户端ip :" + client.getInetAddress().getHostAddress());
            System.out.println("客户端port :" + client.getPort());
            System.out.println("接收到客户端请求:>>");
            InputStream inputStream = client.getInputStream();
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
            String line;

            while ((line = bufferedReader.readLine()) != null) {
                System.out.println(line);
            }
            client.close();
        }
    }
}


TCP客户端

package net1;

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;

public class TcpClient {
    private static final String host = "127.0.0.1";
    private static final int port = 8888;

    public static void main(String[] args) throws IOException {
        Socket client = new Socket(host, port);
        OutputStream outputStream = client.getOutputStream();
        PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(outputStream, "utf-8"));
        printWriter.println("Hello I am client");
        printWriter.flush();
        client.close();
    }
}

以上客户端与服务端建立的为短连接,每次客户端发送了TCP报文,及服务端接收了TCP报文后,双方都会关闭连接。

示例二:请求响应(短连接)

构造一个展示服务端本地某个目录的下一级子文件列表的服务

  1. 客户端先接收键盘输入,表示要展示的相对路径(相对BASE_PATH的路径)
  2. 发送请求:使用客户端Socket的输出流发送TCP报文。即输入的相对路径。
  3. 服务端接收并处理请求:使用服务端Socket的输入流来接收请求报文,根据请求的路径,列出下一级子文件及子文件夹。
  4. 服务端返回响应:使用服务端Socket的输出流来发送响应报文。即遍历子文件和子文件夹,每个文件名一行,返回给客户端。
  5. 客户端接收响应:使用客户端Socket的输入流来接收响应报文。简单的打印输出所有的响应内容,即文件列表。

TCP服务端
 

package net1;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class TcpEchoServer {
    private static final int port = 8888;

    private static final String path = "";

    public static void main(String[] args) throws IOException {
        ServerSocket server = new ServerSocket(port);
        while (true) {
            System.out.println("-------------------------------");
            System.out.println("等待客户端建立TCP连接...");
            Socket socket = server.accept();
            System.out.println("客户端ip :" + socket.getInetAddress().getHostAddress());
            System.out.println("客户端port :" + socket.getPort());
            InputStream inputStream = socket.getInputStream();
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
            String request = bufferedReader.readLine();
            System.out.println("客户端请求的文件路径为:" + path + request);
            File dir = new File(path + request);
            File[] files = dir.listFiles();
            OutputStream outputStream = socket.getOutputStream();
            PrintWriter writer = new PrintWriter(new OutputStreamWriter(outputStream, "utf-8"));
            if (files != null) {
                for (File f : files) {
                    writer.println(f.getName());
                }
            }
            writer.flush();
            socket.close();
        }
    }
}


 TCP客户端

package net1;

import java.io.*;
import java.net.Socket;
import java.util.Scanner;

public class TcpEchoClient {
    private static final String host = "127.0.0.1";

    private static final int port = 8888;

    public static void main(String[] args) throws IOException {
        Scanner scanner = new Scanner(System.in);
        while (true) {
            System.out.println("----------------------------------");
            System.out.println("请输入要展示的目录:");
            String request = scanner.nextLine();
            Socket socket = new Socket(host, port);
            OutputStream outputStream = socket.getOutputStream();
            PrintWriter writer = new PrintWriter(new OutputStreamWriter(outputStream, "utf-8"));
            writer.println(request);
            writer.flush();
            System.out.println("接收到服务端响应:");
            InputStream inputStream = socket.getInputStream();
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
            String line = null;
            while ((line = bufferedReader.readLine()) != null) {
                System.out.println(line);
            }
            socket.close();
        }
    }

}

回顾并理解为什么需要协议

以上我们实现的UDP和TCP数据传输,除了UDP和TCP协议外,程序还存在应用层自定义协议,可以想想分别都是什么样的协议格式。
 

对于客户端及服务端应用程序来说,请求和响应,需要约定一致的数据格式:

  • 客户端发送请求和服务端解析请求要使用相同的数据格式。
  • 服务端返回响应和客户端解析响应也要使用相同的数据格式。
  • 请求格式和响应格式可以相同,也可以不同。
  • 约定相同的数据格式,主要目的是为了让接收端在解析的时候明确如何解析数据中的各个字段。
  • 可以使用知名协议(广泛使用的协议格式),如果想自己约定数据格式,就属于自定义协议。
     

封装/分用 vs 序列化/反序列化

一般来说,在网络数据传输中,发送端应用程序,发送数据时的数据转换(如java一般就是将对象转换为某种协议格式),即对发送数据时的数据包装动作来说:

  • 如果是使用知名协议,这个动作也称为封装
  • 如果是使用小众协议(包括自定义协议),这个动作也称为序列化,一般是将程序中的对象转换为特定的数据格式。

接收端应用程序,接收数据时的数据转换,即对接收数据时的数据解析动作来说:

  • 如果是使用知名协议,这个动作也称为分用
  • 如果是使用小众协议(包括自定义协议),这个动作也称为反序列化,一般是基于接收数据特定的格式,转换为程序中的对象

如何设计协议

对于协议来说,重点需要约定好如何解析,一般是根据字段的特点来设计协议:
对于定长的字段:

  • 可以基于长度约定,如int字段,约定好4个字节即可

对于不定长的字段:

  • 可以约定字段之间的间隔符,或最后一个字段的结束符,如换行符间隔,\3符号结束等等
  • 除了该字段“数据”本身,再加一个长度字段,用来标识该“数据”长度;即总共使用两个字段:“数据”字段本身,不定长,需要通过“长度”字段来解析;“长度”字段,标识该“数据”的长度,即用于辅助解析“数据”字段;


示例三:多线程+自定义协议

以下我们将示例二的业务做以下扩展:

  • 提供多种操作:展示目录下文件列表,文件重命名,删除文件,上传文件,下载文件
  • 在不同的操作中,需要抽象出请求和响应的字段,也即是说,要约定客户端服务端统一的请求协议,同时也要约定服务端与客户端统一的响应协议


本示例中的自定义协议

以下为我们TCP请求数据的协议格式,这里简单起见,约定为换行符及结束符:

  1. 请求类型
  2. 操作的文件或目录路径
  3. 数据\3

说明如下:

  • 以上总共包含3个字段,前2个字段需要按换行符读取,最后一个字段需要按结束符读取
  • 请求类型标识是什么操作:展示目录下文件列表,文件重命名,删除文件,上传文件,下载文件
  • 重命名、上传文件操作,需要“数据”字段,其他操作可以置为空字符串
  • “数据”字段为最后一个字段,使用\3结束符,这样在数据本身有换行符也能正确处理

以下为响应数据的协议格式:

  • 状态码(标识是否操作成功)
  • 数据(展示列表时,返回目录下的文件列表,或下载文件的数据)\3


 以下为展示文件列表操作的自定义协议(请求、响应格式)

以下操作将展示服务端根目录下的子文件及子文件夹:

请求数据格式如下:

1

/
\3

响应数据格式如下:

200
\1
\2
\3
\1.txt
\2.txt\3

以下为上传文件操作的自定义协议(请求、响应格式)

需要先在客户端指定上传的服务端目录,及客户端要上传的文件路径,以下操作将会把客户端
Main.java 文件内容上传到服务端根目录 E:/TMP 下的 /1 目录下:



 请求数据格式如下:

4
/1
package org.example;


public class Main {
……略
}\3

 响应数据格式如下:

200
\3

代码实现如下:

先按照约定的请求协议封装请求类:

每个字段为一个属性:操作类型,操作路径,数据
完成服务端解析请求封装:按约定的方式读,先按行读取前2个字段,再按结束符读第3个字段
完成客户端发送请求封装:按约定的方式写,前2个字段按行输出,第3个字段以\3结束

请求类

  • 每个字段为一个属性:操作类型,操作路径,数据
  • 完成服务端解析请求封装:按约定的方式读,先按行读取前2个字段,再按结束符读第3个字段
  • 完成客户端发送请求封装:按约定的方式写,前2个字段按行输出,第3个字段以\3结束
     
package net1;

import java.io.*;
import java.util.ArrayList;
import java.util.List;

public class Request {
    //操作类型:1(展示目录文件列表),2(文件重命名),3(删除文件),4(上传文件),5(下载文件)
    private Integer type;
    private String url;
    private String data;

    //服务端解析请求时:根据约定好的格式来解析
    public static Request serverParse(InputStream inputStream) throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
        Request request = new Request();
        request.type = Integer.parseInt(bufferedReader.readLine());
        request.url = bufferedReader.readLine();
        List<Character> list = new ArrayList<>();
        while (true) {
            char c = (char) bufferedReader.read();
            if (c == '\3') {
                break;
            }
            list.add(c);
        }
        StringBuilder stringBuilder = new StringBuilder();
        for (char c : list) {
            stringBuilder.append(c);
        }
        request.data = stringBuilder.toString();
        return request;
    }

    //客户端发送请求到服务器
    public void clientWrite(OutputStream outputStream) {
        PrintWriter printWriter = new PrintWriter(outputStream);
        printWriter.println(type);
        printWriter.println(url);
        printWriter.write(data + "\3");
        printWriter.flush();
    }

    @Override
    public String toString() {
        return "Request{" +
                "type=" + type +
                ", url='" + url + '\'' +
                ", data='" + data + '\'' +
                '}';
    }

    public Integer getType() {
        return type;
    }

    public void setType(Integer type) {
        this.type = type;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }
}

 

响应类

  • 每个字段为一个属性:响应状态码
  • 完成客户端解析响应封装:按约定的方式读,先按行读取第1个字段,再按结束符读第2个字段
  • 完成服务端发送响应封装:按约定的方式写,第1个字段按行输出,第2个字段以\3结束
     
package net1;

import java.io.*;
import java.util.ArrayList;
import java.util.List;

public class Response {
    private int status;
    private String data;

    //客户端解析服务端返回的响应数据
    public static Response clientParse(InputStream inputStream) throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
        Response response = new Response();
        response.status = Integer.parseInt(bufferedReader.readLine());
        List<Character> list = new ArrayList<>();
        while (true) {
            char c = (char) bufferedReader.read();
            if (c == '\3') {
                break;
            }
            list.add(c);
        }
        StringBuilder stringBuilder = new StringBuilder();
        for (char c : list) {
            stringBuilder.append(c);
        }
        response.data = stringBuilder.toString();
        return response;
    }

    //服务端返回响应给客户端
    public void serverWrite(OutputStream outputStream) throws UnsupportedEncodingException {
        PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(outputStream, "utf-8"));
        printWriter.println(status);
        printWriter.write(data + "\3");
        printWriter.flush();
    }

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }

    @Override
    public String toString() {
        return "Response{" +
                "status=" + status +
                ", data='" + data + '\'' +
                '}';
    }
}

 

TCP服务端

  • ServerSocket.accept() 为建立客户端服务端连接的方法,为提高效率,使用多线程
  • 先要解析请求数据,即 Request 已封装好的服务端解析请求,返回 Request 对象
  • 返回响应数据,需要根据不同的请求字段,做不同的业务处理,并返回对应的响应内容如果操作的url路径再服务端根目录 E:/TMP 下找不到,则返回响应状态码404正常执行完,返回200响应状态码;要注意根据不同操作类型来执行不同的业务
     
package net1;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.file.Files;
import java.util.UUID;

public class TcpFileServer {
    private static final int port = 8888;
    private static final String path = "";

    public static void main(String[] args) throws IOException {
        ServerSocket server = new ServerSocket(port);
        while (true) {
            Socket socket = server.accept();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println("------------------------------");
                        System.out.println("客户端建立TCP连接...");
                        System.out.println("客户端IP: " + socket.getInetAddress().getHostAddress());
                        InputStream inputStream = socket.getInputStream();

                        Request request = Request.serverParse(inputStream);
                        System.out.println("服务器收到请求: " + request);

                        Response response = build(request);
                        OutputStream outputStream = socket.getOutputStream();
                        System.out.println("服务器返回响应: " + response);
                        response.serverWrite(outputStream);
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }

    public static Response build(Request request) throws IOException {
        Response response = new Response();
        response.setStatus(200);
        File url = new File(path + request.getUrl());

        if (!url.exists()) {
            response.setStatus(404);
            response.setData("");
            return response;
        }

        try {
            switch (request.getType()) {
                case 1: {
                    File[] childer = url.listFiles();
                    if (childer == null) {
                        response.setData("");
                    } else {
                        StringBuilder stringBuilder = new StringBuilder();
                        for (int i = 0; i < childer.length; i++) {
                            File child = childer[i];
                            stringBuilder.append(child.getAbsolutePath().substring(path.length()) + "\n");
                        }
                        response.setData(stringBuilder.toString());
                    }
                    break;
                }
                case 2: {
                    url.renameTo(new File(url.getParent() + File.separator + request.getData()));
                    break;
                }
                case 3: {
                    url.delete();
                    break;
                }
                case 4: {
                    FileWriter upload = new FileWriter(url.getAbsolutePath() + File.separator + UUID.randomUUID());
                    upload.write(request.getData());
                    upload.flush();
                    upload.close();
                    break;
                }
                case 5: {
                    String data = new String(Files.readAllBytes(url.toPath()));
                    response.setData(data);
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return response;
    }
}

 

TCP客户端

  • 先要建立和服务端的连接,连接服务端的IP和端口
  • 根据输入来构建请求数据:先接收操作类型和操作路径重命名操作时,需要指定修改的文件名
    文件上传操作时,需要指定上传的客户端本地文件路径
  •  解析响应数据,并根据响应来执行相应的业务,我们这里暂时简单的解析为 Response 对象,并打印即可
package net1;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Scanner;

public class TcpFileClient {
    private static final String host = "127.0.0.1";
    private static final int port = 8888;

    public static void main(String[] args) throws IOException {
        Scanner scanner = new Scanner(System.in);
        while (true) {
            Request request = build(scanner);
            Socket socket = new Socket(host, port);
            OutputStream outputStream = socket.getOutputStream();
            System.out.println("客户端发送请求:" + request);
            request.clientWrite(outputStream);

            InputStream inputStream = socket.getInputStream();
            Response response = Response.clientParse(inputStream);
            System.out.println("客户端收到响应: " + response);

            socket.close();
        }
    }

    private static Request build(Scanner scanner) throws IOException {
        System.out.println("-----------------------------");
        System.out.println("请输入要操作的类型:1(展示目录文件列表) 2(文件重命名) 3(删除文件) 4(上传文件) 5(下载文件)");
        Request request = new Request();
        int type = Integer.parseInt(scanner.nextLine());
        System.out.println("请输入要操作的路径:");
        String url = scanner.nextLine();
        String data = "";
        if (type == 2) {
            System.out.println("请输入要重命名的名称:");
            data = scanner.nextLine();
        } else if (type == 4) {
            System.out.println("请输入要上传的文件路径:");
            String upload = scanner.nextLine();
            data = new String(Files.readAllBytes(Paths.get(upload)));
        } else if (type != 1 && type != 3 && type != 5) {
            System.out.println("只能输入1-5的数字,请重新输入");
            return build(scanner);
        }
        request.setType(type);
        request.setUrl(url);
        request.setData(data);
        return request;
    }
}

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Java集合类】ArrayList

内部结构 ArrayList内部核心是一个Object数组elementDataObject数组的长度&#xff08;length&#xff09;视为ArrayList当前的容量&#xff08;capacity&#xff09;size对象表示ArrayList当前的元素个数 类上的重要注释 内部是Object数组 允许put null值,会自动扩容 size、…

基于springboot+vue的个人健康信息服务平台

基于springbootvue的个人健康信息服务平台 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目背…

Spring Cloud Nacos实战(八) - Nacos集群配置

Nacos集群配置 更改Nacos启动命令配置原理 我们现在知道&#xff0c;想要启动Naocs只需要启动startup.sh命令即可&#xff0c;但是如果启动3个Nacos那&#xff1f;所以如果我们需要启动多个Nacos&#xff0c;其实Nacos本身默认启动就是集群模式。 注意点&#xff1a;如果是l…

【Redis】Redis 有序集合 Zset 操作 ( 简介 | 查询操作 | 增加操作 | 删除操作 | 修改操作 )

文章目录一、有序集合 Zset二、查询操作1、查询 Zset 所有数据2、查询 Zset 所有数据和评分3、查询指定评分范围的 Zset 数据4、查询指定评分范围的 Zset 数据并从大到小排序5、统计指定评分范围的 Zset 数据个数6、查询指定元素在 Zset 有序集合中的排名三、增加操作1、向 Red…

Kotlin 32. Kotlin 多语言支持

Kotlin 多语言支持 对于 Kotlin 来说&#xff0c;当我们新建一个项目时&#xff0c;会默认在 values/ 文件夹下&#xff0c;生成一个 strings.xml 文件。比如说&#xff0c; <resources><string name"app_name">exampleNewProject</string> <…

Python数据容器、list列表、tuple元组、str字符串、数据容器(序列)切片、set集合、dict字典、字符串大小比较

数据来源 01 数据容器 为什么学习数据容器 数据容器 总结 02 列表 1&#xff09;列表定义 为什么需要列表 列表的定义语法 列表的定义方式 演示 """ 演示数据容器之:list列表 语法:[元素,元素,......] """ # 定义一个列表list my_list …

使用k8s创建nginx服务—通过ingress类型暴露

文章目录1、使用ingress暴露应用—以nginx为例2、使用master节点IP也可以通过域名访问nginx1、使用ingress暴露应用—以nginx为例 把ingress通过 DaemonSet 的方式部署集群中&#xff0c;而且该节点打上污点不允许业务pod进行调度&#xff0c;以避免业务应用与Ingress服务发生…

【Toolformer: Language Models Can Teach Themselves to Use Tools 论文略读】

Toolformer: Language Models Can Teach Themselves to Use Tools 论文略读InformationAbstract1 Introduction2 Approach3 Tools4 Experiments4.1 Experimental Setup4.2 Downstream Tasks4.2.1 LAMA4.2.2 Math Datasets4.2.3 Question Answering4.2.4 Multilingual Question …

详解制造业业务数据模型

业务数据在企业数字化转型或单体应用的开发中都是至关重要的。站在跨业务跨部门的企业数字化转型角度&#xff0c;离不开业务架构的设计&#xff0c;详细的业务领域和业务数据模型是后续应用架构和数据架构的必要输入。站在单部门单场景的信息化角度&#xff0c;应用程序的需求…

DC-5渗透测试教程详解

DC-5渗透测试教程详解 测试机和靶机准备 有需要自取&#xff0c;注意调整DC-5的与kali在同一个为NAT模式下 [测试机kali] [靶机DC-5] 信息收集 kali的IP地址为 192.168.10.140 - kali - dc-5 使用nmap进行扫描 打开浏览器登录 192.168.10.134进行访问&#xff0c;确定其…

前端基础面试题:如何判断对象是否具有某属性?遍历数组的方法有哪些?

一、如何判断对象具有某属性&#xff1f; 如&#xff1a;let obj{name:zhangsan,age:21} 有以下方法 ( property 为属性名的变量&#xff0c;实际上是key&#xff0c;键名)&#xff1a; 1. property in obj 效果如图&#xff1a; in 运算符 2. Reflect.has(obj, property)…

【Redis】Redis 发布订阅通信模式 ( 发布订阅模式 | 订阅频道 | 发布消息 | 接收消息 )

文章目录一、发布订阅模式二、订阅频道三、发布消息四、接收消息一、发布订阅模式 Redis 中 存在一种 发布订阅 消息通信模式 : 消息发布者 : 负责发送消息 , 订阅者需要订阅该发布者频道 ;消息订阅者 : 负责接收消息 ; 订阅者 先 订阅 发布者频道 , 当 发布者 发布消息时 , …

SpringBoot+ActiveMQ-发布订阅模式(生产端)

SpringBootActiveMQ-发布订阅模式&#xff08;生产端&#xff09;Topic 主题* 消息消费者&#xff08;订阅方式&#xff09;消费该消息* 消费生产者将发布到topic中&#xff0c;同时有多个消息消费者&#xff08;订阅&#xff09;消费该消息* 这种方式和点对点方式不同&#xf…

NETCore下CI/CD之自动化测试 (详解篇)

NETCore下CI/CD之自动化测试 &#xff08;详解篇&#xff09; 目录&#xff1a;导读 前言 安装JDK 安装 Tomcat 首先&#xff0c;我们需要指定 Tomcat.PID 进程文件&#xff0c;进入 /usr/local/tomcat/bin&#xff0c;编辑文件 增加 tomcat 账户并赋予权限 防止Jeknins…

JSP脚本指令及标记学习笔记

好久没更新文章了&#xff0c;上次更新的文章还是一个学习笔记。本篇博文介绍的是JSP基本概念 1.JSP开发方法 一个jsp网页只需要加上<%%>就行了。 2.JSP运行机制 3.JSP脚本元素 3.1 JSP脚本代码 <% 脚本代码 %>实例 <% SimpleDateFormat df new SimpleDa…

MathType公式编辑器过期(禁止联网)的解决方案

MathType公式编辑器过期&#xff08;禁止联网&#xff09;的解决方案 Mathtype公式编辑器无法使用解决方案 MathType联网后显示证书失效&#xff0c;需要重新认证或者购买。或者是MathType成了精简版&#xff0c;只剩两行了。 1. 打开控制面板 方法1 首先大家在电脑中打开W…

解析从Linux零拷贝深入了解Linux-I/O(下)

接上文解析从Linux零拷贝深入了解Linux-I/O&#xff08;上&#xff09; 大文件传输场景 零拷贝还是最优选吗 在大文件传输的场景下&#xff0c;零拷贝技术并不是最优选择&#xff1b;因为在零拷贝的任何一种实现中&#xff0c;都会有「DMA 将数据从磁盘拷贝到内核缓存区——P…

加油站会员管理小程序实战开发教程11

我们已经用了10篇的篇幅讲解了首页的功能,首页主要是用来展示信息的。那么接下来就要进入我们的功能页面了,会员管理一个比较重要的功能是充值的功能。 要想实现充值功能,首先需要办一张会员卡,那什么时候办理会员卡呢?需要先注册成为会员,然后进行开卡的动作。这里要注…

c/c++开发,无可避免的模板编程实践(篇五)

一、关联容器简述 容器containers是用来管理某对象数据的集合&#xff0c;每种容器都有其优缺点&#xff0c;为了应对不同应用需求&#xff0c;标准库准备了不同的容器类型&#xff0c;容器可以是数组、链表或者是每个元素有一个特别的键值&#xff08;KEY&#xff09;组织起来…

gocd部署应用

产品需要在多个环境部署测试&#xff0c;为了提高部署测试效率&#xff0c;故计划使用CD工具&#xff0c;jenkins确实足够强大&#xff0c;但是使用部署功能是需要安装插件的&#xff0c;再说自己本身只用部署功能&#xff0c;故决定找一个小巧的CD工具&#xff0c;经过一番查找…