网络编程中的重难点:套接字的应用和理解

news2024/7/6 17:51:10

什么是网络编程

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

发送端和接收端

在一次网络数据传输时:

发送端:数据的发送方进程,称为发送端。发送端主机即网络通信中的源主机。

接收端:数据的接收方进程,称为接收端。接收端主机即网络通信中的目的主机。

请求和响应

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

第一次:请求数据的发送

第二次:相应数据的发送

客户端和服务端

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

客户端获取服务的一方进程,称为客户端。

常见的客户端服务器模型:

1. 客户端先发送请求到服务端

2. 服务端根据请求数据,执行相应的业务处理

3. 服务端返回响应:发送业务处理结果

4. 客户端根据响应数据,展示处理结果(展示获取的资源,或提示保存资源的处理结果)

Socket套接字

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

并且我们认为Socket API是和传输层密切相关的。

Socket套接字主要针对传输层协议分为以下几类:

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

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

UDP无连接不可靠传输面向数据报全双工
TCP有连接可靠传输面向字节流全双工

无连接、有连接:

打电话就是有连接的,需要连接建立了才能通信。连接建立需要对方来接收,如果连接没有建立好,就通信不了。

发短信、发微信就是无连接的。

不可靠传输、可靠传输:

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

面向字节流、面向数据报:

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

数据传输以一个个的“数据报”(可能是若干字节,带有一定格式的)为基本单位,就叫面向数据报。

全双工、半双工:

一个通信通道,可以双向传输,既可以发送也可以接收就叫做全双工。

只能单向传输的就叫做半双工。

UDP

Java中使用UDP协议通信,主要基于DatagramSocket类来创建数据报套接字,并使用DatagramPacket作为发送或接收的UDP数据报,对于一次发送及接收UDP数据报的流程如下:

 以上只是一次发送端的UDP数据报发送,及接收端的数据报接收,并没有返回的数据。也就只有请求,没有响应。对于一个服务器来说,重要的是提供多个客户端的请求处理及响应,流程如下:

TCP

Socket编程

首先先了解一些注意事项:

1.客户端和服务器:开发时,一般是基于一个主机开启两个进程作为客户端和服务器,但真实的场景一般都是不同主机。

2.注意目的IP和目的端口号,标识了一次数据传输时要发送数据的终点主机和进程。

3.Socket编程我们是使用流套接字和数据报套接字,基于传输层的TCP或UDP协议,但应用层协议, 也需要考虑,这块我们在后续来说明如何设计应用层协议。

4.关于端口被占用的问题:

如果一个进程A已经绑定了一个端口,再启动一个进程B绑定该端口,就会报错,这就叫端口被占用。对于Java进程来说,端口被占用的常见报错信息如下:

在cmd输入:netstat -ano | findstr 端口号 就可以显示对应进程的pid,然后在任务管理器中通过pid查找进程。

解决方法:

如果占用端口的进程A不需要运行,就可以关闭A后,再启动需要绑定该端口的进程B。

如果需要运行A进程,则可以修改进程B的绑定端口,换为其他没有使用的端口。

UDP数据报套接字编程

DatagramSocket API

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

在操作系统中,把这个socket对象也当成是一个文件来处理的,相当于是文件描述符表上的一项。只不过普通文件对应的设备是硬盘,而socket文件对应的设备是网卡。

DatagramSocket构造方法:

 DatagramSocket 方法:

DatagramPacket API

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

DatagramPacket 构造方法:

 DatagramPacket 方法:

UDP客户端服务器程序

服务器代码

普通的服务器:收到请求,根据请求计算响应,返回响应。

而echo server(回显服务器)省略了其中的根据请求计算响应,请求是啥,就返回啥。

先来看一遍完整代码:

public class UdpServer {
    private DatagramSocket socket = null;
    public UdpServer(int port) throws SocketException {
        socket = new DatagramSocket(port);
    }

