网络编程(JavaEE初阶系列10)

目录

前言:

1.网络编程的基础

1.1为什么需要网络编程

1.2什么是网络编程

1.3网络编程中的基本概念

1.3.1发送端和接收端

1.3.2请求和响应

1.3.3客户端和服务端

2.Socket套接字

2.1概念

2.2分类

3.UDP数据报套接字编程

3.1DataGramSocket API

3.2DatagramPacket API

3.3基于UDP的回显服务器(echo server)

3.4简单的翻译服务器

4.TCP流套接字

4.1ServerSocket API

4.2Socket API

4.3基于TCP的回显程序

5.再谈协议

结束语:


前言:

在上一节中小编主要是与大家分享了一些有关于网络的基础知识,但是里面的细节和基础的编程还没有给大家来交代,这节中小编就给大家俩交代一下有关于网络基础编程方面的一些基础的编程吧,大家赶快跟上小编的步伐一起来往下看吧。如果还没有看小编网络基础知识的部分的同学建议先去看看这篇博文吧:☞http://t.csdn.cn/aj9ov

1.网络编程的基础

1.1为什么需要网络编程

用户在浏览器中,打开在线视频网站,比如抖音短视频其实质是通过网络,获取到网络上的一个视频资源,与本地打开视频文件类似,只是视频文件这个资源的来源是网络,相比本地资源来说,网络提供了更为丰富的网络资源,所谓的网络资源,其实就是在网络中可以获取的各种数据资源,而所有的网络资源,都是通过网络编程来进行数据传输的。

1.2什么是网络编程

网络编程:指网络上的主机,通过不同的进程,以编程的方式实现网络通信(网络数据传输)

当然,我们只要满足进程不同就行,所以即便是同一个主机,只要是不同进程,基于网络来传输数据,也属于网络编程。

1.3网络编程中的基本概念

1.3.1发送端和接收端

在一次网络数据传输时:

  • 发送端:数据的发送方进程,称为发送端。发送端主机即网络通信中的源主机。
  • 接收端:数据的接收方进程,称为接收端。接收端主机即网络通信中的目的主机。
  • 收发端:发送端和接收端两端,也简称Wie收发端。

注意:发送端和接收端只是相对的概念,只是一次网络数据传输产生数据流向后的概念。

1.3.2请求和响应

一般来说,获取一个网络资源,涉及到两次网络数据传输。

  • 第一次:请求数据的发送。
  • 第二次:响应数据的发送。

就像是在餐厅点饭一样,先发起请求:点一份蛋炒饭。餐厅在给一个响应:提供一份蛋炒饭。

1.3.3客户端和服务端

  • 服务端:在常见的网络数据传输的场景下,把提供服务的这一方进程,称为服务端,可以提供对外服务。
  • 客户端:获取服务的一方进程,称为客户端。

对于服务来说,一般是提供:

  • 客户端获取服务资源。
  • 客户端保存资源在服务端。

就像是我们在银行办事:

  • 银行提供存款服务:用户(客户端)保存现金(资源)在银行(服务端)。
  • 银行提供取款服务:用户(客户端)获取服务端资源(银行替用户保管现金)。

2.Socket套接字

2.1概念

Socket套接字是操作系统提供给应用程序的一组用于网络编程的API。他是基于TCP/IP协议的通信的的基本操作单元。

注意:操作系统原生的Socket API是C语言的但是这里我们学习的是Java封装之后的版本。

2.2分类

Socket套接字主要针对传输层协议划分为如下三类:

  • 数据报套接字:使用传输层UDP协议。

UDP,即User Datagram Protocol(用户数据报协议),传输层协议。它的特点是:无连接、不可靠传输、面向数据报、全双工。

  • 流套接字:使用传输层TCP协议。

TCP,即Transmission Control Protocol(传输控制协议),传输层协议。它的特点是:有连接、可靠传输、面向字节流、全双工。

对于字节流来说,可以简单理解为传输的数据是基于IO流的,流式数据的特征就是在IO流没哟关闭的情况下,是无边界的数据,可以多次发送,也可以分开多次接收。

  • 原始套接字:

原始套接字用于自定义传出层协议,用于读写内核没有处理的IP协议数据,这里我们对此不做过多讨论,我们重点是理解和应用前两个。

TCP特点vsUDP特点:

