网络编程套接字TCP

news2024/11/19 5:43:18

前集回顾

上一篇博客中我们写了一个UDP的echo server,是一个回显服务器:请求是啥,响应就是啥

一个正常的服务器,要做三个事情:

  1. 读取请求并解析
  2. 根据请求,计算响应
  3. 把响应写回到客户端
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), 0, response.getBytes().length,requestPacket.getSocketAddress());
//先得到字节数组,然后取字节,然后取数组的长度,这里的长度单位是“字节数”
String request = new String(requestPacket.getData(), 0, requestPacket.getLength());
//这里的长度单位是“字符数”

客户端的整体流程:

  1. 从控制台读取字符串
  2. 把字符串发送给服务器
  3. 从服务器读取到响应
  4. 把响应打印到控制台上

同时后面的serverIp为127.0.0.1,需要把字符串格式的ip地址转成Java能识别的对象,InetAddress对象,提供了getByName工厂方法,把上述字符串格式的ip地址,转成Java能识别的InetAddress对象了

这个对象里有的就是IP地址,会按照32位整数的形式来保存,计算机认识的是32位整数形式的ip,点分十进制是给人看的

DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), 0, request.getBytes().length,InetAddress.getByName(serverIp), serverPort);
socket.send(requestPacket);
//读取响应数据
DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);
socket.receive(responsePacket);

这个代码在写的时候,上面的send完成之后,就立即执行到下面的receive了,但是实际上,数据在网络传输过程中,也是需要时间的,此处的receive也会阻塞,阻塞到真正到达为止

上述阻塞行为,不需要咱们代码进行任何干预,都是人家的api内部已经实现好了(系统内核完成好了的功能)

在系统内核中,让某个线程阻塞是很容易的事情,通过链表方式来组织PCB,链表不只是一个,而是有多个,有的链表,称为“就绪队列”,这里的PCB就要参与cpu调度,有的链表,称为“阻塞队列”(这里的PCB不参与PCB调度),调用receive的时候,系统会看网卡上是否有数据来了(通过网卡驱动很容易能够感知到),然后如果没来,就把当前线程PCB放到阻塞的队列中即可(链表节点的删除/插入)

服务器代码

客户端代码

运行

先运行服务器,后运行客户端

服务器是被动方,要先准备好,得餐馆先开门,然后才能去吃饭

有个客户端,发来一个请求,请求内容是hello,响应内容也是hello,客户端ip是127.0.0.1,客户端的port是49874(系统自动分配的结果)

如何启动多个客户端

默认情况下,idea里面只能启动一个客户端,要想启动多个,需要如下操作:

在service中可以看到

刚才是客户端和服务器,都在同一个电脑上,如果是不同电脑呢?

Question:如果我把客户端发给你,你在客户端中填写这个ip,能否访问我的这个服务器程序呢?


Answer:不能,要想能访问,有一个前提,需要在同一个局域网下,如何能够让世界上任何一个能上网的人脸上咱们的服务器呢?不是咱们的程序代码不行(代码已经就绪),当前这个电脑的IP只是一个局域网内部使用的私有IP,而不是能够在广域网上直接使用的“公网IP”,需要一个云服务器来提供公网IP

通过XShell可以远程控制云服务器

上述一串数字就是云服务器IP,也就是公网IP,另外,在所有IP地址中,以下三种情况是私网IP,剩下的都是公网IP

私网IP常用格式

  • 10.*
  • 172.16-172.31.*
  • 192.168.*

  1. 把写好的udpechoserver放到云服务器上,需要把服务器程序打一个jar包出来
  2. 把这个jar包传到云服务器上
  3. 运行这个程序

通过命令来运行程序

通过这串代码可以理解两方面:

总结

  1. socket api(UDP)
  2. 服务器程序的典型工作流程
  •  读取请求并解析
  •  根据请求计算响应
  •  把响应写回到客户端
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class UdpEchoServer {
    private DatagramSocket socket = null;

    public UdpEchoServer(int port) throws SocketException {
        socket = new DatagramSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器启动!");
        while (true) {
            // 1) 读取请求并解析
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
            socket.receive(requestPacket);
            // 为了方便在 java 代码中处理 (尤其是后面进行打印) 可以把上述数据报中的二进制数据, 拿出来, 构造成 String
            String request = new String(requestPacket.getData(), 0, requestPacket.getLength());
            // 2) 根据请求计算响应
            String response = this.process(request);
            // 3) 把响应写回到客户端
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), 0, response.getBytes().length,
                    requestPacket.getSocketAddress());
            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();
    }
}

