【JAVA】UDP与TCP套接字编程

news2024/10/6 18:26:24

目录

一、UDP数据报套接字编程

1、DatagramSocket API

2、DatagramPacket API

3、InetSocketAddress API

4、示例一

5、示例二

二、TCP流套接字编程

1、ServerSocket API

2、Socket API

3、TCP中的长短连接

4、示例一

5、示例二


一、UDP数据报套接字编程

1、DatagramSocket API

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

DatagramSocket的构造方法有:

方法签名方法说明
DatagramSocket()创建一个UDP数据报套接字的Socket,绑定到本机任意一个随机端口(一般用于客户端)
DatagramSocket(int port)创建一个UDP数据报套接字的Socket,绑定到本机指定的端口(一般用于服务端)

DatagramSocket的方法有:

方法签名方法说明
void receive(DatagramPacket p)从此套接字接收数据报(如果没有接收到数据报,该方法会阻塞等待)
void send(DatagramPacket p)从此套接字发送数据报(不会阻塞等待,直接发送)
void close()关闭此数据报套接字

2、DatagramPacket API

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

DatagramPacket的构造方法有:

方法签名方法说明
DatagramPacket(byte[] buf, int length)构造一个DatagramPacket以用来接收数据报,接收的数据保存在字节数组(第一个参数buf)中,接收指定长度(第二个参数length)
DatagramPacket(byte[] buf, int length, SocketAddress address)构造一个DatagramPacket以用来发送数据报,发送的数据为字节数组(第一个参数buf)中,从0到指定长度(第二个参数length)。address指定目的主机的IP和端口号

DatagramPacket的方法有:

方法签名方法说明
InetAddress getAddress()从接收的数据报中,获取发送端主机IP地址;或从发送的数据报中,获取接收端主机IP地址
int getPort()从接收的数据报中,获取发送端主机的端口号;或从发送的数据报中,获取接收端主机端口号
byte[] getData()获取数据报中的数据

构造UDP发送的数据报时,需要传入SocketAddress,该对象可以使用InetSocketAddress来创建。

3、InetSocketAddress API

InetSocketAddress(SocketAddress的子类)构造方法有:

方法签名方法说明
InetSocketAddress(InetAddress addr, int port)创建一个Socket地址,包含IP地址和端口号

4、示例一

通过客户端向服务端发送数据,服务端接收数据之后将数据处理一下然后返回客户端。

  • UDP客户端
import java.io.IOException;
import java.net.*;
import java.util.Scanner;

public class UdpEchoClient {
    private DatagramSocket socket = null;
    private String serverIp = "";
    private int serverport = 0;

    public UdpEchoClient(String ip,int port) throws SocketException {
        // 创建这个对象, 不能手动指定端口.
        socket = new DatagramSocket();
        // 由于 UDP 自身不会持有对端的信息. 就需要在应用程序里, 把对端的情况给记录下来.
        // 这里咱们主要记录对端的 ip 和 端口 .
        serverIp = ip;
        serverport = port;
    }

    public void start() throws IOException {
        System.out.println("客户端启动!");
        Scanner scanner = new Scanner(System.in);
        while(true){
            // 1. 从控制台读取数据, 作为请求
            System.out.print("->");
            String request = scanner.next();
            // 2. 把请求内容构造成 DatagramPacket 对象, 发给服务器.
            DatagramPacket packet = new DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName(serverIp),serverport);
            socket.send(packet);
            // 3. 尝试读取服务器返回的响应了.
            DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(responsePacket);
            // 4. 把响应, 转换成字符串, 并显示出来.
            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();
    }
}
  • UDP服务端 
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.Socket;
import java.net.SocketException;

public class UdpEchoServer {
    // 创建一个 DatagramSocket 对象. 后续操作网卡的基础.
    private DatagramSocket socket = null;

    public UdpEchoServer(int port) throws SocketException {
        // 这么写就是手动指定端口
        socket = new DatagramSocket(port);
        // 这么写就是让系统自动分配端口
        // socket = new DatagramSocket();
    }

