基于Java NIO 的数传服务器项目预研demo

9 篇文章 2 订阅

项目背景:

数传服务器项目,需对 与外界交互的 报文进行加/解密,提高数传服务器的报文处理能力;

因为数传服务器需随 便携设备 一起工作,所以并不能简单的依靠增加设备来提高的报文处理能力,因此项目改进点只能是提高单个设备的报文处理能力

相关说明:

数传服务器 属于 服务端功能,对接收到的报文进行 加/解密 后,再转发;

项目 起初是用BIO的方式 完成,考虑使用 NIO 的方式进行改进。

demo说明:

  • NioServer :封装了 NIO 的实现,构造 NioServer 服务对象时,可将 报文处理逻辑 通过 FunctionInterface 传入
  • NioClient :实现了 完整的 NIO 客户端逻辑,但demo中仅仅作为测试角色,给 NIO Server 发送消息

NIO 的原理与基本概念,参考:

  • https://www.cnblogs.com/huangjianping/p/8328464.html
  • https://www.cnblogs.com/nullzx/p/8932977.html
  • https://blog.csdn.net/ty497122758/article/details/78979302

Demo实现

主要有三个类:

  1. Buffers :封装 NIO 的 ByteBuffer;
  2. NioServer :封装了 NIO 的实现,构造 NioServer 服务对象时,可将 报文处理逻辑 通过 FunctionInterface 传入
  3. NioClient :实现了 完整的 NIO 客户端逻辑,但demo中仅仅作为测试角色,给 NIO Server 发送消息

Buffers

封装 NIO 的 ByteBuffer,包括 readBuffer 和 writeBuffer

package com.wj.io.nio;

import java.nio.ByteBuffer;

/**
 * 自定义Buffer类中包含读缓冲区和写缓冲区,用于注册 Channel 时的附加对象
 */
public class Buffers {

    private ByteBuffer readBuffer;
    private ByteBuffer writeBuffer;

    public Buffers(int readCapacity, int writeCapacity){
        readBuffer = ByteBuffer.allocate(readCapacity);
        writeBuffer = ByteBuffer.allocate(writeCapacity);
    }

    public ByteBuffer getReadBuffer(){
        return readBuffer;
    }

    public ByteBuffer gerWriteBuffer(){
        return writeBuffer;
    }
}

NioServer

封装了 NIO 的实现,构造 NioServer 服务对象时,可将 报文处理逻辑 通过 FunctionInterface 传入

package com.wj.io.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;

/**
 * Nio 服务端
 */
public class NioServer implements Runnable {

    /**
     * 收发消息的编码格式
     */
    private final Charset utf8 = Charset.forName("UTF-8");

    /**
     * 标志 Server 端是否结束
     */
    private AtomicBoolean running = new AtomicBoolean();

    /**
     * 服务端的处理逻辑(Function Interface)
     */
    private Function<String, String> service;

    /**
     * 服务端地址
     */
    private InetSocketAddress localAddress;

    /**
     * The maximum number of pending connections
     */
    private int backlog = 100;

    public NioServer(Function<String, String> service, int port) {
        this.service = service;
        this.localAddress = new InetSocketAddress(port);
    }

    public NioServer(Function<String, String> service, String hostname, int port) {
        this.service = service;
        this.localAddress = new InetSocketAddress(hostname, port);
    }

    @Override
    public void run() {
        try (
                // 创建选择器 Selector
                Selector selector = Selector.open();

                // 创建服务器通道 Channel
                ServerSocketChannel channel = ServerSocketChannel.open()

        ) {

            // 初始化 Selector 和 Channel
            init(selector, channel);

            while (running.get()) {

                // 每秒检测一次,观察当前 NIO Server 是否关闭
                if (selector.select(1_000) == 0) {
                    continue;
                }

                // NIO Server 的核心逻辑
                doNioServerWorking(selector, channel);
            }
        } catch (Exception e) {
            e.printStackTrace();
            /* TODO occur Exception when NIO Server working... */
        }
    }

    /**
     * IO Server 的核心逻辑
     * @param selector Selector
     * @param channel ServerSocketChannel
     * @throws IOException occur Exception
     */
    private void doNioServerWorking(Selector selector, ServerSocketChannel channel) throws IOException {
        Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
        while (iterator.hasNext()) {
            SelectionKey key = iterator.next();

            if (key.isAcceptable()) {
                // accept方法会返回一个普通 Channel,每个通道在内核中都对应一个socket缓冲区
                SocketChannel acceptChannel = channel.accept();
                acceptChannel.configureBlocking(false);

                // Channel 向 Selector 注册,关注 可读事件,同时提供这个新通道相关的缓冲区
                acceptChannel.register(selector, SelectionKey.OP_READ, new Buffers(256, 256));

                System.out.println("accept from " + acceptChannel.getRemoteAddress());
            }

            if (key.isReadable()) {
                // 通过 SelectionKey 获取 Channel 对应的 缓冲区
                Buffers buffers = (Buffers) key.attachment();
                ByteBuffer readBuffer = buffers.getReadBuffer();
                ByteBuffer writeBuffer = buffers.gerWriteBuffer();

                // 通过 SelectionKey 获取对应的 Channel
                SocketChannel socketChannel = (SocketChannel) key.channel();

                // 从 底层socket读缓冲区 中读入数据
                socketChannel.read(readBuffer);
                readBuffer.flip();

                // 解码显示,客户端发送来的信息
                System.out.println(utf8.decode(readBuffer));

                readBuffer.rewind();

                /* TODO service 服务处理 收到的 报文,并加以处理 */
                String rspMsg = service.apply(utf8.decode(readBuffer).toString());
                writeBuffer.put(rspMsg.getBytes(StandardCharsets.UTF_8));

                readBuffer.clear();

                // 设置通道写事件
                key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
            }

            if (key.isWritable()) {
                Buffers buffers = (Buffers) key.attachment();

                ByteBuffer writeBuffer = buffers.gerWriteBuffer();
                writeBuffer.flip();

                // 通过 SelectionKey 获取对应的 Channel
                SocketChannel socketChannel = (SocketChannel) key.channel();

                int len = 0;
                while (writeBuffer.hasRemaining()) {
                    len = socketChannel.write(writeBuffer);
                    if (0 == len) {
                        // 说明底层的socket写缓冲已满
                        break;
                    }
                }

                writeBuffer.compact();

                // 说明数据全部写入到底层的 socket写缓冲区
                if (0 != len) {
                    // 取消 Channel 的写事件
                    key.interestOps(key.interestOps() & (~SelectionKey.OP_WRITE));
                }

            }

            /* 防止下次select方法返回已处理过的通道? (应该不要加,每次 select 都会清理 SelectionKey) */
            iterator.remove();
        }
    }

