10.JAVAEE之网络编程

1.网络编程

  • 通过网络,让两个主机之间能够进行通信 =>基于这样的通信来完成一定的功能
  • 进行网络编程的时候,需要操作系统给咱们提供一组 AP1, 通过这些 API才能完成编程(API 可以认为是 应用层 和 传输层 之间交互的路径)(API:Socket API相当于一个插座:通过这一套 Socket AP| 可以完成不同主机之间,不同系统之间的网络通信)
  • 传输层,提供的网络协议,主要是两个:TCP UDP
  • 这俩协议的特性(工作原理) 差异很大.导致,使用这两种协议进行网络编程,也存在一定差别系统就分别提供了两套 API
  • TCP 和 UDP 的区别.(后面网络原理章节, 学习的重点)

       1.TCP 是有连接的, UDP 是无连接的

       2.TCP 是可靠传输的,UDP 是不可靠传输的

       3.TCP 是面向字节流的,UDP 是面向数据报

       4.TCP 和 UDP 都是全双工的

 1.TCP 是有连接的, UDP 是无连接的
(连接 是 抽象 的概念)
计算机中,这种 抽象 的连接是很常见的,此处的连接本质上就是建立连接的双方,各自保存对方的信息两台计算机建立连接,就是双方彼此保存了对方的关键信息~~
TCP 要想通信, 就需要先建立连接 (刚才说的, 保存对方信息),做完之后,才能后续通信(如果 A 想和 B 建立连接, 但是 B 拒绝了! 通信就无法完成!!!)

UDP 想要通信,就直接发送数据即可~~不需要征得对方的同意,UDP 自身也不会保存对方的信息(UDP 不知道,但是写程序的人得知道.UDP 自己不保存,但是你调用 UDP 的 socket api的时候要把对方的位置啥的给传过去)

2.TCP 是可靠传输的,UDP 是不可靠传输的
网络上进行通信, A ->B 发送一个消息,这个消息是不可能做到 100% 送达的!! 

可靠传输,退而求其次.
A ->B 发消息,消息是不是到达 B 这一方,A 自己能感知到.(A 心里有数)进一步的,就可以在发送失败的时候采取一定的措施(尝试重传之类的)

TCP 就内置了可靠传输机制;UDP 就没有内置可靠传输

【tips】可靠传输,听起来挺美好的呀, 为啥不让 UDP 也搞个可靠传输呢??

想要可靠传输,你就是要付出代价的(需要去交换)
可靠传输要付出什么代价?
1)机制更复杂
2)传输效率会降低

3.TCP 是面向字节流的,UDP 是面向数据报
此处说的 字节流 和 文件 操作这里的 字节流 是一个意思!!!
TCP 也是和文件操作一样,以字节为单位来进行传输.
UDP 则是按照数据报为单位,来进行传输的
UDP 数据报是有严格的格式的 

网络通信数据的基本单位,涉及到多种说法~~
1.数据报(Datagram)
2.数据包(Packet)

3.数据帧(Frame)
4.数据段 (Segment) 

4. TCP 和 UDP 都是全双工的
一个信道,允许双向通信, 就是全双工
一个信道,只能单向通信,就是半双工
代码中使用一个 Socket 对象, 就可以发送数据也能接受数据~~ 

2.UDP 的 socket api 如何使用

Datagramsocket 

Datagrampacket 

【回显服务器:(echo server)】

