Socket网络编程(四)——点对点传输场景方案

目录

  • 场景
  • 如何去获取到TCP的IP和Port?
  • UDP的搜索IP地址、端口号方案
  • UDP搜索取消实现
    • 相关的流程:
    • 代码实现逻辑
      • 服务端实现
      • 客户端实现
      • UDP搜索代码执行结果
  • TCP点对点传输实现
    • 代码实现步骤
    • 点对点传输测试结果
  • 源码下载

场景

在一个局域网当中,不知道服务器的IP地址,仅仅知道服务器公共的UDP的端口,在这种情况下,想要实现TCP的连接。TCP是点对点的连接,所以需要知道TCP的连接IP地址和端口Port。

如何去获取到TCP的IP和Port?

可以通过UDP的搜索实现,

  1. 当我们的服务器与我们所有的客户端之间约定了搜索的格式之后,我们可以在客户端发起广播
  2. 然后服务器在收到广播之后判断一下这些收到的广播是否是需要处理的。那么服务器就会回送这些广播到对应的端口(地址)上去。
  3. 客户端就能收到服务器回送过来的UDP的包。收到的这些数据包,里面就包含了端口号、IP地址等。
  4. 根据以上的流程就能够UDP的搜索得到TCP服务器的IP地址和TCP的端口,然后使用这些信息来实现TCP的连接。

UDP的搜索IP地址、端口号方案

  1. 构建基础口令消息
    原理:如果要实现UDP的交互,就要约定一组公共的数据格式,也就是基础的口令头。如果没有约定口令消息,那么别人发送的消息到达我们的服务器后就会去回送,这就会导致我们自己的基本信息(比如IP\Port)的暴露。
  2. 局域网广播口令消息(指定端口)
  3. 接收指定端口回送消息(得到客户端IP、Port,这里的客户端IP指的是server端)

20240228-154508-t3.png
如上图,BroadCast发出广播,如果有设备(服务器)感兴趣就会回送到BroadCast。如果三台(服务器)都感兴趣,就都会回送到BroadCast。

UDP搜索取消实现

相关的流程:

  1. 异步线程接收回送消息
  2. 异步线程等待完成(定时)
  3. 关闭等待-终止线程等待

代码实现逻辑

服务端实现

  1. TCP/UDP基础信息字段
    TCPConstants.java
public class TCPConstants {// 服务器固化UDP接收端口public static int PORT_SERVER = 30401;
}
  1. UDP基础信息
    UDPConstants.java
public class UDPConstants {// 公用头部(8个字节都是7,就是可回复的)public static byte[] HEADER = new byte[]{7,7,7,7,7,7,7,7};// 服务器固化UDP接收端口public static int PORT_SERVER = 30201;// 客户端回送端口public static int PORT_CLIENT_RESPONSE = 30202;
}
  1. 工具类ByteUtils
    用于校验是否为正确的口令。即对HEADER进行校验。
public class ByteUtils {/*** Does this byte array begin with match array content?** @param source Byte array to examine* @param match  Byte array to locate in <code>source</code>* @return true If the starting bytes are equal*/public static boolean startsWith(byte[] source, byte[] match) {return startsWith(source, 0, match);}/*** Does this byte array begin with match array content?** @param source Byte array to examine* @param offset An offset into the <code>source</code> array* @param match  Byte array to locate in <code>source</code>* @return true If the starting bytes are equal*/public static boolean startsWith(byte[] source, int offset, byte[] match) {if (match.length > (source.length - offset)) {return false;}for (int i = 0; i < match.length; i++) {if (source[offset + i] != match[i]) {return false;}}return true;}/*** Does the source array equal the match array?** @param source Byte array to examine* @param match  Byte array to locate in <code>source</code>* @return true If the two arrays are equal*/public static boolean equals(byte[] source, byte[] match) {if (match.length != source.length) {return false;}return startsWith(source, 0, match);}/*** Copies bytes from the source byte array to the destination array** @param source      The source array* @param srcBegin    Index of the first source byte to copy* @param srcEnd      Index after the last source byte to copy* @param destination The destination array* @param dstBegin    The starting offset in the destination array*/public static void getBytes(byte[] source, int srcBegin, int srcEnd, byte[] destination,int dstBegin) {System.arraycopy(source, srcBegin, destination, dstBegin, srcEnd - srcBegin);}/*** Return a new byte array containing a sub-portion of the source array** @param srcBegin The beginning index (inclusive)* @param srcEnd   The ending index (exclusive)* @return The new, populated byte array*/public static byte[] subbytes(byte[] source, int srcBegin, int srcEnd) {byte destination[];destination = new byte[srcEnd - srcBegin];getBytes(source, srcBegin, srcEnd, destination, 0);return destination;}/*** Return a new byte array containing a sub-portion of the source array** @param srcBegin The beginning index (inclusive)* @return The new, populated byte array*/public static byte[] subbytes(byte[] source, int srcBegin) {return subbytes(source, srcBegin, source.length);}
}
  1. 服务器端接收约定数据包,解析成功并回送包的代码
    ServerProvider
