Java【网络编程2】详解ServerSocket和Socket类, 逐行代码解析如何服务器客户端通信(附代码)

news2024/11/20 21:22:57

文章目录

  • 前言
  • 一、认识 Socket(套接字), TCP 协议和 UDP 协议
    • 1, 什么是 Socket(套接字)
    • 2, 浅谈 TCP 协议和 UDP 协议的区别和特点
  • 二、基于 TCP 协议的 Socket API
    • 1, ServerSocket 类
    • 2, Socket 类
  • 三、逐行代码解析网络编程
    • 1, 逐行解析客户端
      • 1.1, 核心成员方法 start()
    • 2, 逐行解析服务器
      • 2.1, 核心成员方法 start()
    • 3, bug 修改
      • 3.1, bug1
      • 3.2, bug2
      • 3.3, 最终运行效果
  • 四、完整代码
    • 1, 客户端
    • 2, 服务器
  • 总结


前言

📕各位读者好, 我是小陈, 这是我的个人主页
📗小陈还在持续努力学习编程, 努力通过博客输出所学知识
📘如果本篇对你有帮助, 烦请点赞关注支持一波, 感激不尽
📙 希望我的专栏能够帮助到你:
JavaSE基础: 基础语法, 类和对象, 封装继承多态, 接口, 综合小练习图书管理系统等
Java数据结构: 顺序表, 链表, 堆, 二叉树, 二叉搜索树, 哈希表等
JavaEE初阶: 多线程, 网络编程, TCP/IP协议, HTTP协议, Tomcat, Servlet, Linux, JVM等(正在持续更新)

上篇文章介绍了基于 UDP 协议的 Socket API, 以及简单写了一个回显服务器, 实现了服务器和客户端之间的网络通信

本篇将介绍网络编程中 : 基于 TCP 协议的 Socket 套接字的相关知识


提示:是正在努力进步的小菜鸟一只,如有大佬发现文章欠佳之处欢迎批评指点~ 废话不多说,直接上干货!

一、认识 Socket(套接字), TCP 协议和 UDP 协议

以下内容上篇介绍过了, 看过上篇文章的读者可以跳过

上篇提到, 我们程序员进行网络编程主要是在 TCP/UDP 五层网络模型中的应用层, 而数据在网络上传输, 需要进行封装和分用, 其中应用层需要调用传输层提供的 API , 这一组 API 就被称作 Socket API

1, 什么是 Socket(套接字)

概念 : Socket 套接字是由系统提供于网络通信的技术, 是基于 TCP/IP 协议的网络通信的基本操作党员, 基于 Socket 套接字的网络程序开发就是网络编程

要进行网络通信, 需要有一个 socket 对象, 一个 socket 对象对应着一个 socket 文件, 这个文件在 网卡上而不是硬盘上, 所以有了 sokcet 对象才能通过操作内存来操作网卡
在 socket 文件中写数据相当于通过网卡发送数据, 在 socket 文件中读数据相当于通过网卡接收数据

Socket API 分为两类 : 基于 TCP 协议的 API , 和基于 UDP 协议的 API, 下面先认识一下 TCP 协议和 UDP 协议的区别和特点


2, 浅谈 TCP 协议和 UDP 协议的区别和特点

TCP 协议说明UDP 协议说明
有连接通信双方需要刻意保存对方的相关信息无链接通信双方不需要刻意保存对方的信息
可靠传输如果数据发送不成功, 发送方会知道不可靠传输发送方不关心数据是否发送成功
面向字节流发送的数据以字节为单位面向数据报发送的数据以 UDP 数据报为单位
全双工双向通信全双工双向通信

这里只做简单介绍, 这两个协议后续会单独详细介绍


二、基于 TCP 协议的 Socket API

首先要明确 TCP 协议和 UDP 协议的很重要的区别 : TCP 协议是有链接, 面向字节流传输, 主要体现在 : 发送方和接收方在网络通信之间要先建立连接, 并且传输的数据的基本单位是字节

基于 UDP 协议的 Socket API 中, 要分清楚以下两个类 :

类名解释
ServerSocket只能服务器使用, 客户端不能使用, 这个类是在等待客户端发起连接之前不做任何事的"监听器"
Socket服务器或客户端都可以使用, 客户端使用这个类向服务器发起连接之后, 双端都使用这个类进行网络通信

这两个类的联系就是, 服务器启动之后, 先使用 ServerSocket 类等待客户端发来连接请求, 连接成功后服务器和客户端都使用 Socket 类进行通信