写一个简单的 UDP 的客户端/服务器 通信的程序.
这个程序没有啥业务逻辑,只是单纯的调用 socket api.
让客户端给服务器发送一个请求,请求就是一个从控制台输入的字符串.
服务器收到字符串之后,也就会把这个字符串原封不动的返回给客户端,客户端再显示出来. 

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;public class UdpEchoServer {// 创建一个 DatagramSocket 对象. 后续操作网卡的基础.private DatagramSocket socket = null;public UdpEchoServer(int port) throws SocketException {// 这么写就是手动指定端口socket = new DatagramSocket(port);// 这么写就是让系统自动分配端口// socket = new DatagramSocket();}public void start() throws IOException {// 通过这个方法来启动服务器.System.out.println("服务器启动!");// 一个服务器程序中, 经常能看到 while true 这样的代码.while (true) {// 1. 读取请求并解析.DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);socket.receive(requestPacket);// 当前完成 receive 之后, 数据是以 二进制 的形式存储到 DatagramPacket 中了.// 要想能够把这里的数据给显示出来, 还需要把这个二进制数据给转成字符串.String request = new String(requestPacket.getData(), 0, requestPacket.getLength());// 2. 根据请求计算响应(一般的服务器都会经历的过程)//    由于此处是回显服务器, 请求是啥样, 响应就是啥样.String response = process(request);// 3. 把响应写回到客户端.//    搞一个响应对象, DatagramPacket//    往 DatagramPacket 里构造刚才的数据, 再通过 send 返回.DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length,requestPacket.getSocketAddress());socket.send(responsePacket);// 4. 打印一个日志, 把这次数据交互的详情打印出来.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 {UdpEchoServer server = new UdpEchoServer(9090);server.start();}
}
import java.io.IOException;
import java.net.*;
import java.util.Scanner;public class UdpEchoClient {private DatagramSocket socket = null;private String serverIp = "";private int serverPort = 0;public UdpEchoClient(String ip, int port) throws SocketException {// 创建这个对象, 不能手动指定端口.socket = new DatagramSocket();// 由于 UDP 自身不会持有对端的信息. 就需要在应用程序里, 把对端的情况给记录下来.// 这里咱们主要记录对端的 ip 和 端口 .serverIp = ip;serverPort = port;}public void start() throws IOException {System.out.println("客户端启动!");Scanner scanner = new Scanner(System.in);while (true) {// 1. 从控制台读取数据, 作为请求System.out.print("-> ");String request = scanner.next();// 2. 把请求内容构造成 DatagramPacket 对象, 发给服务器.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 {UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090);// UdpEchoClient client = new UdpEchoClient("42.192.83.143", 9090);client.start();}
}

【客户端】

  • 对于服务器来说,也就需要把端口号给明确下来了~~
  • 客户端的端口号是不需要确定的.交给系统进行分配即可,
  • 如果你手动指定确定的端口,就可能和别人的程序的端口号冲突
  • 【tips】服务器这边手动指定端口,就不会出现冲突嘛??
    为啥客户端在意这个冲突,而服务器不在意呢??

    服务器是在程序猿手里的,一个服务器上都有哪些程序, 都使用哪些端口,程序猿都是可控的!!程序猿写代码的时候,就可以指定一个空闲的端口,给当前的服务器使用即可
    但是客户端就不可控,客户端是在用户的电脑上;一方面,用户千千万~~ 每个用户电脑上装的程序都不一样,占用的端口也不一样;交给系统分配比较稳妥.系统能保证肯定分配一个空闲的端口

服务器一旦启动,就会立即执行到这里的 receive 方法此时,客户端的请求可能还没来呢~~
这种情况也没关系.receive 就会直接阻塞, 就会一直阻塞到真正客户端把请求发过来为止,(类似于阻塞队列)

【question】

//根据请求计算响应(核心步骤)

这个步骤是一个服务器程序,最核心的步骤!!!
咱们当前是 echo server 不涉及到这些流程,也不必考虑响应怎么计算,只要请求过来,就把请求当做响应

【question】

【question】上述写的代码中,为啥没写 close??
socket 也是文件,不关闭不就出问题了,不就文件资源泄露了么,为啥这里咱们可以不写 close?为啥不写 close 也不会出现文件资源泄露??

private DatagramSocket socket = null;
这个 socket 在整个程序运行过程中都是需要使用的(不能提前关闭)当 socket 不需要使用的时候, 意味着程序就要结束了

进程结束,此时随之文件描述符表就会销毁了(PCB 都销毁了).谈何泄露??
随着销毁的过程,被系统自动回收了~~

啥时候才会出现泄露?代码中频繁的打开文件,但是不关闭在一个进程的运行过程中,不断积累打开的文件,逐渐消耗掉文件描述符表里的内容最终就消耗殆尽了
但是如果进程的生命周期很短,打开一下没多久就关闭了.谈不上泄露
文件资源泄露这样的问题,在服务器这边是比较 严重的, 在客户端这边一般来说影响不大.

 【服务器】

【交互】

1.服务器先启动.服务器启动之后,就会进入循环,执行到 receive 这里并阻塞 (此时还没有客户端过来呢)
2.客户端开始启动,也会先进入 while 循环,执行 scanner.next.并且也在这里阻塞当用户在控制台输入字符串之后,next 就会返回,从而构造请求数据并发送出来~~

3.客户端发送出数据之后,
服务器: 就会从 receive 中返回,进一步的执行解析请求为字符串,执行 process 操作,执行 send 操作
客户端: 继续往下执行,执行到 receive,等待服务器的响应

4.客户端收到从服务器返回的数据之后,就会从 receive 中返回执行这里的打印操作,也就把响应给显示出来了
5.服务器这边完成一次循环之后, 又执行到 receive 这里,客户端这边完成一次循环之后,又执行到 scanner.next 这里双双进入阻塞

 【翻译服务器】

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("dog", "小狗");dict.put("cat", "小猫");dict.put("pig", "小猪");}// 重写 process 方法, 在重写的方法中完成翻译的过程.// 翻译本质上就是 "查表"@Overridepublic String process(String request) {return dict.getOrDefault(request, "该词在词典中不存在!");}public static void main(String[] args) throws IOException {UdpDictServer server = new UdpDictServer(9090);server.start();}
}

