`

基于NIO的服务器陷入写状态有效死循环的原因分析

 
阅读更多

        有一段时间没写博客了,最近在给导师做并行通信的一个程序。在编码过程中发现了一个问题,查阅了很多资料,今天终于知道了原因。

        问题描述:

                       编写基于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 多线程服务器

    基于事件的 NIO 多线程服务器

    java基于NIO实现Reactor模型源码.zip

    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的socket举例

    nio 服务器/客户端模式

    总结一下,NIO服务器/客户端模式通过通道、缓冲区和选择器实现了非阻塞的I/O通信,有效提升了服务器处理并发连接的能力。理解并熟练掌握这些概念和实践,是成为一名优秀的Java网络编程开发者的关键。在实际开发中,...

    java nio服务器

    Java NIO服务器的这种设计模式使得服务器能够有效地处理大量并发连接,尤其适用于高并发场景,如聊天服务器、游戏服务器等。然而,NIO的学习曲线相对较陡,理解和正确使用选择器、通道和缓冲区需要一定的实践。总的...

    基于nio实现的多文件上传源码

    本主题“基于nio实现的多文件上传源码”探讨的是如何利用Java NIO来实现高效的多文件上传功能,尤其对于小文件数量较大的情况。 首先,理解NIO的基本概念是必要的。NIO中的“非阻塞”意味着当数据不可用时,读写...

    NIO 服务器客户端例子

    在这个"NIO 服务器客户端例子"中,`TestServer.java`和`TestClient.java`分别代表服务器端和客户端的实现。 **NIO服务器端(TestServer.java)的关键知识点:** 1. **选择器(Selector)**:服务器通常会创建一个...

    基于nio的简易聊天室

    本项目"基于nio的简易聊天室"旨在通过NIO技术实现一个简单的聊天室服务端和客户端,其特点是有图形用户界面(GUI)供用户交互。 NIO的核心组件包括通道(Channels)、缓冲区(Buffers)和选择器(Selectors)。在这...

    基于NIO的聊天室

    在NIO中,服务器会创建一个Selector(选择器),它能够监控多个Channel(通道)的状态。当有新的连接请求或数据到来时,选择器会唤醒,服务器可以处理这些事件。在聊天室场景下,服务器需要监听客户端的连接请求,并...

    java多线程nio服务器

    Java NIO服务器的多线程设计有助于提高服务器的并发性能,特别是在高并发场景下,可以有效地利用系统资源,避免大量线程导致的内存消耗和上下文切换开销。同时,通过选择器的使用,减少了对主线程的占用,使得服务器...

    基于事件的_NIO_多线程服务器

    ### 基于事件的NIO多线程服务器解析 #### 概述 在Java的网络编程中,NIO(Non-blocking I/O)作为一种高效的数据处理模式,自JDK 1.4版本引入以来,逐渐成为了开发高性能网络应用的重要工具之一。与传统的阻塞I/O...

    高手使用Java NIO编写高性能的服务器

    4. **多路复用器(Multiplexing)**:Java NIO的多路复用器基于操作系统提供的Select或Poll机制,如Linux下的epoll。它能有效地监控大量通道的状态,提高系统资源利用率。 5. **文件系统访问**:NIO也提供了对文件...

    java nio 通信服务器、客户端完整例子

    用java编写的nio通信的例子,nio是io编程的新版本,比io较流行。同时本例子是适用socket通信的。可以在此基础上,添加您的个人应用。本例子适用于:java通信的学习者,android平台通信的学习者。

    基于NIO的多线程聊天系统

    基于NIO的多线程聊天系统,代码很少,很经典,51CTO网站上的代码。有登陆和聊天界面。代码结构层次清晰,系统只有6个类。

    基于事件的NIO多线程服务器

    【事件回调机制】在基于事件的NIO多线程服务器设计中,事件回调机制是核心组件之一。事件回调允许服务器在特定事件发生时调用预定义的处理函数,以执行相应的业务逻辑。这种方式使得代码更加模块化,易于扩展和维护...

    基于Java NIO实现五子棋游戏.zip

    基于Java NIO实现五子棋游戏.zip基于Java NIO实现五子棋游戏.zip 基于Java NIO实现五子棋游戏.zip基于Java NIO实现五子棋游戏.zip 基于Java NIO实现五子棋游戏.zip基于Java NIO实现五子棋游戏.zip 基于Java NIO实现...

    基于NIO的简单HTTP服务器--反向代理+负载均衡

    tuna 是一个基于NIO的简单http服务器,简单的实现了反向代理和负载均衡 这是tuna的配置文件 #this is config file for tuna #some common config keepalived: 5000 username: root password: root proxyServer: ...

    基于时间的NIO多线程服务器

    本文介绍了一种基于时间的NIO多线程服务器模型,通过结合NIO技术和事件驱动机制,有效地提高了服务器的并发处理能力和服务质量。该模型不仅解决了传统多线程服务器存在的线程开销问题,还通过合理的事件机制实现了...

    基于NIO的群聊.zip

    在这个"基于NIO的群聊.zip"项目中,开发者利用NIO实现了服务器端的群聊功能,使得多个客户端可以同时进行聊天交互,并且能够检测客户端的上线和下线状态。 1. **NIO基础概念**: - **通道(Channels)**:NIO的...

Global site tag (gtag.js) - Google Analytics