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

网络编程套接字(2)

UDP数据报套接字编程

API介绍

DatagramSocket

DatagramSocket是UDP的Socket,用于发送和接收数据报.

操作系统中有一类文件,就叫做socket文件(普通文件/目录文件:在硬盘上的)

socket文件:抽象的表示了网卡这样的硬件设备

DatagramSocket就是对socket文件进行读写,也就是借助网卡发送数据.

通过网卡发送数据,就是写socket文件;通过网卡读取数据,就是读socket文件.

DatagramSocket构造方法:

方法签名方法说明
DatagramSocket()

创建一个UDP数据报套接字的Socket,绑定到本机

任意一个随机端口(一般用于客户端)

DatagramSocket(int port)

创建一个UDP数据报套接字的Socket,绑定到本机

指定的端口(一般用于服务端)

DatagramSocket方法:

方法签名方法说明
void receive(DatagramPacket p)

从此套接字接收数据报(如果没有收到数据报,该

方法会阻塞等待)

void send(DatagramPacket p)从此套接字发送数据包(不会阻塞等待,直接发送)
void close()关闭此数据报套接字

DatagramPacket

UDP数据报,每次接收数据的基本单位,就是一个UDP数据报

DatagramPacket是UDP Socket发送和接收的数据报.

DatagramPacket构造方法:

方法签名方法说明
DatagramPacket(byte[] buf, int length)

构造一个DatagramPacket以用来接收数据报,接收的

数据保存在字节数组(第一个参数buf)中,接收指定

长度(第二个参数length)

DatagramPacket(byte[] buf, int offset, int length,

SocketAddress address)

构造一个DatagramPacket以用来发送数据包,发送的

数据为字节数组(第一个参数buf)中,从0到指定长度

(第二个参数length).address指定目的主机IP和端口号

DatagramPacket方法:

方法签名方法说明
InetAddress getAddress()

从接收的数据报中,获取发送端主机IP地址;

或从发送的数据报中,获取接收端主机IP地址

int getPort()

从接收的数据报中,获取发送端主机的端口号;

或从发送的数据报中,获取接收端主机的端口号

byte[] getData()获取数据报中的数据

构造UDP发送的数据报时,需要传入SocketAddress,该对象可以使用InetSocketAddress来创建.

InetSocketAddress

InetSocketAddress (SocketAddress的子类) 构造方法:

方法签名方法说明
InetSocketAddress(InetAddress addr, int port)创建一个Socket地址,包含IP地址和端口号

代码示例

UDP Echo Server

