轻松学会网络编程

news2024/12/27 9:42:12

目录

一、UDP 和 TCP 特点对比

1、有连接和无连接

2、可靠传输和不可靠传输

3、面向字节流和面向数据报

4、全双工和半双工

二、UDP 的 socket.api

1、DatagramSocket

2、DatagramPacket

回显服务器的实现

(1)服务器代码

(2)客户端代码

翻译服务器的实现

三、TCP

1、ServerSocket   

​编辑 2、Socket

回显服务器

(1)服务器代码

(2)客户端代码


网络编程其实就是写一个应用程序,让这个程序可以使用网络通信

这里就需要调用传输层提供的 api

传输层提供的协议一共是两个:

1、UDP 

2、TCP

这两个协议提供了两套不同的 api


一、UDP 和 TCP 特点对比

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

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


1、有连接和无连接

我们在之前学习 JDBC 的时候,有这么一个步骤:

先创建一个 DtaSourse ,再通过 DataSourse 创建 Connection

而这里的 Connection ,就是我们所说的连接

更直观的理解:

打电话的时候,拨号,按下拨号键,知道对方接通,才算完成连接建立

TCP 进行编程的时候,也是存在类似的建立连接的过程

无连接就类似于 发微信 / 短信,不需要建立连接就能进行通信

这里的 ”连接“ 是一个抽象的概念

客户端和服务器之间,使用内存保存对端的信息

双方都保存这个信息,此时 “连接” 就出现了

一个客户端可以连接多个服务器,一个服务器也可以对应多个客户端的连接


2、可靠传输和不可靠传输

可靠传输,并不是说 A 给 B 发的消息100% 能传到

而是,A 尽可能地把消息传给 B,并且在传输失败的时候,A 能感知到,或者在传输成功的时候,也能知道自己传输成功了

TCP 是可靠传输,传输效率就低了

UDP 是不可靠传输,传输效率更高

虽然 TCP 是可靠传输,UDP 是不可靠传输,但是并不能说 TCP 就是比 UDP 安全

“网络安全” 指的是,如果你传输的数据是否容易被黑客截获,以及如果被截获后是否会泄露一些重要信息


3、面向字节流和面向数据报

TCP 和文件操作类似,都是 “流” 式的(由于这里传输的单位都是字节 ,称为字节流)

UDP 是面向数据报,读写的基本单位是一个 UDP 数据报(包含了一系列的数据 / 属性)


4、全双工和半双工

全双工 :一个通道,可以双向通信

半双工: 一个通道,只能单向通信

网线,就是全双工的

网线一共有 8 根铜缆,4 4 一组,有的负责一个方向,有的负责另一个方向


二、UDP 的 socket.api

1、DatagramSocket

是一个 Socket 对象,

操作系统使用文件这样的概念,来管理一些软硬件资源,操作系统也是使用文件的方式来管理网卡的,表示网卡的这类的文件,称为 Socket 文件

Java 中的 Socket 对象,就对应系统里的 Socket 文件(最终还是要落到网卡上)

要进行网络通信,就必须得有 Socket 对象

构造方法:

第一个往往在客户端使用(客户端使用哪个端口是系统自动分配的)

第二个往往在服务器这边使用(服务器使用哪个端口是手动指定的)

一个客户端的主机,上面运行的结果很多,天知道你手动选定的端口是不是被别的程序占用了,所以让系统自动分配一个端口是更加明智的选择

服务器是完全在程序员手里控制的,程序员可以把服务器上的多个程序安排好,让他们使用不同的端口

其它方法:


2、DatagramPacket

表示了一个 UDP 数据报,代表了系统中设定的 UDP 数据报的二进制结构

构造方法:

第一个构造方法:用来接受数据

DatagramPacket 作为一个 UDP 数据报,必然要能够承载一些数据

通过手动指定的 byte[] 作为存储数据的空间

第二个构造方法:用来发送数据