如果要写别的服务器(例如翻译网站),可以继承该服务器,然后重写process方法,在响应方面重新重写,使用Map来记录key-value之间的关系

TCP

TCP的socket api也有两个关键的类

ServerSocket

其中也有三个重要的方法:

接听操作方法

public Socket accept() throws IOException {
    if (isClosed())
        throw new SocketException("Socket is closed");
    if (!isBound())
        throw new SocketException("Socket is not bound yet");
    Socket s = new Socket((SocketImpl) null);
    implAccept(s);
    return s;
}

构造方法

public ServerSocket(int port) throws IOException {
    this(port, 50, null);
}

关闭资源的方法

public void close() throws IOException {
    synchronized(closeLock) {
        if (isClosed())
            return;
        if (created)
            impl.close();
        closed = true;
    }
}

Socket客户端和服务器都要用(两边都要用,不能叫做ClientSocket),TCP传输的是字节流,就是传输字节,传输的基本单位就是byte

Socket

其中重要的方法

构造方法

public Socket(String host, int port) throws UnknownHostException, IOException {
    this(host != null ? new InetSocketAddress(host, port) :
         new InetSocketAddress(InetAddress.getByName(null), port),
         (SocketAddress) null, true);
}

获取socket内部持有的对象

public InputStream getInputStream() throws IOException {
    if (isClosed())
        throw new SocketException("Socket is closed");
    if (!isConnected())
        throw new SocketException("Socket is not connected");
    if (isInputShutdown())
        throw new SocketException("Socket input is shutdown");
    InputStream in = this.in;
    if (in == null) {
        // wrap the input stream so that the close method closes this socket
        in = new SocketInputStream(this, impl.getInputStream());
        if (!IN.compareAndSet(this, null, in)) {
            in = this.in;
        }
    }
    return in;
}

获取socket内部持有的流对象

public OutputStream getOutputStream() throws IOException {
    if (isClosed())
        throw new SocketException("Socket is closed");
    if (!isConnected())
        throw new SocketException("Socket is not connected");
    if (isOutputShutdown())
        throw new SocketException("Socket output is shutdown");
    OutputStream out = this.out;
    if (out == null) {
        // wrap the output stream so that the close method closes this socket
        out = new SocketOutputStream(this, impl.getOutputStream());
        if (!OUT.compareAndSet(this, null, out)) {
            out = this.out;
        }
    }
    return out;
}

同时还可以获得源和目的主机的信息

public InetAddress getInetAddress() {
    if (!isConnected())
        return null;
    try {
        return getImpl().getInetAddress();
    } catch (SocketException e) {
    }
    return null;
}

ServerSocket和Socket这俩起到的作用,截然不同的,服务器一上来要先处理客户端来的连接

Socket clientSocket = serverSocket.accept();
//服务器一启动,就会立即执行到这里,如果客户端没有连接过来,accept也会产生阻塞,直到说有客户端真的连接上来了

举一个比较形象的栗子:有一个西装革履的小哥来到我面前,这个人不是大老板就是销售,他把我领到一个地方,这个地方都是西装革履的人,给我介绍房子,然后小哥就消失了,然后由别的人带我介绍,此时小哥就可以理解为ServerSocket,其他带我介绍的人可以理解为clientSocket

  • ServerScoket用于在服务器端使用的(揽客) - accept
  • Socket用于服务器和客户端来进行通信 - getInputStream,getOutStream

相当于打电话的时候,接通电话后,会说很多话,不是说一句就挂了

Scanner scanner = new Scanner(inputStream);
String request = scanner.next();
//这个读取方式,就是会读到“空白符”才会读取完毕,如果直接按照read方式来读,读出来的就是byte[]还需要转成Srting
//如果直接使用Scanner的话,直接读出来的就是String,Scanner已经帮我们做好上述的转换操作了
//客户端在发送数据的时候,无比要在每个请求的结尾,填上空白符(\n, \t, 回车, 换行)
//上述要求属于咱们对于通信细节的约定

由于TCP上面是按照字节来传输的,而是实际上,我们是希望,若干个字节能够构成一个“应用层数据报”,如何区分从哪到哪是一个应用层数据报?就可以通过“分隔符”的方式来约定,上述代码就是在约定说,使用空白符,来作为一个请求的结束标记