    public void start() throws IOException {// 通过这个方法来启动服务器.
        System.out.println("服务器启动!");
        while(true){
            // 1. 读取请求并解析.
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
            socket.receive(requestPacket);
            // 当前完成 receive 之后, 数据是以 二进制 的形式存储到 DatagramPacket 中了.
            // 要想能够把这里的数据给显示出来, 还需要把这个二进制数据给转成字符串.
            String request = new String(requestPacket.getData(), 0, requestPacket.getLength());
            // 2. 根据请求计算响应(一般的服务器都会经历的过程)
            //    由于此处是回显服务器, 请求是啥样, 响应就是啥样.
            String response = process(request);
            // 3. 把响应写回到客户端.
            //    搞一个响应对象, DatagramPacket
            //    往 DatagramPacket 里构造刚才的数据, 再通过 send 返回.
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length, requestPacket.getSocketAddress());
            socket.send(responsePacket);
            // 4. 打印一个日志, 把这次数据交互的详情打印出来.
            System.out.printf("[%s:%d] req=%s, resp=%s\n", requestPacket.getAddress().toString(), requestPacket.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();
    }
}


我们对上面的服务端进行封装一下,做一个非常简单的翻译器出来:

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

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

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

        dict.put("dog", "小狗");
        dict.put("cat", "小猫");
        dict.put("pig", "小猪");
    }

    // 重写 process 方法, 在重写的方法中完成翻译的过程.
    // 翻译本质上就是 "查表"
    @Override
    public String process(String request){
        return dict.getOrDefault(request, "该词在词典中不存在!");
    }

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

5、示例二

构造一个展示服务端本地某个目录(BASE_PATH)的下一级子文件列表的服务:

  • 客户端先接收键盘输入,表示要展示的相对路径(相对BASE_PATH的路径)
  • 发送请求:将该相对路径作为数据报发送到服务端
  • 服务端接收并处理请求:根据该请求数据,作为本地目录的路径,列出下一级子文件及子文件夹
  • 服务端返回响应:遍历子文件和子文件夹,每个文件名一行,作为响应的数据报,返回给客户端
  • 客户端接收响应:简单的打印输出所有的响应内容,即文件列表。

为了解决空字符或长度不足数据丢失的问题,客户端服务端约定好统一的协议:这里简单的设计为ASCII结束字符\3表示报文结束。 以下为整个客户端服务端的交互执行流程:

以下为客户端与服务端的代码:

  • UDP服务端
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Array;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

public class UdpServer {
    private static final int PORT=8888;
    private static final String BASE_PATH = "E:/TMP";

    public static void main(String[] args) throws IOException{
        DatagramSocket  socket = new DatagramSocket(PORT);

        while(true){
            byte[] requestData = new byte[1024];
            DatagramPacket requestPacket = new DatagramPacket(requestData,requestData.length);

            System.out.println("------------------------------------------");
            System.out.println("等待接收UDP数据报...");
            socket.receive(requestPacket);

            System.out.printf("客户端IP:%s%n",requestPacket.getAddress().getHostAddress());
            System.out.printf("客户端端口号:%s%n", requestPacket.getPort());

            for(int i = 0;i< requestData.length;i++){
                byte b = requestData[i];
                if(b == '\3'){
                    String request = new String(requestData,0,i);

                    System.out.printf("客户端请求的文件列表路径为:%s%n", BASE_PATH + request);
                    File dir = new File(BASE_PATH + request);
                    File[] children = dir.listFiles();

                    StringBuilder response = new StringBuilder();

                    if(children != null){
                        for(File child : children){
                            response.append(child.getName()+'\n');
                        }
                    }

                    response.append("\3");
                    byte[] responseData = response.toString().getBytes(StandardCharsets.UTF_8);
                    System.out.println(response);
                    DatagramPacket responsePacket = new DatagramPacket(responseData,responseData.length,requestPacket.getSocketAddress());
                    socket.send(responsePacket);

                    break;
                }
            }
        }

    }
}
  • UDP客户端
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Array;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

public class UdpServer {
    private static final int PORT=8888;
    private static final String BASE_PATH = "E:/TMP";

