Socket网络编程(四)——点对点传输场景方案

news/2024/5/18 15:29:14 标签: java, 网络, udp

目录

  • 场景
  • 如何去获取到TCP的IP和Port?
  • UDP的搜索IP地址、端口号方案
  • UDP搜索取消实现
    • 相关的流程:
    • 代码实现逻辑
      • 服务端实现
      • 客户端实现
      • UDP搜索代码执行结果
  • TCP点对点传输实现
    • 代码实现步骤
    • 点对点传输测试结果
  • 源码下载

场景

在一个局域网当中,不知道服务器的IP地址,仅仅知道服务器公共的UDP的端口,在这种情况下,想要实现TCP的连接。TCP是点对点的连接,所以需要知道TCP的连接IP地址和端口Port。

如何去获取到TCP的IP和Port?

可以通过UDP的搜索实现,

  1. 当我们的服务器与我们所有的客户端之间约定了搜索的格式之后,我们可以在客户端发起广播
  2. 然后服务器在收到广播之后判断一下这些收到的广播是否是需要处理的。那么服务器就会回送这些广播到对应的端口(地址)上去。
  3. 客户端就能收到服务器回送过来的UDP的包。收到的这些数据包,里面就包含了端口号、IP地址等。
  4. 根据以上的流程就能够UDP的搜索得到TCP服务器的IP地址和TCP的端口,然后使用这些信息来实现TCP的连接。

UDP的搜索IP地址、端口号方案

  1. 构建基础口令消息
    原理:如果要实现UDP的交互,就要约定一组公共的数据格式,也就是基础的口令头。如果没有约定口令消息,那么别人发送的消息到达我们的服务器后就会去回送,这就会导致我们自己的基本信息(比如IP\Port)的暴露。
  2. 局域网广播口令消息(指定端口)
  3. 接收指定端口回送消息(得到客户端IP、Port,这里的客户端IP指的是server端)

20240228-154508-t3.png
如上图,BroadCast发出广播,如果有设备(服务器)感兴趣就会回送到BroadCast。如果三台(服务器)都感兴趣,就都会回送到BroadCast。

UDP搜索取消实现

相关的流程:

  1. 异步线程接收回送消息
  2. 异步线程等待完成(定时)
  3. 关闭等待-终止线程等待

代码实现逻辑

服务端实现

  1. TCP/UDP基础信息字段
    TCPConstants.java
java">public class TCPConstants {
 
    // 服务器固化UDP接收端口
    public static int PORT_SERVER = 30401;
}
  1. UDP基础信息
    UDPConstants.java
java">public class UDPConstants {
 
    // 公用头部(8个字节都是7,就是可回复的)
    public static byte[] HEADER = new byte[]{7,7,7,7,7,7,7,7};
    // 服务器固化UDP接收端口
    public static int PORT_SERVER = 30201;
    // 客户端回送端口
    public static int PORT_CLIENT_RESPONSE = 30202;
}
  1. 工具类ByteUtils
    用于校验是否为正确的口令。即对HEADER进行校验。