public class ServerProvider {private static Provider PROVIDER_INSTANCE;static void start(int port){stop();String sn = UUID.randomUUID().toString();Provider provider = new Provider(sn, port);provider.start();PROVIDER_INSTANCE = provider;}static void stop(){if(PROVIDER_INSTANCE != null){PROVIDER_INSTANCE.exit();PROVIDER_INSTANCE = null;}}private static class Provider extends Thread{private final byte[] sn;private final int port;private boolean done = false;private DatagramSocket ds = null;// 存储消息的Bufferfinal byte[] buffer = new byte[128];public Provider(String sn, int port){super();this.sn = sn.getBytes();this.port = port;}@Overridepublic void run() {super.run();System.out.println("UDDProvider Started.");try {// 监听20000 端口ds = new DatagramSocket(UDPConstants.PORT_SERVER);// 接收消息的PacketDatagramPacket receivePacket = new DatagramPacket(buffer,buffer.length);while(!done){// 接收ds.receive(receivePacket);// 打印接收到的信息与发送者的信息// 发送者的IP地址String clientIp = receivePacket.getAddress().getHostAddress();int clientPort = receivePacket.getPort();int clientDataLen = receivePacket.getLength();byte[] clientData = receivePacket.getData();boolean isValid = clientDataLen >= (UDPConstants.HEADER.length + 2 + 4) && ByteUtils.startsWith(clientData,UDPConstants.HEADER);System.out.println("ServerProvider receive from ip:" + clientIp + "\tport:" + clientIp +"\tport:"+clientPort+"\tdataValid:"+isValid);if(!isValid){//无效继续continue;}// 解析命令与回送端口int index = UDPConstants.HEADER.length;short cmd = (short) ((clientData[index++] << 8) | (clientData[index++] & 0xff));int responsePort = (((clientData[index++]) << 24) |((clientData[index++] & 0xff) << 16) |((clientData[index++] & 0xff) << 8) |((clientData[index++] & 0xff)));// 判断合法性if( cmd == 1 && responsePort > 0){// 构建一份回送数据ByteBuffer byteBuffer = ByteBuffer.wrap(buffer);byteBuffer.put(UDPConstants.HEADER);byteBuffer.putShort((short)2);byteBuffer.putInt(port);byteBuffer.put(sn);int len = byteBuffer.position();// 直接根据发送者构建一份回送信息DatagramPacket responsePacket = new DatagramPacket(buffer,len,receivePacket.getAddress(),responsePort);ds.send(responsePacket);System.out.println("ServerProvider response to:" + clientIp + "\tport:"+responsePort + "\tdataLen: " + len);}else {System.out.println("ServerProvider receive cmd nonsupport; cmd:" + cmd + "\tport:" + port);}}} catch (SocketException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}private void close() {if( ds != null ){ds.close();ds = null;}}/*** 提供结束*/void exit(){done = true;close();}}
}
  1. main方法启动类
public class Server {public static void main(String[] args) {ServerProvider.start(TCPConstants.PORT_SERVER);try{System.in.read();} catch (IOException e){e.printStackTrace();}ServerProvider.stop();}
}

客户端实现

客户端广播发送消息包代码

