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

news2024/9/19 6:35:24

目录

  • 场景
  • 如何去获取到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
public class TCPConstants {
 
    // 服务器固化UDP接收端口
    public static int PORT_SERVER = 30401;
}
  1. UDP基础信息
    UDPConstants.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进行校验。
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
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方法启动类
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
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方法类
public class Client {
 
    public static void main(String[] args) {
        // 定义10秒的搜索时间,如果超过10秒未搜索到,就认为服务器端没有开机
        ServerInfo info = ClientSearcher.searchServer(10000);
        System.out.println("Server:" + info);
    }
}
  1. 客户端接收服务器端回送与广播发送的具体逻辑
    ClientSearcher
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启动方法
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
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方法
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
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.coloradmin.cn/o/1482149.html

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

相关文章

Win11系统安装安卓子系统教程

随着Win11系统的不断普及&#xff0c;以及硬件设备的更新换代&#xff0c;我相信很多同学都已经更新并使用到了最新的Win11系统。那么&#xff0c;Win11系统最受期待的功能“Windows Subsystem for Android”&#xff08;简称WSA&#xff09;&#xff0c;即《安卓子系统》。他可…

CAPL组装IPv4分片包的三种思路(2)

2、使用CAPL的函数自动生成一条完整的ICMPv4 Echo Request报文,然后把数据手动放入两个分片报文中 首先生成一条完整的icmp报文: ethernetPacket ppkt1;//icmpv4 echo requestbyte data[1] = {10};//icmpv4 echo request datappkt1.icmpv4.echo…

湖南湘菜 7页面 美食主题 带设计说明 美食网页设计制作 HTML美食网页成品 美食网页成品 HTML美食网页设计

湖南湘菜 7页面 美食主题 带设计说明 jquery图片轮播特效 滚动文字 aspaccess数据库注册登录留言功能 美食网页设计制作 HTML美食网页成品 美食网页成品 HTML美食网页设计制作 前端美食网页开发 热门美食特产网页制作 静态网页成品 asp/php动态网站设计制作DW定制定做 web前…

佛山50公里徒步组团|真北敏捷社区佛山敏捷DevOps社区

真北敏捷社区&佛山敏捷DevOps社区有两个宗旨&#xff0c;一是求知&#xff0c;二是连接。连接有识之士&#xff0c;同修友士之识。峨峨乎高山&#xff0c;洋洋乎流水。谈笑有鸿儒&#xff0c;往来无白丁。 《柳叶刀》上的研究显示&#xff0c;运动的情绪价值&#xff0c;相…

GitHub Copilot extension activation error: ‘No access to GitHub Copilot found‘

好不容易学生认证通过了&#xff0c;打开vscode用copilot结果一直报这个错误。我的原因是&#xff1a;还未给copilot授权&#xff0c; 通过了学生认证后要进入这里进行授权&#xff1a;

数据分析-Pandas数据探查初步圆饼图

数据分析-Pandas数据探查初步圆饼图 数据分析和处理中&#xff0c;难免会遇到各种数据&#xff0c;那么数据呈现怎样的规律呢&#xff1f;不管金融数据&#xff0c;风控数据&#xff0c;营销数据等等&#xff0c;莫不如此。如何通过图示展示数据的规律&#xff1f; 数据表&am…

PHP使用imap_open读取QQ邮箱

PHP代码&#xff1a; /** PHP使用imap_open读取QQ邮箱imap_open 官方文档&#xff1a;https://www.php.net/function.imap_open */function parse_mailstr($subject) {$a explode(?,$subject);$n count($a);$a $a[$n-2];return base64_decode($a); }function recevie_emai…

点亮城市名片丨计讯物联智慧灯杆系统在通讯基地的成功应用

项目背景 在国家新型城镇化大背景下&#xff0c;十四五规划纲要强调“加快数字化发展&#xff0c;建设数字中国”&#xff0c;明确提出“以数字化助推城乡发展和治理模式创新”&#xff0c;全面提高城市的运行效率和宜居程度。 项目概况 为满足灯杆灯光亮度的远程智能管理、对…

docker构建hyperf环境

一&#xff0c;构建hyperf 镜像 官网git https://github.com/hyperf/hyperf-docker 使用dockerfile构建镜像 根据需要这里我使用8.1 swoole版本的镜像 在/home/hyperfdocker 目录中新建一个Dockerfile文件&#xff0c;将这个git上的Dockerfile内容复制粘贴进去 docker build…