    private void init(Selector selector, ServerSocketChannel channel) throws IOException {
        /* 非阻塞 */
        channel.configureBlocking(false);

        /* 设置监听服务器的端口,并设置最大连接缓冲 */
        channel.bind(localAddress, backlog);

        /* Channel 向 Selector 注册,关注 TCP 连接建立 */
        channel.register(selector, SelectionKey.OP_ACCEPT);

        running.set(true);
        System.out.println("server start with address : " + localAddress);
    }

    // ---------------------------------------------------------------------------
    // setter / getter
    // ---------------------------------------------------------------------------

    public int getBacklog() {
        return backlog;
    }

    public void setBacklog(int backlog) {
        this.backlog = backlog;
    }

    public boolean getRunning() {
        return running.get();
    }

    public void setRunning(boolean running) {
        this.running.set(running);
    }

    public static void main(String[] args) {
        new Thread(new NioServer(msg -> "Msg Server send : " + msg, 7000)).start();
    }
}

NioClient

实现了 完整的 NIO 客户端逻辑,但demo中仅仅作为测试角色,给 NIO Server 发送消息

package com.wj.io.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * NIO Client
 */
public class NioClient implements Runnable {

    /**
     * 收发消息的编码格式
     */
    private final Charset utf8 = Charset.forName("UTF-8");
    /**
     * 标志 Client 端是否结束
     */
    private AtomicBoolean running = new AtomicBoolean();
    /**
     * 客户端名称(可以是唯一标识)
     */
    private String name;
    /**
     * 远程服务端地址
     */
    private InetSocketAddress remoteServerAddress;

    public NioClient(String name, int port) {
        this.name = name;
        this.remoteServerAddress = new InetSocketAddress(port);
    }

    public NioClient(String name, String hostname, int port) {
        this.name = name;
        this.remoteServerAddress = new InetSocketAddress(hostname, port);
    }

    @Override
    public void run() {
        try (
                /* 创建选择器 Selector */
                Selector selector = Selector.open();

                /* 创建服务器通道 Channel */
                SocketChannel channel = SocketChannel.open()

        ) {

            // 初始化 Selector 和 Channel
            init(selector, channel);

            while (running.get()) {
                /* 阻塞等待(NIO Server端可能有连接数限制,一旦连接数打满,则后续连接阻塞) */
                selector.select();

                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();

                    // 通过 SelectionKey 获取对应Channel 的 缓冲区
                    Buffers buffers = (Buffers) key.attachment();
                    ByteBuffer readBuffer = buffers.getReadBuffer();
                    ByteBuffer writeBuffer = buffers.gerWriteBuffer();

                    // 通过 SelectionKey 获取对应的 Channel
                    SocketChannel socketChannel = (SocketChannel) key.channel();

                    /* 此处仅仅打印 NIO Server 反馈的消息 */
                    if (key.isReadable()) {
                        socketChannel.read(readBuffer);
                        readBuffer.flip();

                        System.out.println(utf8.decode(readBuffer));
                        readBuffer.clear();
                    }

                    if (key.isWritable()) {
                        /* 此处 模拟 客户端发送给服务端的消息 */
                        writeBuffer.put((name + " send msg.").getBytes(StandardCharsets.UTF_8));
                        writeBuffer.flip();

                        socketChannel.write(writeBuffer);
                        writeBuffer.clear();

                        Thread.sleep(1000);
                    }

                    /* 防止下次select方法返回已处理过的通道? (应该不要加,每次 select 都会清理 SelectionKey) */
                    iterator.remove();
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
            /* TODO occur Exception when NIO Server working... */
        }
    }

    private void init(Selector selector, SocketChannel channel) throws IOException {
        // 非阻塞
        channel.configureBlocking(false);

        // Channel 向 Selector 注册,关注 可读/可写
        channel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE, new Buffers(256, 256));

        // 向服务器发起连接,一个通道代表一条tcp链接
        channel.connect(remoteServerAddress);

        // 等待三次握手完成
        while (!channel.finishConnect()) {

        }
        running.set(true);
        System.out.println(name + " finished connection...");
    }

    public static void main(String[] args) {
        new Thread(new NioClient("Client-1", 7000)).start();

    }
}

测试时先起 Server 端,再起 Client 即可。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值