一、Socket编程

1、简介

Socket(套接字)是一种抽象概念,它用于在不同计算机之间进行通信。

2、Java中的套接字编程

Socket类与IO类常常结合使用。通过Socket类建立网络连接后,可以使用它提供的输入输出流对象来进行数据的读取和写入。

3、Socket和TCP/UDP的关系

Socket是实现网络编程的工具,而TCP/UDP是网络传输协议。

Socket编程常用于实现基于TCP或UDP的网络通信。TCP和UDP是在传输层上的协议,而Socket是在应用层与传输层之间的一个接口。通过Socket,应用程序可以使用TCP或UDP协议与其他计算机进行通信。

在Java中,可以使用Socket类来创建TCP连接,也可以使用DatagramSocket类来创建UDP连接。通过Socket类和DatagramSocket类,Java程序员可以方便地实现TCP和UDP通信,并进行数据传输。

4、聊天室案例

聊天室案例

Socket编程

  • Socket:用于建立网络连接;在连接成功时,应用程序两端都会产生一个Socket实例
  • ServerSocket:用于服务端,主要完成两个工作:

    1、向服务端操作系统申请服务端口,客户端就是通过这个端口与ServerSocket建立连接。

    2、监听端口,一旦一个客户端建立连接,会立即返回一个Socket。通过这个Socket就可以和该客户端交互了。

TCP套接字的通信模型

服务器端:

/**
 * @document: 聊天室V01:服务器端
 * @Author:SmallG
 * @CreateTime:2023/8/14+15:12
 */

public class Server {
    //创建一个ServerSocket对象,建立通信服务
    private ServerSocket serverSocket;