SocketAddress address 指的是 ip 和 端口号

其它方法:

getData 是指获取 UDP 数据报载荷部分(完整的应用层数据报)


回显服务器的实现

接下来我们开始手写 UDP 客户端服务器

最简单的 UDP 服务器:回显服务器(echo server),客户端发啥,服务器返回啥

(1)服务器代码

编写网络程序的时候,经常见到这种异常,意思就是说 socket 是有可能失败的

最典型的情况,就是端口号被占用

端口号是用来区分主机上的应用程序,一个应用程序可以占用主机上的多个端口,一个端口只能被一个进程占用(有特例,此处不讨论) 

换句话说,当端口已经被其它进程占用了,此时你再尝试创建这个 socket 对象,占用此端口,就会报错

一个服务器,要给很多客户端提供服务,服务器也不知道客户端什么时候来,服务器只能 “时刻准备着” ,随时客户端来了,随时提供服务

一个服务器,运行过程中,要做的事情,主要是三个核心环节

1、读取请求,并解析
2、根据请求,计算出响应
3、把响应写回给客户端

对于回显服务器来说,则不关心第二个流程,请求是啥,就返回什么响应

但是一个商业级的服务器,主要的代码都是在完成第二个步骤

这个方法中,参数的 DatagramPacket 是一个 “输出型参数”

传入 receive 的是一个空的对象,receive 内部就会把这个空的对象的内容给填成上,当 receive 执行结束,于是就得到了一个装满内容的 DatagramPacket

这个对象用来保存数据的内存空间,是需要手动指定的,不像学过的集合类,内部是有自己管理内存的能力的(能够申请内存,释放内存,内存扩容 等功能)

4096 这个数字是随便写出来的,但是也不能写的太大,不能超过 64kb

服务器程序一启动,就会立即执行到循环,立即执行到 receive 了

如果此时客户端的请求还没来,receive 方法就会阻塞等待,阻塞到真正有客户端发起请求过来了

这段代码中,就要构造一个 DatagramPacket 对象,把响应发回给客户端

注意:第二个参数不能写 response,length()

这是因为, response,length() 是按照字符计算长度,而 response.getBytes().length 是按照字节计算长度

如果字符串是全英文的,此时字节数和字符数是一样的,但是如果字符串中含有中文,此时两者计算出的结果就不一样了

socket api 本身,就是按照字节来处理的

requestPacket.getSocketAddress()  这个部分是把数据报,发送给客户端,就需要知道客户端的 ip 和端口

DatagramPacket 这个对象就包含着通信双方的 ip 和 port 

服务器部分代码:

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

//UDP 的回显服务器
//客户端发的请求是啥,服务器的响应就是啥
public class UdpEchoServer {
    private DatagramSocket socket = null;

    public UdpEchoServer(int port) throws SocketException {
        socket = new DatagramSocket(port);
    }