    public void start() throws IOException {
        while(true) {
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
            socket.receive(requestPacket);
            String request = new String(requestPacket.getData(), 0, requestPacket.getLength());

            String response = process(request);
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length, requestPacket.getSocketAddress());
            socket.send(responsePacket);
        }
    }

    public String process(String request){
        return request;
    }

    public static void main(String[] args) throws IOException {
        UdpServer udpServer = new UdpServer(1000);
        udpServer.start();
    }
}

我们一点一点来解析:

    private DatagramSocket socket = null;
    public UdpServer(int port) throws SocketException {
        socket = new DatagramSocket(port);
    }

在操作系统内核中, 使用了一种特殊的叫做 "socket" 这样的文件来抽象表示网卡,因此进行网络通信, 势必需要先有一个 socket 对象。

同时对于服务器来说, 创建 socket 对象的同时, 要让他绑定上一个具体的端口号,如果是操作系统随机分配的端口, 此时客户端就不知道这个端口是啥了, 也就无法进行通信了。

    public void start() throws IOException {
        while(true) {
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
            socket.receive(requestPacket);
    }

对于UDP来说,传输数据的基本单位是DatagramPacket,并且用一个while循环来表示循环接收请求,用DatagramPacket来表示接收到的,然后再用receive把这个数据报给网卡接收到。

String request = new String(requestPacket.getData(), 0, requestPacket.getLength());

 此时的DatagramPacket是一个特殊的对象,并不方便直接进行处理,可以把这里包含的数据拿出来,通过构造字符串的方式来存到request里面去。

之前给的最大长度是4096,但是这里的空间不一定用满了,可能只用了一小部分,因此就通过getLength获取到实际的数据报长度,只把这个实际的有效部分给构造成字符串即可。

    String response = process(request);


    public String process(String request){
        return request;
    }

紧接着我们用一个process方法来表示服务器的响应。实际开发中这个部分是最重要的,服务器的响应是整个网络编程最核心的部分之一。

DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),
 response.getBytes().length, requestPacket.getSocketAddress());

获取到客户端的ip和端口号(这两个信息本身就在requestpacket中)

socket.send(responsePacket);

通过send方法把responsePacket方法里面的信息传出去。

主要的工作流程:

1.读取请求并解析

2.根据请求计算相应

3.构造响应并且写回客户端

客户端代码

一次通信,需要有两个ip,两个端口,客户端的ip是127.0.0.1,客户端的端口是系统自动分配的,服务器ip和端口需要告诉客户端,才能顺利把消息发给服务器。

先来看完整代码:

public class UdpEchoClient {
    private DatagramSocket socket = null;
    private String serverIP = null;
    private int serverPort = 0;

    public UdpEchoClient(String serverIP, int serverPort) throws SocketException {
        socket = new DatagramSocket();
        this.serverIP = serverIP;
        this.serverPort = serverPort;
    }

    public void start() throws IOException {
        System.out.println("客户端启动");
        Scanner scanner = new Scanner(System.in);

        while(true){
            System.out.println(">");
            String request = scanner.next();
            if(request.equals("exit")){
                System.out.println("退出");
                break;
            }
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,InetAddress.getByName(serverIP),serverPort);
            socket.send(requestPacket);

            DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(responsePacket);
            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);
        Client.start();
    }
}
    private DatagramSocket socket = null;
    private String serverIP = null;
    private int serverPort = 0;

    public UdpEchoClient(String serverIP, int serverPort) throws SocketException {
        socket = new DatagramSocket();
        this.serverIP = serverIP;
        this.serverPort = serverPort;
    }