    /**
     * 初始化服务端
     */
    public Server() {
        try {
            System.out.println("正在启动服务器...");
        /*
        实例化ServerSocket,在new的时候需要使用端口号
        端口号是一个数字,取值范围是0~65535,使用时需避免常用端口
        一般是6000以前不要使用
         */
            serverSocket = new ServerSocket(8088);
            System.out.println("服务器启动完毕!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 服务器开始工作的方法
     */
    public void start() {
        //总机的通话设备
        try {
            System.out.println("等待客户端连接...");
            //serverSocket.accept()这句话会造成阻塞,会等待客户端连接
            //直到一个客户端发来了连接  此时accept()方法会返回一个Socket实例
            //通过Socket实例就可以和别的Socket通信了
            Socket socket = serverSocket.accept();
            System.out.println("一个客户端连接上了!"); //如果没有人连,这句话不会打印
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Server server = new Server();
        server.start();


    }
}

客户端:

/**
 * @document: 客户端
 * @Author:SmallG
 * @CreateTime:2023/8/14+15:46
 */

public class Client {
    // 声明客户端工具
    private Socket socket;


    //初始化客户端
    public Client() {
        try {
            System.out.println("正在连接服务器端...");
             /*
             实例化客户端工具(Socket对象)
             */

            /*
            参数1:要连接的服务器地址,可以是域名或Ip地址
            参数2:服务器开放的端口号
             */
            socket = new Socket("localhost", 8088);
            System.out.println("与服务器建立了连接");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Client client = new Client();
    }
}

获取网络输入流和网络输出流

使用 Socket 通讯时,可以通过 Socket 获取输入流与输出流,从而实现数据信息的交互。

  • InputStream getInputStream():该方法用于返回此套接字的输入流
  • OutputStream getOutputStream():该方法用于返回此套接字的输出流

获取网络输入流和网络输出流

服务端:

/**
 * @document: 聊天室V02:服务器端
 * @Author:SmallG
 * @CreateTime:2023/8/14+15:12
 */

public class Server {
    //创建一个ServerSocket对象,建立通信服务
    private ServerSocket serverSocket;

    /**
     * 初始化服务端
     */
    public Server() {
        try {
            System.out.println("正在启动服务器...");
        /*
        实例化ServerSocket,在new的时候需要使用端口号
        端口号是一个数字,取值范围是0~65535,使用时需避免常用端口
        一般是6000以前不要使用
         */
            serverSocket = new ServerSocket(8088);
            System.out.println("服务器启动完毕!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 服务器开始工作的方法
     */
    public void start() {
        //总机的通话设备
        try {
            System.out.println("等待客户端连接...");
            //serverSocket.accept()这句话会造成阻塞,会等待客户端连接
            //直到一个客户端发来了连接  此时accept()方法会返回一个Socket实例
            //通过Socket实例就可以和别的Socket通信了
            Socket socket = serverSocket.accept();
            System.out.println("一个客户端连接上了!"); //如果没有人连,这句话不会打印
            //低级流,负责从socket中读取对方发来的字节数据
            InputStream in = socket.getInputStream();
            //高级流,负责数据转换,将字节流转成字符流
            InputStreamReader isr = new InputStreamReader(in, "UTF-8");
            //高级流,负责加快读取字符效率
            BufferedReader br = new BufferedReader(isr);
            //读取内容
            String s = br.readLine();
            System.out.println("客户端说:" + s);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Server server = new Server();
        server.start();


    }
}

客户端:

/**
 * @document: 客户端
 * @Author:SmallG
 * @CreateTime:2023/8/14+15:46
 */

public class Client {
    // 声明客户端工具
    private static Socket socket;


    //初始化客户端
    public Client() {
        try {
            System.out.println("正在连接服务器端...");
            /*
             实例化客户端工具(Socket对象)
            */

            /*
            参数1:要连接的服务器地址,可以是域名或Ip地址
            参数2:服务器开放的端口号
             */
            socket = new Socket("localhost", 8088);
            System.out.println("与服务器建立了连接");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 客户端开始工作的方法,放服务端发送消息
     */
    public static void start() {
        try {
            //低级流,将字节等信息通过网络发送给对方
            OutputStream out = socket.getOutputStream();
            //高级流,负责衔接字节流于字符流(转换流:字节流转化成字符流)
            OutputStreamWriter osw = new OutputStreamWriter(out, "UTF-8");
            //缓冲流
            BufferedWriter bw = new BufferedWriter(osw);
            //高级流,负责按行写出字符串数据,true表示自动刷新
            PrintWriter pw = new PrintWriter(bw,true);
            //
            pw.println("你好吗,服务器端?");

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Client client = new Client();
        client.start();
    }
}

close方法

调用 close方法关闭 Socket 以释放系统资源。关闭了套接字,也会同时关闭由此获取的输入流与输出流。

close方法

客户端循环发送信息给客户端:

服务器端:

/**
 * @document: 聊天室V03:服务器端
 * @Author:SmallG
 * @CreateTime:2023/8/14+15:12
 */

public class Server {
    //创建一个ServerSocket对象,建立通信服务
    private ServerSocket serverSocket;

    /**
     * 初始化服务端
     */
    public Server() {
        try {
            System.out.println("正在启动服务器...");
        /*
        实例化ServerSocket,在new的时候需要使用端口号
        端口号是一个数字,取值范围是0~65535,使用时需避免常用端口
        一般是6000以前不要使用
         */
            serverSocket = new ServerSocket(8088);
            System.out.println("服务器启动完毕!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 服务器开始工作的方法
     */
    public void start() {
        Socket socket = null;
        //总机的通话设备
        try {
            System.out.println("等待客户端连接...");
            //serverSocket.accept()这句话会造成阻塞,会等待客户端连接
            //直到一个客户端发来了连接  此时accept()方法会返回一个Socket实例
            //通过Socket实例就可以和别的Socket通信了
            socket = serverSocket.accept();
            System.out.println("一个客户端连接上了!"); //如果没有人连,这句话不会打印
            //低级流,负责从socket中读取对方发来的字节数据
            InputStream in = socket.getInputStream();
            //高级流,负责数据转换,将字节流转换为字符流
            InputStreamReader isr = new InputStreamReader(in, "UTF-8");
            //高级流,负责加快读取字符效率
            BufferedReader br = new BufferedReader(isr);
            //读取内容
            String s = null;
            //客户端在发送的时候会发送一个中断信号,在服务器端会接收到一个null值
            while ((s = br.readLine()) != null) {
                //如果为null则说明客户端断开了连接
                System.out.println("客户端说:" + s);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Server server = new Server();
        server.start();
    }
}

客户端:

/**
 * @document: 客户端
 * @Author:SmallG
 * @CreateTime:2023/8/14+15:46
 */

public class Client {
    // 声明客户端工具
    private static Socket socket;


    //初始化客户端
    public Client() {
        try {
            System.out.println("正在连接服务器端...");
            /*
             实例化客户端工具(Socket对象)
            */

            /*
            参数1:要连接的服务器地址,可以是域名或Ip地址
            参数2:服务器开放的端口号
             */
            socket = new Socket("localhost", 8088);
            System.out.println("与服务器建立了连接");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 客户端开始工作的方法,放服务端发送消息
     */
    public static void start() {
        try {
            //低级流,将字节等信息通过网络发送给对方
            OutputStream out = socket.getOutputStream();
            //高级流,负责衔接字节流于字符流(转换流:字符流转化成字节流)
            OutputStreamWriter osw = new OutputStreamWriter(out, "UTF-8");
            //缓冲流
            BufferedWriter bw = new BufferedWriter(osw);
            //高级流,负责按行写出字符串数据,true表示自动刷新
            PrintWriter pw = new PrintWriter(bw, true);
            //输出数据
            //创建一个接收键盘的输入
            Scanner scanner = new Scanner(System.in);
            while (true) {
                String line = scanner.nextLine();
                if (line.equalsIgnoreCase("bye") || line.equalsIgnoreCase("exit")) {
                    break;
                }
                pw.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                /**
                 * 关闭所有的流,同时会给服务器发送一个中断信号
                 */
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Client client = new Client();
        client.start();
    }
}

多线程实现聊天室:

若想使一个服务端可以支持多客户端连接,我们需要解决以下问题:

  • 循环调用 accept 方法侦听客户端的连接
  • 使用线程来处理单一客户端的数据交互

多线程实现聊天室

服务端

/**
 * @document: 聊天室V04:服务器端
 * @Author:SmallG
 * @CreateTime:2023/8/14+15:12
 */

public class Server {
    //创建一个ServerSocket对象,建立通信服务
    private ServerSocket serverSocket;

    /**
     * 初始化服务端
     */
    public Server() {
        try {
            System.out.println("正在启动服务器...");
        /*
        实例化ServerSocket,在new的时候需要使用端口号
        端口号是一个数字,取值范围是0~65535,使用时需避免常用端口
        一般是6000以前不要使用
         */
            serverSocket = new ServerSocket(8088);
            System.out.println("服务器启动完毕!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 服务器开始工作的方法
     */
    public void start() {
        try {
            while (true) {
                System.out.println("等待客户端连接...");
                Socket socket = serverSocket.accept();
                System.out.println("一个客户端连接了");
                //启动线程与客户端交互
                ClientHandler clientHandler = new ClientHandler(socket);
                new Thread(clientHandler).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Server server = new Server();
        server.start();
    }

    /**
     * 创建线程任务
     * 服务器端有一个客户端连接之后 服务器端单独启动一个线程与客户端进行交互
     */
    private class ClientHandler implements Runnable {
        private Socket socket;

        public ClientHandler(Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            try {
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in);
                BufferedReader br = new BufferedReader(isr);
                String s = null;
                String name = Thread.currentThread().getName();
                while ((s = br.readLine()) != null) {
                    System.out.println(name + ": 客户端说:" + s);
                }

            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

客户端

/**
 * @document: 客户端
 * @Author:SmallG
 * @CreateTime:2023/8/14+15:46
 */

public class Client {
    // 声明客户端工具
    private static Socket socket;

    //初始化客户端
    public Client() {
        try {
            System.out.println("正在连接服务器端...");
            /*
             实例化客户端工具(Socket对象)
            */

            /*
            参数1:要连接的服务器地址,可以是域名或Ip地址
            参数2:服务器开放的端口号
             */
            socket = new Socket("localhost", 8088);
            System.out.println("与服务器建立了连接");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 客户端开始工作的方法,放服务端发送消息
     */
    public static void start() {
        try {
            //低级流,将字节等信息通过网络发送给对方
            OutputStream out = socket.getOutputStream();
            //高级流,负责衔接字节流于字符流(转换流:字符流转化成字节流)
            OutputStreamWriter osw = new OutputStreamWriter(out, "UTF-8");
            //缓冲流
            BufferedWriter bw = new BufferedWriter(osw);
            //高级流,负责按行写出字符串数据,true表示自动刷新
            PrintWriter pw = new PrintWriter(bw, true);
            //输出数据
            //创建一个接收键盘的输入
            Scanner scanner = new Scanner(System.in);
            while (true) {
                String line = scanner.nextLine();
                if (line.equalsIgnoreCase("bye")
                        || line.equalsIgnoreCase("exit")) {
                    break;
                }
                pw.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                /**
                 * 关闭所有的流,同时会给服务器发送一个中断信号
                 */
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Client client = new Client();
        client.start();
    }
}

5、获取本地地址和端口号

如果服务器想给客户端发消息,需要获取客户端地址和端口号

通过 Socket 获取远端的地址以及端口号

  • int getPort():用于获取远端使用的端口号
  • InetAddress getInetAddress():该方法用于获取套接字绑定的远端地址
/**
 * @document: 通过Socket获取本地地址
 * @Author:SmallG
 * @CreateTime:2023/8/15+9:13
 */

public class Demo01 {
    public static void main(String[] args) {
        try {
            //创建一个Socket对象,可以指定访问地址
            Socket socket = new Socket("127.0.0.1", 8088);
            InetAddress add = socket.getLocalAddress();//得到本地的地址
            //使用getCanonicalHostName()方法 获取IP地址
            //得到的是完全的地址名(域名)
            System.out.println(add.getCanonicalHostName());
            //getHostAddress()也是获取Ip地址,返回一个文本字符串
            System.out.println(add.getHostAddress());
            System.out.println(socket.getLocalPort()); //通过socket获取端口号
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

聊天室实现服务器端向客户端发送信息

服务器端:

/**
 * @document: 聊天室V05:服务器端
 * 服务器端向客户端发消息
 * @Author:SmallG
 * @CreateTime:2023/8/14+15:12
 */

public class Server {
    //创建一个ServerSocket对象,建立通信服务
    private ServerSocket serverSocket;

    /**
     * 初始化服务端
     */
    public Server() {
        try {
            System.out.println("正在启动服务器...");
        /*
        实例化ServerSocket,在new的时候需要使用端口号
        端口号是一个数字,取值范围是0~65535,使用时需避免常用端口
        一般是6000以前不要使用
         */
            serverSocket = new ServerSocket(8088);
            System.out.println("服务器启动完毕!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 服务器开始工作的方法
     */
    public void start() {
        try {
            while (true) {
                System.out.println("等待客户端连接...");
                Socket socket = serverSocket.accept();
                System.out.println("一个客户端连接了");
                //启动线程与客户端交互
                ClientHandler clientHandler = new ClientHandler(socket);
                new Thread(clientHandler).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Server server = new Server();
        server.start();
    }

    /**
     * 创建线程任务
     * 服务器端有一个客户端连接之后 服务器端单独启动一个线程与客户端进行交互
     */
    private class ClientHandler implements Runnable {
        private Socket socket;
        private String host; //表示客户端的IP地址信息

        public ClientHandler(Socket socket) {
            this.socket = socket;
            //通过socket来获取远程的客户端地址信息
            this.host = socket.getInetAddress().getHostAddress();
        }

        @Override
        public void run() {
            try {
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in);
                BufferedReader br = new BufferedReader(isr);

                OutputStream os = socket.getOutputStream();
                OutputStreamWriter osr = new OutputStreamWriter(os, "UTF-8");
                BufferedWriter bw = new BufferedWriter(osr);
                PrintWriter pw = new PrintWriter(bw, true);
                String s = null;
                String name = Thread.currentThread().getName();
                while ((s = br.readLine()) != null) {
                    System.out.println(name + ": 客户端说:" + s);
                    //把消息发回给客户端
                    pw.println(host + "说" + s);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

客户端:

/**
 * @document: 客户端
 * @Author:SmallG
 * @CreateTime:2023/8/14+15:46
 */

public class Client {
    // 声明客户端工具
    private static Socket socket;

    //初始化客户端
    public Client() {
        try {
            System.out.println("正在连接服务器端...");
            /*
             实例化客户端工具(Socket对象)
            */

            /*
            参数1:要连接的服务器地址,可以是域名或Ip地址
            参数2:服务器开放的端口号
             */
            socket = new Socket("localhost", 8088);
            System.out.println("与服务器建立了连接");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 客户端开始工作的方法,放服务端发送消息
     */
    public static void start() {
        try {
            //低级流,将字节等信息通过网络发送给对方
            OutputStream out = socket.getOutputStream();
            //高级流,负责衔接字节流于字符流(转换流:字符流转化成字节流)
            OutputStreamWriter osw = new OutputStreamWriter(out, "UTF-8");
            //缓冲流
            BufferedWriter bw = new BufferedWriter(osw);
            //高级流,负责按行写出字符串数据,true表示自动刷新
            PrintWriter pw = new PrintWriter(bw, true);
            //输出数据

            InputStream ins = socket.getInputStream();
            InputStreamReader isr = new InputStreamReader(ins,"utf-8");
            BufferedReader br = new BufferedReader(isr);
            //创建一个接收键盘的输入
            Scanner scanner = new Scanner(System.in);
            while (true) {
                String line = scanner.nextLine();
                if (line.equalsIgnoreCase("bye")
                        || line.equalsIgnoreCase("exit")) {
                    break;
                }
                pw.println(line);

                String s = br.readLine();
                System.out.println(s);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                /**
                 * 关闭所有的流,同时会给服务器发送一个中断信号
                 */
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Client client = new Client();
        client.start();
    }
}

服务器向所有客户端发送消息

创建集合,集合中包含所有的客户端,然后遍历集合发消息

聊天室

服务器端:

/**
 * @document: 聊天室V07
 * 服务器端
 * 服务器端向所有客户端发消息
 * @Author:SmallG
 * @CreateTime:2023/8/14+15:12
 */

public class Server {
    //创建一个ServerSocket对象,建立通信服务
    private ServerSocket serverSocket;
    //创建一个存放所有输出流的集合,用于群发消息
    private List<PrintWriter> allPw = new ArrayList<>();

    /**
     * 初始化服务端
     */
    public Server() {
        try {
            System.out.println("正在启动服务器...");
        /*
        实例化ServerSocket,在new的时候需要使用端口号
        端口号是一个数字,取值范围是0~65535,使用时需避免常用端口
        一般是6000以前不要使用
         */
            serverSocket = new ServerSocket(8088);
            System.out.println("服务器启动完毕!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 服务器开始工作的方法
     */
    public void start() {
        try {
            while (true) {
                System.out.println("等待客户端连接...");
                Socket socket = serverSocket.accept();
                System.out.println("一个客户端连接了");
                //启动线程与客户端交互
                ClientHandler clientHandler = new ClientHandler(socket);
                new Thread(clientHandler).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Server server = new Server();
        server.start();
    }

    /**
     * 创建线程任务
     * 服务器端有一个客户端连接之后 服务器端单独启动一个线程与客户端进行交互
     */
    private class ClientHandler implements Runnable {
        private Socket socket;
        private String host; //表示客户端的IP地址信息

        public ClientHandler(Socket socket) {
            this.socket = socket;
            //通过socket来获取远程的客户端地址信息
            this.host = socket.getInetAddress().getHostAddress();
        }

        @Override
        public void run() {
            try {
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in);
                BufferedReader br = new BufferedReader(isr);

                OutputStream os = socket.getOutputStream();
                OutputStreamWriter osr = new OutputStreamWriter(os, "UTF-8");
                BufferedWriter bw = new BufferedWriter(osr);
                PrintWriter pw = new PrintWriter(bw, true);
                String s = null;

                //把输出流存入all集合中
                allPw.add(pw);
                String name = Thread.currentThread().getName();
                while ((s = br.readLine()) != null) {
                    System.out.println(name + ": 客户端说:" + s);
                    //把消息发回给客户端
                    for (PrintWriter pw1 : allPw) {
                        pw1.println(host + "说" + s);
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

客户端:

/**
 * @document: 客户端
 * @Author:SmallG
 * @CreateTime:2023/8/14+15:46
 */

public class Client {
    // 声明客户端工具
    private static Socket socket;

    //初始化客户端
    public Client() {
        try {
            System.out.println("正在连接服务器端...");
            /*
             实例化客户端工具(Socket对象)
            */

            /*
            参数1:要连接的服务器地址,可以是域名或Ip地址
            参数2:服务器开放的端口号
             */
            socket = new Socket("localhost", 8088);
            System.out.println("与服务器建立了连接");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 客户端开始工作的方法,放服务端发送消息
     */
    public void start() {
        try {
            //开始的时候立即启动线程,读取服务器发来的消息线程
            ClientHandler handler = new ClientHandler();
            Thread t = new Thread(handler);
            t.setDaemon(true);
            t.start();

            //低级流,将字节等信息通过网络发送给对方
            OutputStream out = socket.getOutputStream();
            //高级流,负责衔接字节流于字符流(转换流:字符流转化成字节流)
            OutputStreamWriter osw = new OutputStreamWriter(out, "UTF-8");
            //缓冲流
            BufferedWriter bw = new BufferedWriter(osw);
            //高级流,负责按行写出字符串数据,true表示自动刷新
            PrintWriter pw = new PrintWriter(bw, true);

            //创建一个接收键盘的输入
            Scanner scanner = new Scanner(System.in);
            while (true) {
                String line = scanner.nextLine();
                if (line.equalsIgnoreCase("bye")
                        || line.equalsIgnoreCase("exit")) {
                    break;
                }
                pw.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                /**
                 * 关闭所有的流,同时会给服务器发送一个中断信号
                 */
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Client client = new Client();
        client.start();
    }

    //当前线程负责接收服务器发来的消息
    private class ClientHandler implements Runnable {

        @Override
        public void run() {
            //输出数据
            try {
                InputStream ins = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(ins, "utf-8");
                BufferedReader br = new BufferedReader(isr);
                String line = null;
                while ((line = br.readLine()) != null) {
                    System.out.println(line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

二、反射(Reflection)

1、概念

反射机制是程序在运行时能够获得自身的信息。

反射的作用:

  • 可以动态查看一个类或对象的所有属性和方法,包括使用private修饰的属性和方法
  • 可以动态加载类
  • 可以动态的创建一个类的实例
  • 可以动态调用一个对象的方法

反射

2、反射优缺点

反射的优点:

  • 反射允许我们在程序运行期间获得类的信息并操作一个类中的方法,因此可以提高代码的灵活性和扩展性
  • 反射是Java中很多高级特性的基础,比如后面会介绍的注解、动态代理等特性
  • 在很多框架中,对反射技术的使用也非常多,比如大名鼎鼎的Spring框架、各类ORM框架(关系映射)、RPC框架(服务与实现)等

反射的缺点:

  • 反射的代码的可读性和可维护性都比较低
  • 反射的代码执行的性能低

3、反射API

Java提供了反射相关的API,核心是java.lang.Class类,用于加载类和获取类的相关信息。

Java 反射 API 位于 java.lang.reflect 包中,主要包括:

  • Constructor类:用来描述一个类的构造方法
  • Field类:用来描述一个类的成员变量,属性
  • Method类:用来描述一个类的方法
  • Modifier类:用来描述类内各元素的修饰符
  • Array:用来对数组进行操作

4、class类

(1)概述

在程序运行时,JVM首先检查要加载的类对应的Class对象是否已经创建。如果没有创建,JVM会根据类名查找.class文件,将其加载到内存中,并创建相应的Class对象。

需要注意,Class类的构造器被设计为私有的,也就是说开发者不能主动创建Class类的对象。Class类的对象仅能由JVM创建。

class类

(2)类加载器

类加载器(Class Loader)是JVM的一个子系统,负责将class文件加载到内存中,然后在堆中创建一个代表这个类的Class对象,作为方法区中类数据的访问入口。

类加载器

/**
 * @document: java动态加载一个类:四种方式
 * 最常用的是后两种
 * @Author:SmallG
 * @CreateTime:2023/8/15+11:37
 */

public class Demo01 {
    public static void main(String[] args) throws Exception {
        //1、通过对象的getClass()方法
        Person person = new Person();
        Class cla1 = person.getClass(); //Object提供的方法
        //可以获取类名
        System.out.println(cla1.getName()); //获取类全名(类的限定名:包名+类名) day07.reflect.Person
        System.out.println(cla1.getSimpleName()); //类名的简称 Person
        //2、通过类的.class
        Class cla2 = Person.class;
        System.out.println(cla2.getName()); //day07.reflect.Person

        //3、通过Class类的静态方法forName()加载类的对象
        //开发人员主动加载,作为java内存被动加载
        Class cla3 = Class.forName("day07.reflect.Person");
        System.out.println(cla3.getSimpleName()); //Person

        //4、通过java的类的加载器ClassLoader加载类对象
        //通过当前的类获取类的加载器
        ClassLoader classLoader = Demo01.class.getClassLoader();
        Class cla4 = classLoader.loadClass("day07.reflect.Person");
        System.out.println(cla4.getName()); //day07.reflect.Person
    }
}

(4)动态创建对象

通过反射在程序运行的过程中动态创建一个类的对象。getDeclaredConstructor().newInstance()方法

/**
 * @document:
 * @Author:SmallG
 * @CreateTime:2023/8/15+14:05
 */

public class Demo02 {
    public static void main(String[] args) throws Exception {
        //1、动态加载类
        ClassLoader loader = Demo02.class.getClassLoader(); //获取类的加载器
        Class cla = loader.loadClass("day07.reflect.Person");
        //2、动态创建对象
        Person person1 = (Person) cla.newInstance(); //已过时
        System.out.println(person1);  //Person{name='null', age=null}

        //从java 9 开始使用,通过调用类的默认构造方法
        Person person2 = (Person) cla.getDeclaredConstructor().newInstance(); //调用已经声明的构造器
        System.out.println(person2); //Person{name='null', age=null}

        Constructor c = cla.getDeclaredConstructor(String.class, Integer.class);
        System.out.println(c.getName());
        //如果构造方法不是public声明的 都需要授权才能访问
        c.setAccessible(true); //授权

        //调用有参的构造方法 创建对象
        Person person3 = (Person) cla.getDeclaredConstructor(String.class, Integer.class).newInstance("tom", 20);
        System.out.println(person3); //Person{name='tom', age=20}
    }
}

(5)动态调用方法

/**
 * @document: 通过反射动态调用方法
 * @Author:SmallG
 * @CreateTime:2023/8/15+14:34
 */

public class Demo03 {
    public static void main(String[] args) throws Exception {
        //实例化对象 采用类的加载器
        Class cla = Demo03.class.getClassLoader().loadClass("day07.reflect.Person");

        Person person = (Person) cla.getDeclaredConstructor().newInstance();
        //获取方法
        Method method1 = cla.getMethod("hello"); //只能获取public修饰的方法
        method1.setAccessible(true);

        //调用方法
        method1.invoke(person); //通过person对象调用无参的方法

        //获取有参的方法 需要声明参数类型
        Method method2 = cla.getDeclaredMethod("say", String.class);

        //调用有参方法
        method2.invoke(person, "Tom");

        //获取多个参数并且有返回值的方法
        Method method3 = cla.getMethod("sum", Integer.class, Integer.class);

        // 调用多个参数,并且有返回值的方法
        Integer sum = (Integer) method3.invoke(person, 1, 2);
        System.out.println(sum);
    }
}

5、注解

可以将各种信息(例如类、方法、变量等)与程序元素相关联。Java注解可以用于许多目的,例如:

  • 为代码提供元数据信息,例如文档、版本号等
  • 为编译器提供指示,例如抑制警告、生成代码等
  • 为运行时提供指示,例如启用事务、配置参数等
/**
 * @document: 创建接口
 * @Author:SmallG
 * @CreateTime:2023/8/15+15:20
 */
//Target是元注解,用于修饰注解的注解
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
public @interface MyAnnotation {
    String value() default "name"; //属性

    //声明一个注解的成员属性
    String name() default "Tom"; //为注解的属性赋一个默认值
}
/**
 * @document: 利用注解修饰类、方法、属性
 * @Author:SmallG
 * @CreateTime:2023/8/15+15:23
 */
//为注解的属性赋值,这个值默认情况下看不了,只能通过反射看,注解不参与程序运行,只对程序起到约束作用
//如果同时给注解的其他属性赋值,value不可省略
@MyAnnotation()
public class MyClass {

    @MyAnnotation(value = "属性",name = "Spike")
    private Integer age;

    @MyAnnotation()
    public void hello() {
        System.out.println("Hello World");
    }
}

通过反射获取类的注解

需要在注解中添加元注解:@Retention(RetentionPolicy.RUNTIME)

/**
 * @document: 通过反射获取类的注解
 * @Author:SmallG
 * @CreateTime:2023/8/15+15:45
 */

public class Demo01 {
    public static void main(String[] args) throws Exception {
        //获取类对象
        Class cla = Demo01.class.getClassLoader().loadClass("day07.annotation.MyClass");
        //获取类上的注解
        MyAnnotation an1 = (MyAnnotation) cla.getAnnotation(MyAnnotation.class);
        //获取注解的值
        System.out.println(an1.value());

        //获取方法
        Method hello = cla.getMethod("hello");
        //获取方法上的注解
        MyAnnotation an2 = hello.getAnnotation(MyAnnotation.class);
        System.out.println(an2.value() + ":" + an2.name());

        //获取属性上的注解,先获取属性
        Field age = cla.getDeclaredField("age");
        MyAnnotation ann3 = age.getAnnotation(MyAnnotation.class);
        System.out.println(ann3.value() + ":" + ann3.name());

    }
}

三、练习

1 解决聊天室案例的线程安全问题

在聊天室案例中,服务端存在着一个线程安全隐患:服务端使用一个ArrayList集合保存所有已建立连接的客户端的输出流对象。对该集合同时存在着3种操作:

  • 添加:当一个客户端建立连接时,将该客户端的输出流对象存入集合
  • 查询:每当一个客户端发送消息时,遍历该集合,使用输出流对象向所有客户端推送消息
  • 删除:当一个客户端断开连接时,从集合中删除该客户端对应的输出流对象

如果有2个线程,A线程删除某个输出流对象的同时,B线程使用该输出流对象推送消息,则可能出现线程安全问题。

请完善聊天室案例代码,解决上述问题。

提示:应用JUC中提供的Lock API。

服务器端:

/**
 * @document: 聊天室V07
 * 服务器端
 * 服务器端向所有客户端发消息
 * @Author:SmallG
 * @CreateTime:2023/8/14+15:12
 */

public class Server {
    //创建一个ServerSocket对象,建立通信服务
    private ServerSocket serverSocket;

    //服务端使用的读写锁
    private ReadWriteLock readWriteLock;
    private Lock readLock;
    private Lock writeLock;

    //创建一个存放所有输出流的集合,用于群发消息,广播消息
    private List<PrintWriter> allOut = new ArrayList<>();

    /**
     * 初始化服务端
     */
    public Server() {
        try {
            //初始化读写锁
            this.readWriteLock = new ReentrantReadWriteLock();
            this.readLock = readWriteLock.readLock();
            this.writeLock = readWriteLock.writeLock();

            System.out.println("正在启动服务器...");

            serverSocket = new ServerSocket(8088);
            System.out.println("服务器启动完毕!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 服务器开始工作的方法
     */
    public void start() {
        try {
            while (true) {
                System.out.println("等待客户端连接...");
                Socket socket = serverSocket.accept();
                System.out.println("一个客户端连接了");
                //启动线程与客户端交互
                ClientHandler clientHandler = new ClientHandler(socket);
                new Thread(clientHandler).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Server server = new Server();
        server.start();
    }

    /**
     * 创建线程任务
     * 服务器端有一个客户端连接之后 服务器端单独启动一个线程与客户端进行交互
     */
    private class ClientHandler implements Runnable {
        private Socket socket;
        String host; //表示客户端的IP地址信息
        PrintWriter pw = null;

        public ClientHandler(Socket socket) {
            this.socket = socket;
            //通过socket来获取远程的客户端地址信息
            this.host = socket.getInetAddress().getHostAddress();
        }
        /**
         * 向所有客户端推送消息的方法
         */
        private void sendMessage(String message){
            System.out.println(this.host+":客户端说:"+message);
            //获得读锁
            readLock.lock();
            for (PrintWriter o :allOut){
                o.println(this.host+"说:"+message);
            }
            //释放读锁
            readLock.unlock();
        }

        @Override
        public void run() {
            try {
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in);
                BufferedReader br = new BufferedReader(isr);

                OutputStream os = socket.getOutputStream();
                OutputStreamWriter osr = new OutputStreamWriter(os, "UTF-8");
                BufferedWriter bw = new BufferedWriter(osr);
                pw = new PrintWriter(bw, true);

                //获得写锁
                writeLock.lock();
                allOut.add(pw);
                //广播该客户端上线的操作
                sendMessage(host+"上线了,当前在线人数:"+allOut.size());
                writeLock.unlock();
                String message = null;
                while ((message = br.readLine()) != null) {
                   sendMessage(message);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                //获得写锁
                writeLock.lock();
                //处理客户端断开连接后的操作
                allOut.remove(pw);
                //广播该客户端下线
                sendMessage(host+"下线了,当前在线人数:"+allOut.size());
                //释放写锁
                writeLock.unlock();
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

客户端:

/**
 * @document: 客户端
 * @Author:SmallG
 * @CreateTime:2023/8/14+15:46
 */

public class Client {
    // 声明客户端工具
    private static Socket socket;

    //初始化客户端
    public Client() {
        try {
            System.out.println("正在连接服务器端...");
            /*
            参数1:要连接的服务器地址,可以是域名或Ip地址
            参数2:服务器开放的端口号
             */
            socket = new Socket("localhost", 8088);
            System.out.println("与服务器建立了连接");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 客户端开始工作的方法,放服务端发送消息
     */
    public void start() {
        try {
            //开始的时候立即启动线程,读取服务器发来的消息线程
            ClientHandler handler = new ClientHandler();
            Thread t = new Thread(handler);
            t.setDaemon(true);
            t.start();

            //低级流,将字节等信息通过网络发送给对方
            OutputStream out = socket.getOutputStream();
            //高级流,负责衔接字节流于字符流(转换流:字符流转化成字节流)
            OutputStreamWriter osw = new OutputStreamWriter(out, "UTF-8");
            //缓冲流
            BufferedWriter bw = new BufferedWriter(osw);
            //高级流,负责按行写出字符串数据,true表示自动刷新
            PrintWriter pw = new PrintWriter(bw, true);

            //创建一个接收键盘的输入
            Scanner scanner = new Scanner(System.in);
            while (true) {
                String line = scanner.nextLine();
                if (line.equalsIgnoreCase("bye")
                        || line.equalsIgnoreCase("exit")) {
                    break;
                }
                pw.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                /**
                 * 关闭所有的流,同时会给服务器发送一个中断信号
                 */
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Client client = new Client();
        client.start();
    }

    //当前线程负责接收服务器发来的消息
    private class ClientHandler implements Runnable {

        @Override
        public void run() {
            //输出数据
            try {
                InputStream ins = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(ins, "utf-8");
                BufferedReader br = new BufferedReader(isr);
                String line = null;
                while ((line = br.readLine()) != null) {
                    System.out.println(line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

2 完成聊天室的私聊功能

私聊功能是指,客户端之间可以实现一对一的聊天。

服务器端程序启动后,将等待客户端连接,界面效果如下图所示:

练习1

客户端程序运行时,需要用户先输入昵称。用户输入昵称之后,提示用户可以开始聊天。

服务端需要检查客户端发来的昵称是否已被占用,如果已被占用,需要向客户端返回提示信息,如下图所示:

练习2

客户端发送的消息默认为公聊消息,向所有客户端广播,包括发出该消息的客户端,界面效果如下图所示:

练习3

客户端可以使用其他客户端可以通过输入类似“\Jerry:你好”这样的字样和昵称为“jerry”的客户端私聊,界面效果如下图所示:

练习4

如果某客户端程序停止运行,其他客户端程序可以接收到消息并显示。例如,昵称为“Jerry”的客户端停止运行,昵称为“Tom”的客户端的界面效果如图所示:

练习5

提示:

1、服务端可以使用Map集合保存所有客户端的输出流对象,键为客户端的名称,值为该客户端的输出流对象;

2、客户端启动后先输入昵称,未输入完成前不显示客户端发来的消息,如昵称重复,需更换其他昵称;

3、客户端流程图如下所示:

练习6

4、服务端流程图如下所示:

练习7

服务端:

/**
 * @document: 聊天室V07
 * 服务器端
 * 服务器端向所有客户端发消息
 * @Author:SmallG
 * @CreateTime:2023/8/14+15:12
 */

public class Server {
    private ServerSocket serverSocket;
    // 服务端使用的读写锁
    private ReadWriteLock readWriteLock;
    private Lock readLock;
    private Lock writeLock;
    // 所有客户端输出流,key为用户的昵称,value为该用户的输出流
    private Map<String,PrintWriter> allOut;
    public Server(){
        try {
            // 初始化读写锁
            this.readWriteLock = new ReentrantReadWriteLock();
            this.readLock = readWriteLock.readLock();
            this.writeLock = readWriteLock.writeLock();
            allOut = new HashMap<String,PrintWriter>();
            System.out.println("正在启动服务端...");
            serverSocket = new ServerSocket(8088);
            System.out.println("服务端启动完毕!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
     * 服务端开始工作的方法
     */
    public void start(){
        try {
            while(true) {
                System.out.println("等待客户端链接...");
                Socket socket = serverSocket.accept();
                System.out.println("一个客户端链接了!");
                //启动一个线程与该客户端交互
                ClientHandler clientHandler = new ClientHandler(socket);
                Thread t = new Thread(clientHandler);
                t.start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
     * 将输出流存入共享集合,与下面两个方法互斥,保证同步安全
     * @param out
     */
    private void addOut(String nickName,PrintWriter out){
        writeLock.lock();
        allOut.put(nickName,out);
        writeLock.unlock();
    }
    /**
     * 将给定输出流从共享集合删除
     * @param nickName
     */
    private void removeOut(String nickName){
        writeLock.lock();
        allOut.remove(nickName);
        writeLock.unlock();
    }
    /**
     * 将消息转发给所有客户端
     * @param message
     */
    private void sendMessage(String message,boolean isSystemMessage){
        readLock.lock();
        for(PrintWriter o : allOut.values()){
            if (isSystemMessage){
                o.println("系统消息 => " +message);
            }else{
                o.println("公聊消息 => " + message);
            }
        }
        readLock.unlock();
    }
    /**
     * 将消息发送给指定昵称的客户端
     * @param nickName
     * @param message
     * @return true-存在该用户 false-不存在该用户
     */
    private boolean sendMessageToOne(String nickName,String message){
        boolean flag = true;
        readLock.lock();
        PrintWriter out = allOut.get(nickName);
        if(out!=null){
            out.println(message);
        }else{
            flag =false;
        }
        readLock.unlock();
        return flag;
    }
    public static void main(String[] args) {
        Server server = new Server();
        server.start();
    }
    /**
     * 定义线程任务
     * 目的是让一个线程完成与特定客户端的交互工作
     */
    private class ClientHandler implements Runnable{
        private Socket socket;
        // 该客户端的昵称
        private String nickName;
        public ClientHandler(Socket socket){
            this.socket = socket;
        }
        public void run(){
            // 声明输出流引用,便于finally语句块中访问
            PrintWriter pw = null;
            try{
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in, "UTF-8");
                BufferedReader br = new BufferedReader(isr);
                OutputStream out = socket.getOutputStream();
                OutputStreamWriter osw = new OutputStreamWriter(out,"UTF-8");
                BufferedWriter bw = new BufferedWriter(osw);
                pw = new PrintWriter(bw,true);
                //先获取该用户昵称
                this.nickName = getNickName(pw, br);
                addOut(nickName,pw);
                Thread.sleep(100);
                //广播该客户端上线的操作
                sendMessage(this.nickName+"上线了,当前在线人数:" + allOut.size(), true);
                String message = null;
                while ((message = br.readLine()) != null) {
                    //首先查看是不是私聊
                    if(message.startsWith("\\")){
                        /*
                         * 私聊格式:\昵称:内容
                         */
                        //找到:的位置
                        int index = message.indexOf(":");
                        if(index>=0){
                            //截取昵称
                            String name = message.substring(1,index);
                            //截取内容
                            String info = message.substring(index+1,message.length());
                            //拼接内容
                            info = nickName+"对你说:"+info;
                            //发送私聊信息给指定用户
                            boolean flag = sendMessageToOne(name, info);
                            if (!flag){
                                sendMessageToOne(this.nickName,"用户:"+name+"不存在");
                            }
                            //发送完私聊后就不在广播了。
                            continue;
                        }
                    }
                    /*
                     * 遍历所有输出流,将该客户端发送的信息转发给所有客户端
                     * 需要同步
                     */
                    sendMessage(nickName+"说:"+message, false);
                }
            }catch(Exception e){
                // 暂不处理
            }finally {
                removeOut(this.nickName);
                //广播该客户端下线
                sendMessage(this.nickName+"下线了,当前在线人数:" + allOut.size(), true);
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        /**
         * 获取该用户的昵称
         * @return
         */
        private String getNickName(PrintWriter pw, BufferedReader br) throws IOException {
            try {
                //读取客户端发送过来的昵称
                String nickName = br.readLine();
                while(true){
                    //若昵称为空发送失败代码
                    if(nickName.trim().equals("")){
                        pw.println("FAIL");
                    }
                    //若昵称已经存在发送失败代码
                    if(allOut.containsKey(nickName)){
                        pw.println("FAIL");
                        //若成功,发送成功代码,并返回昵称
                    }else{
                        pw.println("OK");
                        return nickName;
                    }
                    //若改昵称被占用,等待用户再次输入昵称
                    nickName = br.readLine();
                }
            } catch (Exception e) {
                throw  e;
            }
        }
    }
}

客户端:

/**
 * @document: 客户端
 * @Author:SmallG
 * @CreateTime:2023/8/14+15:46
 */

/**
 * 聊天室客户端
 */
public class Client {
    private Socket socket;
    public Client() {
        try {
            System.out.println("正在连接服务端...");
            socket = new Socket("localhost", 8088);
            System.out.println("与服务端建立连接!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
     * 客户端开始工作的方法
     */
    public void start() {
        try {
            //创建Scanner读取用户输入内容
            Scanner scanner = new Scanner(System.in);
            //首先输入昵称
            inputNickName(scanner);
            //启动读取服务端发送过来消息的线程
            ServerHandler handler = new ServerHandler();
            Thread t = new Thread(handler);
            t.setDaemon(true);
            t.start();
            OutputStream out = socket.getOutputStream();
            OutputStreamWriter osw = new OutputStreamWriter(out, "UTF-8");
            BufferedWriter bw = new BufferedWriter(osw);
            PrintWriter pw = new PrintWriter(bw, true);
            while(true) {
                String line = scanner.nextLine();
                if("exit".equalsIgnoreCase(line)){
                    break;
                }
                pw.println(line);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        Client client = new Client();
        client.start();
    }
    /**
     * 输入昵称
     */
    private void inputNickName(Scanner scanner)throws Exception{
        //定义昵称
        String nickName = null;
        //创建输出流
        PrintWriter pw = new PrintWriter(
                new OutputStreamWriter(
                        socket.getOutputStream(),"UTF-8")
                ,true);
        //创建输入流
        BufferedReader br = new BufferedReader(
                new InputStreamReader(
                        socket.getInputStream(),"UTF-8")
        );
        /*
         * 循环以下操作
         * 输入用户名,并上传至服务器,等待服务器回应,若昵称可用就结束循环,否则通知用户后
         * 重新输入昵称
         */
        while(true){
            System.out.println("请输入昵称:");
            nickName = scanner.nextLine();
            if(nickName.trim().equals("")){
                System.out.println("昵称不能为空");
            }else{
                pw.println(nickName);
                String pass = br.readLine();
                if(pass!=null&&!pass.equals("OK")){
                    System.out.println("昵称已被占用,请更换。");
                }else{
                    System.out.println("你好!"+nickName+",开始聊天吧!");
                    break;
                }
            }
        }
    }
    /**
     * 该线程负责接收服务端发送过来的消息
     */
    private class ServerHandler implements Runnable{
        public void run(){
            //通过socket获取输入流读取服务端发送过来的消息
            try {
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in,"UTF-8");
                BufferedReader br = new BufferedReader(isr);
                String line;
                //循环读取服务端发送过来的每一行字符串
                while((line = br.readLine())!=null){
                    System.out.println(line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
最后修改:2023 年 08 月 16 日
如果觉得我的文章对你有用,请随意赞赏