java">public class ByteUtils {
    /**
     * Does this byte array begin with match array content?
     *
     * @param source Byte array to examine
     * @param match  Byte array to locate in <code>source</code>
     * @return true If the starting bytes are equal
     */
    public static boolean startsWith(byte[] source, byte[] match) {
        return startsWith(source, 0, match);
    }
 
    /**
     * Does this byte array begin with match array content?
     *
     * @param source Byte array to examine
     * @param offset An offset into the <code>source</code> array
     * @param match  Byte array to locate in <code>source</code>
     * @return true If the starting bytes are equal
     */
    public static boolean startsWith(byte[] source, int offset, byte[] match) {
 
        if (match.length > (source.length - offset)) {
            return false;
        }
 
        for (int i = 0; i < match.length; i++) {
            if (source[offset + i] != match[i]) {
                return false;
            }
        }
        return true;
    }
 
    /**
     * Does the source array equal the match array?
     *
     * @param source Byte array to examine
     * @param match  Byte array to locate in <code>source</code>
     * @return true If the two arrays are equal
     */
    public static boolean equals(byte[] source, byte[] match) {
 
        if (match.length != source.length) {
            return false;
        }
        return startsWith(source, 0, match);
    }
 
    /**
     * Copies bytes from the source byte array to the destination array
     *
     * @param source      The source array
     * @param srcBegin    Index of the first source byte to copy
     * @param srcEnd      Index after the last source byte to copy
     * @param destination The destination array
     * @param dstBegin    The starting offset in the destination array
     */
    public static void getBytes(byte[] source, int srcBegin, int srcEnd, byte[] destination,
                                int dstBegin) {
        System.arraycopy(source, srcBegin, destination, dstBegin, srcEnd - srcBegin);
    }
 
    /**
     * Return a new byte array containing a sub-portion of the source array
     *
     * @param srcBegin The beginning index (inclusive)
     * @param srcEnd   The ending index (exclusive)
     * @return The new, populated byte array
     */
    public static byte[] subbytes(byte[] source, int srcBegin, int srcEnd) {
        byte destination[];
 
        destination = new byte[srcEnd - srcBegin];
        getBytes(source, srcBegin, srcEnd, destination, 0);
 
        return destination;
    }
 
    /**
     * Return a new byte array containing a sub-portion of the source array
     *
     * @param srcBegin The beginning index (inclusive)
     * @return The new, populated byte array
     */
    public static byte[] subbytes(byte[] source, int srcBegin) {
        return subbytes(source, srcBegin, source.length);
    }
}
  1. 服务器端接收约定数据包,解析成功并回送包的代码
    ServerProvider
java">public class ServerProvider {
 
    private static Provider PROVIDER_INSTANCE;
 
    static void start(int port){
        stop();
        String sn = UUID.randomUUID().toString();
        Provider provider = new Provider(sn, port);
        provider.start();
        PROVIDER_INSTANCE = provider;
    }
 
    static void stop(){
        if(PROVIDER_INSTANCE != null){
            PROVIDER_INSTANCE.exit();
            PROVIDER_INSTANCE = null;
        }
    }
 
 
    private static class Provider extends Thread{
        private final byte[] sn;
        private final int port;
        private boolean done = false;
        private DatagramSocket ds = null;
        // 存储消息的Buffer
        final byte[] buffer = new byte[128];
 
        public Provider(String sn, int port){
            super();
            this.sn = sn.getBytes();
            this.port = port;
        }
 
        @Override
        public void run() {
            super.run();
 
            System.out.println("UDDProvider Started.");
 
            try {
                // 监听20000 端口
                ds = new DatagramSocket(UDPConstants.PORT_SERVER);
                // 接收消息的Packet
                DatagramPacket receivePacket = new DatagramPacket(buffer,buffer.length);
                while(!done){
                    // 接收
                    ds.receive(receivePacket);
 
                    // 打印接收到的信息与发送者的信息
                    // 发送者的IP地址
                    String clientIp = receivePacket.getAddress().getHostAddress();
                    int clientPort = receivePacket.getPort();
                    int clientDataLen = receivePacket.getLength();
                    byte[] clientData = receivePacket.getData();
                    boolean isValid = clientDataLen >= (UDPConstants.HEADER.length + 2 + 4) && ByteUtils.startsWith(clientData,UDPConstants.HEADER);
                    System.out.println("ServerProvider receive from ip:" + clientIp + "\tport:" + clientIp +"\tport:"+clientPort+"\tdataValid:"+isValid);
 
                    if(!isValid){
                        //无效继续
                        continue;
                    }
 
                    // 解析命令与回送端口
                    int index = UDPConstants.HEADER.length;
                    short cmd = (short) ((clientData[index++] << 8) | (clientData[index++] & 0xff));
                    int responsePort = (((clientData[index++]) << 24) |
                            ((clientData[index++] & 0xff) << 16) |
                            ((clientData[index++] & 0xff) << 8) |
                            ((clientData[index++] & 0xff)));
 
                    // 判断合法性
                    if( cmd == 1 && responsePort > 0){
                        // 构建一份回送数据
                        ByteBuffer byteBuffer = ByteBuffer.wrap(buffer);
                        byteBuffer.put(UDPConstants.HEADER);
                        byteBuffer.putShort((short)2);
                        byteBuffer.putInt(port);
                        byteBuffer.put(sn);
                        int len = byteBuffer.position();
                        // 直接根据发送者构建一份回送信息
                        DatagramPacket responsePacket = new DatagramPacket(buffer,len,receivePacket.getAddress(),responsePort);
                        ds.send(responsePacket);
                        System.out.println("ServerProvider response to:" + clientIp + "\tport:"+responsePort + "\tdataLen: " + len);
                    }else {
                        System.out.println("ServerProvider receive cmd nonsupport; cmd:" + cmd + "\tport:" + port);
                    }
                }
            } catch (SocketException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
 
        private void close() {
            if( ds != null ){
                ds.close();
                ds = null;
            }
        }
 
        /**
         * 提供结束
         */
        void exit(){
            done = true;
            close();
        }
    }
}
  1. main方法启动类
java">public class Server {
 
    public static void main(String[] args) {
        ServerProvider.start(TCPConstants.PORT_SERVER);
        try{
            System.in.read();
        } catch (IOException e){
            e.printStackTrace();
        }
        ServerProvider.stop();
    }
}

