Java网络编程(二)

139 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第27天,点击查看活动详情

Java网络编程(二)

前言

上一篇文章 Java网络编程(一) 简单介绍了Java中Socket的使用, 我们可以发现其弊端非常明显, 就是一次只能处理一个连接, 属于同步阻塞IO(BIO), 我们可以通过开线程的方式来处理多连接请求(早期的tomcat就是这么玩的)

多线程版

在之前的代码基础上稍微改造一下, 改为多线程处理, 这样就能支持并发请求了

private final static ExecutorService executorService = Executors.newFixedThreadPool(16);

public static void main(String[] args) throws Exception {
    // 绑定端口
    ServerSocket server = new ServerSocket(8080);
    while (true) {
        // 阻塞等待接收连接请求
        Socket accept = server.accept();
        executorService.execute(() -> execute(accept));
    }
}

@SneakyThrows
private static void execute(Socket accept) {
    try (InputStream in = accept.getInputStream(); OutputStream out = accept.getOutputStream()) {
        byte[] bytes = new byte[1024];
        int len;
        while ((len = in.read(bytes)) != -1) {
            System.out.println(new String(bytes, 0, len));
            renderJson(out, "ha");
            break;
        }
    }
}

@SneakyThrows
private static void renderJson(OutputStream out, Object res) {
    out.write(write(res));
}

private static byte[] write(Object res){
    String jsonStr = JSONObject.toJSONString(res);
    return ("HTTP/1.1 200 \r\n" +
            "Content-Type:application/json;charset=utf-8;" +
            "Content-Length:" + jsonStr.getBytes().length + " \r\n\r\n" +
            jsonStr).getBytes();
}

但是这样的弊端也显而易见, 线程资源是非常宝贵的, 对于应用程序来说开线程也是非常重的操作, 即使使用了线程池来维护线程资源, 也会带来其他的一些问题

NIO 版

Java1.4后提供了NIO API, 全称java non-blocking IO, 目前高性能的网络服务通常基于NIO构建, 我们来尝试一下

private static Selector selector = null;

public static void main(String[] args) throws Exception {
    //  初始化socket
    ServerSocketChannel ssc = ServerSocketChannel.open();
    // 绑定端口
    ssc.bind(new InetSocketAddress(8080));
    // 设置非阻塞
    ssc.configureBlocking(false);
    // 初始化多路复用器
    selector = Selector.open();
    // 注册多路复用器, 监听accept操作
    ssc.register(selector, SelectionKey.OP_ACCEPT);
    while (true) {
        // 轮询多路复用器
        while (selector.select() > 0) {
            // 获取多路复用器返回的有效的SelectionKeys迭代器
            Iterator<SelectionKey> it = selector.selectedKeys().iterator();
            while (it.hasNext()) {
                SelectionKey sk = it.next();
                // 不会自动删除, 手动删除SelectionKey
                it.remove();
                if (sk.isAcceptable()) {
                    // 连接操作
                    acceptHandler(sk);
                } else if (sk.isReadable()) {
                    // 读操作
                    readHandler(sk);
                } else if (sk.isWritable()) {
                    // 写操作
                    writeHandler(sk);
                }
            }
        }
    }
}
@SneakyThrows
private static void acceptHandler(SelectionKey key) {
    ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
    // 获取客户端连接
    SocketChannel client = ssc.accept();
    // 设置非阻塞
    client.configureBlocking(false);
    // 分配ByteBuffer缓冲区
    ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
    //  注册多路复用器, 监听read操作, 并分配buffer
    client.register(selector, SelectionKey.OP_READ, buffer);
}
@SneakyThrows
private static void readHandler(SelectionKey key) {
    // 获取SelectionKey上的channel
    SocketChannel sc = (SocketChannel) key.channel();
    // 获取分配的buffer
    ByteBuffer buffer = (ByteBuffer) key.attachment();
    // 清空buffer
    buffer.clear();
    while (true) {
        int len;
        String content = "";
        // 读取请求数据
        while ((len = sc.read(buffer)) > 0) {
            // 翻转buffer, 指的是翻转数据读写操作的指针
            buffer.flip();
            while (buffer.hasRemaining()) {
                byte[] bytes = new byte[buffer.limit()];
                // 取出buffer中的数据
                buffer.get(bytes);
                content = new String(bytes);
            }
        }
        // 等于-1手动关闭通道, 否则会出现空轮询
        if (len < 0) {
            sc.close();
        }
        buffer.clear();
        sc.configureBlocking(false);
        buffer.put(SocketDemo.write("ha"));
        // 注册多路复用器, 监听write操作, 并分配buffer
        sc.register(selector, SelectionKey.OP_WRITE, buffer);
        break;
    }
}
@SneakyThrows
private static void writeHandler(SelectionKey key) {
    // 获取SelectionKey上的channel
    SocketChannel sc = (SocketChannel) key.channel();
    // 获取分配的buffer
    ByteBuffer buffer = (ByteBuffer) key.attachment();
    // 翻转
    buffer.flip();
    // 写服务器响应数据
    sc.write(buffer);
    buffer.clear();
    // 关闭连接
    sc.close();
}

基于NIO构建的网络服务, 即使是单线程也能提供不错的吞吐量, 主要得益于Selector(多路复用器)DirectByteBuffer(字节缓冲区堆外内存), 对比代码可以看出, 基于NIO构建网络服务远比BIO复杂的多, 后续我们介绍下使用Netty来构建网络服务, 体验一下Netty对Java NIO的优雅的封装