Java进阶作业三:使用非阻塞IO写一个EchoServer

写在开头

本篇文章的代码,是用New-IO的API实现非阻塞的EchoServer,所谓EchoSever,就是客户端给服务器发送一段消息,服务器收到之后将消息原样返回给客户端。

很多人将Java NIO单纯的理解为None-Blocking IO(非阻塞IO),实际上这并不完全正确,Java NIO是一套新的IO API,其提供了阻塞IO、非阻塞IO、事件驱动IO三种IO方式。创建的Channel,如果不显式的配置blockingMode参数,默认是阻塞的。示例代码中调用了Channel实例的configureBlocking方法,将IO模式设为非阻塞的。

非阻塞的IO: 非阻塞的IO意味着accept、connect、write、read操作均是非阻塞的,当调用相关的API时,会立即返回,不会阻塞。对于读操作,如果内核缓冲区的数据准备好了,会读取到数据,如果没有准备好,就直接返回,返回值为0表示没有读取到任何数据。对于写操作,调用write函数之后,方法直接返回,而此时内核并不一定已经将数据拷贝到内核缓冲区并且发送出去,真正的发送会在随后的某个时机。

服务端

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

public class NioGreetServer {

    private ServerSocketChannel serverChannel;
    private SocketChannel clientChannel;

    public void startServer(int port) throws Exception {
        serverChannel = ServerSocketChannel.open();
        serverChannel.bind(new InetSocketAddress(port));
        serverChannel.configureBlocking(false);
        System.out.println("server starting...");

        while (true) {
            // 等待连接的行为是非阻塞的,因此需要使用轮训来确保获得连接
            clientChannel = serverChannel.accept();
            if (Objects.nonNull(clientChannel)) {
                clientChannel.configureBlocking(false);
                break;
            } else {
                TimeUnit.SECONDS.sleep(2);
                System.out.println("waiting for connecting...");
            }
        }

        ByteBuffer buffer = ByteBuffer.allocate(1024);
        /* 因为使用了非阻塞的IO方式,读写都是非阻塞的.
         * 非阻塞的读意味着,读操作发生时,在内核缓冲区的数据还没准备好情况下,读操作会立即返回,而此时还没有读到任何数据
         * 非阻塞的写意味着,写操作发生时,可能内核还没有将数据从应用缓冲区拷贝到内核缓冲区就立即返回了。因而此时可能数据还没有真正发送
         * 需要用死循环来确保数据准备好之后能读到数据
         */
        while (true) {
            int read = clientChannel.read(buffer);
            if (read > 0) {
                String msg = readStringFromByteBuffer(buffer, read);
                System.out.println("receive from client:" + msg);

                byte[] bytes = msg.getBytes();
                buffer.put(bytes);
                buffer.flip();

                writeDataToChannel(clientChannel, buffer);
            } else {
                System.out.println("server端等待消息可读...");
                TimeUnit.SECONDS.sleep(2);
            }
        }
    }

    /**
     * 将buffer中的数据写入到Channel
     */
    public void writeDataToChannel(SocketChannel channel, ByteBuffer buffer) throws IOException {
        while (buffer.hasRemaining()) {
            channel.write(buffer);
        }

        buffer.clear();
    }

    /**
     * 将buffer中指定数量的字节转换为字符串
     */
    private String readStringFromByteBuffer(ByteBuffer buffer, int count) {
        byte[] msgBytes = new byte[count];
        for (int i = 0; i < count; i++) {
            msgBytes[i] = buffer.get(i);
        }
        buffer.clear();
        return new String(msgBytes);
    }


    public static void main(String[] args) throws Exception {
        NioGreetServer server = new NioGreetServer();
        server.startServer(8888);
    }
}

客户端

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner;
import java.util.concurrent.TimeUnit;

public class NioClient {

    private SocketChannel client;

    public void connect(String ip, int port) throws Exception {
        client = SocketChannel.open(new InetSocketAddress(ip, port));

        // 设置为非阻塞(默认是阻塞的IO)
        client.configureBlocking(false);

        ByteBuffer buffer = ByteBuffer.allocate(1024);
        while (true) {
            Scanner scanner = new Scanner(System.in);
            String msg = scanner.nextLine();
            byte[] bytes = msg.getBytes();
            buffer.put(bytes);
            // 将数据放到buffer之后,下一步需要将数据从buffer中读到Channel,因此需要flip一下
            buffer.flip();
            writeDataToChannel(client, buffer);
            while (true) {
                int read = client.read(buffer);
                if (read > 0) {
                    String resp = readStringFromByteBuffer(buffer, read);
                    System.out.println("server response:" + resp);
                    buffer.clear();
                    break;
                } else {
                    System.out.println("client端等待消息可读");
                    TimeUnit.SECONDS.sleep(2);
                }
            }
        }
    }

    /**
     * 将buffer中指定数量的字节转换为字符串
     */
    private String readStringFromByteBuffer(ByteBuffer buffer, int count) {
        byte[] msgBytes = new byte[count];
        for (int i = 0; i < count; i++) {
            msgBytes[i] = buffer.get(i);
        }

        return new String(msgBytes);
    }

    /**
     * 将buffer中的数据写入到Channel
     */
    public void writeDataToChannel(SocketChannel channel, ByteBuffer buffer) throws IOException {
        while (buffer.hasRemaining()) {
            channel.write(buffer);
        }

        buffer.clear();
    }

    public static void main(String[] args) throws Exception {
        NioClient client = new NioClient();
        client.connect("127.0.0.1", 8888);
    }
}

运行结果

posted @ 2021-05-27 13:10  陈玉林  阅读(130)  评论(0编辑  收藏  举报