UDP特点TCP特点
无连接:使用UDP通信双方不需要刻意保存对方的相关信息有连接:使用TCP通信双方则需要刻意保存对方的相关信息
不可靠传输:消息发了就发了不关注结果可靠传输:不是说发送之后对方就可以100%能够达到对方,这要求就太高了,只是说尽可能的传输过去。
面向数据报:以UDP数据报为基本单位。面向字节流:以字节为传输的基本单位,读写方式非常灵活
全双工:一条路径,双向通信全双工:一条路径,全向通信。

解释:全双工vs半双工。

  • 全双工:是一条路径,全向通信,你可以理解为,一个双向通道的马路。
  • 半双工:是一条路径,只能由一侧向另一侧通信,你可理解为单向通道的马路。

针对上述的TCP协议和UDP协议也给我们提供了两组不同的API。下面我们来一步一步的了解一下。

3.UDP数据报套接字编程

3.1DataGramSocket API

DataGramSocket 是UDP Socket,用于发送和接收UDP数据报,所谓Socket,是一个特殊的文件,是网卡这个硬件设备的抽象表示,你也可以理解为是一个遥控器,想要进行网络通信就需要有socket文件这样的对象,借助这个socket文件对象,才能够间接的操作网卡。

  • 往这个socket对象里写数据,相当于通过网卡发送消息。
  • 从这个socket对象中读数据,相当于通过网卡接收消息。

DatagramSocket的构造方法,可以绑定一个端口号(服务器),也可以不显示指定客户端。

方法签名方法说明
DatagramSocket()创建一个UDP数据报套接字的Socket,绑定到本机任意一个随机端口(一般用于客户端)
DatagramSocket(int port)创建一个UDP数据报套接字的Socket,绑定到本机指定的端口(一般用于服务端)
  • 服务器这边的socket往往要关联一个具体的端口号。
  • 客户端这边则不需要手动指定,系统会自动分配一个闲置的端口号。

举个例子:

比如现在我开了一家餐厅,要发传单,那么在传单上面我这边可定是要标清楚我的餐厅的具体位置在哪,窗口号是多少,都得事先分配好,此时我开的这家餐馆就相当于是服务器,确定的地址和窗口号就是服务器事先分配好的端口号。那么如果此时客人看到我发的传单就来到我的餐馆吃饭了,那么它点完餐之后,就会随便找一个空着的位置坐下,等饭。此时客人就相当于是客户端,随便找的位置就是系统给随机分配的一个空闲的端口号。

 DatagramSocket方法

方法签名方法说明
void receive(DatagramPacket p)从此套接字接收数据报(如果没有接收到数据报,该方法会阻塞等待)
void send(DatagramPacket p)从此套接字发送数据报(不会阻塞等待,直接发送)
void close()关闭数据报套接字(释放资源)

注意:

  • DatagramPacket表示一个UDP数据报。
  • 在close的时候,到底啥时候调用close,一定是要socket/文件,确定一定以及肯定不再使用,此时才能调用close。

3.2DatagramPacket API

DatagramPacket是UDPSocket发送和接收的数据报。

DatagramPacket构造方法

方法签名方法说明
DatagramPacket(byte[] buf, int length)构造一个DatagramPacket以用来接收数据报,接收的数据保存在字节数组(第一个参数buf)中,接收指定长度(第二个参数length)
DatagramPacket(byte[] buf, int offset, int lenght, SocketAddress address)构造一个DatagramPacket以用来发送数据报,发送的数据为字节数组(第一个参数buf)中,从0到指定长度(第二个参数length),address指定目的的主机的IP和端口号。

 DatagramPacket方法

方法签名方法说明
InetAddress getAddress()从接收的数据报中,获取发送端主机IP地址,或从发送的数据报中,获取接收端主机IP地址。
int getPort()从接收的数据报中,获取发送端主机的端口号,或从发送的数据报中,获取接收端主机端口号。
byte[] getData()获取数据报中的数据。

3.3基于UDP的回显服务器(echo server)

介绍了DatagramSocket 和 DatagramPacket API之后,我们基于UDP socket写一个简单的客户端服务器程序。 也就是让客户端发一个请求,在服务器上返回一个一模一样的响应。

首先来明确一点,一个服务器主要做的三个核心的工作:

  1. 读取请求并解析。
  2. 根据请求并计算响应。(代码中省略了)
  3. 把响应返回给客户端。