上述重写 process 方法,就可以在子类中组织你想要的"业务逻辑",(你要写代码解决一些实际的问题)

3.TCP 的 socket api 如何使用 

TCP 的 socket api 和 UDP 的 socket api 差异又很大~,

但是和前面讲的 文件操作,有密切联系的

两个关键的类

1.ServerSocket(给服务器使用的类,使用这个类来绑定端口号)

2.Socket(既会给服务器用,又会给客户端用)

这俩类都是用来表示 socket 文件的,(抽象了网卡这样的硬件设备)

TCP 是字节流的.传输的基本单位,是 byte

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 的同时, 和服务器 "建立连接", 此时就得告诉 Socket 服务器在哪里~~// 具体建立连接的细节, 不需要咱们代码手动干预. 是内核自动负责的.// 当我们 new 这个对象的时候, 操作系统内核, 就开始进行 三次握手 具体细节, 完成建立连接的过程了.socket = new Socket(serverIp, serverPort);}public void start() {// tcp 的客户端行为和 udp 的客户端差不多.// 都是:// 3. 从服务器读取响应.// 4. 把响应显示到界面上.Scanner scanner = new Scanner(System.in);try (InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream()) {PrintWriter writer = new PrintWriter(outputStream);Scanner scannerNetwork = new Scanner(inputStream);while (true) {// 1. 从控制台读取用户输入的内容System.out.print("-> ");String request = scanner.next();// 2. 把字符串作为请求, 发送给服务器//    这里使用 println, 是为了让请求后面带上换行.//    也就是和服务器读取请求, scanner.next 呼应writer.println(request);writer.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();}
}
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;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;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("服务器启动!");ExecutorService service = Executors.newCachedThreadPool();while (true) {// 通过 accept 方法, 把内核中已经建立好的连接拿到应用程序中.// 建立连接的细节流程都是内核自动完成的. 应用程序只需要 "捡现成" 的.Socket clientSocket = serverSocket.accept();// 此处不应该直接调用 processConnection, 会导致服务器不能处理多个客户端.// 创建新的线程来调用更合理的做法.// 这种做法可行, 不够好
//            Thread t = new Thread(() -> {
//                processConnection(clientSocket);
//            });
//            t.start();// 更好一点的办法, 是使用线程池.service.submit(new Runnable() {@Overridepublic void run() {processConnection(clientSocket);}});}}// 通过这个方法, 来处理当前的连接.public void processConnection(Socket clientSocket) {// 进入方法, 先打印一个日志, 表示当前有客户端连上了.System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress(), clientSocket.getPort());// 接下来进行数据的交互.try (InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()) {// 使用 try ( ) 方式, 避免后续用完了流对象, 忘记关闭.// 由于客户端发来的数据, 可能是 "多条数据", 针对多条数据, 就循环的处理.while (true) {Scanner scanner = new Scanner(inputStream);if (!scanner.hasNext()) {// 连接断开了. 此时循环就应该结束System.out.printf("[%s:%d] 客户端下线!\n", clientSocket.getInetAddress(), clientSocket.getPort());break;}// 1. 读取请求并解析. 此处就以 next 来作为读取请求的方式. next 的规则是, 读到 "空白符" 就返回.String request = scanner.next();// 2. 根据请求, 计算响应.String response = process(request);// 3. 把响应写回到客户端.//    可以把 String 转成字节数组, 写入到 OutputStream//    也可以使用 PrintWriter 把 OutputStream 包裹一下, 来写入字符串.PrintWriter printWriter = new PrintWriter(outputStream);//    此处的 println 不是打印到控制台了, 而是写入到 outputStream 对应的流对象中, 也就是写入到 clientSocket 里面.//    自然这个数据也就通过网络发送出去了. (发给当前这个连接的另外一端)//    此处使用 println 带有 \n 也是为了后续 客户端这边 可以使用 scanner.next 来读取数据.printWriter.println(response);//    此处还要记得有个操作, 刷新缓冲区. 如果没有刷新操作, 可能数据仍然是在内存中, 没有被写入网卡.printWriter.flush();// 4. 打印一下这次请求交互过程的内容System.out.printf("[%s:%d] req=%s, resp=%s\n", clientSocket.getInetAddress(), clientSocket.getPort(),request, response);}} catch (IOException e) {e.printStackTrace();} finally {try {// 在这个地方, 进行 clientSocket 的关闭.// processConnection 就是在处理一个连接. 这个方法执行完毕, 这个连接也就处理完了.clientSocket.close();} catch (IOException e) {e.printStackTrace();}}}public String process(String request) {// 此处也是写的回显服务器. 响应和请求是一样的.return request;}public static void main(String[] args) throws IOException {TcpEchoServer server = new TcpEchoServer(9090);server.start();}
}

