网络编程套接字

news2025/1/16 6:32:20

✏️作者:银河罐头
📋系列专栏:JavaEE

🌲“种一棵树最好的时间是十年前,其次是现在”

目录

  • Socket 套接字
  • UDP 和 TCP
  • UDP数据报套接字编程
    • DatagramSocket API
    • DatagramPacket API
    • UdpEchoServer
    • UdpEchoClient
    • UdpDictServer
    • 端口冲突
  • TCP流套接字编程
    • ServerSocket API
    • Socket API
    • TcpEchoServer
    • TcpEchoClient
    • 服务器同时连接多个客户端
    • Tcp 中的长短连接
    • C10M 问题

Socket 套接字

网络编程这里的核心,Socket API,操作系统给应用程序提供的网络编程 API。

可以认为 Socket API 是和传输层密切相关的。

传输层里提供了两个最核心的协议,UDP 和 TCP。因此 Socket API 也提供了两种风格,UDP 和 TCP

UDP 和 TCP

简单认识下 UDP 和 TCP

UDP :无连接 不可靠传输 面向数据报 全双工

TCP :有连接 可靠传输 面向字节流 全双工

  • 什么是有连接?什么是无连接?

打电话就是有连接的,需要建立连接才能通信,建立连接需要对方"接受"

发短信/微信就是无连接的,直接发就行了

  • 什么是可靠传输?什么是不可靠传输?

网络环境天然是复杂的,不可能保证传输的数据 100% 都能到达。发送方能知道自己的消息是不是发过去了,还是丢了。

打电话是可靠传输,发短信/微信 是不可靠传输(带有已读功能的是可靠传输,比如钉钉)

可靠不可靠和有没有连接没有任何关系

  • 面向字节流 && 面向数据报

面向字节流:数据传输就和文件读写类似,"流式"的

面向数据报:数据传输则以一个一个的"数据报"为基本单位(一个数据报可能是若干个字节,带有一定格式的)

  • 全双工

一个通信通道,可以双向传输(既可以发送,也可以接收)

image-20230128120257779

为啥 TCP 和 UDP 都是全双工呢?

一根网线里有 8 根线

UDP数据报套接字编程

DatagramSocket API

DatagramSocket 使用这个类表示一个 Socket 对象。在操作系统中也是把这个对象当成是一个文件处理的,相当于是文件描述符表上的某一项。

普通的文件对应的硬件设备是硬盘;socket 文件对应的硬件设备是网卡。

一个 Socket 对象就可以和另外一台主机进行通信了,如果要和多个不同的主机进行通信,就要有多个 socket 对象。

  • DatagramSocket 构造方法:

DatagramSocket() 没有指定端口,系统则会自动分配一个空闲的端口
DatagramSocket(int port) 这个版本是要传入一个端口号,此时就是让当前的这个 socket 对象和指定的端口(简单的整数)关联起来。

端口号用来标识主机上不同的应用程序

本质上不是进程和端口建立联系,而是进程里的 socket 对象和端口建立了联系

  • DatagramSocket 方法:

DatagramPacket 表示 UDP 中传输的一个报文,构造这个对象可以指定一些具体的数据进去。

void receive(DatagramPacket p) 此时传入的相当于是一个空的对象,receive 方法内部会对这个空对象进行内容填充,从而构造出结果数据了。这里的参数也是一个"输出型参数"
void send(DatagramPacket p)
void close() 释放资源,用完之后记得关闭

DatagramPacket API

  • DatagramPacket 构造方法:

DatagramPacket(byte[] buf, int length) 把 buf 这个缓冲区给设置进去了

DatagramPacket(byte[] buf, int offset, int length, SocketAddress address) 构造缓冲区 + 地址

SocketAddress 使用这个类表示 IP + 地址

  • DatagramPacket 方法:

InetAddress getAddress()

int getPort()

byte[] getData()

UdpEchoServer

编写一个最简单的 UDP 版本的客户端服务器程序,称之为回显服务器(echo server)

一个普通的服务器:收到请求,根据请求计算响应(业务逻辑),返回响应;

echo server 省略了其中的"根据请求计算响应",请求是啥就返回啥(这个代码没有实际的业务,这个代码也没啥作用和意义,只是展示了 socket api 的基本用法)