服务端代码:

package network;
//服务端
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;public class UdpEchoServer{//需要先定义一个Socket对象//通过网络通信,必须要使用socket对象private DatagramSocket socket = null;//绑定一个端口,不一定能成功//如果某个端口已经被别的进程占用了,此时这里的绑定操作就会出错。//同一个主机上,一个端口,同一个时刻,只能被一个进程绑定public UdpEchoServer(int port) throws 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会阻塞等待,等到客户端那边发送数据过来socket.receive(requestPacket);//为了方便处理这个请求,需要把数据报转换成StringString request = new String(requestPacket.getData(), 0, requestPacket.getLength());//2.根据请求计算响应(此处省略这个步骤)String response = process(request);//3.把响应结果写回到客户端// 根据response 字符串,构造一个DatagramPacket// 和请求packet 不同,此处构造响应的时候,需要指定这个包要发给谁,这里调用requestPacket.getSocketAddress()就可以得知了DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length,requestPacket.getSocketAddress());socket.send(responsePacket);//打印一下请求的地址和请求的端口号,以及请求的内容和响应的内容System.out.printf("[%s:%d] req: %s, resp: %s\n", requestPacket.getAddress().toString(),requestPacket.getPort(), request, response);}}//这个方法是希望我们根据请求计算响应。//由于咱们写的是个回显程序,请求是啥,响应就是啥//如果后续写一个别的服务器,不再回显了,而是具有具体的业务了,就可以修改process方法//根据需求来重新构造响应//之所以单独列成一个方法,就是想让大家知道这个是一个关键的环节。private String process(String request) {return request;}public static void main(String[] args) throws IOException {UdpEchoServer udpEchoServer = new UdpEchoServer(9090);udpEchoServer.start();}
}

客户端代码:

package network;
//客户端
import java.io.IOException;
import java.net.*;
import java.util.Scanner;public class UdpEchoClient {private DatagramSocket socket = null;private String serverIP;//服务器的地址private int serverPort;//服务器的端口//客户端启动,需要知道服务器在哪里public UdpEchoClient(String serverIP, int serverPort) throws SocketException {//对于客户端来说,不需要显示关联空闲的端口//不代表没有端口,而是系统自动分配了一个空闲的端口socket = new DatagramSocket();this.serverIP = serverIP;this.serverPort = serverPort;}public void start() throws IOException {//通过这个客户端可以多次和服务器进行交互Scanner scanner = new Scanner(System.in);while (true) {//1.先从控制台,读取一个字符串过来//先打印一个提示符,提示用户要输入内容System.out.println("->");String request = scanner.next();//2.把字符串构造成UDP packet,并进行发送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显示出来String response = new String(responsePacket.getData(), 0, responsePacket.getLength());System.out.printf("req: %s, resp: %s\n", request, response);}}public static void main(String[] args) throws IOException {UdpEchoClient udpEchoClient = new UdpEchoClient("127.0.0.1", 9090);udpEchoClient.start();}
}


启动服务器和客户端结果展示:

 

注意:这里一定是先启动服务器,再启动客户端!!!

执行流程如下所示:

 

注意:在上述过程中我们是客户端和服务器在同一个主机上,使用的是127这个IP,不同主机则就写实际的IP即可。

在上述通信过程中,站在客户端发送数据的角度:

  • 源IP是:127.0.0.1
  • 源端口是:64982,他是系统自动分配的空闲端口。
  • 目的IP是:127.0.0.1
  • 目的端口是:9090

在上述过程中就有同学好奇了不是说是要使用close来关闭资源的吗?为什么在代码中好像没有看到释放资源这一步,其实对于UdpEchoServer来说,这个socket对象是出了循环就不用了,但是循环结束,意味着start结束,意味着main方法结束,同时意味着进程结束,那么此时进程都结束了所以的资源也就自然释放了,所以就不必显示释放资源了。

3.4简单的翻译服务器

在上述中我们编写的是一个回显服务器,它是没有实际意义的。那么如何写一个提供实在价值的服务器呢?当响应和请求不一样了,响应是根据不同的请求计算得到的,这里就需要我们对上述过程没有写的process方法来进行编写,那么下来我们就具体来实现一下。我们就来编写一个简单的英文单词翻译服务器,请求是一个英文单词,响应是这个单词的中文翻译。