  1. 服务器端消息实体
    ServerInfo
public class ServerInfo {private String sn;private int port;private String address;public ServerInfo(int port, String address, String sn) {this.sn = sn;this.port = port;this.address = address;}省略set/get方法 ……}
  1. 客户端启动main方法类
public class Client {public static void main(String[] args) {// 定义10秒的搜索时间,如果超过10秒未搜索到,就认为服务器端没有开机ServerInfo info = ClientSearcher.searchServer(10000);System.out.println("Server:" + info);}
}
  1. 客户端接收服务器端回送与广播发送的具体逻辑
    ClientSearcher
public class ClientSearcher {private static final int LISTENT_PORT = UDPConstants.PORT_CLIENT_RESPONSE;public static ServerInfo searchServer(int timeout){System.out.println("UDPSearcher Started.");//  成功收到回送的栅栏CountDownLatch receiveLatch = new CountDownLatch(1);Listener listener = null;try{// 监听listener = listen(receiveLatch);// 发送广播sendBroadCast();// 等待服务器返回,最长阻塞10秒receiveLatch.await(timeout, TimeUnit.MILLISECONDS);}catch (Exception e){e.printStackTrace();}// 完成System.out.println("UDPSearcher Finished.");if(listener == null){return null;}List<ServerInfo> devices = listener.getServerAndClose();if(devices.size() > 0){return devices.get(0);}return null;}/*** 监听服务器端回送的消息* @param receiveLatch* @return* @throws InterruptedException*/private static Listener listen(CountDownLatch receiveLatch) throws InterruptedException {System.out.println("UDPSearcher start listen.");CountDownLatch startDownLatch = new CountDownLatch(1);Listener listener = new Listener(LISTENT_PORT, startDownLatch,receiveLatch);listener.start();   // 异步操作,开启端口监听startDownLatch.await();return listener;}/*** 发送广播逻辑* @throws IOException*/private static void sendBroadCast() throws IOException {System.out.println("UDPSearcher sendBroadcast started.");// 作为搜索方,让系统自动分配端口DatagramSocket ds = new DatagramSocket();// 构建一份请求数据ByteBuffer byteBuffer = ByteBuffer.allocate(128);// 头部byteBuffer.put(UDPConstants.HEADER);// CMD命名byteBuffer.putShort((short)1);// 回送端口信息byteBuffer.putInt(LISTENT_PORT);// 直接构建PacketDatagramPacket requestPacket = new DatagramPacket(byteBuffer.array(), byteBuffer.position() + 1);// 广播地址requestPacket.setAddress(InetAddress.getByName("255,255.255.255"));// 设置服务器端口requestPacket.setPort(UDPConstants.PORT_SERVER);// 发送ds.send(requestPacket);ds.close();// 完成System.out.println("UDPSearcher sendBroadcast finished.");}/*** 广播消息的接收逻辑*/private static class Listener extends Thread {private final int listenPort;private final CountDownLatch startDownLatch;private final CountDownLatch receiveDownLatch;private final List<ServerInfo> serverInfoList = new ArrayList<>();private final byte[] buffer = new byte[128];private final int minLen = UDPConstants.HEADER.length + 2 + 4; // 2:CMD命令长度  4:TCP端口号长度private boolean done = false;private DatagramSocket ds = null;private Listener(int listenPort,CountDownLatch startDownLatch,CountDownLatch receiveDownLatch){super();this.listenPort = listenPort;this.startDownLatch = startDownLatch;this.receiveDownLatch = receiveDownLatch;}@Overridepublic void run(){super.run();// 通知已启动startDownLatch.countDown();try{// 监听回送端口ds = new DatagramSocket(listenPort);// 构建接收实体DatagramPacket receivePacket = new DatagramPacket(buffer,buffer.length);while( !done){// 接收ds.receive(receivePacket);// 打印接收到的信息与发送者的信息// 发送者的IP地址String ip = receivePacket.getAddress().getHostAddress();int port = receivePacket.getPort();int dataLen = receivePacket.getLength();byte[] data = receivePacket.getData();boolean isValid = dataLen >= minLen&& ByteUtils.startsWith(data, UDPConstants.HEADER);System.out.println("UDPSearch receive form ip:" + ip + "\tport:" + port + "\tdataValid:" + isValid);if( !isValid ) {// 无效继续continue;}// 跳过口令字节,从具体数据开始ByteBuffer byteBuffer = ByteBuffer.wrap(buffer,UDPConstants.HEADER.length, dataLen);final short cmd = byteBuffer.getShort(); // 占据2个字节final int serverPort = byteBuffer.getInt(); // 占据4个字节if(cmd != 2 || serverPort <= 0){System.out.println("UDPSearcher receive cmd:" + cmd + "\tserverPort:" + serverPort);continue;}String sn = new String(buffer,minLen,dataLen - minLen);ServerInfo info = new ServerInfo(serverPort,ip,sn);serverInfoList.add(info);// 成功接收到一份receiveDownLatch.countDown();}}catch (Exception e){e.printStackTrace();}finally {close();}System.out.println("UDPSearcher listener finished.");}private void close(){if(ds != null){ds.close();ds = null;}}List<ServerInfo> getServerAndClose() {done = true;close();return serverInfoList;}}
}

UDP搜索代码执行结果

服务端启动接收的结果:

UDDProvider Started.
ServerProvider receive from ip:169.254.178.74	port:169.254.178.74	port:61968	dataValid:true
ServerProvider response to:169.254.178.74	port:30202	dataLen: 50

客户端监听并发起广播的执行结果:

UDPSearcher Started.
UDPSearcher start listen.
UDPSearcher sendBroadcast started.
UDPSearcher sendBroadcast finished.
UDPSearch receive form ip:169.254.178.74	port:30201	dataValid:true
UDPSearcher Finished.
Server:ServerInfo{sn='ed4ab162-5d5c-49eb-b80e-6ddeb8b223e0', port=30401, address='169.254.178.74'}
UDPSearcher listener finished.Process finished with exit code 0

由以上结果可知,启动服务端后,客户端在启动listen监听后,向服务器端发送数据包,并获得服务器端的回送,经解析后,该回送的数据包中可以获得 ip/port,可用于TCP连接使用。在UDP解析数据包过程中,通过口令保证了客户端与服务端对消息发送、接收、回送的有效,避免不必要的回应。

TCP点对点传输实现

基于前面UDP广播-搜索的机制,Server-Client获得了建立Socket链接的IP\Port信息。
可以接着使用该信息进行建立TCP的Socket连接,实现点对点的数据收发。

代码实现步骤