1, ServerSocket 类

ServerSocket 类的构造方法 :

方法签名作用
ServerSocket (int port)创建一个 ServerSocket 对象, 一般用于服务器, 需要指定本机端口号

ServerSocket 类的成员方法 :

方法签名作用
Socket accept()开始"监听", 有客户端发来连接请求之后, 返回一个用于服务器使用的 Socket 对象, 如果客户端没有发起连接, 则阻塞等待
void close()关闭 ServerSocket

2, Socket 类

再次说明, Socket 这个类用于客户端, 也可以在服务器与客户端连接之后使用, 无论客户端或服务器使用, 都会保存对端的相关信息

Socket 类的构造方法 :

方法签名作用
Socket(String host, int port)一般用于客户端, 需要指定服务器的 IP 地址和端口号
void close()用于关闭 ServerSocket

Socket 类的成员方法 :
由于 TCP 协议是面向字节流, 所以有两个关于字节流输入输出的成员方法

方法签名作用
InputStream getInputStream()获取 Socket 的字节输入流
OutputStream getOutputStream()获取 Socket 的字节输出流
InetAddress getInetAddress()获取对端的 IP 地址
InetAddress getPort()获取对端的端口号

调用 getInputStream() 和 getOutputStream() 这个两个方法, 就可以通过字节流对象, 从网卡中读写数据
getInputStream()返回的对象用来输入(读), 从网卡读数据到内存(从网卡接收数据)
getOutputStream返回的对象用来输出(写), 从网卡写数据到内存(从网卡发送数据)

先对上述 API 有个印象即可, 接下来逐行解析如何从 0 到 1 地进行客户端和服务器之间地网络编程, 代码敲完之后再消化吸收


三、逐行代码解析网络编程

下面我们还是写一个最简单的客户端服务器网络通信模型 : 客户端给服务器发送什么请求, 服务器就给客户发送什么响应(这是最简单但是毫无意义的回显服务器, 只是方便熟悉 TCP Socket 的 API 使用)

客户端和服务器各自为一个进程在运行, 双方互不干涉(当然我们现在要写的客户端服务器程序是在同一台主机上的)

一定是服务器先启动, 一直等待客户端发来请求, 所以按照时间顺序, 代码逻辑应该如下所示 :

客户端服务器
/1, 启动服务器, 构造 ServerSocket 对象, 调用 accept() 时刻准备和客户端连接
2, 构造 Socket 对象即为发起连接/
/3, 连接成功, 通过 accept() 的返回值得到 Socket 对象
4, 把请求写入网卡
/5, 从网卡读取请求
/6, 处理请求
/7, 把响应写入网卡
8, 从网卡读取响应/

有了这个思路, 下面正式开始使用上述 API 进行网络编程


1, 逐行解析客户端

创建一个类 : TCPEchoClient 作为客户端

成员属性 :
需要定义一个 Scoket 对象来进行和服务器的通信

public class TCPEchoClient {
	// 成员属性
    private Socket socket = null;
}

构造方法 :
用于实例化客户端的 socket 对象, 别忘了需要绑定服务器的 IP 地址和端口号

public class TCPEchoClient {
	// 成员属性
    private Socket socket = null;
    
    // 构造方法
	public TCPEchoClient(String serverIP, int serverPort) throws IOException {
        socket = new Socket(serverIP, serverPort);
    }
}

main 方法 :
1, 构造 tcpEchoClient 对象, 由于服务器在本机, IP 地址为"127.0.0.1", 端口号随意指定 [1024, 65535] 之间的任意一个
2, 调用 TCPEchoClient 类的核心成员方法 start(), 这个方法实现了客户端的核心逻辑

public class TCPEchoClient {
	// 成员属性
    private Socket socket = null;
    
    // 构造方法
	public TCPEchoClient(String serverIP, int serverPort) throws IOException {
        socket = new Socket(serverIP, serverPort);
    }
    
	// main 方法
    public static void main(String[] args) throws IOException {
        TCPEchoClient tcpEchoClient = new TCPEchoClient("127.0.0.1", 9999);
        tcpEchoClient.start();
    }
}

1.1, 核心成员方法 start()

1️⃣构造一个 Scanner 对象, 从控制台输入字符串, 这个字符串当作请求的内容
2️⃣核心逻辑在一个 while(true) 循环中, 实现多次发送请求

public void start() {
        Scanner in = new Scanner(System.in);
        // 发送多个请求
        while (true) {
                
        }
    }
}