服务端代码展示:

package network;
//词典查询服务端
import java.io.IOException;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;//使用继承,是为了复用之前的代码
public class UdpDicServer extends UdpEchoServer{private Map<String, String> dict = new HashMap<>();public UdpDicServer(int port) throws SocketException {super(port);dict.put("dog", "小狗");dict.put("cat", "小猫");dict.put("tiger", "老虎");//注意:这里可以无限添加很多个数据}@Overridepublic String process(String request) {return dict.getOrDefault(request,"该单词没有查到!");}public static void main(String[] args) throws IOException {UdpDicServer udpDicServer = new UdpDicServer(9090);udpDicServer.start();}
}


客户端代码展示:

package network;
//客户端
import java.io.IOException;
import java.net.*;
import java.util.Scanner;public class UdpEchoClient {private DatagramSocket socket = null;private String serverIP;//服务器的地址private int serverPort;//服务器的端口//客户端启动,需要知道服务器在哪里public UdpEchoClient(String serverIP, int serverPort) throws SocketException {//对于客户端来说,不需要显示关联空闲的端口//不代表没有端口,而是系统自动分配了一个空闲的端口socket = new DatagramSocket();this.serverIP = serverIP;this.serverPort = serverPort;}public void start() throws IOException {//通过这个客户端可以多次和服务器进行交互Scanner scanner = new Scanner(System.in);while (true) {//1.先从控制台,读取一个字符串过来//先打印一个提示符,提示用户要输入内容System.out.println("->");String request = scanner.next();//2.把字符串构造成UDP packet,并进行发送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显示出来String response = new String(responsePacket.getData(), 0, responsePacket.getLength());System.out.printf("req: %s, resp: %s\n", request, response);}}public static void main(String[] args) throws IOException {UdpEchoClient udpEchoClient = new UdpEchoClient("127.0.0.1", 9090);udpEchoClient.start();}
}


运行结果展示:

 

注意:在上述编写服务端代码时我们是直接使用了继承,重写了父类的process方法。这样就减少了我们的工作。

4.TCP流套接字

在TCP中有两个核心的类:

  • ServerSocket:是给服务器使用的。
  • Socket:即会给客户端使用,又会给服务器端使用。

下面我们就来分别看看ServerSocket和Socket的具体使用方法。

4.1ServerSocket API

他是创建服务端使用的API。

SocketSocket构造方法:

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

SocketSocket方法: 

方法签名方法说明
Socket accept()开始监听指定端口(创建时绑定的端口),有客户端连接后,返回一个服务端Socket对象,并基于该Socket建立与客户端的连接,否则阻塞等待。
void close()关闭此套接字

这里的accept意思就是接收,在客户端主动向服务器发起连接请求,服务器就得同意一下,但是实际上的这个accept又和我们上述给大家解释的意思不太一样,这里的accept只是在应用层面的接收,实际的TCP连接的接受是在该内核里已经完成了。这个后面在将TCP的时候会给大家交代的。 

4.2Socket API

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

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

Socket的构造方法:

方法签名方法说明
Socket(String host, int port)创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的进程建立连接。

这里的host和port指的是服务器的IP和端口,TCP是有连接的,在客户端new Socket对象的时候就会尝试和指定IP端口的目标建立连接了。 

Socket的方法:

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

 InputStream getInputStream()和OutPutStream getOutStream()是字节流,就可以通过上述字节流对象,进行数据传输了。

