当前位置: 首页 > news >正文

《Java 程序设计》第 18 章 - Java 网络编程

引言

        在当今的互联网时代,网络编程是软件开发中不可或缺的重要部分。Java 提供了丰富的类库来支持网络编程,使得开发者能够轻松地实现各种网络通信功能。本章将详细介绍 Java 网络编程的核心知识,包括网络基础概念、套接字通信、数据报通信以及 URL 编程等内容,并通过完整的代码示例帮助大家掌握实际应用技能。


18.1 网络概述

        网络编程的本质是实现不同设备之间的数据传输。在学习 Java 网络编程之前,我们需要先了解一些计算机网络的基本概念。

18.1.1 网络分层与协议

        为了实现不同计算机之间的通信,网络通信采用分层模型设计,每一层负责特定的功能,并通过协议规范通信方式。

最著名的网络模型有两种:

  • OSI 七层模型:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层
  • TCP/IP 四层模型:网络接口层、网络层、传输层、应用层(或分为五层,将网络接口层细分为物理层和数据链路层)

常用协议

  • 网络层:IP 协议(Internet Protocol)
  • 传输层:TCP 协议(Transmission Control Protocol)和 UDP 协议(User Datagram Protocol)
  • 应用层:HTTP、FTP、SMTP 等

18.1.2 客户 / 服务器结构

在网络通信中,最常见的模式是客户 / 服务器(C/S)结构

  • 服务器(Server):提供服务的计算机或程序,被动等待客户端的连接请求
  • 客户端(Client):请求服务的计算机或程序,主动向服务器发起连接请求

B/S 结构(浏览器 / 服务器)是一种特殊的 C/S 结构,客户端是浏览器,通过 HTTP 协议与服务器通信。

18.1.3 IP 地址和域名

  • IP 地址:网络中每个设备的唯一标识,用于设备之间的定位

    • IPv4:32 位,格式如 192.168.1.1
    • IPv6:128 位,格式如 2001:0db8:85a3:0000:0000:8a2e:0370:7334(为解决 IPv4 地址耗尽问题)
    • 本地回环地址:127.0.0.1,通常映射到域名 localhost
  • 域名:为了方便记忆,用字符串标识网络中的设备,如 www.csdn.net

  • DNS(域名系统):负责将域名解析为对应的 IP 地址。

