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

【网络篇】从零写UDP客户端/服务器:回显程序源码解析

在这里插入图片描述

大家好呀
我是浪前

今天讲解的是网络篇的第四章:从零写UDP客户端/服务器:回显程序源码解析

从零写UDP客户端/服务器:回显程序源码解析

  • UDP 协议特性​
    • 核心类介绍​
  • UDP的socket应该如何使用:
  • 1: DatagramSocket
  • 2: DatagramPacket
    • 回显服务器
    • 进行网络编程的第一步
    • 服务器的代码(回显服务器)
      • 创建一个DatagramSocket的对象:
      • 定义服务器启动方法:
      • 手动创建内存空间:
      • 将读取到的数据转成字符串:
      • 封装分用:
    • 客户端的代码:
    • 通信过程:

UDP 协议特性​

UDP(User Datagram Protocol)作为传输层协议,有着与 TCP 截然不同的特性,在网络通信中扮演着独特的角色,适用于对实时性要求高、能容忍少量数据丢失的场景。​

无连接通信:UDP 在数据传输前,发送方和接收方无需像 TCP 那样进行三次握手建立连接,可直接发送数据。无需复杂的连接建立过程,极大降低了传输延迟。

不可靠传输:它不保证数据一定能到达接收方,也不确保数据的顺序和完整性。若在网络传输中,UDP 数据包丢失或乱序,协议本身不会重传或纠正。

面向数据报:UDP 以独立的数据报为单位传输数据,每个数据报都包含完整的目标地址等信息,可独立传输,服务器收到后直接响应,简单高效。​

全双工通信:同一 UDP Socket 可同时进行数据的发送和接收。在语音通话应用中,双方能同时说话并实时听到对方声音,就是因为 UDP 的全双工特性,保证了语音数据的双向实时传输。​

核心类介绍​

在 Java 的 UDP 网络编程里,DatagramSocket和DatagramPacket是两个关键类,分别负责 socket 通信和数据报的封装传输,相互配合实现 UDP 通信功能。

UDP的socket应该如何使用:

UDP的API主要是提供了两个类:

  1. DatagramSocket
  2. DatagramPacket

1: DatagramSocket

socket: 本质上是操作系统中的一个概念,本质上是一个特殊的文件
这里的socket属于就是把“网卡”这个设备,给抽象成了文件,而进行网络通信最核心的硬件设备就是网卡

往socket文件中写数据,就相当于是通过网卡发送数据
往socket文件中读数据,就相当于是通过网卡接收数据

上面就是把文件操作和网络通信给统一了
在Java中就是使用这个 DatagramSocket 类就是来表示系统内部的socket文件
这个 DatagramSocket 类负责文件读写,也就是借助网卡发送和接收数据

2: DatagramPacket

使用这个类就是来表示一个UDP数据报:
UDP是面向数据报的
每次进行传输,都要以UDP数据报为基本单位
每次传输都只能够传输一个完整的数据报,不可以传输半个数据报,也不可以传输一个半数据报

写一个简单的UDP的客户端/服务器通信的程序:
这个程序没有什么业务逻辑,请求什么,就响应什么
只是单纯滴调用Socket API

让客户端给服务器发送一个请求,请求就是一个从控制台输入的字符串,服务器收到字符串之后,也就会把这个字符串原封不动地返回给客户端,然后客户端再显示出来,
这个程序就是请求什么,就响应什么
这个程序是最简单的网络通信程序,叫做回显服务器

回显服务器

服务器的主要功能:

负责接收客户端的请求,然后根据实际的业务场景来返回不同的响应

有一种服务器是回显服务器:

回显服务器的作用就是客户端发啥请求,回显服务器就立马返回啥请求,没有业务逻辑的

比如:
客户端发送想吃蛋炒饭的请求
服务器就接收到蛋炒饭的请求之后,就返回蛋炒饭的响应

这个回显服务器是网络编程中最简单的程序,相当与网络编程中的“Hello World”

回显服务器的作用

  1. 学会掌握Socket API的基本使用
  2. 学会典型的客户端服务器的工作流程

进行网络编程的第一步

服务器的代码(回显服务器)

我们先来写一个服务器代码:

创建一个DatagramSocket的对象:

注意:
这个对象是在创建在内存中的,直接对内存进行操作就可以影响到网卡

程序启动的同时要关联/绑定一个端口号,这个端口号是专门用来区分主机的

一个主机只能够有一个端口号,同时一个主机中的端口号只能和一个进程进行绑定,
一个端口号和进程A进行了绑定之后,如果进程B也要和这个端口号进行绑定,那么进程B会绑定失败

但是一个进程是可以同时和多个端口号进行绑定的
为什么?
因为每一个端口号对应了一个DatagramSocket对象,如果一个进程中有多个DatagramSocket对象的话,就可以和多个端口号进行绑定
如下图所示:
在这里插入图片描述

同时我们在创建DatagramSocket对象的时候必须要手动指定一个端口号
在运行一个服务器程序的时候,也要手动指定端口号

DatagramSocket对象的代码创建如下:

package netWork;  import java.net.DatagramSocket;  
import java.net.SocketException;  public class Server {  private DatagramSocket socket = null;  public Server(int port) throws SocketException{  socket = new DatagramSocket(port);  }  }