  • 从 InputStream 这里读数据,就相当于是从网卡接收。
  • 往 OutPutStream 这里写数据,就相当于从网卡发送。

注意:

这个Socket和DatagramSocket定位类似,都是构造的时候指定一个具体的端口,让服务器绑定该端口,但是ServerSocket一定要绑定具体的端口。

4.3基于TCP的回显程序

服务端代码展示 :

package network;
//服务端
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 {//serverSocket只有一个,clientSocket会给每一个客户都分配一个private ServerSocket severSocket = null;public TcpEchoServer(int port) throws IOException {severSocket = new ServerSocket(port);}public void start() throws IOException {ExecutorService executorService = Executors.newCachedThreadPool();System.out.println("服务器启动成功!");while (true) {Socket clientSocket = severSocket.accept();//如果直接调用,该方法会影响这个循环的二次执行,导致accept不及时了。//创建新的线程,用新线程来调用processConnection//每次来一个新的客户端都搞一个新的线程即可!
//            Thread t = new Thread(() -> {
//                try {
//                    processConnection(clientSocket);
//                } catch (IOException e) {
//                    e.printStackTrace();
//                }
//            });
//            t.start();executorService.submit(new Runnable() {@Overridepublic void run() {try {processConnection(clientSocket);} catch (IOException e) {e.printStackTrace();}}});}}//读取这个方法来处理一个连接//读取请求//根据请求计算响应//把响应返回给客户端private void processConnection(Socket clientSocket) throws IOException {System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());//使用try()这种写法,()中允许写多个流对象,使用;来分隔try(InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()) {//没有这个scanner和printWriter,完全可以,但是代价就是得一个字节一个字节扣,找到哪个是请求结束的标记\n//不是不能做,而是代替代码比较麻烦//为了简单,把字节流包装成了更方便的字符流Scanner scanner = new Scanner(inputStream);PrintWriter printWriter = new PrintWriter(outputStream);while (true) {//1.读取请求//采用hasNext判定接下来还有没有数据了,如果对端关闭了连接(客户端关闭连接),此时hasNext就会返回false,循环就结束if (!scanner.hasNext()) {//读取的流到了结尾了(对端关闭了)System.out.printf("[%s:%d] 客户端下线了!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());break;}//直接使用scanner读取一段字符串//next会一直往后读,读到空白符结束(空格、换行、制表符、翻页符...都算空白符)//nextLine只是读到换行符结束,所以这里没有使用它String request = scanner.next();//2.根据请求计算响应String response = process(request);//3.把响应写回给客户端,不要忘记了,响应也是要带上换行的//返回响应的时候要把换行符加回来,方便客户端那边来区分从哪里到哪里是一个完整的响应。printWriter.println(response);//flush当数据不够大的时候直接进行强制刷新,将缓冲区中的数据发给客户端printWriter.flush();System.out.printf("[%s:%d] req: %s; resp: %s\n", clientSocket.getInetAddress().toString(), clientSocket.getPort(), request, response);}}catch (IOException e) {e.printStackTrace();}finally {//clientSocket只是一个连接提供服务的,这个还是要进行关闭的clientSocket.close();}}private String process(String request) {return request;}public static void main(String[] args) throws IOException {TcpEchoServer tcpEchoServer = new TcpEchoServer(9090);tcpEchoServer.start();}
}


客户端代码展示:

package network;
//客户端
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 port) throws IOException {//这个操作相当于让客户端和服务器建立TCP连接//这里的连接连上了,服务器的accept就会返回socket = new Socket(serverIP, port);}public void start() throws IOException {Scanner scanner = new Scanner(System.in);try (InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream()) {PrintWriter printWriter = new PrintWriter(outputStream);Scanner scannerFromSocket = new Scanner(inputStream);while (true) {//1.从键盘上读取用户输入的内容System.out.println("->");String request = scanner.next();//2.把读取到的内容构造成请求,发送给服务器//注意,这里的发送,是带有换行的。printWriter.println(request);printWriter.flush();//3.从服务器读取响应的内容String response = scannerFromSocket.next();//4.把响应结果显示到控制台上System.out.printf("req: %s; resp: %s\n", request, response);}}}public static void main(String[] args) throws IOException {TcpEchoClient client = new TcpEchoClient("127.0.0.1", 9090);client.start();}
}

结果展示:

执行流程如下所示:

 

那么这里我们只是启动了一个客户端,在实际中不可能是一个服务器只给一个客户端进行服务,那么如何启动多个客户端呢?这里在idea中是默认下只能启动一个的,那么这里我们需要打开idea配置一下。配置过程如下所示:

 

 

此时当我们再次点击上述的三角形就可以再次启动另一个客户端了。

 

 

5.再谈协议

回顾并理解我们为什需要协议

以上我们实现的UDP和TCP数据传输,除了UDP和TCP之外,程序还存在应用层定义协议,可以想想分别都是什么样的协议格式。