18.1.4 端口号与套接字

 

  • 端口号:标识设备上运行的不同网络程序,范围是 0-65535。

    • 0-1023:知名端口,被系统保留(如 HTTP 的 80,FTP 的 21)
    • 1024-49151:注册端口
    • 49152-65535:动态或私有端口,可用于普通应用程序
  • 套接字(Socket):网络通信的端点,由 IP 地址和端口号组成,用于标识通信的双方

    • 格式:IP地址:端口号(如 192.168.1.100:8080
    • Java 中通过 Socket 类实现套接字功能

18.2 Java 套接字通信

        Java 中基于 TCP 协议的网络通信主要通过套接字(Socket)实现,核心类是 ServerSocket(服务器端)和 Socket(客户端)。

18.2.1 套接字 API

  • ServerSocket:服务器端套接字,用于监听客户端的连接请求

    • 构造方法:ServerSocket(int port) - 绑定到指定端口
    • 核心方法:Socket accept() - 阻塞等待客户端连接,返回与客户端通信的 Socket 对象
  • Socket:客户端套接字,也用于服务器端与客户端通信

    • 构造方法:Socket(String host, int port) - 连接到指定主机的指定端口
    • 核心方法:
      • InputStream getInputStream() - 获取输入流,用于读取数据
      • OutputStream getOutputStream() - 获取输出流,用于发送数据
      • void close() - 关闭套接字
@startuml
class ServerSocket {+ ServerSocket(int port) throws IOException+ Socket accept() throws IOException+ void close() throws IOException+ int getLocalPort()
}class Socket {+ Socket(String host, int port) throws IOException+ InputStream getInputStream() throws IOException+ OutputStream getOutputStream() throws IOException+ void close() throws IOException+ InetAddress getInetAddress()+ int getPort()+ int getLocalPort()
}ServerSocket "1" -- "*" Socket : 创建
@enduml

18.2.2 简单的客户和服务器程序

下面实现一个简单的 TCP 通信程序:客户端向服务器发送一条消息,服务器接收后回复一条消息。

服务器端代码(TCPServer.java)

import java.io.*;
import java.net.*;public class TCPServer {public static void main(String[] args) {ServerSocket serverSocket = null;Socket clientSocket = null;try {// 创建服务器套接字,绑定到8888端口serverSocket = new ServerSocket(8888);System.out.println("服务器已启动,等待客户端连接...");// 阻塞等待客户端连接clientSocket = serverSocket.accept();System.out.println("客户端已连接:" + clientSocket.getInetAddress().getHostAddress());// 获取输入流和输出流BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream(), "UTF-8"));PrintWriter out = new PrintWriter(new OutputStreamWriter(clientSocket.getOutputStream(), "UTF-8"), true);// 读取客户端发送的消息String clientMsg = in.readLine();System.out.println("收到客户端消息:" + clientMsg);// 向客户端发送响应String serverMsg = "Hello Client! 我是服务器";out.println(serverMsg);System.out.println("已向客户端发送消息:" + serverMsg);} catch (IOException e) {e.printStackTrace();} finally {// 关闭资源try {if (clientSocket != null) clientSocket.close();if (serverSocket != null) serverSocket.close();System.out.println("服务器已关闭");} catch (IOException e) {e.printStackTrace();}}}
}

客户端代码(TCPClient.java)

import java.io.*;
import java.net.*;public class TCPClient {public static void main(String[] args) {Socket socket = null;try {// 连接到本地服务器的8888端口socket = new Socket("localhost", 8888);System.out.println("已连接到服务器");// 获取输入流和输出流BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"), true);// 向服务器发送消息String clientMsg = "Hello Server! 我是客户端";out.println(clientMsg);System.out.println("已向服务器发送消息:" + clientMsg);// 读取服务器的响应String serverMsg = in.readLine();System.out.println("收到服务器消息:" + serverMsg);} catch (IOException e) {e.printStackTrace();} finally {// 关闭资源try {if (socket != null) socket.close();System.out.println("客户端已关闭");} catch (IOException e) {e.printStackTrace();}}}
}

运行说明

  1. 先运行 TCPServer,服务器启动并等待连接
  2. 再运行 TCPClient,客户端连接服务器并进行通信
  3. 程序执行后,服务器和客户端控制台会分别输出通信内容

18.2.3 服务多个客户

        上面的服务器程序只能处理一个客户端连接。要实现同时服务多个客户端,需要使用多线程主线程负责监听连接,每收到一个连接就创建一个新线程处理与该客户端的通信。

多线程服务器代码(MultiThreadTCPServer.java)

import java.io.*;
import java.net.*;public class MultiThreadTCPServer {public static void main(String[] args) {ServerSocket serverSocket = null;try {serverSocket = new ServerSocket(8888);System.out.println("多线程服务器已启动,等待客户端连接...");int clientCount = 0; // 客户端计数器while (true) { // 循环接受多个客户端连接Socket clientSocket = serverSocket.accept();clientCount++;System.out.println("第" + clientCount + "个客户端已连接:" + clientSocket.getInetAddress().getHostAddress());// 创建新线程处理客户端通信new ClientHandler(clientSocket, clientCount).start();}} catch (IOException e) {e.printStackTrace();} finally {try {if (serverSocket != null) serverSocket.close();} catch (IOException e) {e.printStackTrace();}}}// 客户端处理线程static class ClientHandler extends Thread {private Socket clientSocket;private int clientId;public ClientHandler(Socket socket, int id) {this.clientSocket = socket;this.clientId = id;}@Overridepublic void run() {BufferedReader in = null;PrintWriter out = null;try {// 获取输入流和输出流in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream(), "UTF-8"));out = new PrintWriter(new OutputStreamWriter(clientSocket.getOutputStream(), "UTF-8"), true);String clientMsg;// 循环读取客户端消息,直到客户端关闭连接while ((clientMsg = in.readLine()) != null) {System.out.println("收到第" + clientId + "个客户端消息:" + clientMsg);// 向客户端发送响应String serverMsg = "服务器已收到你的消息:" + clientMsg;out.println(serverMsg);}System.out.println("第" + clientId + "个客户端已断开连接");} catch (IOException e) {System.out.println("第" + clientId + "个客户端通信异常:" + e.getMessage());} finally {// 关闭资源try {if (in != null) in.close();if (out != null) out.close();if (clientSocket != null) clientSocket.close();} catch (IOException e) {e.printStackTrace();}}}}
}

客户端代码:可以使用前面的 TCPClient.java,也可以稍作修改使其能发送多条消息:

import java.io.*;
import java.net.*;
import java.util.Scanner;public class MultiTCPClient {public static void main(String[] args) {Socket socket = null;Scanner scanner = null;try {socket = new Socket("localhost", 8888);System.out.println("已连接到服务器,输入消息发送(输入exit退出):");// 获取输入流和输出流BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"), true);scanner = new Scanner(System.in);String msg;// 循环输入并发送消息while (true) {msg = scanner.nextLine();out.println(msg);if ("exit".equals(msg)) {System.out.println("客户端将退出");break;}// 读取服务器响应String serverMsg = in.readLine();System.out.println("服务器回复:" + serverMsg);}} catch (IOException e) {e.printStackTrace();} finally {try {if (scanner != null) scanner.close();if (socket != null) socket.close();System.out.println("客户端已关闭");} catch (IOException e) {e.printStackTrace();}}}
}

运行说明

  1. 运行 MultiThreadTCPServer
  2. 可以启动多个 MultiTCPClient 实例,每个客户端都能与服务器独立通信
  3. 在客户端输入消息发送,输入 "exit" 退出

18.3 数据报通信

        UDP(User Datagram Protocol,用户数据报协议)是一种无连接的传输层协议,与 TCP 相比,它不保证数据的可靠传输,但具有传输速度快、开销小的特点,适用于对实时性要求高但对可靠性要求不高的场景(如视频聊天、语音通话、游戏等)。

18.3.1 数据报通信概述

UDP 通信特点

  • 无连接:通信前不需要建立连接
  • 不可靠:不保证数据一定到达,也不保证顺序
  • 面向数据报:数据以数据报(Datagram)为单位传输
  • 速度快:协议简单,开销小

UDP 通信流程

  1. 发送方将数据打包成数据报,指定接收方的 IP 和端口
  2. 数据报通过网络发送
  3. 接收方从网络中接收数据报

18.3.2 DatagramSocket 类和 DatagramPacket 类

Java 中使用以下类实现 UDP 通信:

  • DatagramSocket:用于发送和接收数据报的套接字

    • 构造方法:
      • DatagramSocket() - 创建未绑定的套接字
      • DatagramSocket(int port) - 创建绑定到指定端口的套接字
    • 核心方法:
      • void send(DatagramPacket p) - 发送数据报
      • void receive(DatagramPacket p) - 接收数据报(阻塞)
      • void close() - 关闭套接字
  • DatagramPacket:表示数据报

    • 构造方法(接收数据时):DatagramPacket(byte[] buf, int length)
    • 构造方法(发送数据时):DatagramPacket(byte[] buf, int length, InetAddress address, int port)
    • 核心方法:
      • byte[] getData() - 获取数据报中的数据
      • int getLength() - 获取数据长度
      • InetAddress getAddress() - 获取发送方 / 接收方的 IP 地址
      • int getPort() - 获取发送方 / 接收方的端口号
@startuml
class DatagramSocket {+ DatagramSocket() throws SocketException+ DatagramSocket(int port) throws SocketException+ void send(DatagramPacket p) throws IOException+ void receive(DatagramPacket p) throws IOException+ void close()
}class DatagramPacket {+ DatagramPacket(byte[] buf, int length)+ DatagramPacket(byte[] buf, int length, InetAddress address, int port)+ byte[] getData()+ int getLength()+ InetAddress getAddress()+ int getPort()+ void setData(byte[] buf)+ void setLength(int length)
}DatagramSocket "1" -- "*" DatagramPacket : 发送/接收
@enduml

18.3.3 简单的 UDP 通信例子

下面实现一个简单的 UDP 通信程序:客户端向服务器发送消息,服务器接收后回复

UDP 服务器代码(UDPServer.java)

import java.net.*;
import java.nio.charset.StandardCharsets;public class UDPServer {public static void main(String[] args) {DatagramSocket socket = null;byte[] receiveBuf = new byte[1024]; // 接收缓冲区try {// 创建UDP套接字,绑定到8888端口socket = new DatagramSocket(8888);System.out.println("UDP服务器已启动,等待消息...");while (true) { // 循环接收消息// 创建接收数据报DatagramPacket receivePacket = new DatagramPacket(receiveBuf, receiveBuf.length);// 接收数据报(阻塞)socket.receive(receivePacket);// 解析接收的数据String clientMsg = new String(receivePacket.getData(), 0, receivePacket.getLength(), StandardCharsets.UTF_8);InetAddress clientAddr = receivePacket.getAddress();int clientPort = receivePacket.getPort();System.out.println("收到来自 " + clientAddr.getHostAddress() + ":" + clientPort + " 的消息:" + clientMsg);// 准备回复消息String serverMsg = "服务器已收到:" + clientMsg;byte[] sendData = serverMsg.getBytes(StandardCharsets.UTF_8);// 创建发送数据报DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, clientAddr, clientPort);// 发送回复socket.send(sendPacket);System.out.println("已回复消息:" + serverMsg);// 如果收到"exit",则退出服务器if ("exit".equals(clientMsg)) {System.out.println("服务器将关闭");break;}}} catch (Exception e) {e.printStackTrace();} finally {// 关闭套接字if (socket != null) {socket.close();}}}
}

UDP 客户端代码(UDPClient.java)

import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;public class UDPClient {public static void main(String[] args) {DatagramSocket socket = null;Scanner scanner = null;try {// 创建UDP套接字(不指定端口,系统会分配一个临时端口)socket = new DatagramSocket();// 服务器地址和端口InetAddress serverAddr = InetAddress.getByName("localhost");int serverPort = 8888;System.out.println("UDP客户端已启动,输入消息发送(输入exit退出):");scanner = new Scanner(System.in);while (true) {// 读取用户输入String msg = scanner.nextLine();// 准备发送数据byte[] sendData = msg.getBytes(StandardCharsets.UTF_8);// 创建发送数据报DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, serverAddr, serverPort);// 发送数据报socket.send(sendPacket);System.out.println("已发送消息:" + msg);// 如果输入"exit",则退出客户端if ("exit".equals(msg)) {System.out.println("客户端将关闭");break;}// 接收服务器回复byte[] receiveBuf = new byte[1024];DatagramPacket receivePacket = new DatagramPacket(receiveBuf, receiveBuf.length);socket.receive(receivePacket);// 解析回复数据String serverMsg = new String(receivePacket.getData(), 0, receivePacket.getLength(), StandardCharsets.UTF_8);System.out.println("收到服务器回复:" + serverMsg);}} catch (Exception e) {e.printStackTrace();} finally {// 关闭资源if (scanner != null) scanner.close();if (socket != null) socket.close();}}
}

运行说明

  1. 先运行 UDPServer
  2. 再运行 UDPClient
  3. 在客户端输入消息,服务器会收到并回复
  4. 输入 "exit" 可退出程序

18.4 URL 类编程

        URL(Uniform Resource Locator,统一资源定位符)用于标识互联网上的资源,如网页、图片、文件等。Java 提供了 URL 和 URLConnection 类来方便地访问 URL 指向的资源。

18.4.1 理解 HTTP

        HTTP(HyperText Transfer Protocol,超文本传输协议)是一种基于 TCP 的应用层协议,用于在客户端和服务器之间传输超文本数据(如 HTML)。

HTTP 通信流程

  1. 客户端(如浏览器)向服务器发送 HTTP 请求
  2. 服务器处理请求,返回 HTTP 响应
  3. 客户端解析响应内容并展示

HTTP 请求方法

  • GET:请求获取资源
  • POST:向服务器提交数据
  • PUT:更新资源
  • DELETE:删除资源

HTTP 响应状态码

  • 200:成功
  • 404:资源未找到
  • 500:服务器内部错误

18.4.2 URL 和 URL 类

URL 的格式协议://主机名:端口/路径?查询参数#片段
例如:https://www.csdn.net:443/article/list/1?type=1#content

Java 中的 java.net.URL 类用于表示 URL,并提供了访问 URL 资源的方法:

  • 构造方法:URL(String spec) - 根据字符串创建 URL 对象
  • 常用方法:
    • String getProtocol() - 获取协议
    • String getHost() - 获取主机名
    • int getPort() - 获取端口号
    • String getPath() - 获取路径
    • InputStream openStream() - 获取 URL 资源的输入流

18.4.3 URLConnection 类

   URLConnection 是一个抽象类,用于表示与 URL 所指向资源的连接。它比 URL 类提供了更多的功能,如设置请求头、获取响应头、处理表单提交等。

常用方法:

  • static URLConnection openConnection() - 获取 URLConnection 对象
  • void setRequestProperty(String key, String value) - 设置请求头
  • Map<String, List<String>> getHeaderFields() - 获取响应头
  • InputStream getInputStream() - 获取输入流,用于读取资源
  • OutputStream getOutputStream() - 获取输出流,用于发送数据(如 POST 请求)
  • void connect() - 建立连接

使用 URL 读取网页内容的示例(URLReader.java)

import java.io.*;
import java.net.*;public class URLReader {public static void main(String[] args) {// 要访问的URLString urlStr = "https://www.baidu.com";BufferedReader reader = null;try {// 创建URL对象URL url = new URL(urlStr);System.out.println("协议:" + url.getProtocol());System.out.println("主机:" + url.getHost());System.out.println("端口:" + url.getPort()); // -1表示使用协议默认端口System.out.println("路径:" + url.getPath());// 打开URL连接,获取输入流reader = new BufferedReader(new InputStreamReader(url.openStream(), "UTF-8"));// 读取内容并输出String line;System.out.println("\n网页内容:");while ((line = reader.readLine()) != null) {System.out.println(line);}} catch (MalformedURLException e) {System.out.println("URL格式错误:" + e.getMessage());} catch (IOException e) {System.out.println("读取失败:" + e.getMessage());} finally {// 关闭资源try {if (reader != null) reader.close();} catch (IOException e) {e.printStackTrace();}}}
}

使用 URLConnection 发送 POST 请求的示例(URLConnectionPost.java)

import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;public class URLConnectionPost {public static void main(String[] args) {// 要提交的URL(这里使用一个测试接口)String urlStr = "https://httpbin.org/post";HttpURLConnection connection = null;BufferedReader reader = null;try {// 创建URL对象URL url = new URL(urlStr);// 打开连接connection = (HttpURLConnection) url.openConnection();// 设置请求方法为POSTconnection.setRequestMethod("POST");// 设置允许输出(发送数据)connection.setDoOutput(true);// 设置请求头connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");connection.setRequestProperty("User-Agent", "Mozilla/5.0");// 准备要提交的表单数据Map<String, String> params = new HashMap<>();params.put("name", "张三");params.put("age", "25");params.put("city", "北京");// 构建请求参数字符串StringBuilder postData = new StringBuilder();for (Map.Entry<String, String> entry : params.entrySet()) {if (postData.length() > 0) {postData.append('&');}// 对参数进行URL编码postData.append(URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8.name()));postData.append('=');postData.append(URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8.name()));}byte[] postDataBytes = postData.toString().getBytes(StandardCharsets.UTF_8);// 设置请求内容长度connection.setRequestProperty("Content-Length", String.valueOf(postDataBytes.length));// 获取输出流,发送数据try (DataOutputStream out = new DataOutputStream(connection.getOutputStream())) {out.write(postDataBytes);}// 获取响应状态码int responseCode = connection.getResponseCode();System.out.println("响应状态码:" + responseCode);// 读取响应内容reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8));String line;System.out.println("响应内容:");while ((line = reader.readLine()) != null) {System.out.println(line);}} catch (MalformedURLException e) {System.out.println("URL格式错误:" + e.getMessage());} catch (IOException e) {System.out.println("请求失败:" + e.getMessage());} finally {// 关闭资源try {if (reader != null) reader.close();} catch (IOException e) {e.printStackTrace();}if (connection != null) {connection.disconnect();}}}
}

运行说明

  • URLReader 会读取指定 URL 的内容并输出到控制台
  • URLConnectionPost 会向测试接口发送 POST 请求,并输出响应结果

注意:访问某些网站可能会被拒绝(如设置了反爬机制),可以尝试修改 User-Agent 等请求头信息模拟浏览器行为。


18.5 小结

本章主要介绍了 Java 网络编程的核心内容,包括:

  1. 网络基础概念:网络分层模型、C/S 结构、IP 地址、域名、端口号和套接字
  2. TCP 套接字通信:使用 ServerSocket 和 Socket 类实现可靠的面向连接的通信,以及多线程服务器的实现
  3. UDP 数据报通信:使用 DatagramSocket 和 DatagramPacket 类实现无连接的不可靠通信
  4. URL 编程:使用 URL 和 URLConnection 类访问互联网资源,包括发送 GET 和 POST 请求

        Java 网络编程是 Java 编程中的重要组成部分,掌握这些知识可以帮助我们开发各种网络应用,如客户端 / 服务器程序、网络爬虫、API 调用等。


编程练习

  1. 练习 1:实现文件传输

    • 编写一个 TCP 服务器和客户端,实现文件上传功能(客户端将本地文件发送到服务器,服务器保存文件)
  2. 练习 2:UDP 广播程序

    • 编写一个 UDP 程序,实现局域网内的广播功能(一个客户端发送消息,其他所有客户端都能收到)
  3. 练习 3:简易网页爬虫

    • 使用 URLConnection 编写一个简单的网页爬虫,爬取指定网页中的所有链接(<a> 标签的 href 属性)
  4. 练习 4:多线程聊天程序

    • 基于多线程 TCP 通信,实现一个简易的聊天程序,支持多个客户端之间的群聊功能

        通过这些练习,可以加深对 Java 网络编程的理解和应用能力。在实际开发中,还可以结合线程池、NIO 等技术进一步优化网络程序的性能。

http://www.xdnf.cn/news/1217755.html

相关文章:

  • C++面试5题--6day
  • LLC电源原边MOS管DS增加RC吸收对ZVS的影响分析
  • 开发避坑短篇(11):Oracle DATE(7)到MySQL时间类型精度冲突解决方案
  • PHP 5.5 Action Management with Parameters (English Version)
  • 专业鼠标点击器,自定义间隔次数
  • 网站技术攻坚与Bug围剿手记
  • Spring Cloud『学习笔记』
  • [硬件电路-111]:滤波的分类:模拟滤波与数字滤波; 无源滤波与有源滤波;低通、带通、带阻、高通滤波;时域滤波与频域滤波;低价滤波与高阶滤波。
  • 《Java 程序设计》第 17 章 - 并发编程基础
  • 澳交所技术重构窗口开启,中资科技企业如何破局?——从ASX清算系统转型看跨境金融基础设施的赋能路径
  • 数据结构与算法:队列的表示和操作的实现
  • HighgoDB查询慢SQL和阻塞SQL
  • 模型优化——在MacOS 上使用 Python 脚本批量大幅度精简 GLB 模型(通过 Blender 处理)
  • 打车小程序 app 系统架构分析
  • 【12】大恒相机SDK C#开发 ——多相机开发,枚举所有相机,并按配置文件中的相机顺序 将所有相机加入设备列表,以便于对每个指定的相机操作
  • 深入理解 Slab / Buddy 分配器与 MMU 映射机制
  • 【源力觉醒 创作者计划】对比与实践:基于文心大模型 4.5 的 Ollama+CherryStudio 知识库搭建教程
  • mysql结构对比工具
  • 类与对象(上),咕咕咕
  • ECMAScript2024(ES15)新特性
  • SpringAI 1.0.0发布:打造企业级智能聊天应用
  • AI 安监系统:为工业园安全保驾护航
  • 【Debian】4-‌1 Gitea简介以及与其他git方案差异
  • Windows 10 WSLUbuntu 22.04 安装并迁移到 F 盘
  • 2018 年 NOI 最后一题题解
  • 【预判一手面试问题:排序】
  • 2023 年 NOI 最后一题题解
  • n8n为什么建议在数组的每个item中添加json键?
  • Docker部署Nacos
  • LeetCode 53 - 最大子数组和