客户端实现

客户端广播发送消息包代码

  1. 服务器端消息实体
    ServerInfo
java">public class ServerInfo {
 
    private String sn;
    private int port;
    private String address;
 
    public ServerInfo(int port, String address, String sn) {
        this.sn = sn;
        this.port = port;
        this.address = address;
    }
    
    省略set/get方法 ……
    
}
  1. 客户端启动main方法类
java">public class Client {
 
    public static void main(String[] args) {
        // 定义10秒的搜索时间,如果超过10秒未搜索到,就认为服务器端没有开机
        ServerInfo info = ClientSearcher.searchServer(10000);
        System.out.println("Server:" + info);
    }
}
  1. 客户端接收服务器端回送与广播发送的具体逻辑
    ClientSearcher
java">public class ClientSearcher {
 
    private static final int LISTENT_PORT = UDPConstants.PORT_CLIENT_RESPONSE;
 
    public static ServerInfo searchServer(int timeout){
        System.out.println("UDPSearcher Started.");
 
        //  成功收到回送的栅栏
        CountDownLatch receiveLatch = new CountDownLatch(1);
        Listener listener = null;
        try{
            // 监听
            listener = listen(receiveLatch);
            // 发送广播
            sendBroadCast();
            // 等待服务器返回,最长阻塞10秒
            receiveLatch.await(timeout, TimeUnit.MILLISECONDS);
        }catch (Exception e){
            e.printStackTrace();
        }
        // 完成
        System.out.println("UDPSearcher Finished.");
        if(listener == null){
            return null;
        }
        List<ServerInfo> devices = listener.getServerAndClose();
        if(devices.size() > 0){
            return devices.get(0);
        }
        return null;
    }
 
    /**
     * 监听服务器端回送的消息
     * @param receiveLatch
     * @return
     * @throws InterruptedException
     */
    private static Listener listen(CountDownLatch receiveLatch) throws InterruptedException {
        System.out.println("UDPSearcher start listen.");
        CountDownLatch startDownLatch = new CountDownLatch(1);
        Listener listener = new Listener(LISTENT_PORT, startDownLatch,receiveLatch);
        listener.start();   // 异步操作,开启端口监听
        startDownLatch.await();
        return listener;
    }
 
    /**
     * 发送广播逻辑
     * @throws IOException
     */
    private static void sendBroadCast() throws IOException {
        System.out.println("UDPSearcher sendBroadcast started.");
 
        // 作为搜索方,让系统自动分配端口
        DatagramSocket ds = new DatagramSocket();
 
        // 构建一份请求数据
        ByteBuffer byteBuffer = ByteBuffer.allocate(128);
        // 头部
        byteBuffer.put(UDPConstants.HEADER);
        // CMD命名
        byteBuffer.putShort((short)1);
        // 回送端口信息
        byteBuffer.putInt(LISTENT_PORT);
        // 直接构建Packet
        DatagramPacket requestPacket = new DatagramPacket(byteBuffer.array(), byteBuffer.position() + 1);
        // 广播地址
        requestPacket.setAddress(InetAddress.getByName("255,255.255.255"));
        // 设置服务器端口
        requestPacket.setPort(UDPConstants.PORT_SERVER);
 
        // 发送
        ds.send(requestPacket);
        ds.close();
 
        // 完成
        System.out.println("UDPSearcher sendBroadcast finished.");
 
    }
 
    /**
     * 广播消息的接收逻辑
     */
    private static class Listener extends Thread {
        private final int listenPort;
        private final CountDownLatch startDownLatch;
        private final CountDownLatch receiveDownLatch;
        private final List<ServerInfo> serverInfoList = new ArrayList<>();
        private final byte[] buffer = new byte[128];
        private final int minLen = UDPConstants.HEADER.length + 2 + 4; // 2:CMD命令长度  4:TCP端口号长度
        private boolean done = false;
        private DatagramSocket ds = null;
 
        private Listener(int listenPort,CountDownLatch startDownLatch,CountDownLatch receiveDownLatch){
            super();
            this.listenPort = listenPort;
            this.startDownLatch = startDownLatch;
            this.receiveDownLatch = receiveDownLatch;
        }
 
       @Override
       public void run(){
            super.run();
 
            // 通知已启动
            startDownLatch.countDown();
            try{
                // 监听回送端口
                ds = new DatagramSocket(listenPort);
                // 构建接收实体
                DatagramPacket receivePacket = new DatagramPacket(buffer,buffer.length);
                while( !done){
                    // 接收
                    ds.receive(receivePacket);
                    // 打印接收到的信息与发送者的信息
                    // 发送者的IP地址
                    String ip = receivePacket.getAddress().getHostAddress();
                    int port = receivePacket.getPort();
                    int dataLen = receivePacket.getLength();
                    byte[] data = receivePacket.getData();
                    boolean isValid = dataLen >= minLen
                            && ByteUtils.startsWith(data, UDPConstants.HEADER);
 
                    System.out.println("UDPSearch receive form ip:" + ip + "\tport:" + port + "\tdataValid:" + isValid);
 
                    if( !isValid ) {
                        // 无效继续
                        continue;
                    }
                    // 跳过口令字节,从具体数据开始
                    ByteBuffer byteBuffer = ByteBuffer.wrap(buffer,UDPConstants.HEADER.length, dataLen);
                    final short cmd = byteBuffer.getShort(); // 占据2个字节
                    final int serverPort = byteBuffer.getInt(); // 占据4个字节
                    if(cmd != 2 || serverPort <= 0){
                        System.out.println("UDPSearcher receive cmd:" + cmd + "\tserverPort:" + serverPort);
                        continue;
                    }
 
                    String sn = new String(buffer,minLen,dataLen - minLen);
                    ServerInfo info = new ServerInfo(serverPort,ip,sn);
                    serverInfoList.add(info);
                    // 成功接收到一份
                    receiveDownLatch.countDown();
                }
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                close();
            }
           System.out.println("UDPSearcher listener finished.");
       }
 
       private void close(){
           if(ds != null){
               ds.close();
               ds = null;
           }
       }
 
        List<ServerInfo> getServerAndClose() {
            done = true;
            close();
            return serverInfoList;
        }
    }
}