对于客户端及服务端应用程序来说,请求和响应,需要约定一致的数据格式:

  • 客户端发送请求和服务端解析请求和要使用相同的数据格式。
  • 服务端返回响应和客户端解析响应也要使用相同的数据格式。
  • 请求格式和响应格式可以相同,也可以不同。
  • 约定相同的数据格式,主要目的是为了让接收端在解析的时候明确如何解析数据中的各个字段。
  • 可以使用知名协议(广泛使用的协议格式),如果想自己约定数据格式,就属于自定义协议。

结束语:

这节中小编主要是和大家分享了网络编程中的两个重要的编程UDP和TCP,后期小编还会继续出有关于网络方面的知识的,希望这节对大家了解网络有一定帮助,想要学习的同学记得关注小编和小编一起学习吧!如果文章中有任何错误也欢迎各位大佬及时为小编指点迷津(在此小编先谢过各位大佬啦!)

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

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

相关文章

【游戏客户端】制作你玩我猜Like玩法

【游戏客户端】制作你玩我猜Like玩法 大家好&#xff0c;我是Lampard猿奋~~ “你画我猜”相信大家都不陌生&#xff0c;当初这款小游戏可谓茶余饭后必玩之选&#xff0c;风头一时无二。今天要和大家分享如何实现一个你玩我猜Like的玩法。 我们可以简单的把需求拆成两个个部分&…

python实现对图油画、卡通、梦幻、草图、水彩效果

本篇博客将介绍如何使用wxPython模块和OpenCV库来实现对图像进行灰度化、二值化、伽马校正、色彩空间转换和图像反转这5种效果的合并程序。程序可以通过wxPython提供的GUI界面来选择图片路径和效果类型&#xff0c;程序会将处理后的图像保存到指定路径并打开。 步骤一&#xf…

2021年12月 Python(一级)真题解析#中国电子学会#全国青少年软件编程等级考试

一、单选题(共25题,每题2分,共50分) 第1题 昨天的温度是5摄氏度,今天降温7摄氏度,今天的温度是多少摄氏度? A:12 B:7 C:2 D:-2 正确的答案是 D:-2。 解析: 根据题目描述,昨天的温度是 5 摄氏度,今天降温了 7 摄氏度。降温意味着温度变低,所以今天的温度…

k8s之Pod控制器

目录 一、Pod控制器及其功用二、pod控制器的多种类型2.1 pod容器中的有状态和无状态的区别 三、Deployment 控制器四、SatefulSet 控制器4.1 StatefulSet由以下几个部分组成4.2 为什么要有headless&#xff1f;4.3 为什么要有volumeClaimTemplate&#xff1f;4.4 滚动更新4.5 扩…

解决MAC M1处理器运行Android protoc时出现的错误

Protobuf是Google开发的一种新的结构化数据存储格式&#xff0c;一般用于结构化数据的序列化&#xff0c;也就是我们常说的数据序列化。这个序列化协议非常轻量级和高效&#xff0c;并且是跨平台的。目前&#xff0c;它支持多种主流语言&#xff0c;比传统的XML、JSON等方法更具…

性能优化必知必会

系统性能调优 分为四个层次 基础设施网络编解码分布式系统性能整体提升 一&#xff1a;基础设施优化 从提升单机进程的性能入手&#xff0c;包括高效的使用主机的CPU、内存、磁盘等硬件&#xff0c;通过提高并发编程提升吞吐量&#xff0c;根据业务特性选择合适的算法 01 …

CSS—选择器