从TCP socket中读出一大串字节,可能会包含多个应用层的请求数据,就需要作出区分


不论是发请求,还是返回响应,也要使用Scanner的方式来next,因此就要求服务器返回的响应也要带有\n,使用next读取数据,如果数据中不带有\n等分隔符的话,此时next就会一直阻塞,Scanner是会带有阻塞功能的,

if (!scanner.hasNext()) {
    // 如果发现后续没有数据了, 此时说明 TCP 的连接是已经断开了的.
    System.out.printf("[%s:%d] 客户端下线!\n", clientSocket.getInetAddress(), clientSocket.getPort());
    break;
}
String request = scanner.next();

这里的scanner.hasNext就会阻塞等待请求的到达

  1. 请求到了,有明确的分隔符,返回true
  2. tcp连接断开了,返回false

scanner,可以认为是关联到了服务器这边的socket文件,socket是能感知到tcp断开连接的(本身是系统内核里面会有一系列的流程,四次挥手)

操作系统知道这个事情之后,就会告知socket,对应的scanner读取的文件,就相当于是“无效文件”,类似于读取到EOF这样的效果,使用scanner读文件的时候,就是读到文件末尾,hasNext也是会返回false,一旦tcp连接断开,scanner就相当于读到文件末尾的效果,都是读到EOF,反映到scanner上面就是hasNext为false

Question:

  1. hasNext方法阻塞时间长了就表名TCP连接断开了吗?
  2. 客户端自行断开会影响服务端吗?

Answer:

  1. 不是的,TCP连接断开,hasNext解除阻塞,并返回false,TCP连接仍然存在,但是对方没有发数据过来,hashNext是阻塞的,对方发数据过来了,hasNext解除阻塞,返回true
  2. 在TCP中是这样的,客户端断开连接,服务器是能感知到的
while (true) {
    // 3.1 读取请求并解析
    if (!scanner.hasNext()) {
        // 如果发现后续没有数据了, 此时说明 TCP 的连接是已经断开了的.
        System.out.printf("[%s:%d] 客户端下线!\n", clientSocket.getInetAddress(), clientSocket.getPort());
        break;
    }
    String request = scanner.next();
    // 3.2 根据请求计算响应
    String response = process(request);
    // 3.3 把响应写回给客户端.
    outputStream.write(response.getBytes(), 0, response.getBytes().length);
    // 3.4 服务器打印日志
    System.out.printf("[%s:%d] req=%s, resp=%s\n", clientSocket.getInetAddress(), clientSocket.getPort(), request, response);
}
public void start() {
    System.out.println("客户端启动!");
    Scanner scanner = new Scanner(System.in);
    try (InputStream inputStream = socket.getInputStream();
         OutputStream outputStream = socket.getOutputStream()) {

        Scanner scannerNetwork = new Scanner(inputStream);

        while (true) {
            // 1. 从控制台读取数据
            System.out.print("请输入要发送的数据: ");
            String request = scanner.next();
            // 2. 把请求发送给服务器, 发送的请求要带有 \n, 和服务器的 scanner.next 是对应的.
            //    由于上述通过 next 读到的 request 本身已经没有 \n 结尾了. 需要手动添加上换行
            request += "\n";
            outputStream.write(request.getBytes());
            // 3. 从服务器读取到响应
            if (!scannerNetwork.hasNext()) {
                break;
            }
            String response = scannerNetwork.next();
            // 4. 把响应显示到控制台上
            System.out.println(response);
        }

    } catch (Exception e) {
        e.printStackTrace();
    }
}

重点理解“客户端下线”操作,当强制终止客户端进程,或者通过代码调用客户端socket,close(),就会使客户端所在的主机的操作系统内核,触发TCP断开连接流程(四次挥手),服务器就能感知到,于是就会在hasNext解除阻塞,并返回false(if这里逻辑取反,于是就进入if语句内部,执行打印 下线 的操作,并break了)

上述代码,还存在一些问题

  • 服务器代码这边,对于accept创建的socket对象,是没有进行关闭操作的

 Socket clientSocket = serverSocket.accept();
//这个东西在processConnection中使用之后没有进行close,这个是不科学的
  1. serverSocket是可以不必特别关闭的,因为生命周期是跟随整个服务器进程的
  2. 客户端的socket也是可以不必特别关闭的