Linux--使用 Haproxy搭建Web群集

1、 案例概述 Haproxy是目前比较流行的一种群集调度工具&#xff0c;同类群集调度工具有很多&#xff0c;如LVS 和 Nginx。相比较而言&#xff0c;LVS性能最好&#xff0c;但是搭建相对复杂&#xff1a;Nginx的upstream 模块支持群集功能&#xff0c;但是对群集节点健康检查功能…

java 基础上(1)(核心知识搭配代码)

前言 java的学习分为了上部分以及下部分进行学习&#xff0c;上部分就是对于java的基础知识&#xff0c;面向对象上&#xff0c;面向对象下&#xff0c;异常操作&#xff0c;javaApi&#xff1b;下部主要是集合&#xff0c;泛型&#xff0c;反射&#xff0c;IO流&#xff0c;J…

Ubuntu篇——crontab修改编辑器

输入命令: crontab -e 如果你的系统是第一次使用crontab服务&#xff0c;会首先让你选择一个编辑器 如果已经选择过编辑器&#xff0c;后续想要修改默认编辑器&#xff0c;可以输入sudo select-editor进行修改。

【市工信】2024年青岛市绿色工厂、绿色工业园区等绿色制造示范申报

科大睿智小编从青岛市工信局了解到&#xff0c;为深入贯彻绿色发展理念&#xff0c;牢固树立绿色低碳发展导向&#xff0c;进一步完善绿色制造体系&#xff0c;培育绿色制造先进典型&#xff0c;根据《工业和信息化部关于印发<绿色工厂梯度培育及管理暂行办法>的通知》&a…

【PyTorch][chapter 20][李宏毅深度学习]【无监督学习][ GAN]【实战】

前言 本篇主要是结合手写数字例子,结合PyTorch 介绍一下Gan 实战 第一轮训练效果 第20轮训练效果,已经可以生成数字了 68 轮 目录&#xff1a; 谷歌云服务器&#xff08;Google Colab&#xff09; 整体训练流程 Python 代码 一 谷歌云服务器&#xff08;Google Colab&…

List<Object>集合对象属性拷贝工具类

目录 问题现象&#xff1a; 问题分析&#xff1a; 解决方法&#xff1a; 问题现象&#xff1a; 最近在项目中经常会使用到BeanUtils工具类来作对象的属性字段拷贝&#xff0c;但如果应用到List集合的话就需要遍历去操作了&#xff0c;如下&#xff1a; 打印结果&#xff1a; …

分类问题经典算法 | 二分类问题 | Logistic回归:公式推导

目录 一. Logistic回归的思想1. 分类任务思想2. Logistic回归思想 二. Logistic回归算法&#xff1a;线性可分推导 一. Logistic回归的思想 1. 分类任务思想 分类问题通常可以分为二分类&#xff0c;多分类任务&#xff1b;而对于不同的分类任务&#xff0c;训练的主要目标是…

小乌龟操作Git

1、选择小乌龟作为git客户端 最近使用idea来操作git的时候频频出现问题&#xff0c;要么是提交代码的时候少了某些文件&#xff0c;导致克隆下来无法运行&#xff0c;要么是提交速度太慢。 反正是在idea中操作git体验非常不好&#xff0c;所以决定来换一种方式来操作git。从网…

Java类加载器 和 双亲委派【详解】

一.类加载器&#xff1a; 由JDK提供的&#xff0c;用于加载一些资源文件到JVM内存里的一项技术。主要是加载class文件到内存&#xff0c;也可以加载一些资源文件。 2.JDK提供了三个类加载器&#xff1a; BootstrapClassLoader&#xff1a;引导类加载器&#xff0c; 是c语言编写…

[SUCTF 2019]EasyWeb --不会编程的崽

个人认为&#xff0c;这题还算有些东西。先来看源码 <?php function get_the_flag(){// webadmin will remove your upload file every 20 min!!!! $userdir "upload/tmp_".md5($_SERVER[REMOTE_ADDR]);if(!file_exists($userdir)){mkdir($userdir);}if(!empty…

Node服务器性能分析调优debug

node其实自带提供了性能分析工具&#xff1a;node --profile 但是它分析起来并不是很好用&#xff0c;于是chrome提供了另一种分析和debug工具&#xff1a;Chrome devtool 使用这个工具我们可以分析自己的Node项目调用堆栈里耗时较长的任务&#xff0c;对应去做缓存或者异步调用…