目录 一、CSS简介 二、HTML页面中常用的元素 三、CSS语法规则 四、常用的选择器 五、选择器参考 一、CSS简介 CSS (Cascading Style Sheets&#xff0c;层叠样式表&#xff09;&#xff0c;是一种用来为结构化文档&#xff08;如 HTML 文档或 XML 应用&#xff09;添加样式…

如何把非1024的采样数放入aac编码器

一. aac对数据规格要求 二、代码实现 1.初始化 2.填入数据 3.取数据 三.图解 一. aac对放入的采样数要求 我们知道aac每次接受的字节数是固定的&#xff0c;在之前的文章里有介绍libfdk_aac音频采样数和编码字节数注意 它支持的采样数和编码字节数分别是&#xff1a; fdk_aac …

Go context.WithCancel()的使用

WithCancel可以将一个Context包装为cancelCtx,并提供一个取消函数,调用这个取消函数,可以Cancel对应的Context Go语言context包-cancelCtx 疑问 context.WithCancel()取消机制的理解 父母5s钟后出门&#xff0c;倒计时&#xff0c;父母在时要学习&#xff0c;父母一走就可以玩 …

【mock安装时报错】

node版本需要大于16 切换>16的版本即可 建议使用 nvm进行 node版本状态管理 可参考另一篇文章 nvm多版本管理方案

windows美化任务栏,不使用软件

1.任务栏透明: 效果图: (1).winr打开命令行 输入regedit回车打开注册表 regedit (2).在注册表中打开 \HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced 这个路径 \HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explore…

爬虫获取电影数据----以沈腾参演电影为例

数据可视化&分析实战 1.1 沈腾参演电影数据获取 文章目录 数据可视化&分析实战前言1. 网页分析2. 构建数据获取函数2.1 网页数据获取函数2.2 网页照片获取函数 3. 获取参演影视作品基本数据4. 电影详细数据获取4.1 导演、演员、描述、类型、投票人数、评分信息、电影海…

【51单片机】晨启科技,7针OLED显示驱动程序,STC89C52RC

文章目录 原理图oled.coled.hmain.c 原理图 sbit OLED_SCLP4^3;//SCL-D0 sbit OLED_SDAP4^1;//SDA-D1 sbit OLED_RES P3^6;//RES sbit OLED_DC P3^7;//DC sbit OLED_CSP2^7; //CS oled.c #include "OLED.h"//******************************说明*******************…

Redis 高频数据类型使用详解

目录 一、前言 二、Redis常用数据类型 2.1 常见的数据类型 三、String 类型 3.1 String 类型简介 3.2 String常用操作命令 3.2.1 String 操作命令实践 3.3 常用业务场景 3.3.1 session共享 3.3.2 登录失败计数器 3.3.3 限流 3.3.4 多线程安全控制 四、Hash类型 4…

android 如何分析应用的内存(十五)——Visual Studio Code 调试Android应用

android 如何分析应用的内存&#xff08;十五&#xff09;——Visual Studio Code 调试Android 应用 在上一篇文章介绍了jdb调试java应用 接下来介绍用UI界面调试java应用&#xff0c;达到同jdb一样的效果。 同样的UI界面有很多选择&#xff0c;如Eclipse&#xff0c;Android …

Redis 双写一致性实践及案例

面试问题&#xff1a; 你只要用缓存&#xff0c;就可能会涉及到redis缓存与数据库双存储双写&#xff0c;你只要是双写&#xff0c;就一定会有数据一致性的问题&#xff0c;那么你如何解决一致性问题&#xff1f;双写一致性&#xff0c;你先动缓存redis还是数据库mysql哪一个&…

Python识别抖音Tiktok、巨量引擎滑块验证码识别

由于最近比较忙&#xff0c;所以本周搞了一个相对简单的验证码&#xff0c;就是抖音Tiktok的滑块验证码&#xff0c;这也是接到客户的一个需求。这种验证码通常在电脑端登录抖音、巨量引擎的的时候出现。 首先看一下最终的效果&#xff1a; 验证码识别过程 1、利用爬虫采集图…

【Vue】使用print.js插件实现打印预览功能,超简单

目录 一、实现效果 二、实现步骤 【1】安装插件 【2】在需要打印的页面导入 【3】在vue文件中需要打印的部分外层套一层div&#xff0c;给div设置id。作为打印的区域 【4】在打印按钮上添加打印事件 【5】在methods中添加点击事件 三、完整代码 一、实现效果 二、实现步…

Spring集成Seata

Seata的集成方式有&#xff1a; 1. Seata-All 2. Seata-Spring-Boot-Starter 3. Spring-Cloud-Starter-Seata 本案例使用Seata-All演示&#xff1a; 第一步&#xff1a;下载Seata 第二步&#xff1a;为了更好看到效果&#xff0c;我们将Seata的数据存储改为db 将seata\sc…

《合成孔径雷达成像算法与实现》Figure3.2

代码参数说明&#xff1a;Sf1为书中公式3.19&#xff0c;Sf2为时域信号快速傅里叶表达式&#xff0c;两种频谱表达式所做出的图可看出其区别 代码如下&#xff1a; clc clear all close all%参数设置 TBP 720; %时间带宽积 T 10e-6; %脉冲持续时间%参数计…