通过socket,IP和端口我们才能和服务器端连接起来。

   public void start() throws IOException {
        System.out.println("客户端启动");
        Scanner scanner = new Scanner(System.in);

        while(true){
            System.out.println(">");
            String request = scanner.next();
            if(request.equals("exit")){
                System.out.println("退出");
                break;
            }
            DatagramPacket requestPacket = new DatagramPacket(
            request.getBytes(),
            request.getBytes().length,
            InetAddress.getByName(serverIP),
            serverPort);

            socket.send(requestPacket);

在客户端中,需要用户自己输入,获取到用户的request后,需要打包成requestPacket然后通过socket.send发送给服务器。

DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
socket.receive(responsePacket);
String response = new String(responsePacket.getData(),0,responsePacket.getLength());

System.out.println(response);

完成上一步后,等待服务器的响应,到客户端这边用receive接收,类型为responsePacket。最后再转换成String类型的response打印出来。

客户端发送给服务器后,就进入阻塞等待,这里的receive能阻塞,是因为操作系统原生提供的API就是阻塞的函数,这里的阻塞不是Java实现的,而是系统内核里实现的。

同时最后的main函数中,应该指定好ip和端口号,以便客户端能访问到服务器端。

 

 同时也可以打开这个选项,同时开启多个客户端,共用一个服务器。

端口占用

针对上述的程序,来看看端口冲突是什么效果,一个端口只能被一个进程使用,如果有多个就不行。

  

TCP流套接字编程

TCP和UDP的差别还是有不少的,比如一个有连接一个无连接,一个是可以直接发送,一个需要数据报打包发送。

ServerSocket API

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

构造方法:

方法:

Socket API

Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket。 不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的。

构造方法:

 方法:

TCP客户端服务器程序

服务器代码
public class TcpEchoServer {
    private ServerSocket serverSocket = null;

    private TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("启动服务器");
        while(true){
            Socket clientSocket = serverSocket.accept();
            processConnection(clientSocket);
        }
    }

    public void processConnection(Socket clientSocket) throws IOException {
        System.out.printf("[%s:%d] 客户端上线 \n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
        try(InputStream inputStream = clientSocket.getInputStream();
            OutputStream outputStream = clientSocket.getOutputStream()){
            while(true){
                Scanner scanner = new Scanner(inputStream);
                if(!scanner.hasNext()){
                    System.out.printf("[%s:%d]客户端下线!", clientSocket.getInetAddress().toString(), clientSocket.getPort());
                    break;
                }
                String request = scanner.next();
                String response = process(request);
                PrintWriter printWriter = new PrintWriter(outputStream);
                printWriter.println((response));
                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{
            try{
                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();
    }
}
    private ServerSocket serverSocket = null;

    private TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("启动服务器");
        while(true){
            Socket clientSocket = serverSocket.accept();
            processConnection(clientSocket);
        }
    }

在这里有serverSocket和clientSocket,这两个socket是不同的,serverSocket接收端口号和Ip地址,然后通过clientSocket和客户端连接。因为需要连接上后才能发送消息,所以每用到一个clientSocket就会有一个客户端连接上来,都会返回/创建一个Socket对象,Socket就是文件,每次创建一个clientSocket对象,就要占用一个文件描述符表的位置。

因此这里的socket需要释放。前面的socket都没有释放,一方面这些socket生面周期更长,另一方面这些socket也不多。但是此处的clientSocket数量多,每个客户端都有一个,生命周期也更短。

accept如果没有连接到客户端,就会一直阻塞。

要注意,TCP server一次性只能处理一个客户端

    public void processConnection(Socket clientSocket) throws IOException {
        System.out.printf("[%s:%d] 客户端上线 \n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
        try(InputStream inputStream = clientSocket.getInputStream();
            OutputStream outputStream = clientSocket.getOutputStream()){

通过clientSocket进行processConnection进行了具体的连接以后,通过try with resources来完成InputStream和outputStream来完成字节流的传输。

            while(true){
                Scanner scanner = new Scanner(inputStream);
                if(!scanner.hasNext()){
                    System.out.printf("[%s:%d]客户端下线!", clientSocket.getInetAddress().toString(), clientSocket.getPort());
                    break;
                }
                String request = scanner.next();
                String response = process(request);
                PrintWriter printWriter = new PrintWriter(outputStream);
                printWriter.println((response));
                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{
            try{
                clientSocket.close();
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }

通过InputStream接收到服务器端的数据后,再通过scanner写入到request,request传入到process方法返回服务器相应的数据。接下来应该用outputStream来写入服务器返回的数据,但是outputStream中并没有write String这样的功能,所以此处用println来写入。

并且println中会在发送的数据后面自动带上\n换行,TCP协议是面向字节流的协议,但是接收方如何知道这一次一共需要读多少字节呢?这就需要我们再数据传输中进行明确的规定:

此处代码中,隐式约定使用了\n来作为当前代码的请求、相应分割约定。

所以这里的println也可以当做是服务器发送给客户端的发送行为。

客户端代码
public class TcpEchoClient {
    private Socket socket = null;

    public TcpEchoClient(String serverIp,int serverPort) throws IOException {
        socket = new Socket(serverIp,serverPort);
    }

    public void start(){
        System.out.println("客户端启动");
        Scanner scanner = new Scanner(System.in);
        try(InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream()){
            while(true){
                System.out.println(">");
                String request = scanner.next();
                if(request.equals("exit")){
                    System.out.println("bye");
                    break;
                }
                PrintWriter printWriter = new PrintWriter(outputStream);
                printWriter.println(request);
                printWriter.flush();

                Scanner responseScanner = new Scanner(inputStream);
                String response = responseScanner.next();

                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();
    }
}
public class TcpEchoClient {
    private Socket socket = null;

    public TcpEchoClient(String serverIp,int serverPort) throws IOException {
        socket = new Socket(serverIp,serverPort);
    }

通过socket来接收服务器的ip和端口号。

    public void start(){
        System.out.println("客户端启动");
        Scanner scanner = new Scanner(System.in);
        try(InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream()){

和服务器不同的是,客户端方需要读取用户自己输入的数据,所以通过System.in来接收用户输入的,但是最终是需要用到流式传输中,所以需要用try with resources来包含InputStream和outputStream。

        try(InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream()){
            while(true){
                System.out.println(">");
                String request = scanner.next();
                if(request.equals("exit")){
                    System.out.println("bye");
                    break;
                }
                PrintWriter printWriter = new PrintWriter(outputStream);
                printWriter.println(request);
                printWriter.flush();

                Scanner responseScanner = new Scanner(inputStream);
                String response = responseScanner.next();

                System.out.println(response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

通过request接收到用户输入的数据后,用PrintWriter来写入,再通过println来发送。并且以防万一,我们用flush来刷新缓冲区避免数据传输失败。

等待服务器返回消息后,用responseScanner来接收InputStream传输的数据,再打印出来。

 

 多线程、线程池连接

当前咱们的服务器同一时刻只能给一个客户端提供服务,这是不科学的。当前启动服务器后,先后启动两个客户端,客户端1可以看到正常的上线提示,但是客户端2没有任何提醒。当结束客户端1后,客户端2马上显示上线。

当客户端连接上服务器之后,代码执行到processConnection这个方法中的while循环了,此时意味着,只要这个循环不结束,processConnection方法就结束不了。进一步的也就无法调用到第二次的accept。

解决办法就是:使用多线程

    public void start() throws IOException {
        System.out.println("启动服务器");
        while(true){
            Socket clientSocket = serverSocket.accept();
            Thread t = new Thread(() ->{
                processConnection(clientSocket);
            });
            t.start();
        }
    }

其实修改的部分很小,只要在启动连接的时候,作为一个单独的线程启动就大功告成。

但是呢,这里的多线程版本的程序,最大的问题就是可能会涉及到频繁申请释放线程,当客户端数量足够多,也会造成很大的资源消耗。

所以解决办法就是:使用线程池

    public void start() throws IOException {
        System.out.println("启动服务器");
        ExecutorService threadPool = Executors.newCachedThreadPool();
        while(true) {
            Socket clientSocket = serverSocket.accept();
            threadPool.submit(()->{
                processConnection(clientSocket);
            });
        }
    }

通过线程池的方法,就能进一步减少消耗。

但是呢,如果客户端都在响应,就算使用了线程池了但是还是不够,而且如果客户端非常多,客户端连接迟迟不断开,就会导致机器上有很多线程。

解决办法就是:IO多路复用,IO多路转接

给这个线程安排一个集合,这个集合就放了一堆连接。这个线程就来负责监听这个集合,哪个连接有数据来了,线程就处理哪个连接。这其实就是因为,虽然连接有很多很多,但是这些连接的请求并非完全严格的同时,总还是有先后的。

TCP中的长短连接

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

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

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

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

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

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

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

总结

本文主要介绍了UDP和TCP的相关知识和一些差别。

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

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

这其中很多特点都是可以从代码中直接看到的,但还有比如可靠传输,这个东西隐藏在TCP背后,从代码的角度是感知不到的。TCP诞生的意义,就是为了解决可靠传输的问题~

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

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

相关文章

GD32F103 ADC

1. 模拟量于数字量。 模拟量:反应真实世界中的物理量(比如温度,压力,长度)模拟量通常是通过电压,电流等信号来表示。 数字量:通常是0和1来表示某个物理量的变化。 2. ADC(模拟量转…

通过HTTP发送大量数据的三种方法

在网络的早期时期,人们发送的文件大小仅为几KB。到了2023年,我们享受着高分辨率的MB级别图像,并在几GB的4K(即将是8K)视频中观看。 即使有良好的互联网连接,下载一个5GB的文件仍然需要一些时间。如果你拥有…

Photoshop 2024正式发布!内置最新PS AI,创意填充等功能无限制使用!

PS正式版目前更新到了2024,版本为25.0。 安装教程 1、下载得到安装包后,先解压。鼠标右键,【解压到当前文件夹】 2、双击 Set-up 开始安装 3、这里可以更改安装位置。如果C盘空间不够大,可以把它安装到C盘以外。更改好后&#x…

SpringBoot面试题3:Spring Boot 的核心注解是哪个?它主要由哪几个注解组成的?

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:Spring Boot 的核心注解是哪个?它主要由哪几个注解组成的? Spring Boot 的核心注解是 @SpringBootApplication。 @SpringBootApplication 是一…

【idea】 java: 找不到符号

idea 启动时提示 java: 找不到符号 java: 找不到符号 符号: 方法 getCompanyDisputeCount() 位置: 类型为com.yang.entity.AreaAnalyse的变量 areaAnalyse 在setting ——> Compiler ——>Shared build process VM options: 添加: -Djps.track.ap.dep…

Vue3<script setup>语法糖下,实现父子组件通信以及数据监听的三种方法。

在Vue3的script setup语法糖中,没有办法通过Vue2的ref、props、parent、中央时间总线等等众多方法,通过this指针简单的实现父子组件的通信,网络上也很少有关于script setup语法糖的相关教程,所以决定自己写一个详细教程&#xff0…

【算法|动态规划No.19】leetcode413. 等差数列划分

个人主页:兜里有颗棉花糖 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 兜里有颗棉花糖 原创 收录于专栏【手撕算法系列专栏】【LeetCode】 🍔本专栏旨在提高自己算法能力的同时,记录一下自己的学习过程,希望…

JAVA反序列化漏洞

JAVA反序列化漏洞 原文资料:xiu–》xiu博客 文章目录 JAVA反序列化漏洞idea类继承反序列化漏洞person类Test类 什么是反序列化漏洞 idea 类继承 public class Person {public int age;public String name;public void talk(){System.out.println("Person 说话…

RK3588 USB WIFI调试

一.安卓wifi框架 要使用一个wifi功能需要涉及的部分有内核部分wifi驱动,应用部分wpa_supplicant服务。其中wifi驱动又包含很多部分,分为通讯接口的驱动SDIO、USB、PCIE等,还有上下电部分的驱动,wifi模组提供部分的驱动。应用部分不…

random生成随机数的灵活运用

random返回的 [0,1) 之间的一个随即小数 思考:请写出获取 a-b 之间的一个随机整数,a,b均为整数,比如 a2 , b7 即返回一个数 x > [2,7]Math.random()*(b-a) 返回的就是 [0,b-a](int)(aMath.random()*(b-a1)) 》 (int)(2Math.random()*6) Ma…

常用傅里叶变换表

傅里叶展开 傅里叶变换 傅里叶逆变换 时域信号 弧频域信号 线性变换 时域平移 频域平移 伸缩变换 微分性质 逆变换的微分性质 卷积定理 原函数变换结果 单位阶跃函数: 符号函数: 矩形函数: 辛格函数:

系统架构师备考倒计时22天(每日知识点)

测试阶段划分 单元测试:依据详细设计,模块测试,模块功能、性能、接口等集成测试:依据概要设计,模块间的接口系统测试:依据需求文档,在真实环境下,验证完整的软件配置项能否和系统正…

了解变分自动编码器 (VAE)

一、介绍 在过去的几年中,由于(并暗示)该领域取得了一些惊人的进步,基于深度学习的生成模型引起了越来越多的兴趣。依靠大量的数据、精心设计的网络架构和智能训练技术,深度生成模型表现出了令人难以置信的能力&#x…

Active Session History (ASH) 读书笔记

本文为博文Active Session History (ASH)的读书笔记。 AWR,ADDM,SQL Trace是对过去事件的分析,[G]V$视图包含大量实时信息,但使用界面不友好,对初学者较难。因此Oracle 10g推出了ASH,属于Oracle Diagnosti…

Linux网络编程系列之UDP广播

一、什么是UDP广播 UDP广播是一种网络通信的方式,在广域网或局域网中,UDP广播可以向多个目标主机发送数据包,使得网络中的所有设备都能接收到广播消息。一定是采用UDP协议。 二、特性 1、面向无连接:UDP广播不需要建立连接&#…

PLC易学但是后期如何发展?

今日话题 PLC易学但是后期如何发展? PLC学习简便,但重要性不容小觑。除基础外,以下为几个技术方向: 大规模系统:包括冗余PLC、网络架构和服务器。掌握虚拟化、网络设计和模块化。 特色系统:如电力行业…

FastAdmin前台分片传输上传文件getshell

漏洞概述 FastAdmin框架存在有条件RCE漏洞,由于FastAdmin的前台文件上传功能中提供了分片传输功能, 但在合并分片文件时因对文件路径的拼接处理不当导致可上传任意文件。 漏洞复现 漏洞需要一个低权限的账号 所以我们需要在前台注册一个普通用户 注册成功后进行…

1. 树的建立与基本操作

程序的输入是一个表示树结构的广义表。假设树的根为 root ,其子树森林 F = ( T1 , T2 , … , Tn ),设与该树对应的广义表为 L ,则 L =(原子&#…

TLS/SSL 详解

目录 基础理论入门HTTPS对称加密非对称加密证书TLS握手过程握手总结 TLS 定义(记录层/握手层)HTTPS HTTP over TLS加密记录层分片 (Fragmentation)记录压缩和解压缩 (Record compression and decompression)空或标准流加密 (Null or standard stream cipher)CBC 块加密 (分组加…

NV21图片格式深入解析与代码实战-RGB转NV21与画框

1.NV21格式图片解析 NV21图像格式属于 YUV颜色空间中的YUV420SP格式 每四个Y分量共用一组U分量和V分量,Y连续排序,U与V交叉排序 重点总结 uv交错模式4Y共用一组uv(2个)大小:UV Y 的一半 排列方式如下 Y Y   Y Y   Y Y   Y Y Y Y   Y Y…