【Java网络编程】网络编程中的基本概念及实现UDP、TCP客户端服务器程序

news2024/10/6 12:30:58

目录

一、什么是网络编程?

二、网络编程中的基本概念

1. 客户端和服务器

2. 请求和响应

三、Socket套接字

UDP数据报套接字编程

1. DatagramSocket

2. DatagramPacket

3. UDP回显客户端服务器程序

4. UDP字典客户端服务器程序

TCP流套接字编程

1. ServerSocket

2. Socket

3. TCP回显客户端服务器程序

4. 服务器引入多线程

5. 服务器引入线程池

6. TCP字典客户端服务器程序 


一、什么是网络编程?

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

即便是同一个主机,只要是不同进程,基于网络来传输数据,也属于网络编程。但是,我们的目的是提供网络上的不同主机,基于网络来传输数据资源。

网络编程,本质上就是学习“传输层”给“应用层”提供的API,通过代码的形式,把数据交给传输层,进一步的通过层层封装,就可以把数据通过网卡发送出去了。

二、网络编程中的基本概念

1. 客户端和服务器

客户端:主动发起通信的一方,称为客户端.

服务器:被动接受的一方,称为服务器,可以提供对外服务.

同一个程序在不同场景中,可能是客户端也可能是服务器(服务器可能还需要主动向别的服务器发起通信,此时的服务器相对于被发起通信的服务器来说,就是客户端).

2. 请求和响应

请求(request):客户端给服务器发送数据。

响应(response):服务器给客户端返回数据。

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

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

就比如在快餐店点一份炒饭:

先要发起请求:点一份炒饭;再有快餐店提供的对于响应:提供一份炒饭。

三、Socket套接字

Socket套接字,是由系统提供的用于网络编程的技术,是基于TCP/IP协议的网络通信的基本操作单元。基于Socket套接字的网络程序开发就是网络编程。

前面说过,要想进行网络编程,需要使用的系统API,本质上是由传输层提供的。

传输层涉及到的主要协议有两个:

  1. 流套接字:TCP(传输控制协议)
  2. 数据报套接字:UDP(用户数据报协议)

TCP的特点:                                                                 

  • 有连接(类似于打电话,需要接通才能通信)             
  • 可靠传输(尽可能完成数据传输,起码可以知道当前这个数据对方是否接收到了)                       
  • 面向字节流(此处字节流和文件中的字节流完全一致,网络中传输数据的基本单位就是字节)
  • 全双工(一个信道,可以双向通信)                            

 UDP的特点:

  • 无连接(类似于发微信/短信,直接发送过去)            
  • 不可靠传输
  • 面向数据报(每次传输的基本单位是一个数据报(由一系列的字节构成的)特定的结构)
  • 全双工(一个信道,可以双向通信)     

UDP数据报套接字编程

UDP socket API的使用

1. DatagramSocket

DatagramSocket 是 UDP Socket(套接字),用于发送和接收UDP数据报

构造方法:

重要方法:

2. DatagramPacket

DatagramPacket 是 UDP Socket(套接字)发送和接收的数据报(每次发送接收数据的基本单位)

构造方法:

3. UDP回显客户端服务器程序

通过这个程序,了解 socket api 的使用,和典型的客户端服务器基本工作流程。

对于服务器,需要指定端口号来创建 socket (类似于饭店,需要指定具体位置),主要流程如下:

  1. 接收客户端请求,并解析
  2. 根据请求,计算出响应(回显服务器,则是直接将请求的数据返回)
  3. 将响应写回给客户端

注意事项详见代码注释: 

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

//Udp回显服务器
public class UdpEchoServer {

    //Udp套接字
    private DatagramSocket socket;

    public UdpEchoServer(int port) throws SocketException {
        socket = new DatagramSocket(port); //服务器:指定端口号创建
    }

    public void start() throws IOException {
        System.out.println("服务器启动");
        while (true) {
            //1.接收客户端的请求,并解析
            DatagramPacket requestServer = new DatagramPacket(new byte[4096], 4096);
            socket.receive(requestServer);

            //2.根据请求,计算出响应
            String request = new String(requestServer.getData(), 0, requestServer.getLength());
            String response = process(request);

            //3.将响应写回给客户端(需要指定发送到的IP地址及端口号)
            DatagramPacket responseServer = new DatagramPacket(
                    response.getBytes(), response.getBytes().length, requestServer.getSocketAddress());
            socket.send(responseServer);

            //打印日志
            System.out.printf("[%s:%d] request:%s response:%s\n",
                    responseServer.getAddress(), responseServer.getPort(), request, response);
        }
    }