    public static void main(String[] args) throws IOException{
        DatagramSocket  socket = new DatagramSocket(PORT);

        while(true){
            byte[] requestData = new byte[1024];
            DatagramPacket requestPacket = new DatagramPacket(requestData,requestData.length);

            System.out.println("------------------------------------------");
            System.out.println("等待接收UDP数据报...");
            socket.receive(requestPacket);

            System.out.printf("客户端IP:%s%n",requestPacket.getAddress().getHostAddress());
            System.out.printf("客户端端口号:%s%n", requestPacket.getPort());

            for(int i = 0;i< requestData.length;i++){
                byte b = requestData[i];
                if(b == '\3'){
                    String request = new String(requestData,0,i);

                    System.out.printf("客户端请求的文件列表路径为:%s%n", BASE_PATH + request);
                    File dir = new File(BASE_PATH + request);
                    File[] children = dir.listFiles();

                    StringBuilder response = new StringBuilder();

                    if(children != null){
                        for(File child : children){
                            response.append(child.getName()+'\n');
                        }
                    }

                    response.append("\3");
                    byte[] responseData = response.toString().getBytes(StandardCharsets.UTF_8);
                    System.out.println(response);
                    DatagramPacket responsePacket = new DatagramPacket(responseData,responseData.length,requestPacket.getSocketAddress());
                    socket.send(responsePacket);

                    break;
                }
            }
        }

    }
}


上述的代码中,我们为什么没有写close呢?

socket是文件描述符表中的一个表项。每次打开一个文件,就会占用一个位置。而文件描述符,是在pcb上的。(跟随进程的)

这个socket在整个程序运行过程中都是需要使用的(不能提前关闭)。当socket不需要使用的时候,程序就要结束了。进程结束,此时文件描述符表就会销毁了(PCB 都销毁了)。随着销毁的过程,套接字就被系统自动回收了。

那么什么时候会出现资源泄露呢?

代码中频繁的打开文件,但是不关闭。在一个进程的运行过程中,不断积累打开的文件,逐渐消耗掉文件描述符表里的内容,最终就消耗殆尽了。但是如果进程的生命周期很短,打开一下没多久就关闭了,谈不上泄露。
文件资源泄露这样的问题,在服务器这边是比较严重的,在客户端这边一般来说影响不大。

二、TCP流套接字编程

1、ServerSocket API

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

ServerSocket构造方法是:

方法签名方法说明
ServerSocket(int port)创建一个服务端流套接字Socket,并绑定到指定端口

ServerSocket的一般方法有:

方法签名方法说明
Socket accept()开始监听指定端口(创建时绑定的端口),有客户端连接后,返回一个服务端Socket对象,并基于该Socket建立与客户端的连接,否则阻塞等待
void close()关闭此套接字

2、Socket API

Socket是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket。

不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的。

Socket的构造方法有:

方法签名方法说明
Socket(String host, int port)创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的进程建立连接

Socket的一般方法有:

方法签名方法说明
InetAddress getInetAddress()返回套接字所连接的地址
InputStream getInputStream()返回此套接字的输入流
OutputStream getOutputStream()返回此套接字的输出流

3、TCP中的长短连接

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

  • 短连接:每次接收到数据并返回响应后,都关闭连接,即是短连接。也就是说,短连接只能一次收发数据。
  • 长连接:不关闭连接,一直保持连接状态,双方不停的收发数据,即是长连接。也就是说,长连接可以多次收发数据。

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

  • 建立连接、关闭连接的耗时:短连接每次请求、响应都需要建立连接,关闭连接;而长连接只需要第一次建立连接,之后的请求、响应都可以直接传输。相对来说建立连接,关闭连接也是要耗时的,长连接效率更高。
  • 主动发送请求不同:短连接一般是客户端主动向服务端发送请求;而长连接可以是客户端主动发送请求,也可以是服务端主动发。
  • 两者的使用场景有不同:短连接适用于客户端请求频率不高的场景,如浏览网页等。长连接适用于客户端与服务端通信频繁的场景,如聊天室,实时游戏等。

4、示例一