【服务器】

内核中有一个“队列”(可以视为阻塞队列)

如果有客户端,和服务器建立连接,这个时候服务器的应用程序是不需要做出任何操作(也没有任何感知的),内核直接就完成了连接建立的流程(三次握手).

完成流程之后,就会在内核的队列中(这个队列是每个 serverSocket 都有一个这样的队列)。

排队应用程序要想和这个客户端进行通信,就需要通过一个 accept 方法把内核队列里已经建立好的连接对象,拿到应用程序中。

【question】

前面写过的 DatagramSocket, ServerSocket 都没写 close, 但是我们说这个东西都没关系但是 clientSocket 如果不关闭,就会真的泄露了!!!
DatagramSocket 和 ServerSocket,都是在程序中,只有这么一个对象.申明周期, 都是贯穿整个程序的.

而ClientSocket 则是在循环中,每次有一个新的客户端来建立连接,都会创建出新的clientSocket

每次执行这个,都会创建新的 clientSocket,并且这个 socket 最多使用到 该客户端退出(断开连接)
此时,如果有很多客户端都来建立连接~~此时,就意味着每个连接都会创建 clientSocket.当连接断开clientSocket 就失去作用了,但是如果没有手动 close此时这个 socket 对象就会占据着文件描述符表的位置

【客户端】

【question】出现一个bug