    //使用这个方法启动服务器
    public void start() throws IOException {
        System.out.println("服务器启动");
        while (true){
            //反复的,长期的执行针对客户端请求处理的逻辑
            //一个服务器,运行过程中,要做的事情,主要是三个核心环节
            //1、读取请求,并解析
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(requestPacket);
            //这样的转字符串的前提是,后续客户端发的数据就是一个文本的字符串
            String request = new String(requestPacket.getData(),0,requestPacket.getLength());
            //2、根据请求,计算出响应
            String response = process(request);
            //3、把响应写回给客户端
            //此时需要告知网卡,要发的内容是啥,要发给谁
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,
                    requestPacket.getSocketAddress());
            socket.send(responsePacket);
            //记录日志,方便观察程序执行效果
            System.out.printf("[%s:%d] req: %s, resp: %s\n",responsePacket.getAddress().toString(),responsePacket.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();
    }
}

这个是否关闭了呢?

对于我们这个服务器程序来说,DatagramSocket 不关闭,问题不大,整个程序中只有这一个 socket 对象,不是频繁创建的

这个对象的生命周期非常长,跟随整个程序的,此时,socket 就需要保持打开的状态

socket 对象,对应到了 系统中的 socket 文件,又对于到了文件描述符(最主要的目的是为了是否文件描述符,才要关闭 socket 对象的

进程结束,就把 pcb 回收,里面的文件描述符表也就都销毁了

但是这一点仅限于:只有一个 socket 对象,并且生命周期是跟随进程的,此时就可以不用释放

但是如果有多个 socket 对象,socket 对象生命周期更短,需要频繁创建释放,此时一定要记得去 close


(2)客户端代码

编写客户端,后面的参数指定了发给哪个 IP,发给哪个端口

此处需要的是 InetAdress 对象,所以使用 InetAdress 的静态方法,getByName 来进行构造(工厂模式 / 工厂方法)

 

服务器启动要自动绑定到 9090

客户端接下来就要访问 9090 这个窗口

 客户端代码:

import java.io.IOException;
import java.net.*;
import java.util.Scanner;

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

    //服务器的 ip 和 服务器窗口
    public UdpEchoClient(String ip ,int port) throws SocketException {
        serverIp = ip;
        serverPort = port;
        //这个 new 操作,就不再指定端口了,让系统自动分配一个空闲端口
        socket = new DatagramSocket();
    }

    //客户端启动,让这个客户端反复的从控制台读取用户输入的内容,把这个内容构造成 UPD 请求,发给服务器,再读取服务器返回的 UDP响应
    //最终再显示在客户端的屏幕上
    public void start() throws IOException {
        System.out.println("客户端启动!");
        Scanner scanner = new Scanner(System.in);
        while (true){
            //1、从控制台读取用户输入的内容
            System.out.println("-> ");//命令提示符,提示用户输入字符串
            String request = scanner.next();
            //2、构造请求对象,并发给服务器
            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);
            String response = new String(responsePacket.getData(),0,responsePacket.getLength());
            //4、显示结果
            System.out.println(request);
        }
    }

    public static void main(String[] args) throws IOException {
        UdpEchoClient client = new UdpEchoClient("127.0.1",9090);
        client.start();
    }
}

此时,先运行服务器,再运行客户端,然后输入内容,就可以看到执行结果了

 如果启动多个客户端,多个客户端也是可以被服务器应对的


翻译服务器的实现

翻译服务器,请求的是一些英文单词,响应则是对应得中文翻译

这个服务器和之前的回显服务器的到部分代码是相似的,所以我们让它直接继承之前的服务器

继承,本身就是为了更好的“复用现有代码”

如果在这里,不加@Override ,万一方法名字 / 参数类型 / 参数个数 / 访问权限 万一搞错了,此时就无法构成重写了,并且也不好发现

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

public class UdpDicSever extends UdpEchoServer{
    private Map<String,String> dict = new HashMap<>();

    public UdpDicSever(int port) throws SocketException {
        super(port);

        dict.put("cat","小猫");
        dict.put("dog","小狗");
        dict.put("duck","小鸭");
        //可以在这里继续添加千千万万个单词,每个单词都有一个对应的翻译
    }

    //是要复用之前的代码,但是又要做出调整
    @Override
    public String process(String request){
        //把请求对应单词的翻译给返回回去
        return dict.getOrDefault(request,"该词没有查询到");
    }

    public static void main(String[] args) throws IOException {
        UdpDicSever server = new UdpDicSever(9090);
        //start 就不需要在写一遍了,就直接复用了之前的 start
        server.start();
    }
}

三、TCP

TCP 是字节流,一个字节一个字节的进行传输的

换句话说,一个 TCP 数据报,就是一个字节数组 byte[ ] 

TCP 提供的 api 也是两个类

1、ServerSocket   

给服务器使用的 Socket

构造方法:

其它方法:

 2、Socket

既会给服务器使用,也会给客户端使用

构造方法:

其它方法:


回显服务器