UDP搜索代码执行结果

服务端启动接收的结果:

UDDProvider Started.
ServerProvider receive from ip:169.254.178.74	port:169.254.178.74	port:61968	dataValid:true
ServerProvider response to:169.254.178.74	port:30202	dataLen: 50

客户端监听并发起广播的执行结果:

UDPSearcher Started.
UDPSearcher start listen.
UDPSearcher sendBroadcast started.
UDPSearcher sendBroadcast finished.
UDPSearch receive form ip:169.254.178.74	port:30201	dataValid:true
UDPSearcher Finished.
Server:ServerInfo{sn='ed4ab162-5d5c-49eb-b80e-6ddeb8b223e0', port=30401, address='169.254.178.74'}
UDPSearcher listener finished.
 
Process finished with exit code 0

由以上结果可知,启动服务端后,客户端在启动listen监听后,向服务器端发送数据包,并获得服务器端的回送,经解析后,该回送的数据包中可以获得 ip/port,可用于TCP连接使用。在UDP解析数据包过程中,通过口令保证了客户端与服务端对消息发送、接收、回送的有效,避免不必要的回应。

TCP点对点传输实现

基于前面UDP广播-搜索的机制,Server-Client获得了建立Socket链接的IP\Port信息。
可以接着使用该信息进行建立TCP的Socket连接,实现点对点的数据收发。

