IO/NIO交互模拟及渐进式实现

IO

IO Server

java 复制代码
public class SocketServer {
    public static void main(String[] args)  {
        //server编号和client编号对应,优缺点注释在server端
        //server1();
        //server2();
        server3();
    }

    /**
     * server1的缺点:
     * 1、accept()方法阻塞了线程,要等客户端连接后才能继续处理(一个客户端连接,对应一个服务端处理线程)
     * 2、当客户端与服务端完成一次交互后,程序就结束了
     * 3、流关闭代码比较臃肿、BufferedWriter的内容需要在末尾加'\n'
     * 4、交互的内容写死,不可以通过控制台输入
     * 5、端口号固定、ip固定、readLine读取一行等等
     */
    public static void server1() {
        ServerSocket serverSocket = null;
        BufferedReader bufferedReader = null;
        BufferedWriter bufferedWriter = null;
        try {
            serverSocket = new ServerSocket(8999);
            //阻塞至客户端连接成功
            Socket socket = serverSocket.accept();

            bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            //readLine会阻塞
            String s = bufferedReader.readLine();
            System.out.println("服务端接收到客户端的消息:" + s);

            bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            bufferedWriter.write("服务端发送消息给客户端\n");
            bufferedWriter.flush();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            //各个try catch 必须分开写,聚合成一个会导致一个流关闭异常,无法进入其他流的关闭流程
            if(serverSocket != null) {
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }

            if(bufferedWriter != null) {
                try {
                    bufferedWriter.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            //只需要关闭外层的缓冲区,其close方法内部关闭了了传入的流
            //如果不关闭,只是等待jvm兜底finally,会在不确定的时间内对资源造成占用(如果是文件读写会对文件描述符fd的占用)
            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }


    /**
     * server2优点:
     * 1、服务端和客户端可以任意次交互,可以连续发送消息
     * 2、优化了流关闭的写法
     *
     * server2的缺点:
     * 1、accept()方法阻塞了线程,要等客户端连接后才能继续处理(一个客户端连接,对应一个服务端处理线程)
     * 2、cpu空转
     * 3、ip固定等等
     * 4、一个服务端只能和一个客户端交互
     */
    public static void server2() {
        System.out.println("请输入端口号,并等待客户端连接...");
        try (BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in));
             ServerSocket serverSocket = new ServerSocket(Integer.valueOf(consoleReader.readLine()));
             Socket socket = serverSocket.accept();
             BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
             PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
        ){
            System.out.println("客户端已连接,开始和客户端交互...");
            //这个循环保证可以连续发送或者接收多条消息
            while (true) {
                if (bufferedReader.ready()) {
                    System.out.println("收到客户端的消息:" + bufferedReader.readLine());
                }

                if (consoleReader.ready()) {
                    printWriter.println(consoleReader.readLine());
                    printWriter.flush();
                }
            }
            //只能发一次接收一次
//            while (true) {
//                System.out.println("收到客户端的消息:" + bufferedReader.readLine());
//
//                printWriter.println(consoleReader.readLine());
//                printWriter.flush();
//            }

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * server3优点:
     * 一个服务端可以和一个客户端交互
     * 加上了线程池
     *
     * server3的缺点:
     * 1、accept()方法阻塞了线程,要等客户端连接后才能继续处理(一个客户端连接,对应一个服务端处理线程)
     * 2、cpu空转
     * 3、ip固定等等
     * 4、如果开启多个客户端,因System.in被服务端共享,所以服务端发送消息后,客户端会随机接收其中个别消息
     */
    public static void server3() {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(0, Runtime.getRuntime().availableProcessors() * 2,
                60L, TimeUnit.SECONDS,
                new SynchronousQueue<Runnable>());
        System.out.println("请输入端口号,并等待客户端连接...");
        try (BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in));
             ServerSocket serverSocket = new ServerSocket(Integer.valueOf(consoleReader.readLine()));
        ){
            //这个循环保证多个客户端连接
            while (true) {
                Thread.sleep(1000);
                Socket socket = serverSocket.accept();
                System.out.println("客户端已连接,开始和客户端交互...");

                pool.submit(new Thread(()->{
                    try(
                        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                        PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
                    ) {
                        while (true) {
                            Thread.sleep(1000);
                            if(bufferedReader.ready()) {
                                System.out.println("收到客户端"+ Thread.currentThread() +"的消息:" + bufferedReader.readLine());
                            }

                            if (consoleReader.ready()) {
                                printWriter.println(consoleReader.readLine());
                                printWriter.flush();
                            }
                        }
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    } finally {
                        if(socket != null) {
                            try {
                                socket.close();
                            } catch (IOException e) {
                                throw new RuntimeException(e);
                            }
                        }
                    }
                }));
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

IO Client

java 复制代码
public class SocketClient {
    public static void main(String[] args) {
        //client1();
        //client2();
        client3();
    }

    public static void client1() {
        Socket socket = null;
        BufferedReader bufferedReader = null;
        BufferedWriter bufferedWriter = null;
        try {
            socket = new Socket("127.0.0.1", 8999);

            bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            //换行符必不可少,不然服务端readLine()无法判断一行已经写完
            bufferedWriter.write("客户端发送消息给服务端\n");
            bufferedWriter.flush();

            bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String s = bufferedReader.readLine();
            System.out.println("客户端接收到服务端的消息:" + s);

        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if(socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }

            if(bufferedWriter != null) {
                try {
                    bufferedWriter.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }

            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }


    public static void client2() {
        System.out.println("请输入端口号:");
        try (
                BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in));
                Socket socket = new Socket("127.0.0.1", Integer.valueOf(consoleReader.readLine()));
                PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        ){
            System.out.println("连接成功,开始和服务端交互");
            //可以连续发送或者接收多条消息
            while (true) {
                Thread.sleep(1000);
                if (bufferedReader.ready()) {
                    System.out.println("收到服务端的消息:" + bufferedReader.readLine());
                }

                if (consoleReader.ready()) {
                    printWriter.println(consoleReader.readLine());
                    printWriter.flush();
                }
            }
            //只能发一次接收一次
//            while (true) {
//                printWriter.println(consoleReader.readLine());
//                printWriter.flush();
//
//                System.out.println("收到服务端的消息:" + bufferedReader.readLine());
//            }

        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    public static void client3() {
        System.out.println("请输入端口号:");
        try (
                BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in));
                Socket socket = new Socket("127.0.0.1", Integer.valueOf(consoleReader.readLine()));
                PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        ){
            System.out.println("连接成功,开始和服务端交互");
            //可以连续发送或者接收多条消息
            while (true) {
                if (bufferedReader.ready()) {
                    System.out.println("收到服务端的消息:" + bufferedReader.readLine());
                }

                if (consoleReader.ready()) {
                    printWriter.println(consoleReader.readLine());
                    printWriter.flush();
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

NIO

NIO Server

java 复制代码
public class SocketServer {
    public static void main(String[] args) {
        //server1();
        //server2();
        //server3();
        //server4();
        server5();
    }


    /**
     * 优点:NIO非阻塞方式,连接不会阻塞、读写不会阻塞
     * 缺点:
     * 1、当客户端与服务端完成一次交互后,程序就结束了
     * 2、交互的内容写死,不可以通过控制台输入
     * 3、cpu空转
     * 4、未使用selector多路复用器,其编程其实还是类似与BIO形式(服务端还是每个线程对应一个客户端)
     */
    private static void server1() {

        try (
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        ){
            serverSocketChannel.socket().bind(new InetSocketAddress(9999));
            serverSocketChannel.configureBlocking(false);
            //accept设置了非阻塞,因此需要放置在循环中使用,否则程序直接结束了
            while (true) {
                Thread.sleep(1000);
                SocketChannel socketChannel = serverSocketChannel.accept();
                if(socketChannel != null) {
                    System.out.println("客户端已连接,开始和客户端交互...");
                    socketChannel.configureBlocking(false);
                    ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                    socketChannel.read(readBuffer);
                    readBuffer.clear();

                    System.out.println("接收到客户端消息: " + new String(readBuffer.array(), "utf-8").trim());

                    ByteBuffer writeBuffer = ByteBuffer.wrap("发送消息给客户端".getBytes());
                    socketChannel.write(writeBuffer);
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * 改良:
     * 1、支持客户端和服务端一对一交互,多发
     * 缺点:
     * 1、不支持一个客户端对多个服务端
     * 2、如果开启多个客户端,因System.in被服务端共享,所以服务端发送消息后,客户端会随机接收其中个别消息
     * 3、cpu空转
     * 4、未使用selector多路复用器,其编程其实还是类似与BIO形式(服务端还是每个线程对应一个客户端)
     */
    private static void server2() {
        try (
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in));
        ){
            serverSocketChannel.socket().bind(new InetSocketAddress(9999));
            serverSocketChannel.configureBlocking(false);
            //accept设置了非阻塞,因此需要放置在循环中使用,否则程序直接结束了
            while (true) {
                Thread.sleep(1000);
                SocketChannel socketChannel = serverSocketChannel.accept();
                if(socketChannel != null) {
                    System.out.println("客户端已连接,开始和客户端交互...");
                    socketChannel.configureBlocking(false);
                    ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                    while (true) {
                        if(socketChannel.read(readBuffer) > 0) {
                            System.out.println("接收到客户端消息: " + new String(readBuffer.array(), "utf-8").trim());
                            readBuffer.clear();
                        }

                        if (consoleReader.ready()) {
                            ByteBuffer writeBuffer = ByteBuffer.wrap(consoleReader.readLine().getBytes());
                            socketChannel.write(writeBuffer);
                            writeBuffer.clear();
                        }
                        Thread.sleep(1000);
                    }
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 改良:
     * 1、支持一个客户端对多个服务端
     * 缺点:
     * 1、cpu空转
     * 2、未使用selector多路复用器,其编程其实还是类似与BIO形式(服务端还是每个线程对应一个客户端)
     */
    private static void server3() {
        try (
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in));
        ) {
            serverSocketChannel.socket().bind(new InetSocketAddress(9999));
            serverSocketChannel.configureBlocking(false);
            List<SocketChannel> socketChannels = new ArrayList<>();
            //accept设置了非阻塞,因此需要放置在循环中使用,否则程序直接结束了
            while (true) {
                Thread.sleep(1000);
                boolean ready = consoleReader.ready();
                byte[] bytes = null;
                if (ready) {
                    bytes = consoleReader.readLine().getBytes();
                }
                //调用一次accept再次使用会变成成null,除非有新的客户端连接
                SocketChannel socketChannel = serverSocketChannel.accept();
                if (socketChannel != null) {
                    socketChannels.add(socketChannel);
                    socketChannel.configureBlocking(false);
                    System.out.println("客户端已连接,开始和客户端交互...");
                }
                for (SocketChannel channel : socketChannels) {
                    ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                    if (channel.read(readBuffer) > 0) {
                        System.out.println("接收到客户端消息: " + new String(readBuffer.array(), "utf-8").trim());
                        readBuffer.clear();
                    }

                    if (ready) {
                        ByteBuffer writeBuffer = ByteBuffer.wrap(bytes);
                        channel.write(writeBuffer);
                        writeBuffer.clear();
                    }
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * 改良:
     * 1、支持一个客户端对多个服务端 (服务端发送消息,客户端都能收到,所有客户端发送消息服务端都能收到)
     * 2、相对与server3多线程方式,服务端只需要启动一个主线程即可与所有客户端交互,
     * 缺点:
     * 1、cpu空转
     * 2、未使用selector多路复用器,其编程其实还是类似与BIO形式(服务端还是每个线程对应一个客户端)
     */
    static final List<SocketChannel> socketChannels4 = new CopyOnWriteArrayList<>();
    private static void server4() {
        try (
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in));
        ) {
            serverSocketChannel.socket().bind(new InetSocketAddress(9999));
            serverSocketChannel.configureBlocking(false);
            //accept设置了非阻塞,因此需要放置在循环中使用,否则程序直接结束了
            while (true) {
                SocketChannel socketChannel = serverSocketChannel.accept();
                if (socketChannel != null) {
                    System.out.println("客户端已连接,开始和客户端交互...");
                    socketChannel.configureBlocking(false);
                    socketChannels4.add(socketChannel);
                }
                Iterator<SocketChannel> iterator = socketChannels4.iterator();
                boolean ready = consoleReader.ready();
                byte[] writeBytes = null;
                if (ready) {
                    writeBytes = consoleReader.readLine().getBytes();
                }

                while (iterator.hasNext()) {
                    ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                    SocketChannel channel = iterator.next();
                    if (channel.read(readBuffer) > 0) {
                        System.out.println("接收到客户端消息: " + new String(readBuffer.array(), "utf-8").trim());
                        readBuffer.clear();
                    }

                    if (ready) {
                        ByteBuffer writeBuffer = ByteBuffer.wrap(writeBytes);
                        channel.write(writeBuffer);
                        writeBuffer.clear();
                    }
                }
                Thread.sleep(1000);
            }
        } catch (IOException | InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 改良:
     * 1、支持一个客户端对多个服务端 (服务端发送消息,客户端都能收到,所有客户端发送消息服务端都能收到)
     * 2、相对与server3多线程方式,服务端只需要启动一个主线程即可与所有客户端交互,
     * 缺点:
     * 1、cpu空转
     * 2、未使用selector多路复用器,其编程其实还是类似与BIO形式(服务端还是每个线程对应一个客户端)
     */
    private static void server5() {

        try (ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
             Selector selector = Selector.open();
             BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in));
        ) {
            serverSocketChannel.socket().bind(new InetSocketAddress(9999));
            serverSocketChannel.configureBlocking(false);
            //将serverSocketChannel注册到selector, 并监听accept事件
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            List<SocketChannel> socketChannels = new ArrayList<>();
            while (true) {
                //非阻塞
                selector.select(1000);
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    if(selectionKey.isAcceptable()) {
                        SocketChannel socketChannel = serverSocketChannel.accept();
                        System.out.println("客户端已连接,开始和客户端交互...");
                        socketChannel.configureBlocking(false);
                        socketChannel.register(selector, SelectionKey.OP_READ);
                        socketChannels.add(socketChannel);
                    } else if(selectionKey.isReadable()) {
                        SocketChannel socketChannel = (SocketChannel)selectionKey.channel();
                        ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                        if (socketChannel.read(readBuffer) > 0) {
                            System.out.println("接收到客户端消息: " + new String(readBuffer.array(), "utf-8").trim());
                            readBuffer.clear();
                        }
                    }
                    iterator.remove();
                }

                if(consoleReader.ready()) {
                    byte[] bytes = consoleReader.readLine().getBytes();
                    for (SocketChannel socketChannel : socketChannels) {
                        ByteBuffer writeBuffer = ByteBuffer.wrap(bytes);
                        socketChannel.write(writeBuffer);
                        writeBuffer.clear();
                    }
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

}

NIO Client

java 复制代码
public class SocketClient {

    public static void main(String[] args) {
        //client1();
        //client2_3_4();
        client5();
    }

    private static void client1() {

        try (
            SocketChannel socketChannel = SocketChannel.open();
        ){
            socketChannel.configureBlocking(false);
            socketChannel.connect(new InetSocketAddress("127.0.0.1", 9999));
            while (!socketChannel.finishConnect()) {
                Thread.sleep(1000);
                System.out.println("正在连接客户端...");
            }

            ByteBuffer writeBuffer = ByteBuffer.wrap("向服务端发送消息".getBytes());
            socketChannel.write(writeBuffer);
            writeBuffer.clear();

            ByteBuffer readBuffer = ByteBuffer.allocate(1024);
            while (true) {
                if(socketChannel.read(readBuffer) > 0) {
                    System.out.println("接收到服务端消息: " + new String(readBuffer.array(), "utf-8").trim());
                    readBuffer.clear();
                    break;
                }
                Thread.sleep(1000);
            }

        } catch (IOException | InterruptedException e) {
            throw new RuntimeException(e);
        }
    }


    private static void client2_3_4() {
        try (
            SocketChannel socketChannel = SocketChannel.open();
            BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in));
        ){
            socketChannel.configureBlocking(false);
            socketChannel.connect(new InetSocketAddress("127.0.0.1", 9999));
            while (!socketChannel.finishConnect()) {
                Thread.sleep(1000);
                System.out.println("正在连接客户端...");
            }

            ByteBuffer readBuffer = ByteBuffer.allocate(1024);
            while (true) {
                if(socketChannel.read(readBuffer) > 0) {
                    System.out.println("接收到服务端消息: " + new String(readBuffer.array(), "utf-8").trim());
                    readBuffer.clear();
                }

                if (consoleReader.ready()) {
                    ByteBuffer writeBuffer = ByteBuffer.wrap(consoleReader.readLine().getBytes());
                    socketChannel.write(writeBuffer);
                    writeBuffer.clear();
                }
                Thread.sleep(1000);
            }
        } catch (IOException | InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    private static void client5() {
        try (
            SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9999));
            Selector selector = Selector.open();
            BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in));
        ){
            socketChannel.configureBlocking(false);
            socketChannel.register(selector, SelectionKey.OP_READ);
            while (true) {
                selector.select(1000);
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    if (selectionKey.isReadable()) {
                        SocketChannel channel = (SocketChannel) selectionKey.channel();
                        ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                        if(channel.read(readBuffer) > 0) {
                            System.out.println("接收到服务端消息: " + new String(readBuffer.array(), "utf-8").trim());
                            readBuffer.clear();
                        }
                    }
                    iterator.remove();
                }
                if (consoleReader.ready()){
                    ByteBuffer writeBuffer = ByteBuffer.wrap(consoleReader.readLine().getBytes());
                    socketChannel.write(writeBuffer);
                    writeBuffer.clear();
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

}
相关推荐
哎呦没7 分钟前
SpringBoot框架下的资产管理自动化
java·spring boot·后端
m0_571957582 小时前
Java | Leetcode Java题解之第543题二叉树的直径
java·leetcode·题解
魔道不误砍柴功4 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
NiNg_1_2344 小时前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
闲晨4 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
测开小菜鸟6 小时前
使用python向钉钉群聊发送消息
java·python·钉钉
P.H. Infinity7 小时前
【RabbitMQ】04-发送者可靠性
java·rabbitmq·java-rabbitmq
生命几十年3万天7 小时前
java的threadlocal为何内存泄漏
java
caridle7 小时前
教程:使用 InterBase Express 访问数据库(五):TIBTransaction
java·数据库·express
^velpro^7 小时前
数据库连接池的创建
java·开发语言·数据库