上述的代码抛出的异常为SocketException
这个异常是在创建DatagramSocket对象时,当主机的端口号已经被其他进程绑定的时候会抛出的异常

定义服务器启动方法:

接下来定义一个start方法来作为服务器启动的方法:
start方法的执行逻辑如下所示:

在start方法中会有一个while循环:
由于服务器每天从客户端那里接收到的请求有很多,
所以服务器会每时每刻都在不停地运行,每循环一次,就是服务器在接收到请求,返回响应的过程

在每次while循环中,会经历下面三个步骤 :

  1. 服务器读取客户端发来的请求,解析请求
  2. 服务器根据请求来计算响应(回显服务器没有这一步)
  3. 服务器向客户端返回响应
    代码如下:
public void start(){  System.out.println("服务器启动");  while(true){  //每次循环,都是一次服务器在接收请求,返回响应的过程  //1.读取请求,解析请求  socket.receive();  //2.根据请求计算响应(回显服务器不需要这一步)  //3. 返回响应  }  
}

第一步:读取请求,进行解析,

socket.receive()

上面代码中的这个receive方法中需要填写一个DatagramPacket类型的参数,
这个参数是一个输出型参数,这个参数在文件IO中也涉及到了,实际上在DatagramPacket内部就会包含一个字节数组,如下所示

DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);

这个字符数组会保存收到的消息正文,这个消息正文就是应用层数据包,也就是UDP数据报载荷部分,这个载荷空间的大小可以灵活设置

将这个参数传入socket.receive()中后,会抛出一个异常:

IOException : //网络编程,读写socket本质就是IO

public void start() throws IOException {  System.out.println("服务器启动");  while(true){  //每次循环,都是一次服务器在接收请求,返回响应的过程  //1.读取请求,解析请求  DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);  socket.receive(requestPacket);  //2.根据请求计算响应(回显服务器不需要这一步)  //3. 返回响应  }  
}

手动创建内存空间:

当收到数据的时候,需要搞一个内存空间来保存这个数据

所以上面的requestPacket对象是用来承载从网卡那里读到的数据

但是由于在DatagramPacket的内部是不可以自行分配内存空间的,所以需要手动把内存空间创建好

这里创建了一个字节数组,这个字节数组就是真正的用来承载数据的内存空间
然后再交给DatagramPacket处理:

之后receive就会从这个requestPacket对象中读取数据,然后把读取到的数据填充到socket对象中去

此处receive就可以从网卡中读取一个UDP数据报

这个UDP数据报就是被放进了requestPacket对象中

其中UDP数据报的载荷部分被放进了requestPacket内置的字节数组中,同时,UDP的报头部分和收到的数据源IP,源IP端口都会被保存在 requestPacket的其他属性中

所以我们requestPacket还可以知道数据是从哪里来的(源IP源端口)

如果执行到receive的时候,还没有客户端发来请求,那么此时receive就没有可以读取的数据,此时receive就会发生阻塞,一直阻塞到客户端发来请求为止

将读取到的数据转成字符串:

此时的receive会读取到一个字节数组,
当receive读取完毕之后,数据是以二进制的形式存储到DatagramPacket中

要想能够把这里的数据给显示出来,就需要把这个二进制数据转化为字符串
所以,此时的读的字节数组必须要先转成(字符串)String之后,才方便后续的逻辑处理:

String request = new String(requestPacket.getData(),0,requestPacket.getLength());

基于字节数组构造String,字节数组里面保存的内容不一定就是二进制数据,也可能是文本数据
而字符串(String) 不仅可以保存二进制数据,还可以保存文本数据,所以需要将这个字节数组转成字符串 :

在这里插入图片描述

注意:
在getLength()中获取的字节数组的有效数据的长度不一定就是4096
这个4096是这个字节数组的最大长度,
而getLength()获取到的结果是收到的数据的真实长度,即发送方这一次实际发送了多少个数据
比如:
如果这一次收到的数据长度是10,那么这个getLength()获取到的就是10。
所以我们这里构造字符串是使用有效数据长度来进行构造,不能使用字节数组的最大长度来构造

以上就是把一个请求转化为字符串了

在这里插入图片描述

封装分用:

网路通信过程中涉及到"封装和分用":

只有当应用层调用传输层提供的API的时候,才会把这个数据给读取到;
数据来到服务器时,会经由物理层,一层层分用到应用层:

在传输层中:会给每一个socket对象都分配一个缓冲区(这个缓冲区在操作系统内核里)
每次网卡收到一个数据都是经由层层分用,解析好之后,最终放到缓冲区里
在应用层的应用程序调用receive就是从这个缓冲区里面拿走一个数据
这个本质上就是生产者-消费者模型,而此处给socket对象分配的缓冲区就是阻塞队列

所以从客户端传过来的数据 不是存在socket文件中,而是存在socket对象中的一个内存缓冲区的阻塞队列中

第二步:根据请求来构造响应:

String response = process(request);

这个代码要根据请求构造响应,通过这个process方法来构造响应

public  String process(String request) {  return request;  
}

由于此处是回显服务器,所以只需要单纯滴返回这个请求就可以了

第三步: 把响应返回给客户端:

1: 构造一个响应对象DatagramPacket作为响应对象

同时由于UDP是无连接的,所以UDP不会保存要发给谁

所以就需要在每次发送的时候,重新指定,数据要发送到哪里去

所以在这个响应对象(数据报)中需要指定数据内容,也要指定数据具体要发送给谁。

//构造一个DatagramPacket作为响应对象  
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(StandardCharsets.UTF_8),
response.getBytes().length);