但是服务器的clientSocket就不行了,服务器会对应多个客户端,每个客户端都有一个对应的clientSocket,如果用完了不关闭,就会使当前clientSocket对应的文件描述符得不到释放,引起文件资源泄露

try (InputStream inputStream = clientSocket.getInputStream();
     OutputStream outputStream = clientSocket.getOutputStream()) {

...

} catch (IOException e) {
    e.printStackTrace();
} finally {
    // 在这里关闭是比较靠谱的做法.
    // 即使当前 processConnection 在不同的线程中被调用, 也可以正确关闭.
    clientSocket.close();
}

有的时候,close操作的异常也需要单独处理,没啥太好的方法

  • 当前这个代码,服务器是无法同时给多个客户端提供服务端

使用service操作,虽然有两个客户端了,但是服务器只感知到一个客户端上线,第一个客户端,发来的请求能够顺利处理,第二个客户端的请求就处理不了了,一旦第一个客户端结束,服务器就能立即感知到第二个客户端上线,以及感知到客户端之前的请求,并且立即返回响应

while (true) {
    // 3.1 读取请求并解析
    if (!scanner.hasNext()) {
        // 如果发现后续没有数据了, 此时说明 TCP 的连接是已经断开了的.
        System.out.printf("[%s:%d] 客户端下线!\n", clientSocket.getInetAddress(), clientSocket.getPort());
        break;
    }
    String request = scanner.next();
    // 3.2 根据请求计算响应
    String response = process(request);
    // 3.3 把响应写回给客户端.
    outputStream.write(response.getBytes(), 0, response.getBytes().length);
    // 3.4 服务器打印日志
    System.out.printf("[%s:%d] req=%s, resp=%s\n", clientSocket.getInetAddress(), clientSocket.getPort(), request, response);
}

解决方案:多线程

第一个客户端连接之后,此时accept就返回了,进入到processConnection方法,就在方法内部的while循环,开始循环起来了,第二个客户端来了之后,此时,没有办法执行到第二次accept的(第二个客户端,给服务器打电话,服务器一直没接听!)一方面希望给第一个客户端提供服务,另一方面,还能希望快速第二次调用到accept,我们可以使用多线程

主线程,专门负责循环的处理accept,每次accept获取到一个客户端连接之后,都创建一个新的线程,用新的线程来给客户端循环的提供服务