代码实现步骤

  1. TCP服务端main启动方法
java">public class Server {
 
    public static void main(String[] args) {
 
        TCPServer tcpServer = new TCPServer(TCPConstants.PORT_SERVER);
        boolean isSucceed = tcpServer.start();
        if(!isSucceed){
            System.out.println("Start TCP server failed.");
        }
        UDPProvider.start(TCPConstants.PORT_SERVER);
 
        try{
            System.in.read();
        } catch (IOException e){
            e.printStackTrace();
        }
 
        UDPProvider.stop();
        tcpServer.stop();
    }
}

在UDP搜索的基础上,我们获得了TCP的链接IP。创建tcpServer对相应的端口进行监听客户端链接请求。

  1. 服务端异步线程处理Socket
    TCPServer
java">public class TCPServer {
    private final int port;
    private ClientListener mListener;
 
    /**
     * 构造
     * @param port
     */
    public TCPServer(int port){
        this.port = port;
    }
 
    /**
     * 开始
     * @return
     */
    public boolean start(){
        try{
            ClientListener listener = new ClientListener(port);
            mListener = listener;
            listener.start();
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
        return true;
    }
 
    /**
     * 结束
     */
    public void stop(){
        if(mListener != null){
            mListener.exit();
        }
    }
 
    /**
     * 监听客户端链接
     */
    private static class ClientListener extends Thread {
        private ServerSocket server;
        private boolean done = false;
 
        private ClientListener(int port) throws IOException {
            server = new ServerSocket(port);
            System.out.println("服务器信息: " + server.getInetAddress() + "\tP:" + server.getLocalPort());
        }
 
        @Override
        public void run(){
            super.run();
 
            System.out.println("服务器准备就绪~");
            // 等待客户端连接
            do{
                // 得到客户端
                Socket client = null;
                try {
                    client = server.accept();
                }catch (Exception e){
                    e.printStackTrace();
                }
                // 客户端构建异步线程
                ClientHandler clientHandler = new ClientHandler(client);
                // 启动线程
                clientHandler.start();
            }while (!done);
            System.out.println("服务器已关闭!");
        }
        void exit(){
            done = true;
            try {
                server.close();
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }
 
    /**
     * 客户端消息处理
     */
    private static class ClientHandler extends Thread{
        private Socket socket;
        private boolean flag = true;
 
        ClientHandler(Socket socket ){
            this.socket = socket;
        }
 
        @Override
        public void run(){
            super.run();
            System.out.println("新客户链接: " + socket.getInetAddress() + "\tP:" + socket.getPort());
            try {
                // 得到打印流,用于数据输出;服务器回送数据使用
                PrintStream socketOutput = new PrintStream(socket.getOutputStream());
                // 得到输入流,用于接收数据
                BufferedReader socketInput = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                do {
                    // 客户端拿到一条数据
                    String str = socketInput.readLine();
                    if( "bye".equalsIgnoreCase(str)){
                        flag = false;
                        // 回送
                        socketOutput.println("bye");
                    }else {
                        // 打印到屏幕,并回送数据长度
                        System.out.println(str);
                        socketOutput.println("回送: " + str.length());
                    }
                }while (flag);
                socketInput.close();
                socketOutput.close();
            }catch (IOException e){
                System.out.println("连接异常断开");
            }finally {
                // 连接关闭
                try {
                    socket.close();
                }catch (IOException e){
                    e.printStackTrace();
                }
            }
            System.out.println("客户端已退出:" + socket.getInetAddress() + "\tP:" + socket.getPort());
        }
    }
}

accept() 监听到客户端的链接后,通过输入流读取客户端数据,并通过输出流回送数据长度。

  1. 基于UDP回送结果建立的TCP客户端
    Client main方法
java">public class Client {
 
    public static void main(String[] args) {
        // 定义10秒的搜索时间,如果超过10秒未搜索到,就认为服务器端没有开机
        ServerInfo info = UDPSearcher.searchServer(10000);
        System.out.println("Server:" + info);
 
        if( info != null){
            try {
                TCPClient.linkWith(info);
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }
}

获得UDP的回送后,我们知道了建立TCP的ip、port。也就是serverInfo不为null,取出相关参数建立Socket 链接。

  1. 建立客户端连接类
    TCPClient
java">public class TCPClient {
 
    public static void linkWith(ServerInfo info) throws IOException {
        Socket socket = new Socket();
        // 超时时间
        socket.setSoTimeout(3000);
        // 端口2000;超时时间300ms
        socket.connect(new InetSocketAddress(Inet4Address.getByName(info.getAddress()),info.getPort()));//
 
        System.out.println("已发起服务器连接,并进入后续流程~");
        System.out.println("客户端信息: " + socket.getLocalAddress() + "\tP:" + socket.getLocalPort());
        System.out.println("服务器信息:" + socket.getInetAddress() + "\tP:" + socket.getPort());
 
        try {
            // 发送接收数据
            todo(socket);
        }catch (Exception e){
            System.out.println("异常关闭");
        }
 
        // 释放资源
        socket.close();
        System.out.println("客户端已退出~");
    }
 
    private static void todo(Socket client) throws IOException {
        // 构建键盘输入流
        InputStream in = System.in;
        BufferedReader input = new BufferedReader(new InputStreamReader(in));
 
        // 得到Socket输出流,并转换为打印流
        OutputStream outputStream = client.getOutputStream();
        PrintStream socketPrintStream = new PrintStream(outputStream);
 
        // 得到Socket输入流,并转换为BufferedReader
        InputStream inputStream = client.getInputStream();
        BufferedReader socketBufferedReader = new BufferedReader(new InputStreamReader(inputStream));
 
        boolean flag = true;
        do {
            // 键盘读取一行
            String str = input.readLine();
            // 发送到服务器
            socketPrintStream.println(str);
 
            // 从服务器读取一行
            String echo = socketBufferedReader.readLine();
            if("bye".equalsIgnoreCase(echo)){
                flag = false;
            }else {
                System.out.println(echo);
            }
        }while(flag);
        // 资源释放
        socketPrintStream.close();
        socketBufferedReader.close();
    }
}

建立Socket链接,从键盘读取一行发送到服务器;并从服务器读取一行。以上就是基于UDP广播-搜索实现TCP点对点传输的逻辑。

点对点传输测试结果

基于UDP实现的TCP服务端日志

服务器信息: 0.0.0.0/0.0.0.0	P:30401
服务器准备就绪~
UDDProvider Started.
ServerProvider receive from ip:169.254.178.74	port:169.254.178.74	port:51322	dataValid:true
ServerProvider response to:169.254.178.74	port:30202	dataLen: 50
新客户链接: /169.254.178.74	P:57172
ping
pong

基于UDP实现的TCP客户端日志:

UDPSearcher start listen.
UDPSearcher sendBroadcast started.
UDPSearcher sendBroadcast finished.
UDPSearch receive form ip:169.254.178.74	port:30201	dataValid:true
UDPSearcher Finished.
Server:ServerInfo{sn='10595790-14d1-44dc-a068-4c64c956a944', port=30401, address='169.254.178.74'}
UDPSearcher listener finished.
已发起服务器连接,并进入后续流程~
客户端信息: /169.254.178.74	P:57172
服务器信息:/169.254.178.74	P:30401
ping
回送: 4
pong
回送: 4

源码下载

下载地址:https://gitee.com/qkongtao/socket_study/tree/master/src/main/java/cn/kt/socket/SocketDemo_L5_UDP


http://www.niftyadmin.cn/n/5401349.html

相关文章

深度探析低代码:助力“数智转型”赋能中国制造

随着数字化和智能化技术的飞速发展&#xff0c;我国制造业正面临着从传统制造向智能制造的转型升级。在这个过程中&#xff0c;低代码技术作为一种创新性的软件开发模式&#xff0c;逐渐成为助力我国制造业数智转型的关键驱动力。本文将从低代码技术的原理、应用场景以及在我国…

网络编程详解-UDP-TCP

网络编程 在网络通信协议下&#xff0c;不同计算机上运行的程序&#xff0c;可以进行数据传输 网络编程的三要素是IP地址、端口和协议。具体分析如下&#xff1a; IP地址&#xff1a;IP地址是网络中每一台计算机的唯一标识&#xff0c;它类似于现实生活中的家庭住址&#xf…

工具类从spring里面获取对象

创建工具类 /*** spring容器的相关方法&#xff0c;如根据名称获取spring实例** author 天真热* create 2022-04-28 9:55* desc**/import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.cont…

【ArcGIS】渔网分割提取栅格图+网格化分析图绘制

ArcGIS按渔网分割提取栅格图并绘制网格化分析图 准备数据操作步骤步骤1&#xff1a;创建渔网&#xff08;Create Fishnet&#xff09;步骤2&#xff1a;栅格数据处理步骤3&#xff1a;栅格插值步骤4&#xff1a;数据关联 参考 网格化的目的是让各个数据更加标准化的进行统计。因…

CentOS7 Zookeeper3.8.3 单节点安装

CentOS7 Zookeeper3.8.3 单节点安装 1、把压缩包丢tools里&#xff0c;进tools cd /tools2、解压到training tar -zxvf /tools/apache-zookeeper-3.8.3-bin.tar.gz -C /training3、进training cd /training4、重命名 mv apache-zookeeper-3.8.3-bin zookeeper5、进zookeep…

指针篇章-(1)

指针&#xff08;1&#xff09;学习流程 —————————————————————————————————————————————————————————————————————————————————————————————————————————————…

Android 通过Intent打开第三方App

Android 使用 Intent 打开第三方应用或调用制定 Activity Intent intent new Intent(); intent.setClassName("package name", "activity name"); // 内部调用 intent.setComponent(new ComponentName("package name", "activity name&qu…

tvm交叉编译android opencl

模型编译&#xff1a; #encoding:utf-8 import onnx import numpy as np import tvm import tvm.relay as relay import os from tvm.contrib import ndk onnx_model onnx.load(mobilenet_v3_small.onnx) x np.ones([1,3,224,224]) input_name …