项目背景:
数传服务器项目,需对 与外界交互的 报文进行加/解密,提高数传服务器的报文处理能力;
因为数传服务器需随 便携设备 一起工作,所以并不能简单的依靠增加设备来提高的报文处理能力,因此项目改进点只能是提高单个设备的报文处理能力
相关说明:
数传服务器 属于 服务端功能,对接收到的报文进行 加/解密 后,再转发;
项目 起初是用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.youkuaiyun.com/ty497122758/article/details/78979302
Demo实现
主要有三个类:
- Buffers :封装 NIO 的 ByteBuffer;
- NioServer :封装了 NIO 的实现,构造 NioServer 服务对象时,可将 报文处理逻辑 通过 FunctionInterface 传入
- 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 即可。