由于TCP协议是面向字节流传输, 所以为了方便读写数据, 我们把字节流转化成字符流处理

所以在进入 while 循环之前, 先构造字符流的输入输出对象

public void start() {
        Scanner in = new Scanner(System.in);
        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()) {
            // 把字节流转换成字符流
            PrintWriter printWriter = new PrintWriter(outputStream);
            Scanner inFromSocket = new Scanner(inputStream);

            // 发送多个请求
            while (true) {
            
            }
       } catch (IOException e) {
            e.printStackTrace();
       }
}

getInputStream()返回的对象用来输入(读), 从网卡读数据到内存(从网卡接收数据)
getOutputStream返回的对象用来输出(写), 从网卡写数据到内存(从网卡发送数据)

然后每次进入循环, 主要有两个操作 : 1, 把请求写入网卡 2, 把响应从网卡中读出来, 写的使用调用 println(), 读的时候调用 next(), 这样能以空白符为结束标志进行读写数据

图解如下 :
在这里插入图片描述


2, 逐行解析服务器

创建一个类 TCPEchoServer 作为服务器

成员属性 :
需要定义一个 ServerSocket 对象, 用来等待客户端发来连接的"监听器"

public class TCPEchoServer {
	// 构造方法
    private ServerSocket serverSocket = null;
}

构造方法 :
用于实例化客户端的 ServerSocket 对象, 别忘了需要绑定本机端口号

public class TCPEchoServer {
	// 构造方法
    private ServerSocket serverSocket = null;
    
	// 构造方法
    public TCPEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }
}

main 方法 :
1, 构造 tcpEchoServer 对象, 需要绑定端口号, 必须和客户端那边绑定的一致
2, 调用 tcpEchoServer 类的核心成员方法 start(), 这个方法实现了服务器的核心逻辑

public class TCPEchoServer {
	// 构造方法
    private ServerSocket serverSocket = null;
    
	// 构造方法
    public TCPEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }
    
	// main 方法
    public static void main(String[] args) throws IOException {
        TCPEchoServer tcpEchoServer = new TCPEchoServer(9999);
        tcpEchoServer.start();
    }
}

2.1, 核心成员方法 start()

由于 TCP 是有连接的传输协议, 所以服务器在和客户端连接之前, 要先和客户端建立连接, 也就是调用 accept(), 连接成功之后, 服务器就可以处理这个连接了

如果有多个服务器来和客户端连接, 服务器就需要处理多个连接, 所以把上述过程写在 while(true) 中

    public void start() throws IOException {
        while (true) {
            // 建立连接 返回一个 Socket 对象
            Socket socket = serverSocket.accept();

            // 处理连接到的这个客户端
            processConnection(socket);
        }
    }

处理连接的过程其实就是从网卡中读取数据, 处理响应, 再把响应写回网卡, 我们把这个过程封装成 processConnection(Socket socket);

注意 : 调用 accept() 的是 ServerSocket 的对象, 而这个方法的返回值是Socket, 上面已经强调过了

接下来解析 processConnection() 的过程