在这里插入图片描述

刚刚的socket对象还没有构造完毕,在构造时还需要指定一个socketAddress进去:

DatagramPacket responsePacket = new DatagramPacket(response.getBytes(StandardCharsets.UTF_8),
response.getBytes().length,requestPacket.getAddress(),requestPacket.getPort());

这个requestPacket.getAddress()和requestPacket.getPort()
方法会获取到一个IP和一个端口号

这个IP和端口号是和服务器通信的对端的客户端的IP和 端口号
这个IP和端口号是从这个requestPacket这个客户端数据包中获取的

同时在代码中的response.getBytes().length获取到的是字节

在进行网络传输的时候,一定是使用字节来进行传输的

而response.length() 获取到的是字符,如果全是英文,那么字节和字符的个数一样,但是如果有中文,那么此时字节和字符的个数就不一样了。

为什么要获取到这个客户端的IP和端口号?

这里是把客户端(请求)中的源IP和源端口,作为响应的目的IP和目的端口
此时就可以做到把消息返回给客户端的效果了

此时我们的响应对象就构造好了,只需要将这个responsePacket作为参数使用send方法传递出去即可:

socket.send(responsePacket);

上述代码中,可以看到UDP 是无连接通信,UDP socket自身不保存对端(客户端)的IP和端口号 :

这个IP和端口号是在数据包中有一个,同时在代码中也没有"建立连接"和"接受连接"的操作

这个是直接读取请求,若没有请求,则阻塞等待,若有请求,则对请求进行解析,然后根据请求构造响应,最后返回响应

所谓的UDP不可靠传输目前代码中没有体现:

而UDP的面向数据报有体现: 上述代码中的send和receive的参数接收都是以DatagramPacket为单位进行发送和接收的

UDP的全双工在代码中也有体现:
一个socket既可以发送又可以接收,就叫做全双工

最后在代码中进行一个打印日志的操作:

//打印日志:  
System.out.printf("[%s:%d] req: %s,resp: %s\n",requestPacket.getAddress().toString(),  requestPacket.getPort(), request, response);

在这里插入图片描述

之后撰写一个main方法即可:

public static void main(String[] args) throws IOException {  Server server = new Server(9090);  server.start();  
}

在这里插入图片描述

上述服务器的代码编写完毕,

下面是回显服务器的完整代码:

package netWork;  import javax.xml.crypto.Data;  
import java.io.IOException;  
import java.net.*;  
import java.sql.SQLOutput;  public class UdpServer {  //创建一个DatagramSocket对象,是后续进行网卡的基础  private DatagramSocket socket = null;  public UdpServer(int port)  throws SocketException{  //下面是手动指定端口  socket = new DatagramSocket(port);  //下面这么写就是系统自动分配端口  //socket = new DatagramSocket();  }  //程序的主方法,通过这个方法来启动服务器  public void start() throws IOException {  System.out.println("服务器启动");  //一个服务器要不停滴运行,所以需要一个while循环来进行操作,一个服务器程序是要长时间运行的  //为了保证客户端随时来,随时可以响应,  while(true){  //1.第一步,读取请求并解析  DatagramPacket requestPacket = new DatagramPacket(new byte[4090],4090);  socket.receive(requestPacket);  //将请求转化为字符串  String request = new String(requestPacket.getData(),0,requestPacket.getLength());  //2.根据请求计算响应  //这个步骤是服务器最核心的一个步骤,  String response = process(request);  //3. 把响应返回给客户端  //使用一个响应对象  DatagramPacket  往响应对象中构造刚才的数据,再通过send返回  DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,  requestPacket.getSocketAddress());  //使用send方法传入参数进行发送socket.send(responsePacket);System.out.printf("[%s:%d] req = %s,resp = %s\n",requestPacket.getAddress().toString(),  requestPacket.getPort(),request,response);  }  }  public String process(String request){  return request;  }  public static void main(String[] args) throws IOException {  UdpServer server = new UdpServer(9090);  server.start();  }  
}

为什么上述的代码中没有出现close()?
socket也是一个文件,不进行关闭的话,会造成文件资源泄露

什么是文件资源泄露?
你一直申请,但是一直都没有进行close,没有进行释放,结果到最后,你想用的时候,发现用不了了,就是文件资源泄露。

为什么这里不写close()方法,也不会出现文件资源泄露呢?
因为socket是文件描述符表中的一个表项.

每次打开一个文件,就会占用一个位置,文件描述符是在PCB(进程)上的,是跟随进程的
这个socket在整个程序过程中一直都在使用,不可以提前释放,不可以提前关闭

当socket不使用的时候,此时整个程序也要结束了
当进程结束时,文件描述符表也会跟随着进程的结束被销毁,就可能发生泄露问题了。

总结:

不会泄露的原因是因为socket会随着进程销毁的过程中,被系统自动回收了

什么时候会出现泄露?

代码中频繁地打开文件,但是不关闭,在一个进程的运行过程中,不断积累打开的文件,逐渐消耗掉了文件描述符表里的内容,最后内容会被消耗光,就出现了泄露