(1)服务器代码

我们现在来尝试写一个 TCP 版本的回显服务器

这里会和  UDP 有一些差别:

进入循环之后,要做的事情不是读取客户端的请求,而是先处理客户端的 “连接”

虽然内核中的连接很多,但是在应用程序中,还是得一个一个处理的

内核中的连接就像一个一个待办事项,这些待办事项在一个队列 数据结构中,应用程序就需要一个一个完成这些任务

要完成任务,就需要先取任务,用到 serverSocker.accept()

把内核中的连接获取到应用程序中了,这个过程类似于 “生产者消费者模型”

程序启动,就会立即执行到 accept

当服务器执行到 accept 的时候,此时客户端可能还没来,accpet 就会阻塞

一直阻塞到有客户端连接成功

accept 是把内核中已经建立好的连接,给拿到应用程序中

但是这里的返回值并非是一个 “Connection” 这样的对象,而只是一个 Socket 对象

而这个 Socket 对象就像是一个 耳麦 一样,就可以说话,也能听到对方的声音

通过 Socket 对象,和对方进行网络通信

TCP server 中涉及到两种 socket,serverSocket 和 clientSocket

serverSocket 可以理解成售楼部负责拉客的人,clientSocket 可以理解成售楼部负责介绍详情的人 

IO 操作是比较有开销的

相比于访问内存,进行 IO 的次数越多,程序的速度就越慢

使用一块内存作为缓冲区,写数据的时候,先写到缓冲区里,攒一波数据,统一进行 IO  

PrintWriter 就内置了缓冲区,手动刷新,确保这里的数据是真的通过网卡发出去了,而不是残留在缓冲区中的

这里加上 flash 更稳妥,但是不加也不一定会出错

缓冲区是内置了一定的刷新策略,因此更建议把 flash 给加上

在这个程序中,涉及到两类 socket 

1、ServerSocket (只有一个,生命周期跟随程序,不关闭也没事)

2、Socket

此处的 socket 是在被反复创建的

我们要确保,连接断开之后,socket 能够被关闭,所以在最后 finally 中加上关闭 socket 的代码

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;

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){
            Socket clientSocket = serverSocket.accept();
            processConnection(clientSocket);
        }
    }

    //通过这个方法处理一个连接的逻辑
    private void processConnection(Socket clientSocket) throws IOException {
        System.out.printf("[%s,%d] 客户端上线!\n,", clientSocket.getInetAddress().toString(),clientSocket.getPort());
        //接下来就可以读取请求,根据请求计算响应,返回响应
        //Socket 对象内部,包含两个字节流对象,可以把这两个字节流对象获取到,完成后续的读写工作
        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().toString(),clientSocket.getPort());
                    break;
                }
                //这个代码暗含一个约定:客户端发过来的请求,得是文本数据,同时,还得带有空白符作为分割
                String request = scanner.next();
                //2、根据请求,计算响应
                String response = process(request);
                //3、把响应写回给客户端,把 OutputStream 使用 PrinterWriter 包裹一下,方便进行发数据
                PrintWriter writer = new PrintWriter(outputStream);
                //使用 PrintWriter 的 println 方法,把响应返回给客户端
                //此处使用 println 而不是 print 就是为了在结尾加上换行符,方便客户端读取响应,使用 Scanner.next 读取
                writer.println(response);
                //这里还需要加一个 “刷新缓冲区” 的操作
                writer.flush();

                //日志,打印一下当前的请求详情
                System.out.printf("[%s:%d] req: %s,resp: %s\n",clientSocket.getInetAddress().toString(),clientSocket.getPort(),
                        request,response);

            }
        }finally {
            //在 finally 中,加上 close 操作,确保当前 socket 能够被及时关闭
            clientSocket.close();
        }
    }

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


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

(2)客户端代码

客户端要做的事情:

1、从控制台读取用户的输入

2、把输入的内容构造成请求并发送给服务器