下面以一个简单的回显服务器作为代码示例的程序(回显服务器:客户端发啥请求返回啥响应)

 警告:文本解析巨长无比

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;public class UdpEchoServer {/*最开始的一步:先创建DdtagramSocket对象接下来需要操作网卡,操作网卡是通过socket对象完成的socket对象是在内存中的,通过这个来影响网卡(类似遥控)*/private DatagramSocket socket = null;/*一个主机上的一个端口号只能被一个进程绑定,反过来,一个进程可以绑定多个端口创建对象时,手动指定一个端口号(在运行服务器程序的时候,主动指定端口)程序一启动就需要关联上/绑定上一个操作系统中的端口号端口号也是一个整数,用来区分一个主机上进行网络通信的程序.*///参数时服务器要绑定的端口public UdpEchoServer(int port) throws SocketException {/*SocketException是网络编程中的常见异常,通常表示socket创建失败,比如端口号被别的进程占用,就会失败*/socket = new DatagramSocket(port);}//使用这个方法启动服务器public void start() throws IOException {System.out.println("服务器启动");while(true) {//反复的,长期的执行针对客户端请求处理的逻辑.(不停的收到请求,返回响应)//一个服务器,运行的过程中,要做的事情,主要是三个核心环节.//1.读取请求,并解析.  (一个服务器单位时间处理的请求返回响应越多,服务器越厉害)DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);/*receive就从网卡中读出了一个数据报,就被放入了requestPacket对象中.其中UDP数据报的载荷部分就被放到requestPacket内置的字节数组中了.除了UDP报头,还有其它信息,比如收到的数据:源IP*///如果执行到这个地方,没有客户端请求的话,就会阻塞.socket.receive(requestPacket);//转成字符串,方便逻辑处理(前提:后续客户端发的就是一个文本字符串)String request = new String(requestPacket.getData(), 0, requestPacket.getLength()); //有效长度//2.根据请求,计算出响应(对于回显服务器,这一步什么都不用做)String response = process(request);//3.把响应写回给客户端//此时需要告知网卡,要发的内容是啥,要发给谁//requestPacket是客户端发来的数据报.//通过getSocketAddress()得到InetAddress对象,这个对象就包含了和服务器的通信对端(对应客户端IP)//此时就起到了把消息返回给客户端的效果(还可以看到UDP是无连接通信,socket不包含对端IP,端口)DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length, requestPacket.getSocketAddress());socket.send(responsePacket);//记录日志,方便观察程序执行效果System.out.printf("[%s : %d] req: %s, resq: %s\n", requestPacket.getAddress()requestPacket.getPort(), request, response);}}/*上述代码中,可以看到UDP是无连接的通信~UDPsocket自身不保存对端的IP和端口而是在每个数据报中有一个.另外代码中也"建立连接","接受连接"的擦做面向数据报,send和receive都是以DatagramPacket为主*///根据请求计算响应public String process(String request) {return request;}public static void main(String[] args) throws IOException {//指定任何想要的端口,但要确保这个端口在机器上未被其它进程占用UdpEchoServer server = new UdpEchoServer(9090);server.start();}
}

 UDP Echo Client

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.Scanner;public class UdpEchoClient {private DatagramSocket socket = null;private String serverIp;private int serverPort;//服务器的ip和服务器的端口 (因为发起请求的前提就是知道服务器在哪).public UdpEchoClient(String ip, int port) throws SocketException {serverIp = ip;serverPort = port;//这个new操作,就不再指定端口了.让系统分配一个空闲端口socket = new DatagramSocket();}/*在服务器中,在代码中需要手动指定端口号,才能保证端口始终固定如果不手动指定,依赖系统自动分配导致服务器重启之后,端口号就变了,客户端可能找不到服务器在哪了服务器这个机器,是在程序员手里的,是可控的.程序员能知道服务器有哪些端口被使用,客户端是在普通用户的机器上*///让这个客户端反复的从控制台读取用户输入的内容,把这个内容构成UDP请求,发送给服务器//最终显示在客户端的屏幕上public void start() throws IOException {Scanner sc = new Scanner(System.in);System.out.println("客户端启动!");while(true) {//1.从控制台读取用户输入内容System.out.println("->"); //命令提示符,提示用户要输入的字符串String request = sc.next(); //从控制台读取,最好使用next而不是nextLine//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);String response = new String(responsePacket.getData(), 0, responsePacket.getLength());//4.显示到屏幕上System.out.println(response);}}public static void main(String[] args) throws IOException {UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090);client.start();}
}

让我们结合一下服务器和客户端,来看一下执行的流程:

 1.服务器启动,启动之后,立即进入while循环,执行到receive(),进入阻塞等待,这时还未收到客户端的请求.

2.客户端启动,启动之后进入while循环,执行到输入那里堵塞,此时用户未输入内容.

3.用户在客户端输入字符串回车.此时阻塞解除,next会返回刚才的内容.基于用户输入内容,构造一个DatagramPacket对象,并进行send.send执行完之后,继续receive操作,等待服务器响应数据(此时服务器还没响应,就会阻塞).

4.服务器收到请求之后,就会从receive阻塞中返回.返回之后,就会根据读到的DatagramPacket对象,构造String request,通过process方法构造出一个String response,再根据response构造一个DatagramPacket表示响应对象.再通过send来进行发送给客户端.(执行这个的过程中,客户端也在阻塞等待).

5.客户端从receive中返回执行,就能获取到服务器返回的响应,并且打印到控制台上,与此同时,服务器进入下一个环节,也就是进入到第二轮receive阻塞.等待下一个请求了.

UDP Dict Server

我们之前写的服务器是回显服务器,我们来扩展以下,写一个处理简单英译汉功能的服务器.