  1. TCP服务端main启动方法
public class Server {public static void main(String[] args) {TCPServer tcpServer = new TCPServer(TCPConstants.PORT_SERVER);boolean isSucceed = tcpServer.start();if(!isSucceed){System.out.println("Start TCP server failed.");}UDPProvider.start(TCPConstants.PORT_SERVER);try{System.in.read();} catch (IOException e){e.printStackTrace();}UDPProvider.stop();tcpServer.stop();}
}

在UDP搜索的基础上,我们获得了TCP的链接IP。创建tcpServer对相应的端口进行监听客户端链接请求。

  1. 服务端异步线程处理Socket
    TCPServer
public class TCPServer {private final int port;private ClientListener mListener;/*** 构造* @param port*/public TCPServer(int port){this.port = port;}/*** 开始* @return*/public boolean start(){try{ClientListener listener = new ClientListener(port);mListener = listener;listener.start();}catch (Exception e){e.printStackTrace();return false;}return true;}/*** 结束*/public void stop(){if(mListener != null){mListener.exit();}}/*** 监听客户端链接*/private static class ClientListener extends Thread {private ServerSocket server;private boolean done = false;private ClientListener(int port) throws IOException {server = new ServerSocket(port);System.out.println("服务器信息: " + server.getInetAddress() + "\tP:" + server.getLocalPort());}@Overridepublic void run(){super.run();System.out.println("服务器准备就绪~");// 等待客户端连接do{// 得到客户端Socket client = null;try {client = server.accept();}catch (Exception e){e.printStackTrace();}// 客户端构建异步线程ClientHandler clientHandler = new ClientHandler(client);// 启动线程clientHandler.start();}while (!done);System.out.println("服务器已关闭!");}void exit(){done = true;try {server.close();}catch (IOException e){e.printStackTrace();}}}/*** 客户端消息处理*/private static class ClientHandler extends Thread{private Socket socket;private boolean flag = true;ClientHandler(Socket socket ){this.socket = socket;}@Overridepublic void run(){super.run();System.out.println("新客户链接: " + socket.getInetAddress() + "\tP:" + socket.getPort());try {// 得到打印流,用于数据输出;服务器回送数据使用PrintStream socketOutput = new PrintStream(socket.getOutputStream());// 得到输入流,用于接收数据BufferedReader socketInput = new BufferedReader(new InputStreamReader(socket.getInputStream()));do {// 客户端拿到一条数据String str = socketInput.readLine();if( "bye".equalsIgnoreCase(str)){flag = false;// 回送socketOutput.println("bye");}else {// 打印到屏幕,并回送数据长度System.out.println(str);socketOutput.println("回送: " + str.length());}}while (flag);socketInput.close();socketOutput.close();}catch (IOException e){System.out.println("连接异常断开");}finally {// 连接关闭try {socket.close();}catch (IOException e){e.printStackTrace();}}System.out.println("客户端已退出:" + socket.getInetAddress() + "\tP:" + socket.getPort());}}
}

accept() 监听到客户端的链接后,通过输入流读取客户端数据,并通过输出流回送数据长度。