但是如果进程的生命周期很短,打开一下就关闭了,也就不会出现泄露了
所以文件资源泄露的问题在服务器上经常出现,因为服务器的进程生命周期很长,要一直运行
泄露的问题在客户端上很少出现,因为客户端的进程的生命周期很短,客户端打开之后用一下就直接关闭了

客户端的代码:

接下来我们去 编写客户端的代码:

注意:服务器需要手动指定端口号
但是客户端不需要手动指定端口号,不手动指定也有端口号,
因为系统会自动给客户端分配一个空闲的端口号

为什么服务器必须要自己手动指定一个端口号?
因为服务器要保证端口号是固定不变的

因为只有在服务器代码中手动指定一个端口号

才能保证端口始终是固定的,如果不手动指定,服务器依赖系统自动分配端口号,就会导致服务器每次开机重启后,系统自动分配的端口号就发生了改变 。

如果服务器的端口号发生了改变,那么客户端就可能会找不到这个服务器在哪里了,所以服务器的端口号必须要在代码中手动指定

那么为什么客户端中的端口号不需要手动指定, 可以通过系统自动分配呢?

客户端的端口号让系统随机分配,系统会去分配给客户端一个空间中可用的端口号
如果是手动指定端口号,那么就无法确定这个端口号是不是可控的,有没有被别的进程占用

为什么服务器的端口号就不怕被别的进程占用呢?

因为服务器这个机器是在程序员手中的,程序员对于服务器上有哪些端口号是可用的一清二楚

但是客户端是在用户手中的,有千千万万个用户,上面的环境也千差万别,程序员无法得知端口号是否被占用,如果强行手动指定客户端的端口号就会导致端口绑定失败.

所以程序员手中的服务器的端口号是可以手动指定的,但是在用户手中的客户端的端口号是不能手动指定的,只能靠系统自动分配一个空闲的端口号

在构造方法中,由于UDP自身不会保存对端的信息,所以就需要在应用程序中,把对端的情况给记录下来,在构造方法中主要记录的就是对端的IP和端口,也就是目的IP和目的端口:

  
public class Client {  //首先要创建socket对象,但是此处不需要手动指定端口号  DatagramSocket socket = null;  //构造方法:要传输服务器IP(目的IP)和服务器端口(目的端口)  public Client(String serverIp, int serverPort) throws SocketException {  socket = new DatagramSocket();  }  
}

在这里插入图片描述

接下来创建start方法来启动客户端:
在这个start方法中,依然是使用一个循环来不停滴发送请求:
在循环中一共要做四件事情:

  1. 从控制台中读取请求数据
  2. 构造请求并发送
  3. 读取服务器的响应
  4. 把响应显示到控制台上
public void start(){  System.out.println("客户端启动");  Scanner scanner = new Scanner(System.in);  while(true){  System.out.println("-> ");  //1. 从控制台中读取请求数据   //2. 构造请求并发送  //3. 读取服务器的响应  //4. 把响应显示到控制台上  }  
}

第一步: 从控制台中读取数据,作为请求:

String request = scanner.next();

这里从控制台读取请求,使用scanner读取字符串,最好使用next来读取,而不是使用nextLine

因为如果使用nextLine读取,可能会读取不到空格

nextLine遇到空格就自动作为分隔符了,所以需要手动输入换行符

使用enter来控制,由于enter键不仅仅会产生\n,还会产生其他的字符,就会导致当前的这个读取到的内容会出问题

而使用next其实是以“空白符”作为分隔符,包括但不限于换行,回车,空格,制表符,垂直制表符

总结:

如果从控制台读取内容,就使用next()
如果是从文件读取内容,那么使用next()和nextLine()都可以

第二步: 把请求的内容构造成一个DatagramPacket对象,在对象中保存数据,长度, 目的IP和目的端口:

DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),  request.getBytes().length, InetAddress.getByName(serverIp),serverPort);

然后将这个对象发给服务器:

socket.send(requestPacket);

OK,此时客户端已经向服务器发送了请求,那么接下来就只需要去读取服务器返回的响应即可:

第三步: 尝试读取服务器返回的响应:
此时我们也是需要先构造一个空的DatagramPacket对象来接收响应的数据:

DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);

接下来使用这个responsePacket对象对响应进行接收即可:

socket.receive(responsePacket);

第四步: 把响应转换成字符串,并显示出来:

//4. 把响应转化成字符串,并显示到控制台上  
String response = new String(responsePacket.getData(),0,responsePacket.getLength());  
System.out.println(response);

上述的start方法就结束了,最后再补上一个main方法即可:

  
public static void main(String[] args) throws IOException {  Client client = new Client("127.0.0.1",9090);  client.start();  
}

综上所述:客户端的完整代码如下所示:

package netWork;  import java.io.IOException;  
import java.net.*;  
import java.util.Scanner;  public class Client {  //首先要创建socket对象,但是此处不需要手动指定端口号  DatagramSocket socket = null;  private String serverIp;  private int serverPort;  //构造方法:要传输服务器IP(目的IP)和服务器端口(目的端口)  public Client(String serverIp, int serverPort) throws SocketException {  this.serverIp = serverIp;  this.serverPort = serverPort;  //下面就是自动分配一个端口号  socket = new DatagramSocket();  }  public void start() throws IOException {  System.out.println("客户端启动");  Scanner scanner = new Scanner(System.in);  while(true){  System.out.println("-> ");  //1. 从控制台中读取请求数据  String request = scanner.next();  //2. 构造请求并发送  DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),  request.getBytes().length, InetAddress.getByName(serverIp),serverPort);  socket.send(requestPacket);  //3. 读取服务器的响应  DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);  socket.receive(responsePacket);  //4. 把响应转化成字符串,并显示到控制台上  String response = new String(responsePacket.getData(),0,responsePacket.getLength());  System.out.println(response);  }  }  public static void main(String[] args) throws IOException {  Client client = new Client("127.0.0.1",9090);  client.start();  }  }

通信过程:

此时客户端和服务器就可以相互配合,完成通信过程:

步骤如下:

  1. 先启动服务器
  2. 再启动客户端
  3. 在客户端中编写hello
  4. 然后就可以在服务器中看见

下面是 客户端的界面展示:
在这里插入图片描述

下面是服务器的界面展示:
在这里插入图片描述

执行过程:

  1. 第一步:服务器启动,进入while循环,执行到receive这里时发生阻塞(此时客户端还没有发送请求)
  2. 第二步:客户端开始启动: 也会进入while循环,执行scanner.next,并且在这里阻塞,直到用户在控制台输入,当用户输入字符串之后,next就会返回,从而构造请求数据并发送出来
  3. 第三步:客户端发送出数据之后,在服务器那边,就会从receive中返回数据,进一步的解析请求为字符串,执行process操作,执行send操作。 此时的客户端也会继续往下执行,执行到receive,等待服务器的响应
  4. 客户端收到服务器返回的数据之后,就会从receive中返回,执行这里的打印操作,也就把响应给显示出来了
  5. 服务器完成一次循环之后,就又会执行到receive,重新进入阻塞
  6. 客户端完成一次循环之后,就又会执行到scanner.next,重新进入阻塞

我们重点要理解网络程序的交互逻辑

刚刚的两个程序都是在一个主机上的,没有实现跨主机通信的效果

能否让同学使用客户端程序来访问老师的服务器代码呢?

如果我的服务器就在我的电脑上,此时,你是不可以直接访问的,除非老师和同学的电脑都在同一个局域网下,即同一个路由器下,才可以

但是还有一种方式“ 云服务器”
有了这个,就可以访问老师的电脑了,因为老师的电脑没有公网IP,但是云服务器有公网IP

jar包是java打包的一种基本方式

把刚才的UDP服务器部署到云服务器上,进一步的,就可以让大家来访问了

之后可以调整一下客户端的代码,让客户端访问云服务器上的服务程序,就只需要把IP地址换成云服务器的IP即可

在这里插入图片描述

相关文章:

【网络篇】从零写UDP客户端/服务器:回显程序源码解析

大家好呀 我是浪前 今天讲解的是网络篇的第四章:从零写UDP客户端/服务器:回显程序源码解析 从零写UDP客户端/服务器:回显程序源码解析 UDP 协议特性​核心类介绍​ UDP的socket应该如何使用:1: DatagramSocket2: DatagramPacket回…...

学习笔记:黑马程序员JavaWeb开发教程(2025.3.23)

11.2 案例-文件上传-简介 文件上传的前端页面的代码需要放到springboot项目的static里面,也就是resource文件夹下面的static文件夹里面 服务端接收前端上传的数据,再服务端定义一个controller来接收数据,再controller中定义一个…...

提示词构成要素对大语言模型跨模态内容生成质量的影响

提示词构成要素对大语言模型跨模态内容生成质量的影响 提示词清晰度、具象性与质量正相关 限定指向性要素优于引导指向性要素 大语言模型生成内容保真度偏差 以讯飞星火大模型为实验平台,选取100名具备技术素养的人员,从提示词分类、构成要素和实践原则归纳出7种提示词组…...

浅聊docker的联合文件系统

前言: 在我们pull镜像的时候,就会发现一个神奇的地方,在将镜像pull到本地的时候它是分层下载的,如下图: 这时候我就有一个疑问,为什么是分层下载的?怎么和我们平时下载软件的时候不一样呢? 联…...

计算机视觉cv入门之Haarcascade的基本使用方法(人脸识别为例)

Haar CascadeXML特征分类器,是一种基于机器学习的方法,它利用了积分图像(或总面积)的概念有效地提取特征(例如,边缘、线条等)的数值。“级联分类器”即意味着不是一次就为图像中的许多特征应用数百个分类器,而是一对一地应用分类器…...

【NLP 62、实践 ⑮、基于RAG + 智谱语言模型的Dota2英雄故事与技能介绍系统】

羁绊由我而起,痛苦也由我承担 —— 25.4.14 英雄介绍文件: 通过网盘分享的文件:RAG 智谱语言模型的Dota2英雄故事与技能介绍系统 链接: https://pan.baidu.com/s/1G7Xo5TRvFl2BzUnE0NFaBA?pwd4d4j 提取码: 4d4j --来自百度网盘超级会员v3的…...

Keil MDK 编译问题:function “HAL_IncTick“ declared implicitly

问题与处理策略 问题描述 ..\..\User\stm32f1xx_it.c(141): warning: #223-D: function "HAL_IncTick" declared implicitlyHAL_IncTick(); ..\..\User\stm32f1xx_it.c: 1 warning, 0 errors问题原因 在 stm32f1xx_it.c 文件中调用了 HAL_IncTick(),但…...

OpenCV基础01-图像文件的读取与保存

介绍: OpenCV是 Open Souce C omputer V sion Library的简称。要使用OpenCV需要安装OpenCV包,使用前需要导入OpenCV模块 安装 命令 pip install opencv-python 导入 模块 import cv2 1. 图像的读取 import cv2 img cv2.imread(path, flag)这里的flag 是可选参数&…...

IP数据报

IP数据报组成 IP数据报(IP Datagram)是网络中传输数据的基本单位。 IP数据报头部 版本(Version) 4bit 告诉我们使用的是哪种IP协议。IPv4版本是“4”,IPv6版本是“6”。 头部长度(IHL,Intern…...

视频联网平台与AI识别技术在电力行业的创新应用

一、电力行业智能化转型的迫切需求 在能源革命与数字化转型的双重推动下,电力行业正面临着前所未有的智能化升级需求。随着特高压电网的大规模建设和新能源占比的不断提高,传统的电力运维管理模式已经难以满足现代电网安全、高效运行的要求。据统计&…...

Apache Parquet 文件组织结构

简要概述 Apache Parquet 是一个开源、列式存储文件格式,最初由 Twitter 与 Cloudera 联合开发,旨在提供高效的压缩与编码方案以支持大规模复杂数据的快速分析与处理。Parquet 文件采用分离式元数据设计 —— 在数据写入完成后,再追加文件级…...

深度学习方向急出成果,是先广泛调研还是边做实验边优化?

目录 有限资源下本科生快速发表深度学习顶会论文的实战策略 1.短周期内可出成果的研究路径 2.论文阅读与复现的优先顺序 3.无一对一指导时的调研与实验组织 4.成功案例:本科生顶会论文经验 5.快速上手的研究子方向推荐 大家好这里是AIWritePaper官方账号&…...

Python 深度学习实战 第11章 自然语言处理(NLP)实例

Python 深度学习实战 第11章 自然语言处理(NLP)实例 内容概要 第11章深入探讨了自然语言处理(NLP)的深度学习应用,涵盖了从文本预处理到序列到序列学习的多种技术。本章通过IMDB电影评论情感分类和英西翻译任务,详细介绍了如何使…...

9、Hooks:现代魔法咒语集——React 19 核心Hooks

一、魔法咒语的本质革新 "类组件如同古老的魔杖挥舞仪式,而Hooks是新时代的无杖施法!"霍格沃茨魔法研究院的魔杖动力学教授惊叹道。React Hooks通过函数式能量场重构了魔法运作模式,让组件能量流转如尼可勒梅的炼金术。 ——以《国…...

FutureTask底层实现

一、FutureTask的基本使用 平时一些业务需要做并行处理,正常如果你没有返回结果的需求,直接上Runnable。 很多时候咱们是需要开启一个新的线程执行任务后,给我一个返回结果。此时咱们需要使用Callable。 在使用Callable的时候,…...

深入浅出:LDAP 协议全面解析

在网络安全和系统管理的世界中,LDAP(轻量级目录访问协议,Lightweight Directory Access Protocol)是一个不可忽视的核心技术。它广泛应用于身份管理、认证授权以及目录服务,尤其在企业级环境中占据重要地位。本文将从基…...

学习笔记—C++—string(练习题)

练习题 仅仅反转字母 917. 仅仅反转字母 - 力扣(LeetCode) 题目 给你一个字符串 s ,根据下述规则反转字符串: 所有非英文字母保留在原有位置。所有英文字母(小写或大写)位置反转。 返回反转后的 s 。…...

论文阅读:2024 arxiv DeepInception: Hypnotize Large Language Model to Be Jailbreaker

总目录 大模型安全相关研究:https://blog.csdn.net/WhiffeYF/article/details/142132328 DeepInception: Hypnotize Large Language Model to Be Jailbreaker DeepInception:催眠大型语言模型,助你成为越狱者 https://arxiv.org/pdf/2311.0…...

OC底层原理【一】 alloc init new

OC底层原理【一】 alloc init && new 文章目录 OC底层原理【一】 alloc init && new前言allocslowpath(checkNil && !cls)) 和 fastpath(!cls->ISA()->hasCustomAWZ())!cls->ISA()->hasCustomAWZ()) obj->initInstanceIsa();将类与isa关…...