作为一个真正的服务器,"根据请求计算响应"这个环节是最重要的

举个栗子🌰:你去一家餐馆,点一份蛋炒饭,跟老板说"老板,我要一份蛋炒饭",老板把饭炒好以后,给你端上来一份蛋炒饭,其中制作蛋炒饭的过程是最困难的,你点餐和老板给你把饭端上来这两个动作都是简单的

//UDP 版本的回显服务器
public class UdpEchoServer {
    //网络编程,本质上是要操作网卡
    //但是网卡不方便直接操作,在操作系统内核中华,使用了一种特殊的叫做 "socket"这样的文件来操作网卡
    //因此进行网络通信,首先得要有一个 socket 对象
    private DatagramSocket socket = null;
    //对于服务器来说,创建 socket 对象的同时要给他绑定上一个具体的端口号
    //服务器一定要关联上一个具体的端口
    //因为在网络传输中,服务器是被动的一方,如果是操作系统随机分配的端口,那么客户端就不知道端口是啥了,也就无法进行通信了
    public UdpEchoServer(int port) throws SocketException {
        socket = new DatagramSocket(port);
    }
    public void start() throws IOException {
        System.out.println("服务器启动");
        //服务器不是只给一个客户端提供服务就完了,需要服务很多客户端
        while(true){
            //只要有客户端过来,就可以提供服务
            //1.读取客户端发来的请求是啥
            // receive 方法的参数是一个输出型参数,需要先构造好一个空白的 DatagramPacket 对象,发给 receive 进行填充
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(requestPacket);
            //此时这个 DatagramPacket 是一个特殊的对象,并不方便直接进行处理,可以把这里包含的数据拿出来,构造成一个字符串
            String request = new String(requestPacket.getData(),0,requestPacket.getLength());
            //2.根据请求计算响应,由于这里是回显服务器,请求和响应相同
            String response = process(request);
            //3.把响应协会到客户端,send的参数也是一个 DatagramPacket,需要把这个 Packet 对象给构造好
            //此处这里的响应对象,不能是空的字节数组构造了,而是要使用响应数据来构造
            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 {
        //端口号可以在 1024 ~ 65535 里任意指定一个
        UdpEchoServer server = new UdpEchoServer(9090);
        server.start();
    }
}

关于服务器一定要关联上一个具体的端口这一点

举个栗子🌰:假设我在某学校三食堂 5 号窗口,租了个店面,卖肉夹馍。

此时的三食堂就相当于我的 IP 地址,5 号窗口相当于我的端口号。我开了个餐厅,相当于我搭起了个服务器。

如果我作为一个服务器,我是一个随机端口,会有啥效果?(如果不是固定端口)。每次服务器启动就是一个不同的端口了。

有顾客觉得好吃下一回再来这个5号窗口时,发现不卖肉夹馍了?!

因此作为服务器得固定端口,才方便客户端找到我

对于 UDP 来说,传输数据的基本单位,DatagramPacket

receive 内部会针对参数对象填充数据,填充的数据来自网卡。

image-20230128203846526

image-20230128211300831

服务器的工作流程:

1.读取请求并解析

2.根据请求计算响应

3.构造响应发给对应的客户端

UdpEchoClient

public class UdpEchoClient {
    private DatagramSocket socket = null;
    private String serverIP = null;
    private int serverPort = 0;
    //一次通信需要 2 个 IP ,2 个端口
    //客户端的 IP 是 127.0.0.1 已知的
    //客户端的端口是系统分配的
    //服务器的 IP 和端口需要告诉客户端,才能顺利把消息发给服务器
    public UdpEchoClient(String serverIP,int serverPort) throws SocketException {
        socket = new DatagramSocket();
        this.serverIP = serverIP;
        this.serverPort = serverPort;
    }
    public void start(){
        System.out.println("客户端启动");
        while(true){
            //1.从控制台读取要发送的数据
            System.out.print("> ");
            String request = scanner.next();
            if(request.equals("exit")){
                System.out.println("goodbye");
                break;
            }
            //2.构造成 UDP 请求并发送
            //构造这个 Packet 的时候,需要把 serverIP和 Port 都传入过来,但是此处的 IP 地址需要填写的是一个 32 位的整数形式,
            //上述 IP 地址是一个字符串,需要使用 InetAddress.getByName()来做一个转换
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,
                    InetAddress.getByName(serverIP),serverPort);
            socket.send(requestPacket);
            //3.读取 UDP 的响应并解析
            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 udpEchoClient = new UdpEchoClient("127.0.0.1",9090);
        udpEchoClient.start();
    }
}

构造这个 socket 对象,不需要显示的绑定一个端口,让操作系统自动分配一个端口

对于服务器来说,端口必须是确定好的;

对于客户端来说,端口可以是系统分配的。

服务器的端口是要固定指定的:目的是为了方便客户端找到服务器程序

客户端的端口是由系统随机分配的:如果手动指定,可能会和客户端其他程序的端口冲突(服务器上面的程序可控,客户端是运行在用户电脑上,环境更复杂,不可控)

127.0.0.1 => 32位的整数(给计算机看的)

​ => 点分十进制(给人看的)

​ 每个部分是 0 ~ 255 一个字节

服务器 - 客户端 交互执行过程:

1.一定是服务器先启动,服务器运行到 receive() 阻塞

2.客户端读取用户输入的内容

3.客户端发送请求

4.客户端阻塞等待响应过来;服务器收到请求,从阻塞中返回。

5.服务器根据请求计算响应

6.服务器发送响应

7.客户端从阻塞中返回,读到响应了

启动服务器,客户端

image-20230202162210611

在 IDEA 上可以手动设置打开多个客户端,让服务器同时和多个客户端进行通信

image-20230202163021830

image-20230202163050965

每次点 运行 都是创建了一个客户端进程。

当前的服务器和客户端的程序,都是在自己的本机上跑的。而实际上网络存在的意义是跨主机通信。当前按这个程序可以做到跨主机通信。

举个栗子🌰:如果张三(服务器)在北京,李四(客户端)在南京。李四想要和张三实现网络通信,不可行。因为张三的电脑没有外网IP,只能在局域网内部进行访问,除非李四到张三家里才可以和张三通信。

不过,李四可以连上"云服务器"(有外网 IP)这样的特殊的电脑,任何一个连上网络的设备都能访问。

UdpDictServer

回显服务器缺少业务逻辑,在上述代码的基础上稍作调整,实现一个"查词典"的服务器。(英文翻译成中文)

package network;
import java.io.IOException;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;

//对于 DictServer 来说,和 EchoServer 相比,大部分都是一样的,
// 主要是"根据请求计算响应"这一步不太一样
public class UdpDictServer extends UdpEchoServer{
    private Map<String,String> dict = new HashMap<>();
    public UdpDictServer(int port) throws SocketException {
        super(port);
        //给这个 Dict 设置下内容
        dict.put("cat","小猫");
        dict.put("dog","小狗");
        //这里可以无限多的设置键值对...
    }
    @Override
    public String process(String request){
        //查词典的过程
        return dict.getOrDefault(request,"当前单词没有查到结果!");
    }

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

image-20230203141718666

端口冲突

一个端口只能被一个进程使用,如果有多个使用就不行。

前面的UdpEchoServer 和 UdpDictServer 端口都是 9090

UdpEchoServer server = new UdpEchoServer(9090);
UdpDictServer udpDictServer = new UdpDictServer(9090);

此时如果同时运行 UdpEchoServer 和 UdpDictServer 就会抛异常

image-20230203143227337

TCP流套接字编程

TCP 提供的 API 主要是 2 个类。

ServerSocket API

ServerSocket : 专门给服务器使用的 Socket 对象

  • ServerSocket 构造方法:

    ServerSocket(int port) :创建一个服务端流套接字Socket,并绑定到指定端口

  • ServerSocket 方法:

Socket accept() :开始监听指定端口(创建时绑定的端口),有客户端连接后,返回一个服务端Socket
对象,并基于该Socket建立与客户端的连接,否则阻塞等待。

TCP是有连接,此处的 accept() 相当于是"接电话"

Socket API

2)Socket : 既会给客户端使用,也会给服务器使用

注意,TCP不需要一个类来表示 “TCP 数据报”,因为 UDP 是以数据报为单位来传输的,而 TCP 是以字节为单位进行传输的

Socket 在服务器这边是由 accept() 返回的;

在客户端这边,代码里构造指定一个 IP 和端口号(此处的 IP 和端口是服务器的 IP 和端口),有了这个信息就能和服务器建立连接了。

  • Socket 方法:

InputStream getInputStream() :返回此套接字的输入流

OutputStream getOutputStream() :返回此套接字的输出流

进一步通过 Socket 对象获取到内部的流对象,通过流对象来发送和接收

之前的 文件操作是操作硬盘,这里是 操作网卡,读网卡,写网卡。

TcpEchoServer

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){
            //使用 clientSocket 和具体的客户端进行交流
            Socket clientSocket = serverSocket.accept();
            //效果是建立连接,前提是有客户端来连接
            //客户端在构造 Socket 对象的时候指定服务器的 IP 和 端口
            //如果没有客户端来连接,就会阻塞
            processConnection(clientSocket);
        }
    }

    //使用这个方法来处理一个连接
    //这一个连接对应一个客户端,但是这里面可能涉及到多次交互
    private void processConnection(Socket clientSocket) {
        System.out.printf("[%s:%d] 客户端上线\n",clientSocket.getInetAddress().toString(),
                clientSocket.getPort());
        //基于上述 socket 对象和客户端进行通信
        try(InputStream inputStream = clientSocket.getInputStream();
                OutputStream outputStream = clientSocket.getOutputStream()){
            //由于要处理多个请求和响应,此处用 while 循环
            while(true){
                //1.读取请求
                Scanner scanner = new Scanner(inputStream);
                if(!scanner.hasNext()){
                    //没有下个数据说明读完了,(客户端关闭了连接)
                    System.out.printf("[%s:%d] 客户端下线!\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
                    break;
                }
                //next一直读到 换行/空格/空白符 结束,最终返回结果里不包换上述空白符
                String request = scanner.next();
                //2.根据请求构造响应
                String response = process(request);
                //3.返回响应结果
                //OutputStream 没有 write String 这样的功能,可以把 String 里面的字节数组拿出来,进行写入
                //也可以用字符流来转换一下
                PrintWriter printWriter = new PrintWriter(outputStream);
                //此处用 println 来写入,让结果中带有一个 \n 换行,方便对端来进行接收解析
                printWriter.println(response);
                //flush 来刷新缓冲区,保证当前写入的数据,确实是发送出去了
                printWriter.flush();
                System.out.printf("[%s:%d] req:%s res:%s",clientSocket.getInetAddress().toString(),
                                  clientSocket.getPort(),request,response);
            }
        }catch(IOException e){
            e.printStackTrace();
        }finally {
            //更合适的做法,是把 close 放到 finally 里面,保证他一定能够执行到
            try {
                clientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    private String process(String request) {
        return request;
    }
}

image-20230203162233053

任意一个 客户端连上来都会返回/创建一个 socket 对象,socket 就是文件,每次创建一个 clientSocket 对象,就要占用一个文件描述符表的位置,因此在使用完毕之后要释放。

前面的 UdpEchoServer 的 socket 没有手动释放,一方面是因为这些 socket 的生命周期更长(伴随整个程序),另一方面是这些 socket 不多,固定数量。

而此处的 clientSocket ,数量多,每个客户端有一个,生命周期更短。

TcpEchoClient

public class TcpEchoClient {
    private Socket socket = null;
    public TcpEchoClient(String serverIp,int serverPort) throws IOException {
        //socket 构造方法 能够识别 点分十进制 的 IP地址,比 DatagramPacket 更方便
        //new 这个对象的同时就会进行 TCP 连接操作
        socket = new Socket(serverIp,serverPort);

    }
    public void start(){
        Scanner scanner = new Scanner(System.in);
        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()){
            while(true){
                //1.先从键盘读取用户输入的内容
                System.out.print("> ");
                String request = scanner.next();
                if(request.equals("exit")){
                    System.out.println("goodbye");
                    break;
                }
                //2.把读到的内容构造成请求,发送给服务器
                PrintWriter printWriter = new PrintWriter(outputStream);
                printWriter.println(request);
                printWriter.flush();
                //3.读取服务器发来的响应
                Scanner respScanner = new Scanner(inputStream);
                String response = respScanner.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();
    }
}

image-20230203165328255

image-20230203202804446

服务器同时连接多个客户端

当前代码中还有一个重要的问题:当前的服务器,同一时刻只能处理一个连接(只能给一个客户端提供服务)

当启动服务器之后,启动客户端1可以看到正常的上线提示;再启动客户端2此时发现没有任何提醒了。

客户端1给服务器发送消息正常,而客户端2发送消息则没有任何提示。

(相当于打电话占线了)

当客户端1退出之后,客户端2就可以正常发消息了。

image-20230203205511727

针对 TcpEchoServer 里的代码而言,当客户端连上服务器之后,代码就执行到了 processConnection 这个方法的循环中,此时意味着只要processConnection 这个方法的循环不结束,processConnection 这个方法就结束不了,从而无法第二次调用 accept().

那如果 processConnection 里面不用循环可行吗?

不行,虽然不使用循环,读取请求的时候,可能会阻塞,(next一直读到空白符才结束)

//next一直读到 换行/空格/空白符 结束,最终返回结果里不包换上述空白符
String request = scanner.next();

image-20230203211156438

这里的解决办法是可以采用多线程。

每次收到一个连接,就创建一个新线程,由这个新线程负责处理这个新的客户端。每个线程都是独立的执行流,每个独立的执行流是各自执行各自的逻辑,彼此之间是并发的逻辑,不会发生这边阻塞影响到另一边的情形。

public class TcpEchoServer {
   public void start() throws IOException {
        System.out.println("服务器启动");
        while (true){
            //使用 clientSocket 和具体的客户端进行交流
            Socket clientSocket = serverSocket.accept();
            Thread t = new Thread(()->{
                processConnection(clientSocket);
            });
            t.start();
        }
    }
}

如果客户端特别多,很多客户端频繁的来建立连接,就需要频繁创建/销毁线程了。此时就可以用线程池来做进一步的优化。

public void start() throws IOException {
        System.out.println("服务器启动");
        //此处使用 cachedThreadPool,使用 FixedThreadPool不太合适(线程数量不太应该是固定的)
        ExecutorService threadPool = Executors.newCachedThreadPool();
        while (true){
            //使用 clientSocket 和具体的客户端进行交流
            Socket clientSocket = serverSocket.accept();
            
            //使用线程池
            threadPool.submit(()->{
                processConnection(clientSocket);
            });
        }
    }

Tcp 中的长短连接

TCP有连接的场景下,针对连接这个概念有两种典型的表现形式。

1)短连接:客户端每次给服务器发消息,先建立连接,发送请求,读取响应,关闭连接,下次再发送则重新建立连接

2)长连接:客户端,建立连接之后,连接先不着急断开,然后再发送请求读取响应,再发送请求读取响应,若干轮之后,客户端确实短时间之内不再需要使用这个连接了,此时再断开。

C10M 问题

上述 TcpEchoServer 虽然是使用了线程池了,但是还不够。

如果客户端非常多,而且客户端连接都迟迟不断开,就会导致咱们的机器上有很多线程,如果一个服务器有几千个客户端就得是几千个线程,有几万个客户端,几万个线程…

这个事情对于机器来说是个很大的负担。

多开服务器是能解决这个问题,但是多开服务器意味着成本的增加。

是否有办法解决单机支持更大量客户端的问题呢?C10M 问题

C10K 问题:单机处理 1 w 个客户端

C10M 问题:单机处理 1kw 个客户端(1kw不是具体数量,只是为了描述比C10K多很多)

针对上述多线程的版本,最大问题是机器承担不了这么大的线程开销。

是否有办法让 1 个线程处理多个客户端的连接?

IO多路复用(IO多路转接)

举个栗子🌰:相当于一个人同时接 2 个电话,这2个电话传输过来的内容是有停顿的,IO过程中也会有等待,IO多路复用就是充分利用等待时间,做别的事。

比如生活中到饭点我去买饭,打算取买个饭再买杯奶茶再去取个快递,干这3件事,最省时间的做法是我先去买饭点好饭之后,利用这个等饭的时间取点杯奶茶,然后立马去取快递,这样做就充分利用了这个等待时间。

给这个线程安排个集合,这个集合就放了一堆连接,这个线程就负责监听这个集合,哪个连接有数据来了线程就来处理哪个连接。这个其实就应用了一个事实,虽然连接有很多,但是这些连接的请求并非严格意义的同时,总还是有间隔时间的。

在操作系统里,提供了一些原生 API:select , poll , epoll。在 Java 中,提供了一组 NIO 这样的类,就封装了上述多路复用的 API。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

分享62个JS返回顶部特效,总有一款适合您

分享62个JS返回顶部特效&#xff0c;总有一款适合您 62个JS返回顶部特效下载链接&#xff1a;https://pan.baidu.com/s/1X1fSwxibtEDXKeYqj0sHXQ?pwde2kp 提取码&#xff1a;e2kp Python采集代码下载链接&#xff1a;https://wwgn.lanzoul.com/iKGwb0kye3wj import os im…

[Android]Toolbar

Toolbar是由AndroidX库提供的&#xff0c;它的强大之处在于&#xff0c;它不仅继承了ActionBar的所有功能&#xff0c;并且灵活度很高&#xff0c;可以配合其他控件完成一些Material Design的效果。 使用Toolbar替代ActionBar 在themes的两个xml文件中&#xff0c; 都指定一个…

视频连载08 - 这个为生信学习和生信作图打造的开源R教程真香!!!

点击阅读原文跳转完整教案。1 思考题2 R基础2.1 R安装2.2 Rstudio基础2.2.1 Rstudio版本2.2.2 Rstudio安装2.2.3 Rstudio 使用2.3 R基本语法2.3.1 获取帮助文档&#xff0c;查看命令或函数的使用方法、事例或适用范围2.3.2 R中的变量及其初始化2.3.3 变量类型和转换2.3.4 R中矩…

ECharts接收dataset类型数据封装各类型图形组件

数据平台整合matabase图表&#xff0c;调用matabase已有接口使用echarts实现图表展示 目标 将各类型图形独立封装为组件 将多个组件整体封装成一个组件 使用时只需传入组件名和对应数据即可 展示 数据格式 ECharts中dataset配置 公共组件 示例饼图 pie-chart pie-chart comm…

AcWing 1083. Windy数(数位DP)

AcWing 1083. Windy数&#xff08;数位DP&#xff09;一、问题二、分析状态表示状态转移初末状态循环设计注意事项三、代码一、问题 二、分析 这道题考察的是数位DP的知识&#xff0c;对于数位DP的分析方法作者在之前的文章中做过详细地介绍&#xff1a;AcWing 1081. 度的数量…

java面试题(九)集合篇

2.1 Java中有哪些容器&#xff08;集合类&#xff09;&#xff1f; 参考答案 Java中的集合类主要由Collection和Map这两个接口派生而出&#xff0c;其中Collection接口又派生出三个子接口&#xff0c;分别是Set、List、Queue。所有的Java集合类&#xff0c;都是Set、List、Qu…

【LeetCode】Day206-二叉树着色游戏

题目 1145. 二叉树着色游戏【中等】 题解 官解说的实在是抽象了&#xff0c;看了下高赞题解&#xff0c;果然很清晰易懂 以 x 为根&#xff0c;它的三个邻居&#xff08;左儿子、右儿子和父节点&#xff09;就对应着三棵子树&#xff1a; 左子树右子树父节点子树 哪棵子树…

微服务项目框架及多模块开发

目录 项目模式 技术栈 项目架构图 模块 案例演示 主模块 zmall-common子模块 zmall-user子模块 项目模式 电商模式&#xff1a;市面上有5种常见的电商模式&#xff0c;B2B、B2C、 C2B、 C2C、O2O; 1、B2B模式 B2B (Business to Business)&#xff0c;是指 商家与商家…

java递归-八皇后问题(回溯算法)

1.八皇后问题介绍 八皇后问题&#xff0c;是一个古老而著名的问题&#xff0c;是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯贝瑟尔于 1848 年提出&#xff1a;在 88 格的国际象棋上摆放八个皇后&#xff0c;使其不能互相攻击&#xff0c;即&#xff1a;任意两个皇后都…

Day04-数据分析模型

文章目录数据分析模型数据分析流程第一&#xff1a;定性法第二&#xff1a;定量法一、数据分析要解决什么问题&#xff1f;1. 研究历史2. 解释现状4. 洞察商机5. 寻求最佳方案二、数据分析师的工作三、数据分析流程1. 数据分析框架2. 数据获取3. 数据处理4. 数据分析5. 撰写报告…

大数据技术架构(组件)18——Hive:FileFormats(1)

1.5、FileFormats1.5.1、FileFormat对比&#xff1a;1.5.1.1、Text File每一行都是一条记录&#xff0c;每行都以换行符&#xff08;\ n&#xff09;结尾。数据不做压缩&#xff0c;磁盘开销大&#xff0c;数据解析开销大。可结合Gzip、Bzip2使用&#xff08;系统自动检查&…

Python 3 基本数据类型,包含示例演示(初学友好)

嗨害大家好鸭~ 我是小熊猫 有好好学习python吗&#xff1f; Python学习资料电子书 点击此处跳转文末名片获取 Python3 基本数据类型 Python 中的变量不需要声明。每个变量在使用前都必须赋值&#xff0c;变量赋值以后该变量才会被创建。 在 Python 中&#xff0c;变量就是变量…

Redis实现分布式锁

基于Redis实现分布式锁。分为单Redis节点实现和Redis集群实现。 基于单个Redis节点实现分布式锁 作为分布式锁实现过程中的共享存储系统&#xff0c;Redis可以使用键值对来保护锁变量&#xff0c;在接收和处理不同客户端发送的加锁的操作请求。 客户端A、C同时请求加锁&#…

【Linux】基本开发工具的使用-yumvimgcc/g++git

文章目录Linux 软件包管理器-yum什么是软件包window和Linux互传文件的工具: lrzszyum注意事项查看软件包注意事项如何安装软件注意事项如何卸载软件好玩的指令sl 小火车cowsay 打印一只说话的小牛boxes 打印一个ASCII的动画linux_logo 显示linux系统的logocurl http://wttr.in …

2021美赛D题艺术家思路整理

问题整理 使用influence_data数据集或其部分创建音乐影响力的&#xff08;多个&#xff09;定向网络&#xff0c;其中影响者与关注者相连。开发捕捉此网络中“音乐影响的参数”。通过创建定向影响网络的子网络来探索音乐影响力的子集。描述这个子网络。你的“音乐影响”指标在…

((蓝桥杯 刷题全集)【备战(蓝桥杯)算法竞赛-第3天】( 从头开始重新做题,记录备战竞赛路上的每一道题 )距离蓝桥杯还有64天

&#x1f3c6;&#x1f3c6;&#x1f3c6;&#x1f3c6;&#x1f3c6;&#x1f3c6;&#x1f3c6; 欢迎观看我的博客&#xff0c;如有问题交流&#xff0c;欢迎评论区留言&#xff0c;一定尽快回复&#xff01;&#xff08;大家可以去看我的专栏&#xff0c;是所有文章的目录&a…

PMP知识点1

根据PMBOK和参考书籍自己总结的一些不熟悉知识点&#xff0c;当做笔记放这复习。 1. 项目战略管理、组合管理、项目及管理、项目管理区别 战略管理项目组合管理项目集管理项目管理工作内容明确组织战略目标选择有利于实现战略目标的项目分析并利用各项目之间有机联系规范有序…

RAS《Research on Offense and Defense of DDos Based on Evolutionar Game Theory》

Read abstract and personal Summary of《Research on Offense and Defense of DDos Based on Evolutionar Game Theory》Ⅰ&#xff1a;Read abstract(阅读摘要)0&#xff1a;Proper noun interpretation&#xff08;专有名词解释&#xff09;1&#xff1a;The question raise…

windows 休眠后风扇狂转的解决方法

windows 休眠后风扇狂转的解决方法 问题描述&#xff1a; 在Windows电脑接入usb设备后进入休眠状态时&#xff0c;风扇立刻最大功率运行&#xff0c;在拔出usb/唤醒电脑后风扇会恢复正常。 解决方法&#xff1a; 使用powercfg 查询唤醒电脑的设备&#xff0c;然后移除此设备…

C语言基础(五)—— 数组、数组地址(步长+1)、字符串输入输出、随机数

1. 概述在程序设计中&#xff0c;为了方便处理数据把具有相同类型的若干变量按有序形式组织起来——称为数组。数组就是在内存中连续的相同类型的变量空间。同一个数组所有的成员都是相同的数据类型&#xff0c;同时所有的成员在内存中的地址是连续的。数组属于构造数据类型&am…