我们模仿UDP示例一来写一下TCP服务端与客户端:

  • TCP服务端
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;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

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("服务器启动!");
        ExecutorService service = Executors.newCachedThreadPool();
        while (true) {
            // 通过 accept 方法, 把内核中已经建立好的连接拿到应用程序中.
            // 建立连接的细节流程都是内核自动完成的. 应用程序只需要 "捡现成" 的.
            Socket clientSocket = serverSocket.accept();
            // 此处不应该直接调用 processConnection, 会导致服务器不能处理多个客户端.
            // 创建新的线程来调用更合理的做法.
            // 这种做法可行, 不够好
//            Thread t = new Thread(() -> {
//                processConnection(clientSocket);
//            });
//            t.start();

            // 更好一点的办法, 是使用线程池.
            service.submit(new Runnable() {
                @Override
                public void run() {
                    processConnection(clientSocket);
                }
            });
        }
    }

    // 通过这个方法, 来处理当前的连接.
    public void processConnection(Socket clientSocket) {
        // 进入方法, 先打印一个日志, 表示当前有客户端连上了.
        System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress(), clientSocket.getPort());
        // 接下来进行数据的交互.
        try (InputStream inputStream = clientSocket.getInputStream();
             OutputStream outputStream = clientSocket.getOutputStream()) {
            // 使用 try ( ) 方式, 避免后续用完了流对象, 忘记关闭.
            // 由于客户端发来的数据, 可能是 "多条数据", 针对多条数据, 就循环的处理.
            while (true) {
                Scanner scanner = new Scanner(inputStream);
                if (!scanner.hasNext()) {
                    // 连接断开了. 此时循环就应该结束
                    System.out.printf("[%s:%d] 客户端下线!\n", clientSocket.getInetAddress(), clientSocket.getPort());
                    break;
                }
                // 1. 读取请求并解析. 此处就以 next 来作为读取请求的方式. next 的规则是, 读到 "空白符" 就返回.
                String request = scanner.next();
                // 2. 根据请求, 计算响应.
                String response = process(request);
                // 3. 把响应写回到客户端.
                //    可以把 String 转成字节数组, 写入到 OutputStream
                //    也可以使用 PrintWriter 把 OutputStream 包裹一下, 来写入字符串.
                PrintWriter printWriter = new PrintWriter(outputStream);
                //    此处的 println 不是打印到控制台了, 而是写入到 outputStream 对应的流对象中, 也就是写入到 clientSocket 里面.
                //    自然这个数据也就通过网络发送出去了. (发给当前这个连接的另外一端)
                //    此处使用 println 带有 \n 也是为了后续 客户端这边 可以使用 scanner.next 来读取数据.
                printWriter.println(response);
                //    此处还要记得有个操作, 刷新缓冲区. 如果没有刷新操作, 可能数据仍然是在内存中, 没有被写入网卡.
                printWriter.flush();
                // 4. 打印一下这次请求交互过程的内容
                System.out.printf("[%s:%d] req=%s, resp=%s\n", clientSocket.getInetAddress(), clientSocket.getPort(),
                        request, response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                // 在这个地方, 进行 clientSocket 的关闭.
                // processConnection 就是在处理一个连接. 这个方法执行完毕, 这个连接也就处理完了.
                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();
    }
}
  • TCP客户端 
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

public class TcpEchoClient {
    private Socket socket = null;

    public TcpEchoClient(String serverIp, int serverPort) throws IOException {
        // 需要在创建 Socket 的同时, 和服务器 "建立连接", 此时就得告诉 Socket 服务器在哪里~~
        // 具体建立连接的细节, 不需要咱们代码手动干预. 是内核自动负责的.
        // 当我们 new 这个对象的时候, 操作系统内核, 就开始进行 三次握手 具体细节, 完成建立连接的过程了.
        socket = new Socket(serverIp, serverPort);
    }

    public void start() {
        // tcp 的客户端行为和 udp 的客户端差不多.
        // 都是:
        // 3. 从服务器读取响应.
        // 4. 把响应显示到界面上.
        Scanner scanner = new Scanner(System.in);
        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()) {
            PrintWriter writer = new PrintWriter(outputStream);
            Scanner scannerNetwork = new Scanner(inputStream);
            while (true) {
                // 1. 从控制台读取用户输入的内容
                System.out.print("-> ");
                String request = scanner.next();
                // 2. 把字符串作为请求, 发送给服务器
                //    这里使用 println, 是为了让请求后面带上换行.
                //    也就是和服务器读取请求, scanner.next 呼应
                writer.println(request);
                writer.flush();
                // 3. 读取服务器返回的响应.
                String response = scannerNetwork.next();
                // 4. 在界面上显示内容了.
                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();
    }
}

5、示例二

构造一个展示服务端本地某个目录(BASE_PATH)的下一级子文件列表的服务:

  • 客户端先接收键盘输入,表示要展示的相对路径(相对BASE_PATH的路径)
  • 发送请求:使用客户端Socket的输出流发送TCP报文。即输入的相对路径。
  • 服务端接收并处理请求:使用服务端Socket的输入流来接收请求报文,根据请求的路径,列出下一级子文件及子文件夹。
  • 服务端返回响应:使用服务端Socket的输出流来发送响应报文。即遍历子文件和子文件夹,每个文件名一行,返回给客户端。
  • 客户端接收响应:使用客户端Socket的输入流来接收响应报文。简单的打印输出所有的响应内容,即文件列表。

以下为服务端和客户端代码:

  • TCP服务端
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class TCPsockServer {
    //服务器socket要绑定固定的端口
    private static final int PORT = 8888;
    //本地文件目录要展示的根路径
    private static final String BASE_PATH = "E:/TMP";

    public static void main(String[] args) throws IOException {
        // 1.创建一个服务端ServerSocket,用于收发TCP报文
        ServerSocket server = new ServerSocket(PORT);
        // 不停的等待客户端连接
        while(true) {
            System.out.println("---------------------------------------------------");
            System.out.println("等待客户端建立TCP连接...");

            // 2.等待客户端连接,注意该方法为阻塞方法
            Socket socket = server.accept();
            System.out.printf("客户端IP:%s%n", socket.getInetAddress().getHostAddress());
            System.out.printf("客户端端口号:%s%n", socket.getPort());

            // 5.接收客户端的数据,需要从客户端Socket中的输入流获取
            InputStream is = socket.getInputStream();
            // 为了方便获取字符串内容,可以将以上字节流包装为字符流
            BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"));

            // 客户端请求只发送一行数据,我们也只需要读取一行
            String request = br.readLine();

            // 6.根据请求处理业务:本地目录根路径+请求路径,作为要展示的目录,列出下一级子文件
            //请求的文件列表目录
            System.out.printf("客户端请求的文件列表路径为:%s%n", BASE_PATH + request);
            File dir = new File(BASE_PATH + request);

            //获取下一级子文件,子文件夹
            File[] children = dir.listFiles();

            // 7.返回响应给客户端:通过客户端socket中的输出流发送响应数据
            OutputStream os = socket.getOutputStream();

            // 为了方便输出字符串作为发送的内容,可以将以上字节流包装为字符流
            PrintWriter pw = new PrintWriter(new OutputStreamWriter(os, "UTF-8"));
            // 7-1.返回的响应内容:每个文件及目录名称为一行
            if(children != null){
                for (File child : children) {
                    pw.println(child.getName());
                }
            }
            // 7-2.有缓冲区的IO操作,真正传输数据,需要刷新缓冲区
            pw.flush();
            // 7-3.双方关闭连接:服务端是关闭客户端socket连接
            socket.close();
        }
    }
}
  • TCP客户端
import java.io.*;
import java.net.Socket;
import java.util.Scanner;

public class TCPsockClient {
    //服务端IP或域名
    private static final String SERVER_HOST = "localhost";

    //服务端Socket进程的端口号
    private static final int SERVER_PORT = 8888;

    public static void main(String[] args) throws IOException {
        // 准备要发送的数据:这里调整为键盘输入作为发送的内容
        Scanner scanner = new Scanner(System.in);

        while(true) {
            System.out.println("---------------------------------------------------");
            System.out.println("请输入要展示的目录:");
            // 每输入新行(回车),就作为发送的TCP请求报文
            String request = scanner.nextLine();
            // 3.创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的进程建立连接
            Socket socket = new Socket(SERVER_HOST, SERVER_PORT);

            // 发送TCP数据,是通过socket中的输出流进行发送
            OutputStream os = socket.getOutputStream();

            // 为了方便输出字符串作为发送的内容,可以将以上字节流包装为字符流
            PrintWriter pw = new PrintWriter(new OutputStreamWriter(os, "UTF-8"));
            // 发送数据:
            pw.println(request);

            // 有缓冲区的IO操作,真正传输数据,需要刷新缓冲区
            pw.flush();

            // 接收返回的响应数据:通过socket中的输入流获取
            System.out.println("接收到服务端响应:");
            InputStream is = socket.getInputStream();

            // 为了方便获取字符串内容,可以将以上字节流包装为字符流
            BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
            String line;

            // 一直读取到流结束:TCP是基于流的数据传输,一定要服务端关闭Socket输出流才表示客户端接收的IO输入流结束
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
            // 双方关闭连接:客户端关闭socket连接
            socket.close();
        }
    }
}

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

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

相关文章

《ElementPlus 与 ElementUI 差异集合》el-select 显示下拉列表在 Cesium 场景中无法监听关闭

前言 仅在 Element UI 时有此问题&#xff0c;Element Plus 由于内部结构差异较大&#xff0c;不存在此问题。详见《el-select 差异点&#xff0c;如&#xff1a;高、宽、body插入等》&#xff1b; 问题 点击空白处&#xff0c;下拉列表可监听并关闭&#xff1b;但在 Cesium…

图解《图搜索算法》及代码实现

关注我&#xff0c;持续分享逻辑思维&管理思维&#xff1b; 可提供大厂面试辅导、及定制化求职/在职/管理/架构辅导&#xff1b; 有意找工作的同学&#xff0c;请参考博主的原创&#xff1a;《面试官心得--面试前应该如何准备》&#xff0c;《面试官心得--面试时如何进行自…

Jmeter之Beanshell详解

一、 Beanshell概念 Beanshell: BeanShell是一种完全符合Java语法规范的脚本语言,并且又拥有自己的一些语法和方法;BeanShell是一种松散类型的脚本语言(这点和JS类似);BeanShell是用Java写成的,一个小型的、免费的、可以下载的、嵌入式的Java源代码解释器,具有对象脚本语言特性…

LayuiMini使用时候初始化模板修改(下载源码)

忘记加了 下载 地址 &#xff1a; layui-mini: layuimini&#xff0c;后台admin前端模板&#xff0c;基于 layui 编写的最简洁、易用的后台框架模板。只需提供一个接口就直接初始化整个框架&#xff0c;无需复杂操作。 LayuiMini使用时候初始化模板官网给的是&#xff1a; layu…

用Excel做一个功能完备的仓库管理系统

1 基本设计思路 用到的Excel技术&#xff1a;sumif, vlookup, 表格(table)。基本思路&#xff1a;在有基础的商品、仓库等信息的情况下&#xff0c;对商品的每一个操作都有对应的单据&#xff0c;然后再汇总统计。标识&#xff1a;为了在不同的维度统计数量&#xff0c;各单据…

国产FTP文件传输服务器需要具备哪些关键特性?

国产FTP文件传输服务器是指根据中国国内信息技术创新&#xff08;信创&#xff09;的要求和标准&#xff0c;自主研发的文件传输服务器软件。这类软件旨在替代传统的FTP服务器&#xff0c;以更好地适应国产化和信息安全的需要。国产FTP文件传输服务器通常需要具备以下要求&…

图书租赁系统-扣费服务

resources中添加moment.js文件。 然后引入moment.js文件&#xff1a; <script src"/js/moment.js"></script>借阅结束时间选完后changeDate事件&#xff1a; $("input[nameendTime]").datetimepicker({format: "yyyy-mm-dd hh:ii",…

Linux:进程与计划任务

文章目录 Linux&#xff1a;进程与计划任务一、进程1、进程是什么2、进程状态 二、列出进程命令1、查看静态的进程统计信息——“ps”Play1&#xff1a;“ps aux”Play2:ps -elf 2、查看静态的进程统计信息——“top”段首解析进程信息区解释 三、运行与终止进程3.1、运行进程3…

为什么要写技术方案?

技术方案是为研究解决各类技术问题&#xff0c;有针对性&#xff0c;系统性的提出的方法、应对措施及相关对策。技术方案设计是一个技术开发者必备的能力&#xff0c;特别是对于高级、资深、架构师等角色。技术方案设计不仅能够帮助我们明确需求&#xff0c;规划架构&#xff0…

2024新算法爱情进化算法(LEA)和经典灰狼优化器(GWO)进行无人机三维路径规划设计实验

简介&#xff1a; 2024新算法爱情进化算法&#xff08;LEA&#xff09;和经典灰狼优化器&#xff08;GWO&#xff09;进行无人机三维路径规划设计实验。 无人机三维路径规划的重要意义在于确保飞行安全、优化飞行路线以节省时间和能源消耗&#xff0c;并使无人机能够适应复杂环…

windows系统下python开发工具安装

一. 简介 前一篇文章学习了安装 python解释器&#xff0c;文章如下&#xff1a; windows系统下python解释器安装-CSDN博客 本文来学习如何下载安装 python开发工具 PyCharm。 二. python开发工具 PyCharm下载安装 1. PyCharm官网 PyCharm开发工具 PyCharm为 python代码…

STM32的GPIO输入和输出函数详解

系列文章目录 STM32单片机系列专栏 C语言术语和结构总结专栏 文章目录 1. GPIO模式 2. GPIO输出 2.1 RCC 2.2 GPIO 3. 代码示例 3.1 RCC时钟 3.2 GPIO初始化 3.3 GPIO输出函数 3.4 推挽输出和开漏输出 4. GPIO输入 4.1 输入模式 4.2 数据读取函数 5. C语言语法 1…

Mudem,打造私密安全、高效稳定的私人空间

Mudem 是 Codigger 平台中的一个关键组件&#xff0c;它提供基础通讯服务&#xff0c;确保不同类型的机器之间可以进行安全和高效的连接。它其设计理念在于将本地机器、公有云以及私有云上的设备无缝地整合为一个可远程在线访问的工作站&#xff08;Workstation&#xff09;。这…

Android SDK Manager安装Google Play Intel x86 Atom_64 System Image依赖问题

Package Google Play Intel x86 Atom_64 System Image,Android API R, revision 2 depends on SDK Platform Android R Preview, revision 2 问题 一开始以为网络还有依赖包没有勾选&#xff0c;尝试了很多次&#xff0c;勾选这边报错对应的license即可。此时点击一下其他licen…

【LeetCode】---118.杨辉三角

一、题目解析&#xff1a; 二、知识回顾&#xff1a; 1.二维数组&#xff1a; 2. C语言中的二维数组访问方式和vector二维数组的访问&#xff0c; 不同区别&#xff1a; &#xff08;1&#xff09;表面是一样的&#xff0c;但底层不同&#xff01; &#xff08;2&#xff09;静…

“PowerInfer:消费级GPU上的高效大语言模型推理引擎“

PowerInfer是由上海交通大学IPADS实验室开发的一个高效大语言模型&#xff08;LLM&#xff09;推理引擎&#xff0c;专为个人电脑&#xff08;PC&#xff09;上的消费者级GPU设计。它通过利用LLM推理中的高局部性&#xff0c;实现了快速且资源消耗低的模型推理&#xff0c;这一…

【插件】IDEA 热部署插件 JRebel

1 搜索安装插件 JRebel 2 选中Team URL 1、在上面的框中输入激活的url地址 https://jrebel.qekang.com/{GUID} http://jrebel-license.jiweichengzhu.com/{GUID} GUID生成工具 Create GUID online (guidgen.com) 备用 404 Not Found (ofmonkey.com) 如果上述激活地址不能…

蓝桥杯2024年第十五届省赛真题-小球反弹

以下两个解法感觉都靠谱&#xff0c;并且网上的题解每个人答案都不一样&#xff0c;目前无法判断哪个是正确答案。 方法一&#xff1a;模拟 代码参考博客 #include <iostream> #include <cmath> #include <vector>using namespace std;int main() {const i…

第48期|GPTSecurity周报

GPTSecurity是一个涵盖了前沿学术研究和实践经验分享的社区&#xff0c;集成了生成预训练Transformer&#xff08;GPT&#xff09;、人工智能生成内容&#xff08;AIGC&#xff09;以及大语言模型&#xff08;LLM&#xff09;等安全领域应用的知识。在这里&#xff0c;您可以找…

从0到1—POC编写基础篇(一)

POC编写基础篇 POC的概念 在网络安全领域中&#xff0c;POC的概念是指"Proof of Concept"&#xff0c;也被称为"攻击验证"。它是指安全研究人员或黑客用来证明某个漏洞、弱点或安全问题存在的实证或演示。 网络安全研究人员经常通过开发POC来展示一个漏洞的…