有一段时间没写博客了,最近在给导师做并行通信的一个程序。在编码过程中发现了一个问题,查阅了很多资料,今天终于知道了原因。
问题描述:
编写基于NIO服务器的时候,客户端向服务端发送一条消息之后,服务端的Selector.select()陷入写有效的死循环中。
在分析问题之前先推荐一些博客和书籍,也是我最近正在拜读和学习的,个人感觉非常的好。
林昊先生的《分布式Java应用基础与实践》
Doug Lea的论文《Scalable IO in Java》
http://www.jdon.com/concurrent/nio.pdf
三石.道的博客:http://www.molotang.com/java
并发编程网的几篇博客:http://ifeve.com/?s=Selector
还有就是一本书《Java NIO》:
http://xxing22657-yahoo-com-cn.iteye.com/blog/899279
好了现在开始分析问题,这个问题是我在拜读Doug Lea的论文《Scalable IO in Java》时,探究Reactor模式的时候发现的。先贴出我写的NIO服务器的和客户端通信的核心代码,有两个类Reactor和Handle:
//Reactor 类 package com.wjy.server; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.Set; public class Reactor implements Runnable{ final Selector selector; final ServerSocketChannel serverSocket; final int timeOut=6000; @Override public void run() { // TODO Auto-generated method stub try { while(!Thread.interrupted()){ if(selector.select(timeOut)==0) { System.out.println("."); continue; } Set selected=selector.selectedKeys(); Iterator it=selected.iterator(); while(it.hasNext()){ dispatch((SelectionKey)(it.next())); } selected.clear(); } } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } } void dispatch(SelectionKey k){ Runnable runnable=(Runnable)(k.attachment()); if(runnable!=null){ runnable.run(); } } Reactor(int port) throws IOException{ // TODO Auto-generated constructor stub selector=Selector.open(); serverSocket=ServerSocketChannel.open(); serverSocket.bind(new InetSocketAddress(port)); serverSocket.configureBlocking(false); SelectionKey sk=serverSocket.register(selector, SelectionKey.OP_ACCEPT); sk.attach(new Acceptor()); } class Acceptor implements Runnable{ public void run(){ try { SocketChannel c=serverSocket.accept(); if(c!=null){ new Handler(selector,c); } } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } } } }
//Handler package com.wjy.server; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; public final class Handler implements Runnable{ final int MAXIN=1024; final int MAXOUT=1024; final SocketChannel socket; final String localCharSetName="gb2312"; final SelectionKey sk; ByteBuffer input=ByteBuffer.allocate(MAXIN); ByteBuffer output=ByteBuffer.allocate(MAXOUT); static final int READING=0,SENDING=1; int state=READING; Handler(Selector sel,SocketChannel c) throws IOException{ socket=c; socket.configureBlocking(false); //这一步很重要 sk=socket.register(sel, 0); sk.attach(this); sk.interestOps(sk.interestOps() | SelectionKey.OP_READ); sel.wakeup(); } boolean inputIsComplete(){ return true; } boolean outputIsComplete(){ return true; } void process(){ input.flip(); try { String receivedString=Charset.forName(localCharSetName).newDecoder().decode(input).toString(); System.out.println("Received: "+receivedString); } catch (CharacterCodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } } @Override public void run() { try { // if (state == READING) read(); // else if (state == SENDING) send(); if(sk.isReadable()) { read(); }else if(sk.isWritable()) { send(); } } catch (IOException ex) { ex.printStackTrace(); } } void read() throws IOException { input.clear(); socket.read(input); if (inputIsComplete()) { process(); state = SENDING; // Normally also do first write now //关键在这一句,有了它selector.select总能选到write请求,会陷入死循环。 sk.interestOps(sk.interestOps() | SelectionKey.OP_WRITE); } } void send() throws IOException { String sendString="Hello,Client. I have received your message: "; output.clear(); output=ByteBuffer.wrap(sendString.getBytes(localCharSetName)); socket.write(output); state=READING; sk.interestOps(sk.interestOps() & (~SelectionKey.OP_WRITE)); //这句太重要了,否则进入write的死循环。 // if (outputIsComplete()) // sk.cancel(); } }
我直接将正确的代码贴出来了,原因是这样的:
当用户连接(connect)服务器的时候,激发了Accept。
当用户向服务器写东西的时候,Selecter.select会发现有有效的Readable的key。
我们读取完数据后,注册了SelectionKey.OP_WRITE。
接下来问题出现了,通过断点调试发现Selecter.select()总能返回非0值(其实是1),而且选到的key是isWriteable的。一直死循环下去。
原因:
不应该注册写事件。写操作的就绪条件为底层缓冲区有空闲空间,而写缓冲区绝大部分时间都是有空闲空间的,所以当注册写事件后,写操作一直是就绪的,选择处理线程会占用整个CPU资源。所以,只有当确实有数据要写时再注册写操作,并在写完以后马上取消注册。
解决办法:
服务端读到东西后,注册写事件。等写完东西后取消写事件的注册。
就像这样:sk.interestOps(sk.interestOps() & (~SelectionKey.OP_WRITE));
相关推荐
基于事件的 NIO 多线程服务器
java基于NIO实现Reactor模型源码java基于NIO实现Reactor模型源码java基于NIO实现Reactor模型源码java基于NIO实现Reactor模型源码java基于NIO实现Reactor模型源码java基于NIO实现Reactor模型源码java基于NIO实现...
基于NIO的socket举例 基于NIO的socket举例 基于NIO的socket举例 基于NIO的socket举例 基于NIO的socket举例基于NIO的socket举例 基于NIO的socket举例
总结一下,NIO服务器/客户端模式通过通道、缓冲区和选择器实现了非阻塞的I/O通信,有效提升了服务器处理并发连接的能力。理解并熟练掌握这些概念和实践,是成为一名优秀的Java网络编程开发者的关键。在实际开发中,...
Java NIO服务器的这种设计模式使得服务器能够有效地处理大量并发连接,尤其适用于高并发场景,如聊天服务器、游戏服务器等。然而,NIO的学习曲线相对较陡,理解和正确使用选择器、通道和缓冲区需要一定的实践。总的...
本主题“基于nio实现的多文件上传源码”探讨的是如何利用Java NIO来实现高效的多文件上传功能,尤其对于小文件数量较大的情况。 首先,理解NIO的基本概念是必要的。NIO中的“非阻塞”意味着当数据不可用时,读写...
在这个"NIO 服务器客户端例子"中,`TestServer.java`和`TestClient.java`分别代表服务器端和客户端的实现。 **NIO服务器端(TestServer.java)的关键知识点:** 1. **选择器(Selector)**:服务器通常会创建一个...
本项目"基于nio的简易聊天室"旨在通过NIO技术实现一个简单的聊天室服务端和客户端,其特点是有图形用户界面(GUI)供用户交互。 NIO的核心组件包括通道(Channels)、缓冲区(Buffers)和选择器(Selectors)。在这...
在NIO中,服务器会创建一个Selector(选择器),它能够监控多个Channel(通道)的状态。当有新的连接请求或数据到来时,选择器会唤醒,服务器可以处理这些事件。在聊天室场景下,服务器需要监听客户端的连接请求,并...
Java NIO服务器的多线程设计有助于提高服务器的并发性能,特别是在高并发场景下,可以有效地利用系统资源,避免大量线程导致的内存消耗和上下文切换开销。同时,通过选择器的使用,减少了对主线程的占用,使得服务器...
### 基于事件的NIO多线程服务器解析 #### 概述 在Java的网络编程中,NIO(Non-blocking I/O)作为一种高效的数据处理模式,自JDK 1.4版本引入以来,逐渐成为了开发高性能网络应用的重要工具之一。与传统的阻塞I/O...
4. **多路复用器(Multiplexing)**:Java NIO的多路复用器基于操作系统提供的Select或Poll机制,如Linux下的epoll。它能有效地监控大量通道的状态,提高系统资源利用率。 5. **文件系统访问**:NIO也提供了对文件...
用java编写的nio通信的例子,nio是io编程的新版本,比io较流行。同时本例子是适用socket通信的。可以在此基础上,添加您的个人应用。本例子适用于:java通信的学习者,android平台通信的学习者。
基于NIO的多线程聊天系统,代码很少,很经典,51CTO网站上的代码。有登陆和聊天界面。代码结构层次清晰,系统只有6个类。
【事件回调机制】在基于事件的NIO多线程服务器设计中,事件回调机制是核心组件之一。事件回调允许服务器在特定事件发生时调用预定义的处理函数,以执行相应的业务逻辑。这种方式使得代码更加模块化,易于扩展和维护...
基于Java NIO实现五子棋游戏.zip基于Java NIO实现五子棋游戏.zip 基于Java NIO实现五子棋游戏.zip基于Java NIO实现五子棋游戏.zip 基于Java NIO实现五子棋游戏.zip基于Java NIO实现五子棋游戏.zip 基于Java NIO实现...
tuna 是一个基于NIO的简单http服务器,简单的实现了反向代理和负载均衡 这是tuna的配置文件 #this is config file for tuna #some common config keepalived: 5000 username: root password: root proxyServer: ...
本文介绍了一种基于时间的NIO多线程服务器模型,通过结合NIO技术和事件驱动机制,有效地提高了服务器的并发处理能力和服务质量。该模型不仅解决了传统多线程服务器存在的线程开销问题,还通过合理的事件机制实现了...
在这个"基于NIO的群聊.zip"项目中,开发者利用NIO实现了服务器端的群聊功能,使得多个客户端可以同时进行聊天交互,并且能够检测客户端的上线和下线状态。 1. **NIO基础概念**: - **通道(Channels)**:NIO的...