import java.io.IOException;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;public class UdpDictServer extends UdpEchoServer{private Map<String, String> dict = new HashMap<>();public UdpDictServer(int port) throws SocketException {super(port);dict.put("cat", "小猫");dict.put("dog", "小狗");dict.put("fuck", "我爱你");//可以在这里添加千千万万个单词.使每一个单词都有对应的翻译}@Overridepublic String process(String request) {//把请求对应单词的翻译,给返回回去return dict.getOrDefault(request, "该单词没有查询到!");}public static void main(String[] args) throws IOException {UdpDictServer server = new UdpDictServer(9090);server.start(); //在此处会除法多态}
}

TCP流套接字编程 

 和刚才的UDP类似.但是TCP是面向字节流的,传输的基本单位是字节

API介绍

ServerSocket

这个Socket类对应到网卡,只能给服务器使用.

ServerSocket是创建TCP服务端Socket的API.

ServerSocket构造方法:

方法签名方法说明
ServerSocket(int port)创建一个服务端流套接字Socket,并绑定到指定端口

ServerSocket方法:

方法签名方法说明
Socket accept()

开始监听指定端口(创建时绑定的端口),有客户端

连接后,返回一个服务端socket对象,并基于该

Socket建立与客户端的连接,否则阻塞等待

void close()关闭此套接字

 Socket

对应到网卡,既可以给客户端使用,也可以给服务器使用.

Socket是客户端Socket,或服务端接收到客户端建立的连接(accept方法)的请求后,返回的服务端Socket.

不管是客户端还是服务端Socket,都是双方建立连接之后,保存对端信息,及用来与对方收发数据的.

Socket构造方法:

方法签名方法说明
Socket(String host, int port)

创建一个客户端流套接字Socket,并与对应IP的主机

上,对应端口的进程进行连接

Socket方法:

方法签名方法说明
InetAddress getInetAddress()返回套接字所连接的地址
InputStream getInputStream()返回此套接字的输入流
OutputStream getOutputStream()返回此套接字的输出流

代码示例

TCP Echo Server

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
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 {System.out.println("服务器启动!");while(true) {//这是个建立链接的过程,就相当于接地阿努好的时候这边拨完号打过去,另一边在响铃//通过accept()操作接听电话,然后进行通信//clientSocket负责后续通信交互Socket clientSocket = serverSocket.accept();/*accept可能会产生阻塞.没有客户端,就会阻塞.有客户端的时候再处理有一个客户端来了, accept一次就能返回一次.有多个客户端连过来了,accept就会执行多次*/processConnection(clientSocket);}}//通过这个方法来处理一个连接的逻辑private void processConnection(Socket clientSocket) {System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress(), clientSocket.getPort());//接下来就可以读取请求,根据请求计算响应,返回响应这三步走了.//Socket对象包含两个字节流对象,可以把这两字节流对象获取到,完成后续的读写/*inputStream是从网卡中读,OutputStream是向网卡中写TCP是面向字节流的.和文件操作以一样的类和方法来完成tcp socket的读写*/try(InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()) {//一次连接中,可能会涉及到多次请求/响应while(true) {//1.读取请求并解析..为了读取方便,直接使用ScannerScanner sc = new Scanner(inputStream);//客户端退出的时候就会触发Tcp的"断开连接"流程.//服务器这边的代码也会感知到,对应Scanner就在hasNext()处返回false.if(!sc.hasNext()) {//读取完毕,客户端下线.System.out.printf("[%s:%d] 客户端下线!\n", clientSocket.getInetAddress(), clientSocket.getPort());break;}//这个代码暗含一个约定,客户端发过来的请求,得是文本数据. next读的时候要督导空白符才会结束.// 因此就要客户端发来的请求必须带有空白符结尾.比如\n或者空格.String request = sc.next();//2.根据请求计算相应String response = process(request);//3.把响应写回客户端// 通过这种方式可以写回,但是这种方式不方便给返回的响应中添加\n//outputStream.write(response.getBytes(), 0, response.getBytes().length);// 也可以给outputStream套上一层,完成更方便的写入.PrintWriter printWriter = new PrintWriter(outputStream);printWriter.println(response);//这里还需要加一个"刷新缓冲区"的操作printWriter.flush();//日志,打印当前的请求详细.System.out.printf("[%s:%d] req: %s, resq: %s\n", clientSocket.getInetAddress(),clientSocket.getPort(), request, response);}} catch (IOException e) {e.printStackTrace();} finally {//在finally中加上close操作,确保当前socket被正确关闭/*tcp的clientSocket是每个客户端都有一个.随着客户端越来越多.这里消耗的socket也会越来越多(如果不释放,就可能把文件操作符表占满)*/try {clientSocket.close();} catch (IOException e) {e.printStackTrace();}}}private String process(String request) {return request;}public static void main(String[] args) throws IOException {TcpEchoServer server = new TcpEchoServer(9090);server.start();}
}

Tcp Echo Client

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 {//这个new操作完成之后,就完成了tcp连接的建立socket = new Socket(serverIp, serverPort);}public void start() {System.out.println("客户端启动!");try (InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream()) {Scanner scannerConsole = new Scanner(System.in);Scanner scannerNetwork = new Scanner(inputStream);PrintWriter printWriter = new PrintWriter(outputStream);while(true) {//1.从控制台中读取字符串System.out.println("->");if(!scannerConsole.hasNext()) {break;}String request = scannerConsole.next();//2.把请求发给服务器//使用println带上换行, 后续服务器读取请求,就使用scanner.next()读取printWriter.println(request);printWriter.flush();// 不要忘记flush, 确保数据是真的发出去了//3.从服务器中读取响应String response = scannerNetwork.next();//4.把响应打印出来System.out.println(response);}} catch (IOException e) {e.printStackTrace();}}public static void main(String[] args) throws IOException {TcpEchoClient client = new TcpEchoClient("127.0.0.1", 9090);client.start();}
}

服务器引入多线程

如果只是单个线程,无法同时响应多个客户端.

虽然第二个客户端和服务器在内核层面上建立tcp连接了,但是应用程序这里无法把连接拿到应用程序里处理(人家给你打电话,电话一直响,但是不接)

这个问题,不是tcp引起的,而是因为代码结构不够好,两层循环所引起.UDP服务器只有一层循环.就不涉及该问题.之前UDP服务器天然可处理多个客户端请求.

所以主要的问题就是无法既能给客户端循环提供服务,又能去快速调用到第二个accept.

这时我们就想到了多线程的方法,此处给每个客户端都分配一个线程.

//启动服务器
public void start() throws IOException {System.out.println("服务器启动!");while(true) {Socket clientSocket = serverSocket.accpet();//每次来一个客户端,就创建一个新线程.每次走一个,就销毁一个线程Thread t = new Thread(() -> {//此时,就把processConnection交给新线程负责了.//主循环就会快速执行完一次后,回到accept这里阻塞等待新的客户端来processConnection(clientSocket);});t.start();}
}

但是,我们不由得要思考一个新的问题,就是引入多线程,免不了频繁的线程销毁与创建,在资源申请与释放上开销比较大.所以有什么更好的方法可以解决呢>

服务器引入线程池

为了避免频繁创建销毁线程,也可以引入线程池.

//启动服务器
public void start() throws IOException {System.out.println("服务器启动!");ExecutorService service = Executors.newCachedThreadPool();while(true) {Socket clientSocket = serverSocket.accept();//使用线程池来解决上述问题service.submit(new Runnable() {@Overridepublic void run() {processConnection(clientSocket);}});}
}

线程,解决的是线程的频繁销毁创建问题.如果,当前的场景是线程频繁的创建,而不是销毁呢

就比如像游戏服务器这种,服务器可能处理的时间非常长.

此时如果继续使用线程池/多线程,就会导致服务器一下积累大量线程,对于服务器的负担也非常重.

长短连接

TCP发送数据时,需要先建立连接,什么时候关闭连接就决定是长连接还是短连接:

短连接:每次接收到数据并返回响应后,都关闭连接,即是短连接.也就是说,短连接只能一次收发数据.

长连接:不关闭连接,一直保持连接状态,双方不停的收发数据,即是长连接.也就是说, 长连接可以多次收发数据.

对比以上长短连接,两者区别如下:

建立连接,关闭连接的耗时:短连接每次请求,响应都需要建立连接,关闭连接;而长连接只需要第一次建立连接,之后的请求,响应都可以直接传输.相对来说建立连接,关闭连接也是要耗时的,长连接效率更高.

主动发送请求不同:短连接一般是客户端主动向服务端发送请求;而长连接可以是客户端主动发送请求,也可以是服务端主动发.

两者的使用场景不同:短连接使用于客户端请求频率不高的场景,如浏览网页等.长连接适用于客户端和服务端通信频繁的场景,如聊天室,实时游戏等.

扩展了解:

基于BIO(同步阻塞IO)的长连接会一直占用系统资源.对于并发很高的服务端系统来说,这样的消耗是不能承受的.

由于每个连接都需要不停的阻塞等待接收数据,所以每个连接都会在一个线程中运行.

一次阻塞等待对应着一次请求,响应,不停处理也就是长连接的特性:一直不关闭连接,不停的处理请求.

实际应用时,服务端一般是基于NIO(即同步非阻塞IO)来实现长连接,性能可以极大的提升.

相关文章:

网络编程套接字(2)

UDP数据报套接字编程 API介绍 DatagramSocket DatagramSocket是UDP的Socket,用于发送和接收数据报. 操作系统中有一类文件,就叫做socket文件(普通文件/目录文件:在硬盘上的) socket文件:抽象的表示了网卡这样的硬件设备 DatagramSocket就是对socket文件进行读写,也就是借助网…...

Elasticsearch:入门(二)

九. Elasticsearch的映射和分析 Elasticsearch的强大搜索引擎功能不仅源于其高效的分布式架构&#xff0c;还在于对数据的映射和分析的深度支持。通过合理的字段类型定义和灵活的分析器配置&#xff0c;可以使搜索更加精准、快速&#xff0c;并满足不同业务场景的需求。 9.1 …...

Debezium日常分享系列之:Debezium 2.6.0.Alpha1发布

Debezium日常分享系列之&#xff1a;Debezium 2.6.0.Alpha1发布 一、重大改变1.MongoDB2.重新选择列后处理器 二、改进和变化1.添加了新的匹配集合 API2.CloudEvents 架构名称自定义3.Oracle Infinispan 缓存改进4.支持 Spanner NEW_ROW_AND_OLD_VALUES 值捕获类型 一、重大改变…...

Phoncent博客,探索Rie Kudan的GPT创作之举

近日&#xff0c;大家都在谈论日本作家Rie Kudan&#xff0c;她凭借其小说《东京共鸣塔》&#xff08;"Tokyo-to Dojo-to"&#xff09;荣获了日本极具声望的芥川奖。这本小说引起了广泛的讨论和思考&#xff0c;因为令人惊讶的是&#xff0c;Kudan在其中直接引用了人…...

力扣hot100 划分字母区间 贪心 思维 满注释版

Problem: 763. 划分字母区间 文章目录 思路复杂度Code 思路 &#x1f468;‍&#x1f3eb; 代码随想录 复杂度 时间复杂度: O ( n ) O(n) O(n) 空间复杂度: O ( n ) O(n) O(n) Code class Solution {public List<Integer> partitionLabels(String s){// 创建哈希…...

linux下使用swap分区扩展内存

swap分区是什么&#xff1f; Swap分区是硬盘上的一个特殊区域&#xff0c;被操作系统用作虚拟内存。当系统的物理内存&#xff08;RAM&#xff09;被全部使用时&#xff0c;操作系统会将一部分数据移动到swap分区&#xff0c;以释放RAM上的空间。这个过程被称为"交换&quo…...

实现sleep函数

作用&#xff1a;让线程休眠&#xff0c;等到指定时间在重新唤起。 基于Date实现&#xff1a; 以上的代码不会让线程休眠&#xff0c;而是通过高负荷计算使cpu无暇处理其他任务。缺点是在sleep的过程中其他所有的任务都会被暂停&#xff0c;包括dom的渲染。sleep的过程中程序会…...

汽车销量可视化分析

目录 一.分析的背景、目的、意义 1、背景 2、目的 3、意义 二.数据来源 三.图表分析 1、汽车品牌销量柱状图 2、中国汽车销量柱状图 3、汽车销量前10排行柱状图 4、汽车厂商销量折线图 ​编辑5、汽车销量词云图 6、汽车车型销量 7、汽车价格分布雷达图 8、汽车分…...

代码随想录算法训练营DAY8 | 字符串(1)

一、LeetCode 344 反转字符串 题目链接&#xff1a; 344.反转字符串https://leetcode.cn/problems/reverse-string/ 思路&#xff1a;双指针法交换。 class Solution {public void reverseString(char[] s) {int n s.length;int left 0, right n-1;while(left < right){c…...

如何更改Outlook阅读邮件时的默认字体?

如果收到的邮件中未指定字体&#xff0c;outlook默认使用宋体显示。 如果觉得不好看&#xff0c;可以进行更改。但不是在outlook中更改&#xff0c;outlook中只是修改编辑器中的字体&#xff0c;和纯文本邮件浏览的字体&#xff0c;不能更改未指定字体的HTML邮件的显示字体。 …...

【C++基础入门】三、运算符(算术运算符、赋值运算符、比较运算符、逻辑运算符)

三、运算符 作用&#xff1a;用于执行代码的运算 本章我们主要讲解以下几类运算符&#xff1a; 运算符类型作用算术运算符用于处理四则运算赋值运算符用于将表达式的值赋给变量比较运算符用于表达式的比较&#xff0c;并返回一个真值或假值逻辑运算符用于根据表达式的值返回…...

ES7.17由于IP变化导致的故障及恢复

背景 1. k8s 升级&#xff0c;导致环境中的ES集群&#xff08;7.17版本&#xff09;重启 2. 集群由于在公有云环境&#xff0c;IP不固定&#xff08;重启后IP可能发生变化&#xff09;&#xff0c;通过 svc 进行访问 curl xxx-master-svc:9200/_cat/health 3. 由多个sts一…...

uniapp H5 touchstart touchend 切换背景会失效,或者没用

uniapp H5 touchstart touchend 切换背景会失效&#xff0c;或者没用 直接上代码 &#xff08;使用 class 以及 hover-class来设置样式&#xff09; class 设置默认的背景图或者样式 hover-class 来设置按下的背景图 或者样式 抬起 按下 <view class"mp_zoom_siz…...

【word visio绘图】关闭visio两线交叉的跳线(跨线)

【visio绘图】关闭visio两线交叉的跳线&#xff08;跨线&#xff09; 1 如何在Visio绘图中关闭visio两线交叉的跳线&#xff08;跨线&#xff09;第一步&#xff1a;打开Visio并创建您的图形第二步&#xff1a;绘制您的连接线第三步&#xff1a;关闭跳线第四步&#xff1a;手动…...

meson、ninja编译dpdk

解压目录meson编译dpdk meson buildmeson编译dpdk debug版 meson setup --buildtypedebug debugbuildmeson编译使用静态库&#xff0c;编译example meson .. --prefix/usr/local --buildtypedebugoptimized --default-librarystatic -Dexamplesallninja编译 ninjaninja安装…...

diff命令详解

diff是Unix系统的一个很重要的工具程序。 它用来比较两个文本文件的差异&#xff0c;是代码版本管理的基石之一。你在命令行下&#xff0c;输入&#xff1a; $ diff < 变动前的文件 > < 变动后的文件 >; diff就会告诉你&#xff0c;这两个文件有何差异。它的显示结…...

Backtrader 文档学习- Broker - Slippage

Backtrader 文档学习- Broker - Slippage 1.概述 回测无法保证真实的市场条件。无论市场模拟有多好&#xff0c;在真实市场条件下都可能发生滑点。这意味着&#xff1a; 请求的价格可能无法与真实市场的价格匹配 集成的回测broker支持滑点。以下参数可以传递给broker &#…...

三子棋游戏小课堂

&#x1fa90;&#x1fa90;&#x1fa90;欢迎来到程序员餐厅&#x1f4ab;&#x1f4ab;&#x1f4ab; 今天的主菜是&#xff0c;C语言实现的三子棋小游戏&#xff0c; 所属专栏&#xff1a; C语言知识点 主厨的主页&#xff1a;Chef‘s blog 前言&…...

golang开源的可嵌入应用程序高性能的MQTT服务

golang开源的可嵌入应用程序高性能的MQTT服务 什么是MQTT&#xff1f; MQTT&#xff08;Message Queuing Telemetry Transport&#xff09;是一种轻量级的、开放的消息传输协议&#xff0c;设计用于在低带宽、高延迟或不可靠的网络环境中进行通信。MQTT最初由IBM开发&#xf…...

uniapp微信小程序-请求二次封装(直接可用)

一、请求封装优点 代码重用性&#xff1a;通过封装请求&#xff0c;你可以在整个项目中重用相同的请求逻辑。这样一来&#xff0c;如果 API 发生变化或者需要进行优化&#xff0c;你只需在一个地方修改代码&#xff0c;而不是在每个使用这个请求的地方都进行修改。 可维护性&a…...

UE4 C++ 结构体

先在UCLASS()前写入&#xff1a; USTRUCT(BlueprintType) struct FMyStruct //必须以"F"开头 {GENERATED_BODY() //必须添加“GENERATED_BODY()”UPROPERTY(EditAnywhere, BlueprintReadWrite, Category "MyStruct1")int32 Health;UPROPERTY(EditAnywher…...

软件工程知识梳理0-概述

学好软件工程就必须理解软件工程到底是干什么的&#xff0c;为什么需要软件工程&#xff0c;以及怎么干的&#xff01;只有理解了软件工程的本质&#xff0c;才能更好的理解软件工程中各种工程手段和方法的目的。 个人开发模式 —> 小作坊开发模式 —> 软件工程开发模式 …...

贪吃蛇---C语言---详解

引言 C语言已经学了不短的时间的&#xff0c;这期间已经开始C和Python的学习&#xff0c;想给我的C语言收个尾&#xff0c;想起了小时候见过别人的老人机上的贪吃蛇游戏&#xff0c;自己父母的手机又没有这个游戏&#xff0c;当时成为了我的一大遗憾&#xff0c;这两天发现C语…...

Airflow原理浅析

⭐️ airflow基本原理 Apache Airflow 是一个开源的工作流自动化工具&#xff0c;它用于调度和管理复杂的数据工作流。Airflow 的原理基于有向无环图&#xff08;DAG&#xff09;的概念&#xff0c;它通过编写和组织任务的有向图来描述工作流程。 以下是 Apache Airflow 的一…...

uniapp 使用canvas 画海报,有手粘贴即可用

html部分 <view click"doposter">下载海报</view> <canvas canvas-id"myCanvas" type2d style"width: 370px; height: 550px;opcity:0;position: fixed;z-index:-1;" id"myCanvas" />js 部分 drawBackground() {c…...

Vite+Vue3+TS 引入使用Cesium.js

申请 Cesium Token 进入Cesium 注册账号 cesium 离谱的是 E宝 (Epic) 居然可以快捷登录&#xff1f;&#xff01; 登录后点击导航栏的 Access Token 再右侧即可看到默认Token 安装&引入 # Cesium pnpm pnpm install cesium# 如果项目同时存在Three.js 需避免使用pnpm T…...

Cocos creator 动作系统

动作系统简介 是用于控制物体运动的一套系统&#xff0c;完全依赖代码进行实现&#xff0c;动态调节节点的移动。 移动 cc.moveTo 移动到某个坐标&#xff08;x,y&#xff09; //1秒时间内&#xff0c;移动到0,0let action1 cc.moveTo(1,0,0)this.node.runAction(action1)c…...

对Spring当中AOP的理解

AOP(面向切面编程)全称Aspect Oriented Programminge AOP就是把系统中重复的代码抽取出来&#xff0c;单独开发&#xff0c;在系统需要时&#xff0c;使用动态代理技术&#xff0c;在不修改源码的基础上&#xff0c;将单独开发的功能通知织入(应用)到系统中的过程&#xff0c;完…...

【Vue】2-8、Axios 网络请求

cdn&#xff1a;<script src"https://unpkg.com/axios/dist/axios.min.js"></script> 注&#xff1a;使用 CDN 链接就可以不需要去下载对应的 js 文件到本地&#xff0c;只需要联网即可使用&#xff0c;可以减少项目的体积 <!DOCTYPE html> <…...

Vue中嵌入原生HTML页面

Vue中嵌入html页面并相互通信 需求&#xff1a;b2b支付需要从后获取到数据放到form表单提交跳转&#xff0c;如下&#xff1a; 但是vue目前暂时没找到有类似功能相关文档&#xff0c;所以我采用iframe嵌套的方式 1. Vue中嵌入Html <iframe src"/static/gateway.htm…...