  1. 基于UDP回送结果建立的TCP客户端
    Client main方法
public class Client {public static void main(String[] args) {// 定义10秒的搜索时间,如果超过10秒未搜索到,就认为服务器端没有开机ServerInfo info = UDPSearcher.searchServer(10000);System.out.println("Server:" + info);if( info != null){try {TCPClient.linkWith(info);}catch (IOException e){e.printStackTrace();}}}
}

获得UDP的回送后,我们知道了建立TCP的ip、port。也就是serverInfo不为null,取出相关参数建立Socket 链接。

  1. 建立客户端连接类
    TCPClient
public class TCPClient {public static void linkWith(ServerInfo info) throws IOException {Socket socket = new Socket();// 超时时间socket.setSoTimeout(3000);// 端口2000;超时时间300mssocket.connect(new InetSocketAddress(Inet4Address.getByName(info.getAddress()),info.getPort()));//System.out.println("已发起服务器连接,并进入后续流程~");System.out.println("客户端信息: " + socket.getLocalAddress() + "\tP:" + socket.getLocalPort());System.out.println("服务器信息:" + socket.getInetAddress() + "\tP:" + socket.getPort());try {// 发送接收数据todo(socket);}catch (Exception e){System.out.println("异常关闭");}// 释放资源socket.close();System.out.println("客户端已退出~");}private static void todo(Socket client) throws IOException {// 构建键盘输入流InputStream in = System.in;BufferedReader input = new BufferedReader(new InputStreamReader(in));// 得到Socket输出流,并转换为打印流OutputStream outputStream = client.getOutputStream();PrintStream socketPrintStream = new PrintStream(outputStream);// 得到Socket输入流,并转换为BufferedReaderInputStream inputStream = client.getInputStream();BufferedReader socketBufferedReader = new BufferedReader(new InputStreamReader(inputStream));boolean flag = true;do {// 键盘读取一行String str = input.readLine();// 发送到服务器socketPrintStream.println(str);// 从服务器读取一行String echo = socketBufferedReader.readLine();if("bye".equalsIgnoreCase(echo)){flag = false;}else {System.out.println(echo);}}while(flag);// 资源释放socketPrintStream.close();socketBufferedReader.close();}
}

建立Socket链接,从键盘读取一行发送到服务器;并从服务器读取一行。以上就是基于UDP广播-搜索实现TCP点对点传输的逻辑。

点对点传输测试结果

基于UDP实现的TCP服务端日志

服务器信息: 0.0.0.0/0.0.0.0	P:30401
服务器准备就绪~
UDDProvider Started.
ServerProvider receive from ip:169.254.178.74	port:169.254.178.74	port:51322	dataValid:true
ServerProvider response to:169.254.178.74	port:30202	dataLen: 50
新客户链接: /169.254.178.74	P:57172
ping
pong

基于UDP实现的TCP客户端日志:

UDPSearcher start listen.
UDPSearcher sendBroadcast started.
UDPSearcher sendBroadcast finished.
UDPSearch receive form ip:169.254.178.74	port:30201	dataValid:true
UDPSearcher Finished.
Server:ServerInfo{sn='10595790-14d1-44dc-a068-4c64c956a944', port=30401, address='169.254.178.74'}
UDPSearcher listener finished.
已发起服务器连接,并进入后续流程~
客户端信息: /169.254.178.74	P:57172
服务器信息:/169.254.178.74	P:30401
ping
回送: 4
pong
回送: 4

源码下载

下载地址:https://gitee.com/qkongtao/socket_study/tree/master/src/main/java/cn/kt/socket/SocketDemo_L5_UDP

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LabVIEW齿轮传动健康状态静电在线监测

LabVIEW齿轮传动健康状态静电在线监测 随着工业自动化的不断发展&#xff0c;齿轮传动作为最常见的机械传动方式之一&#xff0c;在各种机械设备中发挥着至关重要的作用。然而&#xff0c;齿轮在长期运行过程中易受到磨损、变形等因素影响&#xff0c;进而影响整个机械系统的稳…

BUUCTF------[HCTF 2018]WarmUp

开局一个表情&#xff0c;源代码发现source.php <?phphighlight_file(__FILE__);class emmm{public static function checkFile(&$page){$whitelist ["source">"source.php","hint">"hint.php"];if (! isset($page) |…

Vue - 调用接口获取文件数据流并根据类型预览

Vue - 调用接口获取文件数据流并根据类型预览 一、接口返回的数据流格式二. 方法实现1. image 图片类型2. txt 文件类型3. pdf 文件类型 一、接口返回的数据流格式 二. 方法实现 1. image 图片类型 <img :src"imageUrl" alt"" srcset"" /&g…

uipath调用python代码获取网站验证码

用uipath自带的ocr读验证码不是很准确&#xff0c;选择调用python读验证码&#xff0c;需要导入ddddocr&#xff08;3.8以下版本支持ddddocr&#xff09; 用uipath程序将验证码图片保存到本地&#xff08;也可以直接用python处理图片&#xff0c;保存到本地比较简单&#xff0…

XUbuntu22.04之报错:No module named lsb_release(二百一十九)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

解决uni-app中使用webview键盘弹起遮挡input输入框问题

这个平平无奇的回答&#xff0c;可能是全网最靠谱的解决方案。 这里我用的是vue3 setup .vue文件的方式 <view> <web-view :fullscreen"false" :webview-styles"{top: statusBarHeight40,height:height,progress: {color: green,height:1px } }"…

VUE3项目学习系列--element-plus集成(三)

1、安装依赖 Element-plus官网&#xff1a;快速开始 | Element Plus (element-plus.org) pnpm i element-plus 在项目main.ts中引入element-plus: import { createApp } from "vue"; import App from "./App.vue"; // 从Element官网上参考&#xff0c;…

mTLS: openssl创建CA证书

证书可以通过openssl或者keytool创建&#xff0c;在本篇文章中&#xff0c;只介绍openssl。 openssl 生成证书 申请操作流程 生成ca证书私钥, 文件名&#xff1a;ca.key生成ca证书&#xff0c;文件名&#xff1a;ca.crt生成Server/Client 证书私钥&#xff0c;文件名&#x…

Windows Docker 部署 Redis

部署 Redis 打开 Docker Desktop&#xff0c;切换到 Linux 内核。然后在 PowerShell 执行下面命令&#xff0c;即可启动一个 redis 服务。这里安装的是 7.2.4 版本&#xff0c;如果需要安装其他或者最新版本&#xff0c;可以到 Docker Hub 中进行查找。 docker run -d --nam…

【深度学习】脑部MRI图像分割

案例4&#xff1a;脑部MRI图像分割 相关知识点&#xff1a;语义分割、医学图像处理&#xff08;skimage, medpy&#xff09;、可视化&#xff08;matplotlib&#xff09; 1 任务目标 1.1 任务简介 本次案例将使用深度学习技术来完成脑部MRI(磁共振)图像分割任务&#xff0c…

Vue3学习记录(三)--- 组合式API之生命周期和模板引用

一、生命周期 1、简介 ​ 生命周期&#xff0c;指的是一个 Vue 实例从创建到销毁的完整阶段&#xff0c;强调的是一个时间段。 ​ 生命周期钩子函数&#xff0c;指的是 Vue 实例提供的内置函数&#xff0c;函数的参数为一个回调函数。这些钩子函数会在实例生命周期的某些固定…

快速上手:在 Android 设备上运行 Pipy

Pipy 作为一个高性能、低资源消耗的可编程代理&#xff0c;通过支持多种计算架构和操作系统&#xff0c;Pipy 确保了它的通用性和灵活性&#xff0c;能够适应不同的部署环境&#xff0c;包括但不限于云环境、边缘计算以及物联网场景。它能够在 X86、ARM64、海光、龙芯、RISC-V …

VR 全景模式OpenGL原理

VR 全景模式OpenGL原理 VR 全景模式原理 VR 全景模式原理将画面渲染到球面上&#xff0c;相当于从球心去观察内部球面&#xff0c;观察到的画面 360 度无死角&#xff0c;与普通播平面渲染的本质区别在渲染图像部分&#xff0c;画面渲染到一个矩形平面上&#xff0c;而全景需…

【代码随想录算法训练营Day34】860.柠檬水找零;406.根据身高重建队列;452.用最少数量的箭引爆气球

❇️Day 34 第八章 贪心算法 part04 ✴️今日任务 860.柠檬水找零406.根据身高重建队列452.用最少数量的箭引爆气球 ❇️860.柠檬水找零 本题看上好像挺难&#xff0c;其实挺简单的&#xff0c;大家先尝试自己做一做。题目链接&#xff1a;https://leetcode.cn/problems/lem…

即时设计-高效易用的界面工具

界面工具是设计师的得力助手&#xff0c;为设计师快速创建精美易用的用户界面提供了丰富的功能和直观的界面。在众多的界面工具中&#xff0c;有的支持预设模板、图标库和样式库&#xff0c;有的更注重原型和互动。如何选择优秀的界面工具&#xff1f;这里有一个高效易用的界面…

python并发 map函数的妙用

1.map是什么&#xff1f; map函数是Python中的一个内置函数&#xff0c;用于将一个函数应用到一个或多个可迭代对象的每个元素上&#xff0c;生成一个新的可迭代对象。它的一般形式是&#xff1a; map(function, iterable1, iterable2, ...)其中&#xff0c;function是一个函…

JS逆向进阶篇【去哪儿旅行登录】【下篇-逆向Bella参数JS加密逻辑Python生成】

目录&#xff1a; 每篇前言&#xff1a;引子——本篇目的1、 代码混淆和还原&#xff08;1&#xff09;单独替换&#xff1a;&#xff08;2&#xff09;整个js文件替换&#xff1a; 2、算法入口分析3、 深入分析&#xff08;0&#xff09;整体分析&#xff1a;&#xff08;1&am…

STM32启动过程及反汇编

STM32从Flash启动的过程&#xff0c;主要是从上电复位到main函数的过程&#xff0c;主要有以下步骤&#xff1a; 1.初始化堆栈指针 SP_initial_sp&#xff0c;初始化 PC 指针Reset_Handler 2.初始化中断向量表 3.配置系统时钟 4.调用 C 库函数_main 初始化用户堆栈&#xf…

华为配置WLAN高密业务示例

配置WLAN高密业务示例 组网图形 图1 配置高密WLAN环境网络部署组网图 业务需求组网需求数据规划配置思路配置注意事项操作步骤配置文件 业务需求 体育场由于需要接入用户数量很大&#xff0c;AP间部署距离较小&#xff0c;因此AP间的干扰较大&#xff0c;可能导致用户上网网…

Qt 中Qwidget相关属性

文章目录 1. QWidget 核心属性1.1 enabled1.2 geometry1.2.1 window frame 的影响 1.3 windowTitle1.4 windowIcon1.4.1 qrc的使用 1.5 windowOpacity1.6 cursor1.7 focusPolicy1.8 styleSheet 1. QWidget 核心属性 在 Qt 中, 使⽤ QWidget 类表⽰ “控件”. 像按钮, 视图, 输…