和客户端一样, 要先把字节流转化成字符流, 方便读写数据

	private void processConnection(Socket socket) {
        System.out.println(socket.getInetAddress() + " + " + socket.getPort() + " + " + "此客户端上线");
        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream() ) {
            PrintWriter printWriter = new PrintWriter(outputStream);
            Scanner inFromSocket = new Scanner(inputStream);

            // 处理多个请求
            while(true) {
            
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

getInputStream()返回的对象用来输入(读), 从网卡读数据到内存(从网卡接收数据)
getOutputStream返回的对象用来输出(写), 从网卡写数据到内存(从网卡发送数据)

然后就是while循环, 进入循环后主要就三个操作: 1, 从网卡中读取数据 2, 处理响应 3, 再把响应写回网卡

图解如下 :
在这里插入图片描述

看到这里, 应该感受到了 TCP 和 UDP 的不同之处体现在哪了
首先是 TCP 的服务器需要先使用 ServerSocket 建立连接, 建立连接之后服务器和客户端都是用 Socket 进行通信
通信时, TCP 进行传输使用的是字节流, 直接从网卡读写, 但我们可以转化成字符流, 而 UDP 进行传输是把数据封装成 DatagramPacket(数据报), 再进行发送和接收


3, bug 修改

3.1, bug1

上述代码中, 有个隐性的严重的 bug, 由于服务器可能是处理多个客户端连接, 那么处理完客户端 A 后, 服务器这个进程不一定会结束, 很有可能还要处理客户端 B

所以服务器和某个客户端进行通信时打开的 Socket 文件就必须在 finally 语句块中调用 close(), 以避免内存资源泄露, 修改后的代码如下 :

    private void processConnection(Socket socket) throws IOException {
        System.out.println(socket.getInetAddress() + " + " + socket.getPort() + " + " + "此客户端上线");
        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream() ) {
            // 处理多个请求
            while(true) {
                
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            socket.close();
        }
    }

客户端那边不需要调用 close() 是因为在当前场景下, 客户端的 Socket 生命争取伴随着整个客户端进程, 不会出现频繁创建 Socket 但没有 close 导致内存资源泄露


3.2, bug2

还有一个显性的 bug, 我们首先打开两个客户端, 步骤如下 :
在这里插入图片描述
在这里插入图片描述

然后先运行服务器, 再运行两个客户端, 观察运行效果 :
1 号客户端 :
在这里插入图片描述
2 号客户端 :
在这里插入图片描述
服务器 :
在这里插入图片描述

会发现, 第二个开启的客户端并没有和服务器成功通信, 这是因为, 我们的服务器处理多个连接时, 是在一个while循环中, 如果第一个连接的客户端没有下线, 就不会接收第二个客户端的连接

    public void start() throws IOException {
        while (true) {
            // 建立连接 返回一个 Socket 对象
            Socket socket = serverSocket.accept();

            // 处理连接到的这个客户端
            processConnection(socket);
        }
    }

正确的代码应该是, 每连接成功一个客户端, 就开启一个线程来处理这个连接, 修改后的代码如下 :

    public void start() throws IOException {
        while (true) {
            // 建立连接 返回一个 Socket 对象
            Socket socket = serverSocket.accept();

            // 处理连接到的这个客户端
            Thread thread = new Thread( () -> {
                try {
                    processConnection(socket);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
            // 别忘了调用 start() 启动线程
            thread.start();
        }
    }

3.3, 最终运行效果

1 号客户端 :
在这里插入图片描述

2 号客户端 :
在这里插入图片描述

服务器 :
在这里插入图片描述


四、完整代码

1, 客户端

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

public class TCPEchoClient {
    private Socket socket = null;

    public TCPEchoClient(String serverIP, int serverPort) throws IOException {
        socket = new Socket(serverIP, serverPort);
    }

    public void start() {
        Scanner in = new Scanner(System.in);
        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()) {
            // 把字节流转换成字符流
            PrintWriter printWriter = new PrintWriter(outputStream);
            Scanner inFromSocket = new Scanner(inputStream);

            // 发送多个请求
            while (true) {
                // 1,从控制台输入字符串
                String requestString = in.next();

                // 2,写入请求
                printWriter.println(requestString);
                printWriter.flush();

                // 3,读取请求
                String responseString = inFromSocket.next();

                // 控制台 打印请求字符串 + 响应字符串
                System.out.println(requestString + " + " + responseString);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException {
        TCPEchoClient tcpEchoClient = new TCPEchoClient("127.0.0.1", 9999);
        tcpEchoClient.start();
    }
}

2, 服务器

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class TCPEchoServer {
    private ServerSocket serverSocket = null;

    public TCPEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        while (true) {
            // 建立连接 返回一个 Socket 对象
            Socket socket = serverSocket.accept();

            // 处理连接到的这个客户端
            Thread thread = new Thread( () -> {
                try {
                    processConnection(socket);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
            // 别忘了调用 start() 启动线程
            thread.start();
        }
    }

    private void processConnection(Socket socket) throws IOException {
        System.out.println(socket.getInetAddress() + " + " + socket.getPort() + " + " + "此客户端上线");
        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream() ) {
            PrintWriter printWriter = new PrintWriter(outputStream);
            Scanner inFromSocket = new Scanner(inputStream);

            // 处理多个请求
            while(true) {
                if (!inFromSocket.hasNext()) {
                    System.out.println(socket.getInetAddress() + " + " + socket.getPort() + " + " + "此客户端下线");
                    break;
                }

                // 1,读取请求
                String requestString = inFromSocket.next();

                // 2,处理请求
                String responseString = process(requestString);

                // 3,写入响应
                printWriter.println(responseString);
                printWriter.flush();

                // 控制台打印 客户端IP地址 + 客户端端口号 + 请求字符串 + 响应字符串
                System.out.println(socket.getInetAddress() + " + " + socket.getPort() + " + " + requestString + " + " + responseString);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            socket.close();
        }
    }

    private String process(String requestString) {
        return requestString;
    }

    public static void main(String[] args) throws IOException {
        TCPEchoServer tcpEchoServer = new TCPEchoServer(9999);
        tcpEchoServer.start();
    }
}

总结

以上就是本篇的全部内容, 主要介绍了 : 基于 TCP协议的 Socket API , 以及利用这些 API 写了一个最简单但无意义的客户端服务器网络通信程序

再回顾一下, Socket 类的成员方法 :
由于TCP协议是面向字节流, 所以有两个成员方法是关于字节流输入输出的

方法签名作用
InputStream getInputStream()获取 Socket 的字节输入流
OutputStream getOutputStream()获取 Socket 的字节输出流
InetAddress getInetAddress()获取对端的 IP 地址
InetAddress getPort()获取对端的端口号

如果本篇对你有帮助,请点赞收藏支持一下,小手一抖就是对作者莫大的鼓励啦😋😋😋~


上山总比下山辛苦
下篇文章见

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

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

相关文章

【黑马笔记】Servlet简易教程

1. Servlet demo 0. 新建web app项目 记得去 web.xml 中删除多余的配置&#xff0c;以及新建对应的文件夹 1. 导入 Servlet依赖坐标 <dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version&…

四块ACM区域赛金牌,我队友

是的&#xff0c;毕业12年以后&#xff0c;他来找我。     痛失网名了属于是&#xff0c;但是这不重要&#xff0c;​怎么说呢&#xff1f;有点激动&#xff0c;我得把这件事情记录下来&#xff0c;这是一位重量级的人物&#xff0c;也是大家眼中别人家的孩子。     他…

Python网络爬虫:Scrapy和Beautiful Soup的使用和数据处理技巧

章节一&#xff1a;引言 在当今互联网时代&#xff0c;数据的价值越来越被重视&#xff0c;而网络爬虫作为一种强大的工具&#xff0c;可以帮助我们从互联网中提取有用的数据。Python作为一门广泛应用于数据科学和网络开发的编程语言&#xff0c;有着丰富的库和框架来支持网络…

SD卡数据恢复软件哪个最好 SD卡数据恢复多少钱

SD卡是一种常见的数字存储卡&#xff0c;由于其体积小、存储能力强&#xff0c;至今仍然被应用在手机&#xff0c;行车记录仪&#xff0c;微型摄像机中。但SD卡在使用过程中可能会出现一些故障或者数据丢失的情况。那么今天小编就给大家介绍一下SD卡数据恢复软件哪个最好&#…

泊松分布和指数分布的关系

泊松分布和指数分布的关系 泊松分布用于描述单位时间&#xff08;或面积内&#xff09;随机事件发生的次数&#xff08;离散型随机变量&#xff09;&#xff0c; λ \lambda λ表示随机事件在单位时间&#xff08;或面积内&#xff09;发生的平均次数 The Poisson distributio…

操作系统进程调度算法——先来先服务、时间片轮转、优先级调度算法

一、先来先服务调度算法 &#xff08;1&#xff09;算法内容&#xff1a;先来先服务调度算法是一种最简单的调度算法&#xff0c;可以应用于高级调度也可以运用于低级调度。高级调度时&#xff0c;FCFS调度算法按照作业进入后备作业队列的先后顺序选择作业进入内存&#xff0c…

FL Studio 20汉化补丁及详细激活使用说明/fl studio21怎么设置中文?

音乐在人们心中的地位日益增高&#xff0c;近几年音乐选秀的节目更是层出不穷&#xff0c;喜爱音乐&#xff0c;创作音乐的朋友们也是越来越多&#xff0c;音乐的类型有很多&#xff0c;好比古典&#xff0c;流行&#xff0c;摇滚等等。对新手友好程度基本上在首位&#xff0c;…

AI新风向标PaLm2?能否取代ChatGPT成为AI领域的老大

AI新风向标PaLm2&#xff1f; ​ 前几天Google发布了&#xff0c;PaLM2作为Google的下一代大型语言模型&#xff0c;它会取代ChatGPT成为更智能的AI工具吗? 关于PaLM2 ​ PaLM 2是Google的下一代大型语言模型&#xff0c;它建立在谷歌在机器学习和负责任的人工智能方面的突破…

新一代硬件安全第5章 TRNG

title: “第5章 真随机数生成器的本征熵” author: date: 2023-05-18 output: word_document Chapter 5 Intrinsic Entropy for True Random Number Generation 5.1 Chapter Introduction True Random Number Generators (TRNGs) form an essential and indispensable part …

【LLM系列之底座模型对比】LLaMA、Palm、GLM、BLOOM、GPT模型结构对比

LLama [GPT3] 使用RMSNorm&#xff08;即Root Mean square Layer Normalization&#xff09;对输入数据进行标准化&#xff0c;RMSNorm可以参考论文&#xff1a;Root mean square layer normalization。[PaLM]使用激活函数SwiGLU&#xff0c; 该函数可以参考PALM论文&#xff…

Java:异常

异常:就是代表程序出现的问题 作用&#xff1a; 作用一:异常是用来查询bug的关键参考信息作用二:异常可以作为方法内部的一种特殊返回值&#xff0c;以便通知调用者底层的执行情况&#xff08;抛异常&#xff09; 1.Error:代表的系统级别错误 &#xff08;属于严重问题) 2.…

Jetpack Compose中的列表控件LazyRow和LazyColumn详解

背景 如果你需要显示大量的条目&#xff08;或一个未知长度的列表&#xff09;&#xff0c;使用像 Column 这样的布局会导致性能问题&#xff0c;因为所有的条目都会被组合和布局&#xff0c;无论它们是否可见。那么&#xff0c;在Compose中有没有像RecycleView的控件可以滑动…

python类的高级函数

类的高级函数 __str__ 如果定义了该函数&#xff0c;当print当前实例化对象的时候&#xff0c;会返回该函数的return信息 用法&#xff1a; def __str__(self): return str_type 参数&#xff1a;无 返回值&#xff1a;一般返回对于该类的描述信息 __getattr__ 当调用的…

C++类模板——嵌套使用

目录 类模板的嵌套分类 一、数组的成员是栈 &#xff08;一&#xff09;Vector大小为2&#xff0c;Stack大小为3&#xff1b;不对Vector进行扩展&#xff1b;&#xff08;浅拷贝&#xff09;&#xff0c;只对C内置数据类型进行拷贝 1&#xff09;代码 2&#xff09;注意事…

代码随想录算法训练营第四十五天 | dp最小值题目

70. 爬楼梯 的改进版 改进条件 改为&#xff1a;一步一个台阶&#xff0c;两个台阶&#xff0c;三个台阶&#xff0c;…&#xff0c;直到 m个台阶。问有多少种不同的方法可以爬到楼顶呢&#xff1f; 文档讲解&#xff1a;代码随想录 (programmercarl.com) 状态&#xff1a;能…

IS420ESWBH3A DIN导轨或潜在面板安装

马克维耶斯IONet交换机IS420ESWBH3A由通用电气公司生产&#xff0c;工作在24/28 VDC&#xff0c;这种型号没有任何光纤端口&#xff0c;但有16个铜端口。IS420ESWBH3A开关在DIN导轨安装时需要安装夹&#xff0c;在面板安装时只需要螺钉。 IS420ESWBH3A技术规范 光纤端口数量没…

chatgpt赋能Python-python3_10安装numpy

Python3.10安装numpy&#xff1a;一步一步教你如何轻松完成 Python3.10虽然已经发布了&#xff0c;但是有些模块还需要手动安装&#xff0c;例如numpy。在这篇文章中&#xff0c;我们将会详细介绍如何安装numpy模块&#xff0c;以及为什么要使用numpy模块。 什么是numpy模块&…

Redis教程-基础篇

第一部分&#xff1a;入门 1.1 Redis简介 Redis&#xff08;Remote Dictionary Server&#xff09;是一个高性能的键值存储系统&#xff0c;它是一种开源、基于内存的数据结构服务器。Redis以其出色的性能和灵活的数据结构而受到广泛关注和应用。下面展开描述Redis的定义和特…

chatgpt赋能Python-python3gui

Python3 GUI- 让你的应用程序更酷炫 随着技术的发展&#xff0c;图形用户界面(Graphical User Interface, GUI)已经成为软件开发过程中不可或缺的一部分。Python3是一个用于快速开发应用程序的强大编程语言&#xff0c;支持多种GUI库。本文将为您介绍Python3 GUI的一些基本概念…

web安全第一天 ,域名,dns

第一天 什么是域名&#xff1f;域名就是网络地址 在hhtp之后的就是域名 域名在哪里注册呢 国内注册商有很多&#xff0c;在网络上搜索一下阿里云万网就可以注册 什么是二级域名和多级域名 域名通常都是www.开头 &#xff0c;而www.被称为顶级域名&#xff0c;在搜索的时候…