3、从服务器读取响应

4、把响应显示到控制台上

当前代码中,对于 Scanner 和 PrintWriter 没有进行 close,是否会发生文件泄露呢?

不会!!!

流对象中持有的资源,有两个部分:

1、内存(对象销毁了,内存就回收了),while 循环一圈,内存自然销毁

2、文件描述符

Scanner 和 printWriter 没有持有文件描述符,持有的是 inputstream 和 outputstream 的引用,而这俩个对象已经进行了关闭

更准确的说,是 socket 对象持有的,所以把 socket 对象关闭就可以了

不是每个流对象都持有文件描述符,持有文件描述符,是要调用操作系统提供的 open方法

hasNext 在客户端没有发请求的时候,也会阻塞,一直阻塞到客户端真的发了数据,或者客户端退出,hasNext 就返回了

当前代码,还存在一个很大的问题:当我们启动两个客户端,会怎么样呢?

现象:

当第一个客户端连接好了之后,第二个客户端不能被正确处理

服务器看不到客户端上线,同时客户端发来的请求也无法被处理

当第一个客户端退出之后,之前第二个客户端发的请求,就都能响应了

当一个客户端来了,accept 就会返回,进入 processConnection

循环会处理该客户端的请求,一直到这个客户端结束了,方法才结束,才回到第一层这里

问题关键在于,处理一个客户端的请求过程中,无法第二次调用 accept ,也就是说即使第二个客户端来了,也没法处理

此处,客户端处理 processConnection 本身就是要长时间执行的,因为不知道客户端什么时候结束,也不知道客户端要发多少请求

那么我们期望在执行这个方法的同时,也能够调用到 accept,此时可以使用多线程

我们可以在主线程里,专门负责拉客,拉到客人之后,创建新的线程,让新的线程负责处理客户端的各种请求

经过上述改进,只要服务器资源足够,有几个客户端都是可以的

其实,如果我们刚才的代码,不写成这个样子,比如要求每个客户端只能发一次请求,发完就断开,上述情况就能得到一定的缓解,但是还是会有类似的问题的

处理多个消息,自然就会延长 proessConnection 的执行时间,就让这个问题更加严重了

TCP 程序的时候,涉及到两种写法:

1、一个连接中只传输一次请求和响应(短连接)

2、一个请求可以传输多次请求和响应(长连接) 

现在我们是,有一个连接,就有一个新的线程

如果有很多客户端,频繁的来 连接 / 断开,服务器就涉及到频繁 创建 / 释放 线程了

使用线程池是更好的方案

这样写是不对的!!!

processConnection 和主线程是不同的线程了,执行 processConnecton 过程中,主线程 try 就执行完毕了,这就会导致 clientSocket 还没有用完,就关闭了 

因此,还是需要把 clientSocket 交给 processConnection 里面来关闭,所以应该这样写:

虽然使用线程池,避免了频繁创建销毁线程,但是毕竟是每个客户端对应一个线程

如果服务器对应的客户端很多,服务器就需要创建出大量的线程,,对于服务器的开销是很大的

当客户端进一步增加,线程数目进一步增加,系统的负担就越来越重,响应的速度也会大打折扣

是否有办法,使用一个线程(或者最多三四个线程),能够高效处理很多个客户端的并发请求

这个也叫做 C10M 问题:同一时刻,有 1kw 的客户端并发请求

引入了很多技术手段,其中一个非常有效 / 必要的手段:IO多路复用 / IO 多路转接

这个东西是解决高并发(C10M)的重要手段之一

解决高并发,说白了就是四个字:

1、开源:引入更多的硬件资源

2、节流:提高单位硬件资源能够处理的请求

IO 多路复用,就是节流的方式,同样的请求,消耗的硬件资源更少了

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

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

相关文章

GaussDB数据库SQL系列-子查询