    //根据请求计算响应(由于是回显程序,直接返回请求的内容)
    public String process(String request) {
        return request;
    }

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

对于客户端,服务器的端口号可以由系统随机分配,但需要知道服务器的IP地址及端口号(去饭店吃饭,需要知道饭店的地址及具体是哪个门店),主要流程如下:

  1. 客户端读取用户请求
  2. 构造请求的数据报,并发送到服务器(此时就需要指定服务器的IP地址及端口号)
  3. 读取服务器的响应,并解析出响应的内容
  4. 输出服务器的响应
import java.io.IOException;
import java.net.*;
import java.util.Scanner;

//Udp回显客户端
public class UdpEchoClient {

    private DatagramSocket socket;
    private String address;
    private int port;

    //客户端需要知道服务器的IP地址及端口号
    public UdpEchoClient(String address, int port) throws SocketException {
        this.address = address;
        this.port = port;
        socket = new DatagramSocket(); //服务器:随机端口号创建
    }

    public void start() throws IOException {
        System.out.println("客户端启动");
        Scanner in = new Scanner(System.in);
        while (true) {
            System.out.print("-> ");
            if (!in.hasNext()) {
                break;
            }
            //1.控制台读取请求内容
            String request = in.next();

            //2.构造请求的数据报,并发送到服务器(需要指定目的IP地址和目的端口号发送请求)
            DatagramPacket requestPacket = new DatagramPacket(
                    request.getBytes(), request.getBytes().length, InetAddress.getByName(address), port);
            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 udpEchoClient = new UdpEchoClient("127.0.0.1", 9090);
        udpEchoClient.start();
    }
}

先运行服务器,再运行客户端,看程序的执行效果:

  • 可以看到,服务器端能够正确接收到请求并作出响应,并打印出日志(客户端IP,客户端端口号,请求内容,响应内容)
  • 客户端也能够正确发送请求,并正确解析服务器端返回的响应

这个程序并不能直接做到“跨主机通信”,因为这台主机可能不能直接访问到另一台主机(NAT机制)。但是可以通过以下手段实现“跨主机通信”:

  1. 将服务器端程序打成 jar 包
  2. 把 jar 包传到云服务器上,并运行

经过这样的操作,其他主机通过运行上述的客户端程序,就能够发起通信了。

4. UDP字典客户端服务器程序

基于上述回显服务器,还可以实现出一些其他带有一点业务逻辑的服务器。

改进成一个“字典服务器”,英译汉的效果。请求是一个英文单词,响应返回对应的中文翻译。

主要逻辑其实和回显服务器基本一致,唯一的区别就在于,服务器端将客户端请求的数据,计算成响应的方式不一致。回显服务器是直接返回客户端请求的数据,这里的字典服务器则是英译汉效果。

而上述代码中,这个根据请求数据计算响应数据的操作,是通过process方法实现的。因此只需要让这个字典服务器继承回显服务器,并重写process方法即可。这里英译汉的业务逻辑通过打表的方式实现。

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

//Udp字典服务器
public class UdpDictServer extends UdpEchoServer {

    Map<String, String> map;

    public UdpDictServer(int port) throws SocketException {
        super(port);
        map = new HashMap<>();

        map.put("cat", "小猫");
        map.put("dog", "小狗");
        map.put("animal", "动物");
    }

    //通过重写 计算响应的process方法,达成 英->汉 的效果
    @Override
    public String process(String request) {
        return map.getOrDefault(request, "找不到该单词");
    }

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

还是先运行字典服务器,再运行回显客户端(这里客户端是通用的,因为回显客户端只进行发送请求和接收响应并解析的操作),看程序的执行效果:

  • 同样,服务器端能够正确接收到请求、解析请求,并计算出响应、写回给客户端,并打印出日志(客户端IP,客户端端口号,请求内容,响应内容)
  • 客户端也能够正确发送请求,并正确解析服务器端返回的响应

TCP流套接字编程

1. ServerSocket

ServerSocket 类是创建TCP服务器端Socket的API. (只能给服务器端使用)

构造方法:

重要方法:

2. Socket

Socket 类用于创建客户端 Socket,或服务器端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket. (服务器端和客户端都能使用)

构造方法:

重要方法:

3. TCP回显客户端服务器程序

使用TCP协议实现回显客户端服务器程序。与UDP协议实现的最大区别是,TCP是有连接的,和打电话一样,需要一方(客户端)拨号,一方(服务器)接通,因此TCP协议首要操作就是等待客户端连接。

和UDP回显服务器一样,对于这里的服务器,同样需要指定端口号创建TCP服务器端Socket,即ServerSocket。

  1. 服务器启动后,就需要监听当前绑定端口(accept方法),等待客户端连接。
  2. 当成功建立连接后,会返回一个Socket对象。这个对象保存了对端信息,即客户端信息,可以用来接收和发送数据(TCP是面向字节流的,通过这个Socket对象获取对应输入流和输出流,通过输入输出流进行对 socket 的读写,达成接收和发送数据的功能)。

后续流程和UCP回显服务器一致。此处由于每有一个客户端连接,就会有一个clientSocket,这里消耗的Socket会越来越多,因此每当一个客户端连接结束,就需要释放这个clientSocket。

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;

//TCP回显服务器
public class TcpEchoServer {

    private ServerSocket serverSocket;

    public TcpEchoServer(int port) throws IOException {
        //指定服务器端口号,创建一个serverSocket
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器启动!");
        while (true) {
            //监听当前绑定的端口,等待客户端连接 连接后,返回一个socket,里面保存客户端(对端)信息
            Socket clientSocket = serverSocket.accept();
            processConnection(clientSocket);
        }
    }

    private void processConnection(Socket clientSocket) throws IOException {
        System.out.printf("[%s:%d] 客户端上线\n", clientSocket.getInetAddress(), clientSocket.getPort());
        try (InputStream inputStream = clientSocket.getInputStream();
             OutputStream outputStream = clientSocket.getOutputStream()) {
            while (true) {
                //1.读取客户端请求的数据
                //利用scanner读取客户端输入的信息
                Scanner scanner = new Scanner(inputStream);
                if (!scanner.hasNext()) {
                    System.out.printf("[%s:%d] 客户端下线\n",
                            clientSocket.getInetAddress(), clientSocket.getPort());
                    break;
                }
                //这里的next()需要遇到\n才停止,因此需要对端写入的时候,要同时写入\n换行符
                String request = scanner.next();

                //2.解析请求的数据,并计算出响应
                String response = process(request);

                //3.将响应写回到客户端
                //outputStream.write(response.getBytes(), 0, response.getBytes().length);
                PrintWriter writer = new PrintWriter(outputStream);
                writer.println(response);
                writer.flush();

                //打印日志
                System.out.printf("[%s:%d] request:%s response:%s\n",
                        clientSocket.getInetAddress(), clientSocket.getPort(), request, response);
            }

        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            clientSocket.close();
        }
    }

    //回显服务器,直接返回原数据
    public String process(String request) {
        return request;
    }

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

对于客户端,需要指定服务器的IP和端口号建立连接,即使用 Socket(String host, int port) 创建Socket的时候,就开始发起与对应服务器建立连接的请求了。

主要流程和UDP回显客户端程序的流程也基本一致,只需要注意请求和响应数据的方式是不同的,是通过操作输入输出流完成的即可。

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Scanner;

//TCP回显客户端
public class TcpEchoClient {

    private Socket clientSocket;

    //需要指定服务器的IP和端口号
    public TcpEchoClient(String serverAddress, int serverPort) throws IOException {
        //与对应客户端建立连接
        clientSocket = new Socket(InetAddress.getByName(serverAddress), serverPort);
    }