集合框架拓展--stream流的使用

Stream(JDK8新特性) 什么是Stream? 也叫stream流,是JDK8开始新增的一套API(java.util.stream.*),可以用于操作集合或数组中的数据 优势:Stream流大量地结合了Lambda的语法风格来编程&#xff…...

Beszel​​ 轻量级服务器监控平台的详细安装步骤

什么是 Beszel Beszel 是一个轻量级的服务器监控平台,包含 Docker 统计信息、历史数据和警报功能。 它拥有友好的 Web 界面、简单的配置,并且开箱即用。它支持自动备份、多用户、OAuth 身份验证和 API 访问 https://beszel.dev/zh/guide/what-is-besz…...

Spring 微服务解决了单体架构的哪些痛点?

1. 部署困难 (Deployment Difficulty & Risk) 单体痛点: 整体部署: 对单体应用的任何微小修改(哪怕只是一行代码),都需要重新构建、测试和部署整个庞大的应用程序。部署频率低: 由于部署过程复杂且风险高,发布周期通常很长&a…...

Kotlin delay方法解析

本文记录了kotlin协程(Android)中delay方法的字节码实现,并解析了delay方法如何实现挂起操作。 一、delay方法介绍 1.1、delay方法使用举例 class TestDelay {suspend fun testDelay() {Log.d("TestDelay", "before delay")delay(1000)Log.d…...

C# 类型、存储和变量(用户定义类型)

本章内容 C#程序是一组类型声明 类型是一种模板 实例化类型 数据成员和函数成员 预定义类型 用户定义类型 栈和堆 值类型和引用类型 变量 静态类型和dynamic关键字 可空类型 用户定义类型 除了C#提供的16种预定义类型,还可以创建自己的用户定义类型。有6种类型可以…...

C语言之高校学生信息快速查询系统的实现

🌟 嗨,我是LucianaiB! 🌍 总有人间一两风,填我十万八千梦。 🚀 路漫漫其修远兮,吾将上下而求索。 C语言之高校学生信息快速查询系统的实现 目录 任务陈述与分析 问题陈述问题分析 数据结构设…...

Windows串口通信

Windows串口通信相比较Android串口通信,在开发上面相对方便一些。原理都是一样,需要仔细阅读厂商设备的串口通信协议。结合串口调试助手进行测试,测试通过后,编写代码实现。 比如近期就接触到了一款天平,其最大测量值为100g,测量精度0.001g。 拿到手之后我就先阅读串口通…...

从零开始用Pytorch实现LLaMA 4的混合专家(MoE)模型

近期发布的LLaMA 4模型引入了混合专家(Mixture of Experts, MoE)架构,旨在提升模型效率和性能。尽管社区对LLaMA 4的实际表现存在一些讨论,但MoE作为一种重要的模型设计范式,继Mistral等模型之后再次受到关注。 所以我…...

python3GUI--仿网课答题播放器 By:PyQt5(分享)

文章目录 一.前言二.相关知识1.PyQt52.QMediaPlayer3.QThread4.Sqlite3 二.展示1.主界面2.课程播放&问答3.字幕调整4.播放列表折叠5.添加课程 三.心得与分享1.数据本地化2.自定义组件3.系统流程图与代码量4.免责声明 四&#…...

Python基础总结(八)之循环语句

文章目录 一、for循环1.1 for循环格式1.2 for ...else1.3 for...break1.4 for...continue 二、while循环2.1 while循环格式2.2 while...break2.3 while...continue2.4 while ...else 循环语句就如其名,就是重复的执行一段代码,直到满足退出条件时&#x…...

21. git apply

基本概述 git apply 的作用是&#xff1a;应用补丁文件 基本用法 1.命令格式 git apply [选项] <补丁文件>2.应用补丁 git apply patchfile.patch将补丁应用到工作目录&#xff0c;但不会自动添加到暂存区&#xff08;需手动 git add&#xff09; 常用选项 1.检查…...

第一章:MySQL视图基础

1. 视图是什么&#xff1f; ​​定义​​&#xff1a;视图&#xff08;View&#xff09;是一种虚拟表&#xff0c;其内容基于一个或多个真实表&#xff08;基表&#xff09;的查询结果。视图不实际存储数据&#xff0c;而是通过查询动态生成数据。​​核心特点​​&#xff1a…...

深入理解基线检查:网络安全的基石

深入理解基线检查&#xff1a;网络安全的基石 一、引言 在信息技术飞速发展的今天&#xff0c;网络安全已成为企业和组织正常运营的关键保障。从日常办公系统到关键业务应用&#xff0c;任何环节的安全漏洞都可能导致严重的后果&#xff0c;如数据泄露、系统瘫痪等。基线检查作…...

33-公交车司机管理系统

技术&#xff1a; 基于 B/S 架构 SpringBootMySQLvueelementui 环境&#xff1a; Idea mysql maven jdk1.8 node 用户端功能 1.首页:展示车辆信息及车辆位置和线路信息 2.模块:车辆信息及车辆位置和线路信息 3.公告、论坛 4.在线留言 5.个人中心:修改个人信息 司机端功能…...

【AI实践】使用DeepSeek+CherryStudio绘制Mermaid格式图表

目录 工具准备创建DeepSeek API Key安装CherryStudioMermaid在线编辑器 绘制图表编写提示词在CherryStudio中调用DeepSeek复制源码到Mermaid编辑器中进行微调 图表示例流程图思维导图甘特图 工具准备 创建DeepSeek API Key 打开DeepSeek开放平台&#xff0c; 注册并充值成功后…...

TCP报文段解析:从抽象到具象的趣味学习框架

TCP报文段解析&#xff1a;从抽象到具象的趣味学习框架 一、What&#xff1a;TCP报文段长什么样&#xff1f; 核心结构&#xff08;类比快递包裹&#xff09;&#xff1a; 复制 下载 | 源端口&#xff08;16位&#xff09;| 目的端口&#xff08;16位&#xff09;| |-----…...

B+树节点与插入操作

B树节点与插入操作 设计B树节点 在设计B树的数据结构时&#xff0c;我们首先需要定义节点的格式&#xff0c;这将帮助我们理解如何进行插入、删除以及分裂和合并操作。以下是对B树节点设计的详细说明。 节点格式概述 所有的B树节点大小相同&#xff0c;这是为了后续使用自由…...

rollup使用讲解

rollup 总结 什么是 rollup? rollup 是一个 JavaScript 模块打包器,在功能上要完成的事和 webpack 性质一样,就是将小块代码编译成大块复杂的代码,例如 library 或应用程序。在平时开发应用程序时,我们基本上选择用 webpack,相比之下,rollup.js 更多是用于 library 打…...

高边开关和低边开关的区别

高边驱动和低边驱动的区别 在高边驱动和低边驱动中&#xff0c;开关的位置直接影响电路在负载短路时的安全性和电流路径。以下是关键原理的分步解释&#xff1a; 1. 高低边驱动的结构对比 高边驱动&#xff08;High-Side Drive&#xff09; 电路结构&#xff1a; 电源正极 →…...

PG psql --single-transaction 参数功能

文章目录 PG psql --single-transaction 参数功能 PG psql --single-transaction 参数功能 test.sql 文件 create table test1(id int); CREATE OR REPLACE FUNCTION func_test() RETURNS INTEGER AS $BODY$ BEGINxxxreturn 0; END; $BODY$ LANGUAGE plpgsql VOLATILE CALLE…...

C++ 多态

1.多态的概念 多态&#xff08;polymorphism&#xff09;通俗来说就是多种形态。多态分为编译时多态&#xff08;静态多态&#xff09;和运行时多态&#xff08;动态多态&#xff09;&#xff0c;这里我们重点是运行时多态&#xff0c;编译时多态主要就是我们前面的函数重载和…...

【matlab|python】矢量棍棒图应用场景和代码

【matlab|python】矢量棍棒图应用场景和代码 矢量棍棒图的介绍和作用 矢量棍棒图&#xff08;stick plot&#xff09;是一种用于可视化 方向性时间序列数据 的图形工具。它常用于大气科学和海洋科学中&#xff0c;以直观地展示 风场、海流 或 其他矢量变量 随时间的变化情况。 …...

Matlab 五相电机仿真

1、内容简介 Matlab 208-五相电机仿真 可以交流、咨询、答疑 2、内容说明 略 3、仿真分析 略 4、参考论文 略...

计算机视觉cv2入门之视频处理

在我们进行计算机视觉任务时&#xff0c;经常会对视频中的图像进行操作&#xff0c;这里我来给大家分享一下&#xff0c;如何cv2中视频文件的操作方法。这里我们主要介绍cv2.VideoCapture函数的基本使用方法。 cv2.VideoCapture函数...

力扣每日一题781题解-算法:贪心,数学公式 - 数据结构:哈希

https://leetcode.cn/problems/rabbits-in-forest/description/?envTypedaily-question&envId2025-04-20 781.推测兔子数 算法&#xff1a;贪心&#xff0c;数学公式 数据结构&#xff1a;哈希 用哈希存每个兔子报告的同色数量&#xff0c;作为key&#xff0c;同个key…...

MAC-QueryWrapper中用的exists,是不是用join效果更好

在使用MyBatis-Plus的QueryWrapper中的exists方法时,是否改为使用join效果会更好,以及如何 修改。这涉及到SQL优化和MyBatis-Plus的用法。 首先,需要理解exists和join在SQL中的区别。exists用于检查子查询是否返回结果,而join则是将 两个表连接起来,根据某些条件合并行…...

使用 Visual Studio 2022 中的 .http 文件

转自微软技术文档&#xff1a; https://learn.microsoft.com/zh-cn/aspnet/core/test/http-files?viewaspnetcore-9.0 Visual Studio 2022.http 文件编辑器提供了一种便捷的方式来测试 ASP.NET Core项目&#xff0c;尤其是 API 应用。 编辑器提供一个 UI&#xff0c;用于&am…...

相得益彰 — 基于 GraphRAG 事理图谱驱动的实时金融行情新闻资讯洞察

*本文为亚马逊云科技博客文章&#xff0c;仅用于技术分享&#xff0c;不构成投资建议或金融决策支持。文中涉及的公司名称仅用于技术示例&#xff0c;不代表亚马逊云科技观点或与这些公司的商业合作关系。 背景介绍 在当今这个信息爆炸的时代&#xff0c;金融市场每天都在产生…...

为什么this与super不能出现在同一构造器的原因

在 Java 中&#xff0c;this() 和 super() 不能同时出现在同一个构造器中&#xff0c;因为它们都必须作为构造器的第一条语句&#xff0c;而一个构造器的第一条语句只能有一个。以下是详细解释和示例&#xff1a; ⚠️ 核心规则 只能二选一&#xff1a; 每个构造器的第一条语句…...

Linux:网络基础

hello&#xff0c;各位小伙伴&#xff0c;本篇文章跟大家一起学习《Linux&#xff1a;网络基础》&#xff0c;感谢大家对我上一篇的支持&#xff0c;如有什么问题&#xff0c;还请多多指教 &#xff01; 如果本篇文章对你有帮助&#xff0c;还请各位点点赞&#xff01;&#xf…...

C++入门篇(下)

目录 1、引用 1.1 引用概念 1.2 引用特性 1.3 常引用 1.4 使用场景 1.4.1 引用做参数 1.4.2 引用做返回值 1.5 引用和指针的区别 2、内联函数 2.1 概念 2.2 特性 3、auto关键字 4、基于范围的for循环 5、指针空值nullptr 5.1 C98 中的指针空值处理 5.2 C11 …...