【记录】Java NIO实现网络模块遇到的BUG
1.背景
通过JavaNio实现一个简单的网络模块,有点像Netty的线程模型,一个线程(AcceptThread)建立新连接,把新连接绑定到某个SelectorThread,SelectorThread处理读/写。
- AcceptThread:拥有一个Selector,上面只注册了一个ServerSocketChannel,监听客户端新连接,当接收到新连接时,把新连接注册到SelectorThread上。
- SelectorThread:拥有一个Selector,用于注册客户端连接,并且处理读/写事件。
2.代码
public class NonblockingServer {
/** 处理客户端读写 */
private SelectorThread[] selectorThreads;
/** 建立新连接 */
private AcceptThread acceptThread;
/** 选择器 */
private SelectNextSelectorThread nextSelectorThread;
/** 默认selectorThread数量 */
private static int defaultSelectorNum = 3; public NonblockingServer() {
this(defaultSelectorNum);
} public NonblockingServer(int selectorThreadNum) {
if (selectorThreadNum < 1) {
throw new IllegalArgumentException("SelectorThread线程数量不能低于1");
}
selectorThreads = new SelectorThread[selectorThreadNum];
try {
for (int i = 0; i < selectorThreads.length; i++) {
selectorThreads[i] = new SelectorThread("selector-thread-" + i);
}
acceptThread = new AcceptThread("accept-thread");
nextSelectorThread = new SelectNextSelectorThread(Arrays.asList(selectorThreads));
} catch (Exception e) {
throw new RuntimeException(e);
}
} public void listen(String host, int port) {
try {
acceptThread.listen(host, port);
} catch (IOException e) {
throw new RuntimeException(e);
}
} /**
* 处理连接事件,把新连接注册到一个SelectorThread上
*/
class AcceptThread extends Thread {
private Selector selector;
private ServerSocketChannel serverChannel; AcceptThread(String name) throws IOException {
super(name);
selector = Selector.open();
} public void listen(String host, int port) throws IOException {
serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
serverChannel.bind(new InetSocketAddress(host, port));
this.start();
} @Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
int select = selector.select();
if (select == 0) {
continue;
}
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iterator = keys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
if (key.isAcceptable()) {
handAccept();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
} private void handAccept() throws IOException {
SocketChannel clientChannel = serverChannel.accept();
SelectorThread selectorThread = nextSelectorThread.nextThread();
// 懒加载
if (!selectorThread.isStart()) {
selectorThread.start();
}
selectorThread.register(clientChannel);
}
} /**
* 处理客户端连接的读/写事件
*/
class SelectorThread extends Thread {
private Selector selector;
private volatile boolean start = false; SelectorThread(String name) throws IOException {
super(name);
selector = Selector.open();
} @Override
public void run() {
while (start) {
try {
selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iterator = keys.iterator();
while (start && iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
SocketChannel channel = (SocketChannel) key.channel();
if (key.isReadable()) {
handRead(channel);
} else if (key.isWritable()) {
handWrite(channel);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
} /**
* 这里简单的把输出打印出来
*/
private void handRead(SocketChannel channel) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(1024);
int read = channel.read(buffer);
if (read == -1) {
System.out.println(channel.getRemoteAddress() + " 断开连接");
channel.close();
} else {
byte[] bytes = new byte[read];
buffer.flip();
buffer.get(bytes);
System.out.println(new String(bytes));
buffer.clear();
}
} private void handWrite(SocketChannel channel) {
// nothing
} /**
* 把新连接注册到本线程
*/
public void register(SocketChannel clientChannel) throws IOException {
clientChannel.configureBlocking(false);
clientChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
} @Override
public synchronized void start() {
start = true;
super.start();
} public void shutdown() {
this.start = false;
this.interrupt();
} public boolean isStart() {
return start;
}
} static class SelectNextSelectorThread {
private final Collection<? extends SelectorThread> threads;
private Iterator<? extends SelectorThread> iterator; public <T extends SelectorThread> SelectNextSelectorThread(Collection<T> threads) {
this.threads = threads;
iterator = this.threads.iterator();
} /**
* 选择下一个SelectorThread,这里为轮询
*
* @return SelectorThread
*/
public SelectorThread nextThread() {
if (!iterator.hasNext()) {
iterator = threads.iterator();
}
return iterator.next();
}
}
}
NonblockingServer
3.问题
当用客户端连接几次后,发现AcceptThread被卡死了!新连接无法建立。
用jvisualVM可以看到AcceptThread状态为监视,说明此时的AcceptThread在等待某个锁。
通过IDEA的dump threads也可以看到:
观察SelectThread线程:
SelectorImpl.lockAndDoSelect代码:
private int lockAndDoSelect(long var1) throws IOException {
synchronized(this) {
if (!this.isOpen()) {
throw new ClosedSelectorException();
} else {
int var10000;
synchronized(this.publicKeys) {
synchronized(this.publicSelectedKeys) {
var10000 = this.doSelect(var1);
}
} return var10000;
}
}
}
SelectorImpl.register代码:
protected final SelectionKey register(AbstractSelectableChannel var1, int var2, Object var3) {
if (!(var1 instanceof SelChImpl)) {
throw new IllegalSelectorException();
} else {
SelectionKeyImpl var4 = new SelectionKeyImpl((SelChImpl)var1, this);
var4.attach(var3);
synchronized(this.publicKeys) {
this.implRegister(var4);
} var4.interestOps(var2);
return var4;
}
}
这两个方法都需要获取this.publicKeys
的锁,经过调试发现,SeletorThread在获取Selector的产生的事件时selector.select()
,一直在不断的循环执行SelectorImpl.lockAndDoSelect方法,一直在不停的获取this.publicKeys
的锁,所以这里的register方法很难有机会拿到this.publicKeys
的锁,AcceptThread就卡死在获取锁的过程中了。
4.解决方法
Netty在注册新连接时,是把这个注册过程封装成一个任务,交给SelectorThread执行的,就不会发生线程冲突。
Thrift是把新连接放到SelecttorThread中的BlockingQueue中,也是由SelectorThread执行的。
5.修正后的代码
public class NonblockingServer {
/** 处理客户端读写 */
private SelectorThread[] selectorThreads;
/** 建立新连接 */
private AcceptThread acceptThread;
/** 选择器 */
private SelectNextSelectorThread nextSelectorThread;
/** 默认selectorThread数量 */
private static int defaultSelectorNum = 3; public NonblockingServer() {
this(defaultSelectorNum);
} public NonblockingServer(int selectorThreadNum) {
if (selectorThreadNum < 1) {
throw new IllegalArgumentException("SelectorThread线程数量不能低于1");
}
selectorThreads = new SelectorThread[selectorThreadNum];
try {
for (int i = 0; i < selectorThreads.length; i++) {
selectorThreads[i] = new SelectorThread("selector-thread-" + i);
}
acceptThread = new AcceptThread("accept-thread");
nextSelectorThread = new SelectNextSelectorThread(Arrays.asList(selectorThreads));
} catch (Exception e) {
throw new RuntimeException(e);
}
} public void listen(String host, int port) {
try {
acceptThread.listen(host, port);
} catch (IOException e) {
throw new RuntimeException(e);
}
} /**
* 处理连接事件,把新连接注册到一个SelectorThread上
*/
class AcceptThread extends Thread {
private Selector selector;
private ServerSocketChannel serverChannel; AcceptThread(String name) throws IOException {
super(name);
selector = Selector.open();
} public void listen(String host, int port) throws IOException {
serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
serverChannel.bind(new InetSocketAddress(host, port));
this.start();
} @Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
int select = selector.select();
if (select == 0) {
continue;
}
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iterator = keys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
if (key.isAcceptable()) {
handAccept();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
} private void handAccept() throws IOException {
SocketChannel clientChannel = serverChannel.accept();
SelectorThread selectorThread = nextSelectorThread.nextThread();
// 懒加载
if (!selectorThread.isStart()) {
selectorThread.start();
}
selectorThread.register(clientChannel);
}
} /**
* 处理客户端连接的读/写事件
*/
class SelectorThread extends Thread {
private Selector selector;
private BlockingQueue<Runnable> tasks;
private volatile boolean start = false; SelectorThread(String name) throws IOException {
super(name);
selector = Selector.open();
tasks = new LinkedBlockingQueue<>();
} @Override
public void run() {
while (start) {
try {
selector.select();
handTask();
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iterator = keys.iterator();
while (start && iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
SocketChannel channel = (SocketChannel) key.channel();
if (key.isReadable()) {
handRead(channel);
} else if (key.isWritable()) {
handWrite(channel);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
} private void handTask() {
Runnable task;
while (start && (task = tasks.poll()) != null) {
task.run();
}
} /**
* 这里简单的把输出打印出来
*/
private void handRead(SocketChannel channel) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(1024);
int read = channel.read(buffer);
if (read == -1) {
System.out.println(channel.getRemoteAddress() + " 断开连接");
channel.close();
} else {
byte[] bytes = new byte[read];
buffer.flip();
buffer.get(bytes);
System.out.println(new String(bytes));
buffer.clear();
}
} private void handWrite(SocketChannel channel) {
// nothing
} /**
* 把新连接注册到本线程
*/
public void register(SocketChannel clientChannel) {
submit(() -> {
try {
clientChannel.configureBlocking(false);
clientChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
} catch (Exception e) {
try {
clientChannel.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
});
selector.wakeup();
} public void submit(Runnable task) {
tasks.offer(task);
} @Override
public synchronized void start() {
start = true;
super.start();
} public void shutdown() {
this.start = false;
this.interrupt();
} public boolean isStart() {
return start;
}
} static class SelectNextSelectorThread {
private final Collection<? extends SelectorThread> threads;
private Iterator<? extends SelectorThread> iterator; public <T extends SelectorThread> SelectNextSelectorThread(Collection<T> threads) {
this.threads = threads;
iterator = this.threads.iterator();
} /**
* 选择下一个SelectorThread,这里为轮询
*
* @return SelectorThread
*/
public SelectorThread nextThread() {
if (!iterator.hasNext()) {
iterator = threads.iterator();
}
return iterator.next();
}
}
}
NonblockingServer
【记录】Java NIO实现网络模块遇到的BUG的更多相关文章
- JAVA NIO学习记录1-buffer和channel
什么是NIO? Java NIO(New IO)是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java IO API.NIO与原来的IO有同样的作用和目的,但是使用的方式完全不 ...
- Java NIO学习与记录(八): Reactor两种多线程模型的实现
Reactor两种多线程模型的实现 注:本篇文章例子基于上一篇进行:Java NIO学习与记录(七): Reactor单线程模型的实现 紧接着上篇Reactor单线程模型的例子来,假设Handler的 ...
- java nio的一个严重BUG
java nio的一个严重BUG Posted on 2009-09-28 19:27 dennis 阅读(4588) 评论(5) 编辑 收藏 所属分类: java .源码解读 这个BU ...
- Java nio 空轮询bug到底是什么
编者注:Java nio 空轮询bug也就是Java nio在Linux系统下的epoll空轮询问题. epoll机制是Linux下一种高效的IO复用方式,相较于select和poll机制来说.其高效 ...
- JAVA NIO学习记录2-非阻塞式网络通信
一.阻塞与非阻塞 传统的IO 流都是阻塞式的.也就是说,当一个线程调用read() 或write() 时,该线程被阻塞,直到有一些数据被读取或写入,该线程在此期间不能执行其他任务.因此,在完成网络通信 ...
- Java NIO学习与记录(五): 操作系统的I/O模型
操作系统的I/O模型 在开始介绍NIO Reactor模式之前,先来介绍下操作系统的五种I/O模型,了解了这些模型,对理解java nio会有不小的帮助. 先来看下一个服务端处理一次网络请求的流程图: ...
- Java NIO 入门
本文主要记录 Java 中 NIO 相关的基础知识点,以及基本的使用方式. 一.回顾传统的 I/O 刚接触 Java 中的 I/O 时,使用的传统的 BIO 的 API.由于 BIO 设计的类实在太 ...
- 源码分析netty服务器创建过程vs java nio服务器创建
1.Java NIO服务端创建 首先,我们通过一个时序图来看下如何创建一个NIO服务端并启动监听,接收多个客户端的连接,进行消息的异步读写. 示例代码(参考文献[2]): import java.io ...
- java nio系列文章
java nio系列教程 基于NIO的Client/Server程序实践 (推荐) java nio与并发编程相关电子书籍 (访问密码 48dd) 理解NIO nio学习记录 图解ByteBuff ...
随机推荐
- ASP.NET MVC中的捆绑和压缩技术
概述 在众多Web性能优化的建议中有两条: 减少Http请求数量:大多数的浏览器同时处理向网站处理6个请求(参见下图),多余的请求会被浏览器要求排队等待,如果我们减少这些请求数,其他的请求等待的时间将 ...
- Bootstrap的本地引入
今天用前端框架时选择了Bootstrap,然后东西都下好了本地就是引入不进去. 查了一下发现必须jquery要在BootStrap之前引入,然后我更改了引入顺序,发现还是不行 <script s ...
- promise函数
一.promise函数是干什么的 promise函数是解决异步编程调用代码逻辑编写过于复杂的问题的,当网络请求非常复杂时,就会出现回调地狱,这样如果将这些代码写在一起就会看起来很复杂,且不利于阅读,如 ...
- Android Fastboot 与 Recovery 和刷机 千山万水迷了鹿
1. 首先来看下Android系统的分区: Android系统的分区.jpg Android分区解释.png 安卓系统一般把rom芯片分成7个区,如果再加上内置sd卡这个分区,就是8个: hb ...
- JSON对象及方法
1.JSON JSON 包括 JSON 字符串和 JSON 对象.JSON 通常用于与服务端交换数据,在给服务器接收和发送数据时用的都是字符串,可以是 JSON 字符串或者一般的键值对字符串.把Jav ...
- 【UNR #2】黎明前的巧克力 解题报告
[UNR #2]黎明前的巧克力 首先可以发现,等价于求 xor 和为 \(0\) 的集合个数,每个集合的划分方案数为 \(2^{|S|}\) ,其中 \(|S|\) 为集合的大小 然后可以得到一个朴素 ...
- <三剑客> 老二:sed命令用法
sed命令的用法: sed是一种流编辑器,它是文本处理中非常中的工具,能够完美的配合正则表达式使用,功能不同凡响.处理时,把当前处理的行存储在临时缓冲区中,称为“模式空间”(pattern space ...
- (转)Kubernetes设计架构
转:https://www.kubernetes.org.cn/kubernetes设计架构 Kubernetes集群包含有节点代理kubelet和Master组件(APIs, scheduler, ...
- 记录MNIST实现与理解
之前半个月的时间几乎都在看理论书籍,最近两天开始撸代码,一个跟Hello World同级别的教程例子就出来了,那就是MNIST.实现代码应该很多地方都有: #!/usr/bin/env python ...
- Python多进程编程-进程间协作(Queue、Lock、Semaphore、Event、Pipe)
进程与进程之间是相互独立的,互不干扰.如果多进程之间需要对同一资源操作,就需要进程间共享变量,上一篇文章介绍了进程间共享数据的三大类Value.Array.Manager,这三种类的主要区别在于管理的 ...