目录 一、前言 二、GaussDB SQL子查询表达式 1、EXISTS/NOT EXISTS 2、IN/NOT IN 3、ANY/SOME 4、ALL 三、GaussDB SQL子查询实验示例 1、创建实验表 2、EXISTS/NOT EXISTS示例 3、IN/NOT IN 示例 4、ANY/SOME 示例 5、ALL示例 四、注意事项及建议 五、小结 一、…

投资不识筹码峰,炒遍A股也枉然? | 如何用python计算筹码分布数据

你听说过股市上著名的丁蟹效应吗&#xff1f; 你知道丁蟹报仇点到为止&#xff0c;丁蟹报恩家破人亡吗&#xff1f; 你又是否曾在微信群中见过这些表情包&#xff1f; 01 大时代 不知道大家有没有看过《大时代》这部剧&#xff0c;看过的欢迎点我头像交流讨论。 剧中逆天强运…

Java:JVM虚拟机的三种模式

在JVM中有三种模式&#xff1a; 混合模式&#xff1a;解释器热点代码编译 编译模式&#xff1a;启动快&#xff0c;执行慢 解释模式&#xff1a;启动慢&#xff0c;执行快 使用 在我们的JVM虚拟机中一般默认的是混合模式 如上所示&#xff0c;我们可以看到后面有mixed&#xf…

【mysql异常】Specified key was too long; max key length is 1000 bytes

最近在创建数据库的时候&#xff0c;报错内容如下所示&#xff1a; Caused by: java.sql.SQLSyntaxErrorException: Specified key was too long; max key length is 1000 bytesat com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:120) ~[mysql-conn…

Vue中实现自动匹配搜索框内容 关键字高亮文字显示

实现效果如下: 1.首先需要给输入框进行双向绑定 2.拿到搜索的结果去渲染页面 将返回的结果和搜索的关键字进行比对 如果相同的 就变红 上代码 html部分 //输入框<div class"search"><div class"shuru"><input type"请输入要查询的…

软件测试报告有哪些测试内容?

软件测试报告可以包含以下测试内容&#xff1a; 1、功能测试&#xff1a;测试软件的基本功能是否实现&#xff0c;是否符合要求。 2、性能测试&#xff1a;测试软件的响应速度、并发能力、稳定性等性能指标。 3、界面测试&#xff1a;测试软件的用户界面是否友好、易于使用。 …

开车打电话买什么样的蓝牙好,分享几款通话性能最好的蓝牙耳机

随着时间的推移&#xff0c;如今的年轻人越来越倾向于使用骨传导耳机&#xff0c;因为他们都知道&#xff0c;骨传导耳机最大的优点就是带着很舒服的感觉&#xff0c;它不仅比普通的入耳式耳机更容易戴上&#xff0c;而且还比普通的入耳式耳机更安全&#xff0c;能有效地减少中…

try-with-resource语法使用

try-with-resources 是 Java 7 引入的一种语法结构&#xff0c;用于更方便地管理需要关闭的资源&#xff08;如 I/O 流、数据库连接等&#xff09;。它可以在代码块结束后自动关闭资源&#xff0c;无需显式调用 close() 方法&#xff0c;从而避免资源泄漏。 基本结构 try (Res…

opencv-python使用鼠标点击图片显示该点坐标和像素值IPM逆透视变换车道线

OpenCV的鼠标操作 实现获取像素点的功能主要基于OpenCV的内置函数cv2.setMouseCallback()&#xff0c;即鼠标事件回调 setMouseCallback(winname, onMouse,userdata0) winname: 接收鼠标事件的窗口名称 onMouse: 处理鼠标事件的回调函数指针 userdata: 传给回调函数的用户数据…

交流充电桩控制主板的优点

你是否曾经担心过充电桩可能会对你的电动车电池造成危害?让我们来探讨一下交流充电桩主板的优点&#xff0c;让你安心充电。 首先&#xff0c;交流充电桩主板采用了高安全性的电源设计&#xff0c;能够有效地保护电池免受电流、电压过高的危害&#xff0c;确保电池的安全使用。…