    public void start() {
        System.out.println("客户端启动!");
        try (Scanner scannerConsole = new Scanner(System.in);
             InputStream inputStream = clientSocket.getInputStream();
             OutputStream outputStream = clientSocket.getOutputStream()) {
            while (true) {
                //1.用户从控制台输入数据
                System.out.print("-> ");
                String request = scannerConsole.next();

                //2.将该数据作为请求,发送给服务器
                //outputStream.write(request.getBytes(), 0, request.getBytes().length);
                //outputStream.write('\n');
                PrintWriter writer = new PrintWriter(outputStream);
                writer.println(request);
                writer.flush(); //刷新缓冲区,确保数据发送出去

                //3.读取服务器的响应,并解析响应的内容
                Scanner scannerNetwork = new Scanner(inputStream);
                String response = scannerNetwork.next();

                //4.将响应输出到控制台
                System.out.println(response);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) throws IOException {
        TcpEchoClient tcpEchoClient = new TcpEchoClient("127.0.0.1", 9090);
        tcpEchoClient.start();
    }
}

先运行服务器,再运行客户端,看执行效果:

可以看到,服务器和客户端都能满足我们的需求,但这里其实还存在一个问题。

当我们开启多个客户端想要进行连接通信时,只有第一个连接到的客户端才能正确通信,其他的客户端是没有反应的。要想某个客户端能正常通信,只有当其他客户端都下线(结束程序),这个客户端才能接收到响应数据。

可以看到,此处的这个客户端并没有正确通信,当另一个客户端下线之后,该客户端此前发送的数据又正常请求并响应了。

分析过程:

  • 第一个客户端连上服务器之后,服务器就会从accept这里返回(解除阻塞),然后进入到processConnection方法中.
  • 接下来服务器就会在processConnection循环处理客户端的请求,只有当客户端退出之后,连接结束,才会退出循环.
  • 而服务器在循环处理客户端请求的时候,第二个客户端发起连接请求,而服务器这里并不能执行到accept。因此并不能成功连接,只有当客户端退出,才会执行回到accept进行连接.

第二个客户端之前发的请求为什么能被立即处理?

  • 当前TCP在内核中,每个 socket 都是由缓冲区的。客户端发送的数据通过客户端代码,已经写入到该客户端的输出流(缓冲区)了,这里数据确实发送了,只不过数据在服务器的接收缓冲区中。
  • 一旦第一个客户端退出,回到第一层循环,执行accept连接操作,后续processConnection方法里的 next 就能把之前缓冲区的内容给读出来。

解决上述问题的核心思路就是使用多线程:

  • 单个线程,无法既能给客户端提供服务,又能去快速执行到第二次 accept 方法进行连接。
  • 通过引入多线程,让主线程只负责执行 accept。每次有一个客户端连接上来,就分配一个新的线程,由新的线程负责给客户端提供服务。

由于这里不涉及多个线程修改共享变量,因此没有线程安全问题,我们只需要改动 start 方法即可。

4. 服务器引入多线程

    //多线程
    public void start() throws IOException {
        System.out.println("服务器启动!");
        while (true) {
            //监听当前绑定的端口,等待客户端连接 连接后,返回一个socket,里面保存客户端(对端)信息
            Socket clientSocket = serverSocket.accept();

            Thread t = new Thread(() -> {
                try {
                    processConnection(clientSocket);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            });
            t.start();
        }
    }

通过引入多线程,这里的服务器就能支持多个客户端同时与其通信了。

上述问题,不是TCP引起的,而是代码两次循环嵌套引起的,UDP服务器,就是只有一层循环,因此不会有这个问题。

而这个多线程版本同样还有一些问题: 

  • 每有一个客户端连接,就会创建一个新的线程,每当这个客户端结束,就要销毁这个线程。
  • 如果客户端比较多,并且频繁连接、关闭,就会使服务器频繁创建和销毁线程。

前面讲过,线程池解决的就是线程频繁创建和销毁的问题,因此,这里的优化方案就是使用线程池。

5. 服务器引入线程池

    public void start() throws IOException {
        System.out.println("服务器启动!");
        ExecutorService threadPool = Executors.newCachedThreadPool();
        while (true) {
            //监听当前绑定的端口,等待客户端连接 连接后,返回一个socket,里面保存客户端(对端)信息
            Socket clientSocket = serverSocket.accept();

            threadPool.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        processConnection(clientSocket);
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
        }
    }

线程开销问题解决了。但是,如果当前的场景使线程频繁创建,但是不销毁呢?

  • 这种情况下,如果继续使用多线程/线程池,就会导致当前服务器积累大量的线程,此时,对于服务器的负担是非常重的。

要解决这个新问题,还可以引入其他的方案:

  1. 协程:轻量级线程,本质上还是一个线程,用户态可以通过手动调度的方式让这一个线程“并发”执行多个任务。
  2. IO 多路复用:系统内核级别的机制,本质上是让一个线程同时去负责处理多个socket。

6. TCP字典客户端服务器程序 

同UDP字典客户端服务器程序:

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

//TCP字典服务器
public class TcpDictServer extends TcpEchoServer {

    Map<String, String> map;

    public TcpDictServer(int port) throws IOException {
        super(port);

        map = new HashMap<>();
        map.put("cat", "小猫");
        map.put("dog", "小狗");
        map.put("animal", "动物");
    }

    @Override
    public String process(String request) {
        return map.getOrDefault(request, "未找到该单词");
    }

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

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

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

相关文章

【JS】js数字转k、w结尾 | 1000 = 1k

问题 数字转k、w结尾 如&#xff1a;10001k 100001w 码 /*** 数字转k,w* param {Number} num * returns String*/ const numberTokw num > {if (num < 1000) return numlet endStr w,numVal 10000;if (num > 999 && num < 10000) {endStr knumVal …

设计模式之创建型模式详解

设计模式 创建型模式 创建型模式(Creational Pattern)对类的实例化过程进行了抽象&#xff0c;能够将软件模块中对象的创建和对象的使用分离。为了使软件的结构更加清晰&#xff0c;外界对于这些对象只需要知道它们共同的接口&#xff0c;而不清楚其具体的实现细节&#xff0…

物联网实战--驱动篇之(九)NB-IOT(BC260)

目录 一、NB-IOT简介 二、NB-IOT要素 三、代码详解 四、平台端 一、NB-IOT简介 实际上&#xff0c;就是NB-Iot彻底引爆了物联网的&#xff0c;大概2018年左右&#xff0c;NB推广如火如荼&#xff0c;同时广域网、低功耗的LPWAN网络也逐渐传开&#xff0c;现在回头来看&…

Flyweight 享元

意图 运用共享技术有效地支持大量细粒度的对象。 结构 其中 Flyweight描述一个接口&#xff0c;通过这个接口Flyweight可以接受并作用于外部状态。ConcreteFlyweight实现Flyweight接口&#xff0c;并作为内部状态&#xff08;如果有&#xff09;增加存储空间。ConcreteFlywe…

[Java基础揉碎]集合

目录 集合的理解和好处 数组 集合的理解和好处 继承图 ​编辑 简单实例 Collection接口和常用方法 1) add:添加单个元素 2) remove:删除指定元素 3) contains:查找元素是否存在 4) size:获取元素个数 5) isEmpty:判断是否为空 ​编辑 6) clear:清空 7) addAll:添…

Prior Knowledge-Guided Transformer for Remote Sensing Image Captioning

Prior Knowledge-Guided Transformer for Remote Sensing Image Captioning 1. 摘要 遥感图像(RSI)字幕生成旨在为遥感图像生成有意义且语法正确的句子描述。然而,相比于自然图像字幕,RSI字幕生成面临着由于RSI特性而产生的额外挑战。第一个挑战源于这些图像中存在大量物体。…

ssh-key关于authorized_keys电脑与linux互相认证

思路&#xff1a; 在A上生成公钥私钥。将公钥拷贝给server B&#xff0c;要重命名成authorized_keys(从英文名就知道含义了)Server A向Server B发送一个连接请求。Server B得到Server A的信息后&#xff0c;在authorized_key中查找&#xff0c;如果有相应的用户名和IP&#xf…

冰达ROS机器人快速使用指南

欢迎来到《冰达ROS机器人极简使用指南》 Q&#xff1a;这份教程适合谁&#xff1f; A&#xff1a;适合完全0基础新手&#xff0c;需要快速跑起来机器人的基本功能。也适合技术大佬需要快速的了解冰达ROS机器人的使用方法。 Q&#xff1a;这份教程内容很少&#xff0c;是不是…

路由器热备份

HSRP HSRP&#xff08;Hot Standby Routing Protocol&#xff09;热备份路由选择协议 HSRP是思科私有的协议&#xff0c;HSRP起到一个双网关热备份的一个目的&#xff0c;不考虑线路问题针对设备而言&#xff0c;一个设备挂了还有另外一台设备&#xff0c;所以双网关也叫双机…

动力学重构/微分方程参数拟合 - 基于模型

这一篇文章&#xff0c;主要是给非线性动力学&#xff0c;对微分方程模型参数拟合感兴趣的朋友写的。笼统的来说&#xff0c;这与混沌系统的预测有关&#xff1b;传统的机器学习的模式识别虽然也会谈论预测结果&#xff0c;但他们一般不会涉及连续的预测。这里我们考虑的是&…

URL地址解析至页面展示全过程(面试详细解答)

目录 1、解析URL 2、缓存判断 ​编辑3、DNS解析 ​编辑4、获取MAC地址 5、TCP三次握手 6、HTTP请求 7、服务器处理请求&#xff0c;返回HTTP响应 8、页面渲染 9、TCP四次挥手 10、浏览器解析HTML 11、浏览器布局渲染 1、解析URL 首先会对 URL 进行解析&#xff0c;…

C++_类型转换

文章目录 学习目标&#xff1a;1.static_cast2. reinterpret_cast3.const_cast4. dynamic_cast 学习过程1.static_cast2. reinterpret_cast3.const_cast在这里插入图片描述4. dynamic_cast 学习目标&#xff1a; 标准C为了加强类型转换的可视性&#xff0c;引入了四种命名的强…

黑马程序员Linux简单入门学习笔记

Linux介绍 内核提供系统最核心的功能&#xff0c;如: 调度CPU、调度内存、调度文件系统、调度网络通讯、调度等系统级应用程序&#xff0c;可以理解为出厂自带程序&#xff0c;可供用户快速上手操作系统&#xff0c;如:文件管理器、任务管理器、图片查看、音乐播放等 目录结构 …

在PostgreSQL中如何实现递归查询,例如使用WITH RECURSIVE构建层次结构数据?

文章目录 解决方案使用WITH RECURSIVE进行递归查询示例代码 总结 在PostgreSQL中&#xff0c;递归查询是一种非常强大的工具&#xff0c;它可以用来查询具有层次结构或树形结构的数据。例如&#xff0c;你可能会在员工-经理关系、目录结构或组织结构图中遇到这样的数据。为了处…

*Linux系统的进程和计划任务管理

目录 一、查看进程 1、程序和进程的关系 *2、ps查看静态进程信息 1&#xff09;ps aux 2&#xff09;ps -elf *3、top查看动态进程信息 4、pgrep查看进程信息 5、pstree查看进程树 二、控制进程 1、进程启动方式 2、进程的前后台调度 3、终止进程的运行 三、计划任…

VSCode搭建内核源码阅读开发环境

0. 参考链接 使用VSCode进行linux内核代码阅读和开发_vscode阅读linux内核-CSDN博客 1. 搭建Linux内核源码阅读环境 现状&#xff0c;Linux内核源码比较庞大文件非常多&#xff0c;其中又包含的众多的宏定义开关配置选项&#xff0c;这使得阅读内核源代码称为一件头疼的事。 …

Android AIDL接口

一.AlDI接口简介 AIDL&#xff08;Android Interface Definition Language&#xff09;是一种 IDL 语言&#xff0c;用于生成可以在 Android 设备上两个进程之间进行进程间通信&#xff08;IPC&#xff09;的代码。 通过 AIDL&#xff0c;可以在一个进程中获取另一个进程的数据…

Go源码--Strings库

1. 简介 strings库 存储了 一些针对 字符串的具体操作 其 代码短小精悍 可以学习到很多编程的思路 尤其是 涉及到字符串使用性能的方面&#xff0c;其源码库有好多的优秀案例可以学习。向强者对齐不一定成为强者&#xff0c;但向弱者对齐一定变为弱者。 介绍思路是先介绍 stri…

.net6项目模板

1.集成log4net 安装依赖包&#xff1a; 安装扩展依赖即可&#xff0c;已经包含了log4net依赖&#xff1a; Microsoft.Extensions.Logging.Log4Net.AspNetCore 添加日志配置文件&#xff1a; 日志配置文件属性设置为始终复制&#xff1a; 注入服务&#xff1a; #region 注入…

React【Day4】

路由快速上手 1. 什么是前端路由 一个路径 path 对应一个组件 component 当我们在浏览器中访问一个 path 的时候&#xff0c;path 对应的组件会在页面中进行渲染 2. 创建路由开发环境 # 使用CRA创建项目 npm create-react-app react-router-pro# 安装最新的ReactRouter包 …