public void start() throws IOException {
    System.out.println("服务器启动!");

    // 能一直扩容
    // 如果用固定线程个数的线程池, 不太合适, 就限制了最多有多少个客户端同时连接.
    ExecutorService pool = Executors.newCachedThreadPool();

    while (true) {
        Socket clientSocket = serverSocket.accept();
        // 代码改成多线程的形式, 就不能通过 try ( ) 的方式来关闭 clientSocket 了.
        // 否则就会使 clientSocket 被立即关闭, 此时 processConnection 还没来得及使用的.
//            Thread t = new Thread(() -> {
//                try {
//                    processConnection(clientSocket);
//                    // clientSocket.close();
//                } catch (IOException e) {
//                    e.printStackTrace();
//                }
//            });
//            t.start();

        // 把任务添加到线程池中
        pool.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    processConnection(clientSocket);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

如果线程太多,电脑会撑不住,所以这里核心解决方案:IO多路复用 + 多个服务器(分布式系统)

IO多路复用

通过AI可以知道IO多路复用是什么机制!


IO多路复用是一种同步非阻塞的IO模型,它允许单个线程处理多个文件描述符(FD),从而管理多个网络连接或IO流。这种机制使得服务器能够高效地处理大量的并发连接而不需要为每个连接创建一个独立的线程或进程。

IO多路复用主要通过三种机制实现:select、poll和epoll。

  • select:这是最早的IO多路复用机制,它使用一个文件描述符集合来进行监控,并通过内核检查每个FD来确定是否有事件发生。select的缺点包括对文件描述符数量有限制(通常是1024),以及需要在用户态和内核态之间复制文件描述符集合,这会导致额外的性能开销。
  • poll:poll是select的改进版,它没有最大文件描述符数量的限制,并且使用动态数组来存储FD。尽管如此,poll仍然需要遍历整个FD集合来查找哪些FD已经就绪,这在大量FD的情况下效率不高。
  • epoll:这是Linux特有的IO多路复用机制,它比select和poll更高效。epoll使用内核事件表来管理FD,并且不需要复制文件描述符集合,从而减少了数据复制的开销。epoll还支持水平触发(LT)和边缘触发(ET)两种模式,其中边缘触发模式可以避免大量不必要的事件通知。

IO多路复用的优势在于它能够提高单个线程处理多个IO请求的能力,减少了线程创建和上下文切换的开销,提高了系统的并发性能。它在网络编程中特别有用,可以用于实现高性能的服务器和客户端应用程序。

在实际应用中,IO多路复用可以用于网络编程、高性能服务器、文件操作和定时器事件调度等场景。例如,Web服务器可以使用IO多路复用来同时处理大量并发请求,而不会因为某个请求的IO操作而阻塞其他请求的执行。

总结来说,IO多路复用是一种强大的技术,可以在单个线程中处理多个I/O操作,提高程序的实时性能,特别是在处理大量数据、提高实时性能和提高系统性能方面具有非常重要的作用。

当前写的tcp server和client这里,就涉及到三种socket

  • 服务器ServerSocket
  • 服务器Socket(通过这个Socket和客户端提供交互能力)
  • 客户端Socket(通过这个Socket和服务器进行交互)

第二个socket,服务器这边会有多个这样的socket,每个客户端都有一个对应的socket,这个socket在客户端断开连接之后,就不再使用了,就需要关闭掉

第一个和第三个都是生命周期跟随整个进程,程序只要在运行,就需要这个socket,不能提前close的,随着进程结束,这些socket自然释放

何时关闭第二个socket?这个连接用完了之后

private void processConnection(Socket clientSocket) throws IOException;

此处的processConnection这个方法就表示一个客户端连接的,整个处理过程,这个方法执行完毕,就是用完了(这个方法里也是感知到客户端关闭(断开连接)才返回的),此时就可以在这个方法的末尾,进行close了

自定义应用层协议

  • 信息
  • 确定数据的格式
  1. 基于行文本的方式来传输
  2. 基于xml的方式
  3. 基于json
  4. yml
  5. protobuffer:针对要传输的数据进行压缩,虽然可读性不好,但是能够把空间最充分的利用,最节省网络带宽,效率也最高

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

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

相关文章

车辆重识别(2021NIPS在图像合成方面,扩散模型打败了gans网络)论文阅读2024/10/01

本文在架构方面的创新: ①增加注意头数量: 使用32⇥32、16⇥16和8⇥8分辨率的注意力,而不是只使用16⇥16 ②使用BigGAN残差块 使用Big GAN残差块对激活进行上采样和下采样 ③自适应组归一化层 将经过组归一化操作后的时间步和类嵌入到每…

十三、减少磁盘延迟时间的方法

1.交替编号 让逻辑上相邻的扇区在物理上不相邻; 原因:由于磁头在读取完一个扇区之后需要等待一段时间才能再次读入下一个扇区,如果逻辑上相邻的扇区在物理上相邻的话,需要等待磁盘转完一圈才能读取到。 2.错位命名 让相邻盘面上…

观测云对接 SkyWalking 最佳实践

简介 SkyWalking 是一个开源的 APM(应用性能监控)和可观测性分析平台,专为微服务、云原生架构和基于容器的架构设计。它提供了分布式追踪、服务网格遥测分析、度量聚合和可视化一体化的解决方案。如果您的应用中正在使用SkyWalking &#xf…

opencv实战项目二十八:基于Shi-Tomasi算法的箱子角点检测

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、角点检测方法概述二、Shi-Tomasi角点检测法 前言 随着科技的飞速发展,计算机视觉技术在我们的日常生活中扮演着越来越重要的角色。从无人驾驶汽…

Oracle exadata存储节点更换内存操作及报错处理

1.报错信息 在进行Oracle exadata巡检时,发现cell节点有一根内存报错,报错信息如下: 报错内存位置为:CPU1 P1/D2槽位 报错内存信息: 根据报错信息确认内存PN号、大小等息,并将信息反馈公司,及…

git初级使用学习(图文)

以后工作少不了使用git,记录一下今天的学习,防止忘记 Git 是一个分布式版本控制系统,常用于代码管理和团队协作 首先新建一个文件夹,作为本地仓库 mkdir git-practice 初始化仓库 git init 新建个test1.cpp文件,…

CSS中字体图标的使用

引言: 在网页设计当中,会有很多很简洁的图标,比如箭头,照相机,放大镜等 这些大概率都是使用字体图标来完成的,因为字体图标比较简洁高效,不会像图片一样需要向浏览器请求数据。那么字体图标该…

第七篇:重定向和管道相关知识总结

续第六篇:Linux进程的相关知识总结(2)-CSDN博客 目录 第五章:管道和重定向 5.1重定向 5.1.1产生重定向的原因 5.1.2重定向的应用 5.1.3查看现有的进程号、文件描述符 5.1.3.1文件描述符(FD、文件句柄&#xff0…

Redis介绍及整合Spring

目录 Redis介绍 Spring与Redis集成 Redis介绍 Redis是内存数据库,Key-value型NOSQL数据库,项目上经常将一些不经常变化并且反复查询的数据放入Redis缓存,由于数据放在内存中,所以查询、维护的速度远远快于硬盘方式操作数据&#…

Vortex GPGPU的github流程跑通与功能模块波形探索

文章目录 前言一、跟着官方文档走一遍二、cache子模块的波形仿真2.1 必要的文件内容解释2.2 cache子模块波形仿真——目前环境没啥问题了,就vcd因为配置问题出不来 总结 前言 看了那么久的verilog代码和文档,但还是没怎么接触过Vortex GPGPU全流程跑通与…

Vscode、小皮面板安装

Vscode下载官网:Visual Studio Code - Code Editing. Redefined 小皮面板官网:小皮面板-好用、安全、稳定的Linux服务器面板! (xp.cn) 一、进入vscode官网下载 后面就是一通无脑下一步下一步 安装好后的界面如下图 二、下载小皮&#xff0…

面试题1-fail-safe机制与fail-fast 机制

1.定义 Fail-safe 和 Fail-fast,是多线程并发操作集合时的一种失败处理机制。 1.1.Fail-Safe机制 1.1.1.定义 Fail-Safe 机制的设计目标是在发生故障时,系统仍然能够继续运行,尽量避免导致整个系统崩溃。即使发生错误或异常,系统…

记录|Modbus-TCP产品使用记录【摩通传动】

目录 前言一、摩通传动实验图1.1 配置软件 IO_Studio1.2 测试软件Modbus Poll1.2.1 读写设置测试1.2.2 AI信号的读取 1.3 对应的C#连接Modbus的测试代码如下【自制,仅供参考】1.4 最终实验图 更新时间 前言 参考文章: 自己需要了解和对比某些产品的Modbu…

GAN|对抗| 生成器更新|判别器更新过程

如上图所示,生成对抗网络存在上述内容: 真实数据集;生成器;生成器损失函数;判别器;判别器损失函数;生成器、判别器更新(生成器和判别器就是小偷和警察的关系,他们共用的…

【STM32单片机_(HAL库)】4-4【定时器TIM】脉冲计数配置步骤及实验

脉冲计数配置步骤 1.硬件 STM32单片机最小系统按键模块 2.软件 定时器HAL驱动层文件添加counter驱动文件添加GPIO常用函数main.c程序 #include "sys.h" #include "delay.h" #include "led.h" #include "uart1.h" #include "…

热销的五款骨传导耳机真的好用吗?无广测评五款骨传导耳机

在科技快速发展的背景下,产品设计的重点开始转向考虑人们的行为方式与健康需求。耳机,已成为现代生活中不可或缺的一部分,无论是出于日常习惯、隐私考量,还是在公共场合的礼貌需求,耳机都始终陪伴着我们。 随着耳机在…

JAVAEE如何实现网页(jsp)间的数据传输?一文总结

刚刚接触到JAVAEE的WEB开发,解释不周的地方希望感谢指正!!! 情景如下: 我的使用是21版的IDEA,9.03版本的tomcat,来做一个示范。 构建项目 点击下一步 -> 完成,等待项目构建结束…

如何设计具体项目的数据库管理

### 例三:足协的数据库管理算法 #### 角色: - **ESFP学生**:小明 - **ENTP老师**:张老师 #### 主题:足协的数据库管理算法 --- **张老师**:小明,今天我们来讨论一下足协的数据库管理算法。你…

CATIA风扇

记录下风扇绘制的要点 1、 图纸 2、 先画出投影面的草图,下图中白色线,然后目标是获得紫色线,紫色线是白色线在淡黄色面上的投影。红色线和蓝色线是螺旋线。 3、根据以下投影视图,螺旋线从起点到终点,围绕旋转轴旋转一…

【C++】第一节:C++入门

1、C关键字 2、命名空间 在C/C中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染&am…