当前启动两个客户端,同时连接服务器.
其中一个客户端(先启动的客户端) 一切正常.
另一个客户端 (后启动的客户端)则没法和服务器进行任何交互,(服务器不会提示"建立连接”,也不会针对 请求 做出任何响应)

上述bug和代码结构密切相关

确实如刚才推理的现象一样,第一个客户端结束的时候,就从 processConnection 返回了就可以执行到第二次 accept 了,也就可以处理第二个客户端了~~
很明显,如果启动第三个客户端,第三个客户端也会僵硬住,又会需要第二个客户端结束才能活过来...

如何解决上述问题?让一个服务器可以同时接待多个客户端呢??

关键就是,在处理第一个客户端的请求的过程中,要让代码能够快速的第二次执行到 accept ~~~【多线程】

上述这里的关键,就是让这两个循环能够"并发"执行.
各自执行各自的,不会因为进入一个循环影响到另一个~~

【刚才出现这个问题的关键在于两重循环在一个线程里进入第二重循环的时候,无法继续执行第一个循环.
Udp 版本的服务器,当时是只有一个循环,不存在类似的问题~~(前面部署到云服务器的时候)】

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

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

相关文章

NDK 基础(一)—— C 语言知识汇总

本系列文章主要是介绍一些 NDK 开发所需的基础知识&#xff0c;目录如下&#xff1a; NDK 基础&#xff08;一&#xff09;—— C 语言知识汇总 NDK 基础&#xff08;二&#xff09;—— C 语言基础与特性1 NDK 基础&#xff08;三&#xff09;—— C 语言基础与特性2 NDK 基础…

比较美观即将跳转html源码

源码介绍 比较美观即将跳转html源码&#xff0c;源码由HTMLCSSJS组成&#xff0c;记事本打开源码文件可以进行内容文字之类的修改&#xff0c;双击html文件可以本地运行效果&#xff0c;也可以上传到服务器里面 源码截图 比较美观的一个跳转界面&#xff0c;修改方法如上&…

FreeRTOS-系统时钟节拍和时间管理

一、前言 任何操作系统都需要提供一个时钟节拍&#xff0c;以供系统处理诸如延时&#xff0c;超时等与时间相关的事件。时钟节拍是特定的周期性中断&#xff0c; 这个中断可以看做是系统心跳。 中断之间的时间间隔取决于不同的应用&#xff0c;一般是 1ms – 100ms。时钟的节拍…

【C++ —— 多态】

C —— 多态 多态的概念多态的定义和实现多态的构成条件虚函数虚函数的重写虚函数重写的两个例外协变&#xff1a;析构函数的重写 C11 override和final重载、覆盖(重写)、隐藏(重定义)的对比 抽象类概念接口继承和实现继承 多态的继承虚函数表多态的原理动态绑定和静态绑定 单继…

BERT一个蛋白质-季军-英特尔创新大师杯冷冻电镜蛋白质结构建模大赛-paipai

关联比赛: “创新大师杯”冷冻电镜蛋白质结构建模大赛 解决方案 团队介绍 paipai队、取自 PAIN AI&#xff0c;核心成员如我本人IvanaXu(IvanaXu GitHub)&#xff0c;从事于金融科技业&#xff0c;面向银行信用贷款的风控、运营场景。但我们团队先后打过很多比赛&#xf…

算法系列--BFS解决拓扑排序

&#x1f495;"请努力活下去"&#x1f495; 作者&#xff1a;Lvzi 文章主要内容&#xff1a;算法系列–算法系列–BFS解决拓扑排序 大家好,今天为大家带来的是算法系列--BFS解决拓扑排序 前言:什么是拓扑排序 拓扑排序–解决有顺序的排序问题(要做事情的先后顺序) …

docker各目录含义

目录含义builder构建docker镜像的工具或过程buildkit用于构建和打包容器镜像&#xff0c;官方构建引擎&#xff0c;支持多阶段构建、缓存管理、并行化构建和多平台构建等功能containerd负责容器生命周期管理&#xff0c;能起、停、重启&#xff0c;确保容器运行。负责镜管理&am…

Java设计模式 _结构型模式_组合模式

一、组合模式 1、组合模式 组合模式&#xff08;Composite Pattern&#xff09;是这一种结构型设计模式。又叫部分整体模式。组合模式依据树形结构来组合对象&#xff0c;用来表示部分以及整体层次关系。即&#xff1a;创建了一个包含自己对象组的类&#xff0c;该类提供了修改…

Idea报错:无法访问org.springframework.boot.SpringApplication

在开发项目时&#xff0c;常常会遇到这种问题&#xff0c;报错信息如下图所示 版本号与jdk版本号存在对应关系&#xff0c;61.0对应jdk17&#xff0c;52.0对应jdk8 所以是某个依赖的版本太高&#xff0c;降低该依赖的版本即可 具体步骤&#xff1a; ①修改pom.xml中spring b…

ASP.NET实验室预约系统的设计

摘 要 实验室预约系统的设计主要是基于B/S模型&#xff0c;在Windows系统下&#xff0c;运用ASP.NET平台和SQLServer2000数据库实现实验室预约功能。该设计主要实现了实验室的预约和管理功能。预约功能包括老师对实验室信息、实验项目和实验预约情况的查询以及对实验室的预约…

ubuntu系统搭建pytorch环境详细步骤【笔记】

实践设备&#xff1a;华硕FX-PRO&#xff08;NVIDIA GeForce GTX 960M&#xff09; 搭建PyTorch环境的详细步骤如下&#xff1a; 1.安装Ubuntu系统&#xff1a; 下载Ubuntu的镜像文件并制作启动盘。将启动盘插入计算机&#xff0c;启动计算机并按照提示安装Ubuntu系统。 2.…

Linux内核之原子操作:atomic_long_dec用法实例(六十七)

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

一起Talk Android吧(第五百五十八回:lombok用法)

文章目录 1. 概述2. 使用方法3. 内容总结 各位看官们大家好&#xff0c;上一回中介绍了如何获取文件读写权限的知识,本章回中将介绍lombok相关的知识。闲话休提&#xff0c;言归正转&#xff0c;让我们一起Talk Android吧&#xff01; 1. 概述 这是一个java库&#xff0c;用来…

ES全文检索支持拼音和繁简检索

ES全文检索支持拼音和繁简检索 1. 实现目标2. 引入pinyin插件2.1 编译 elasticsearch-analysis-pinyin 插件2.2 安装拼音插件 3. 引入ik分词器插件3.1 已有作者编译后的包文件3.2 只有源代码的版本3.3 安装ik分词插件 4. 建立es索引5.测试检索6. 繁简转换 1. 实现目标 ES检索时…

flutter开发实战-build apk名称及指令abiFilters常用gradle设置

flutter开发实战-build apk名称及指令abiFilters常用gradle设置 最近通过打包flutter build apk lib/main.dart --release&#xff0c;发现apk命名规则需要在build.gradle设置。这里记录一下。 一、apk命名规则 在android/app/build.gradle中需要设置 android.applicationVa…

Pandas入门篇(二)-------Dataframe篇4(进阶)(Dataframe的进阶用法)(机器学习前置技术栈)

目录 概述一、复合索引&#xff08;一&#xff09;创建具有复合索引的 DataFrame1. 使用 set_index 方法&#xff1a;2.在创建 DataFrame 时直接指定索引&#xff1a; &#xff08;二&#xff09;使用复合索引进行数据选择和切片&#xff08;三&#xff09;重置索引&#xff08…

rabbitMq 0 到1

前言 工作中MQ的使用场景是数不胜数&#xff0c;每个公司的技术选型又不太一样&#xff0c;用的哪个MQ&#xff0c;我们必须要先玩起来&#xff0c;RabbitMQ在windows安装遇到很多问题&#xff0c;博客也是五花八门&#xff0c;算了还是自己搞吧&#xff0c;记录一下&#xff…

五大开放式耳机推荐,选对耳机让运动更带感!

看似精彩的户外运动经历背后&#xff0c;其实是枯燥的体能运动和训练&#xff0c;以及独自长途和长时间旅行伴随的孤独感&#xff0c;而排解这些不良情绪的最佳方式就是音乐。如果你希望在运动、舒适、安全和音质之间获得一个最佳平衡&#xff0c;那相比入耳式耳机&#xff0c;…

护航智慧交通安全 | 聚铭精彩亮相2024交通科技创新及信创产品推广交流会

4月26日&#xff0c;石家庄希尔顿酒店内&#xff0c;河北省智能交通协会盛大举办2024年度交通科技创新及信创产品推广交流会。聚铭网络受邀参与&#xff0c;携旗下安全产品及解决方案精彩亮相&#xff0c;为智慧交通安全保驾护航。 为深化高速公路创新驱动发展战略&#xff0…

pthread线程相关

LWP :轻量级 进程&#xff0c;本质仍是进程 进程 &#xff1a;独立地址空间&#xff0c;拥有PCB 线程&#xff1a;有独立的TCB&#xff0c;但没有独立的地址空间&#xff08;共享&#xff09; 区别 &#xff1a;在与是否共享地址文件 进程 &#xff08;独居&#xff09;&am…