解决执行 spark.sql 时版本不兼容的一种方式

场景描述 hive 数据表的导入导出功能部分代码如下所示&#xff0c;使用 assemble 将 Java 程序和 spark 相关依赖一起打成 jar 包&#xff0c;最后 spark-submit 提交 jar 到集群执行。 public class SparkHiveApplication {public static void main(String[] args){long sta…

Dubbo—核心优势

一、快速易用 无论你是计划采用微服务架构开发一套全新的业务系统&#xff0c;还是准备将已有业务从单体架构迁移到微服务架构&#xff0c;Dubbo 框架都可以帮助到你。Dubbo 让微服务开发变得非常容易&#xff0c;它允许你选择多种编程语言、使用任意通信协议&#xff0c;并且…

什么是低价治理服务

当商品的销售价低于品牌要求的建议价时&#xff0c;就会被认为是低价销售&#xff0c;销售的主体是店铺&#xff0c;那店铺的运营方就成了低价的主导者&#xff0c;低价行为大部分品牌都会跟进&#xff0c;低价店铺的信息品牌也会去收集&#xff0c;因为只有掌握了低价链接、低…

什么是 脏写,脏读,幻读,不可重复读?怎样能解决这四种问题?

我们通过如下语句先创建一个 student 学生表。我就以对学生表的操作来解释什么是脏写&#xff0c;脏读&#xff0c;幻读&#xff0c;不可重复读 创建完成之后随便插入一条数据 1. 脏写&#xff1f; 对于两个事务 SessionA&#xff0c;SessionB&#xff0c;如果SessionA修改了另…

无公网IP,公网SSH远程访问家中的树莓派教程

文章目录 前言 如何通过 SSH 连接到树莓派步骤1. 在 Raspberry Pi 上启用 SSH步骤2. 查找树莓派的 IP 地址步骤3. SSH 到你的树莓派步骤 4. 在任何地点访问家中的树莓派4.1 安装 Cpolar内网穿透4.2 cpolar进行token认证4.3 配置cpolar服务开机自启动4.4 查看映射到公网的隧道地…

Timeplate Definition

timeplate定义描述单个tester cycle&#xff0c;并指定所有event edges被放置在cycle的位置。 必须在引用之前定义所有的timeplates。一个procedure必须有至少一个timeplate定义&#xff0c;所有的时钟必须在timeplate定义中进行定义&#xff0c;timeplate的定义有以下格式&am…

C++ STL关联式容器(详解)

STL关联式容器 C STL关联式容器是什么&#xff1f; 在《C STL容器》一节中讲到&#xff0c;C 容器大致分为 2 类&#xff0c;即序列式容器和关联式容器。其中&#xff0c;序列式容器&#xff08;包括 array、vector、list、deque 和 forward_list&#xff09;已经在前面章节中…

【校招VIP】前端JS语言考点之Vue考察

考点介绍&#xff1a; Vue是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是&#xff0c;Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层&#xff0c;不仅易于上手&#xff0c;还便于与第三方库或既有项目整合。另一方面&#xff0c;当与现代化的工具…

我的编程语言学习笔记

前言 作为一名编程初学者&#xff0c;我深知学习编程需要不断积累和记录。在这篇博客文章中&#xff0c;我将分享一些我在学习C/C编程语言过程中记录的常用代码、特定函数、复杂概念以及特定功能。希望能与大家一起切磋进步&#xff01; 常用代码&#xff1a; 1. 输入输出操作…

19----C/C++之加密解密带空格字符串的读入

本文主要通过一道题目来讲解C语言相关的简单加密和解密&#xff0c;以及如何读入带有空格的字符串 小试牛刀 题目描述 在情报传递过程中&#xff0c;为了防止情报被截获&#xff0c;往往需要对情报用一定的方式加密。我们给出一种最简单的加密方法&#xff0c;对给定的一个字…