`
iakuxgnaw
  • 浏览: 2320 次
社区版块
存档分类
最新评论

使用多线程的NIO构建简易的多线程java服务器

阅读更多

 之前看过关于NIO的介绍,但是没有深究。。前几天研究了java的NIO,然后偶然在IBMdeveloper上看到了一个年代久远的“基于时间的NIO多线程服务器”文章,于是我就仔细研究了下。http://www.ibm.com/developerworks/cn/java/l-niosvr/这是这篇文章的地址。相同的地方我就不贴了,直接看原帖就好了。我贴的是大致的流程和一些重要的类吧。。使用的也就是channel 和selector,还有资源池。。

 

它这篇文章它使用了观察者模式,并且将客户端传来的信息封装成一个request类,将要写给客户端的也封装成一个response类,这都是为了后续处理方便。。还有观察者模式的那些处理我就不粘贴了。它的这篇文章实现的也是短连接,即客户端连上之后读取一次内容并且输入一次之后就断开连接了。

 

在程序中开了三个线程,一个是主线程,一个时读通道数据的线程,另一个是写通道数据的线程。



 主线程:

 

package nio1;

import java.net.InetSocketAddress;
import java.net.ServerSocket;
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.LinkedList;
import java.util.List;
import java.util.Set;

public class Server implements Runnable {

	//主线程中的写通道池,用来存放从读取之后可以进行写操作的通道。
	private static List<SelectionKey> wpool = new LinkedList<SelectionKey>();
	private Notify notify = Notify.getInstance();
	private ServerSocketChannel ssChannel;
	private static Selector selector;
	private int port;
	
	public Server(int port) throws Exception{
		
		this.port = port;
		
		//创建了一个读线程和一个写线程
		new Thread(new Reader()).start();
		new Thread(new Writer()).start();
		
		selector = Selector.open();
		ssChannel = ServerSocketChannel.open();
		ssChannel.configureBlocking(false);
		ServerSocket ss = ssChannel.socket();
		InetSocketAddress address = new InetSocketAddress(port);
		ss.bind(address);
		ssChannel.register(selector, SelectionKey.OP_ACCEPT);
		
	}
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		System.out.println("Server start..");
		System.out.println("Server listening on port: " + port);
		while (true){
			try{
				int num = 0;
				
				// 阻塞着等待着感兴趣的通道完成,但是如果此时一个读通道已经完成了,可以进行写通道的任务时,根据processWriteRequest唤醒它,唤醒后num=0;
				num = selector.select();
				if (num > 0) {
					Set<SelectionKey> set = selector.selectedKeys();
					Iterator<SelectionKey> it = set.iterator();
					while (it.hasNext()){
						SelectionKey sk = it.next();
						
						//在已选择集中删除,如果不删除下次选择时它还是会存在的。
						it.remove();
						
						if (sk.isAcceptable()) {
							ServerSocketChannel ssc = (ServerSocketChannel) sk.channel();
							
							//这步没有用到,可以用来做已连接数的检测
							notify.fireOnAccept();
							
							SocketChannel sc = ssc.accept();
							sc.configureBlocking(false);
							Request request = new Request(sc);
							
							// 注册读操作,以进行下一步的读操作, 这里将request作为一个附加对象加入进去。
							sc.register(selector, SelectionKey.OP_READ, request);
						}
						
						//可读的通道就绪了
						else if (sk.isReadable()) {
							Reader.processRequest(sk);
							sk.cancel();
						}
						//可写的通道就绪了
						else if (sk.isWritable()) {
							Writer.processRequest(sk);
							sk.cancel();
						}
					}
				}
				else{
					addRegister();  // 在Selector中注册新的写通道
				}
			}catch (Exception e) {
				// TODO: handle exception
				notify.fireError("Error occured in Server: " + e.getMessage());
				e.printStackTrace();
			}
		}
	}
	
	
	//在读线程中将会调用这个方法,告诉主线程读通道的事件已经完成,现在可以进行写处理
	public static void processWriteRequest(SelectionKey key){
		synchronized (wpool) {
            wpool.add(wpool.size(), key);
            wpool.notifyAll();
        }
		selector.wakeup();   // 解除selector的阻塞状态,以便注册新的写通道,否则他会一直在select哪里阻塞着等待
	}

	//这个方法是将主线程中通道池里的准备好写的通道,注册到selector中。
	public void addRegister(){
		synchronized (wpool) {
		
			while (!wpool.isEmpty()) {
				SelectionKey key = wpool.remove(0);
				SocketChannel sc = (SocketChannel) key.channel();
				try{
					//将这个通道中的从客户端发来的包装好的request当做key中的附带物想selector注册
					sc.register(selector, SelectionKey.OP_WRITE, key.attachment());
				}catch (Exception e) {
					// TODO: handle exception
					try{
						sc.finishConnect();
						sc.socket().close();
						sc.close();
					}catch (Exception ex) {
						// TODO: handle exception
						ex.printStackTrace();
					}
					e.printStackTrace();		
				}
			}
			
		}
	}
	
}

 

 

读线程:

 

package nio1;

import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.LinkedList;
import java.util.List;


public class Reader implements Runnable {

	//读线程中的可读的通道池
	private static List<SelectionKey> pool = new LinkedList<SelectionKey>();
	private static final int MAX_CAPACITY = 1024;
	private static Notify notify = Notify.getInstance();
	
	public Reader(){
		
	}
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while (true){
			try{
				SelectionKey key;
				synchronized (pool) {
					while (pool.isEmpty()) {
						pool.wait();
					}
					key = pool.remove(0);
				}
				read(key);
				
			}catch (InterruptedException e) {
				// TODO: handle exception
				e.printStackTrace();
			}
		}
	}
	
	public void read(SelectionKey key){
		
		try{
			SocketChannel sc = (SocketChannel) key.channel();
			byte[] clientData = readRequest(sc);
			Request request = new Request(sc);
			request.setDataInput(clientData);
			
			//触发读事件,将request包装起来发给具体需要处理的业务逻辑
			notify.fireOnRead(request);
			
			// 提交主控线程进行写处理,就是通知主线程,通道中的信息我已经读完了,可以进行写入了。
            Server.processWriteRequest(key);
			
		}catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
	}
	
	public byte[] readRequest(SocketChannel sc) throws Exception{
		
		ByteBuffer buffer = ByteBuffer.allocate(MAX_CAPACITY);
		sc.read(buffer);
		byte[] req = new byte[MAX_CAPACITY * 10];
		req = buffer.array();
		
		return req;	
	}

	//当有可以被读的通道进来时,加入到通道池中,然后唤醒通道池以便处理
	public static void processRequest(SelectionKey key){
		synchronized (pool) {
			pool.add(pool.size(), key);
			pool.notifyAll();
		}
	}
}

 

 

写线程:

 

package nio1;

import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.LinkedList;
import java.util.List;

public class Writer implements Runnable {

	//写线程中的写通道池
	private static List<SelectionKey> pool = new LinkedList<SelectionKey>();
	private static Notify notify = Notify.getInstance();
	
	public Writer(){
		
	}
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while (true) {
			try{
				SelectionKey key;
				synchronized (pool) {
					while (pool.isEmpty()) {
						pool.wait();
					}
					key = pool.remove(0);
				}
				write(key);
				
			}catch (Exception e) {
				// TODO: handle exception
				e.printStackTrace();
			}

		}
	}
	
	
	public void write(SelectionKey key){
		
		try{
			SocketChannel sc = (SocketChannel) key.channel();
			Response response = new Response(sc);
			
			//触发写事件
			notify.fireOnWrite((Request) key.attachment(), response);
			
			//写完一次之后就关闭了
			sc.finishConnect();
			sc.socket().close();
			sc.close();
			
		}catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
	}
	
	/**
     * 当写通道就绪之后,唤醒写线程中的通道池,处理将要写入的数据
     */
	public static void processRequest(SelectionKey key) {
        synchronized (pool) {
            pool.add(pool.size(), key);
            pool.notifyAll();
        }
    }

	
}

 

 

一个从开始到结束的访问顺序过程为:

       开启了主线程、读线程和写线程 -> 客户端连接 -> isAcceptable(),注册读通道 -> isReadable(),交给读线程 -> Reader.processRequest(),唤醒读通道池 -> read(),读通道数据 -> Server.processWriteRequest(),通知主线程可以写了 -> addRegister(),唤醒主线程中的写通道池,并且注册写通道 -> isWritable() -> Writer.processRequest(),唤醒写通道池 ->write(),写通道数据 -> 关闭!

 

据说这是mina的原理。。我也没研究过mina也不知道,但是使用了channel和selector的方法确实比以前的I/O方法的效率要高很多,这也是现在java越来越适合于写服务器的原因?

 

还有一点,在遍历selectedKey的时候要用迭代器,别用foreach循环,会出错。具体我也没试过,但是也给大家提个醒。

 

写的都是好久之前的东西了。。谁让以前都没弄清楚过呢。。。哎 过几天再研究NIO2!!

 

分享到:
评论

相关推荐

    多线程NIO客户端实例

    通过这段多线程NIO客户端实例代码,我们可以看到Java NIO框架如何利用多线程和非阻塞I/O来构建高性能的网络应用。在实际开发中,这种设计模式可以大大提高客户端的响应速度和处理能力,尤其是在高并发场景下。然而,...

    java nio服务器

    Java NIO(New Input/Output)是Java标准库中提供的一种I/O模型,与传统的 Blocking I/O(BIO)相比,...总的来说,Java NIO是现代Java服务器开发中不可或缺的一部分,它为高性能、高并发的网络应用提供了强大的支持。

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

    NIO与传统的IO( Blocking IO)模型相比,最大的区别在于其非阻塞特性,使得一个线程可以同时处理多个连接请求,而无需为每个请求创建一个新的线程,从而避免了线程创建和销毁的开销,以及多线程同步的问题。...

    基于多线程的web服务器java源码

    10. **性能优化**:除了多线程,服务器还需要考虑其他性能优化策略,如连接复用、缓存策略、异步I/O(Java NIO)等。 【总结】 了解和分析基于多线程的Web服务器Java源码,不仅有助于提升Java编程技能,还能加深对...

    多线程精品资源--Java NIO+多线程实现聊天室.zip

    在这个“多线程精品资源--Java NIO+多线程实现聊天室”的压缩包中,我们可以推测它包含了一套关于如何使用Java NIO和多线程技术来创建一个实时聊天应用的教程或示例代码。 首先,多线程是Java中并行处理的基础。...

    java NIO.zip

    Java NIO支持多种类型的通道,包括文件通道(FileChannel)、套接字通道(SocketChannel)和服务器套接字通道(ServerSocketChannel)等。通道可以同时进行读写操作,并且可以实现异步读写。 2. **缓冲区(Buffers...

    Java NIO实现多个客户端之间的消息互发,客户端与服务器完整代码

    Java NIO(Non-blocking Input/...在实际开发中,Java NIO的使用需要对多线程、网络编程以及NIO API有深入的理解。通过这种方式构建的系统可以高效地处理大量并发连接,非常适合于聊天、游戏等实时性要求高的应用场景。

    多线程Web服务器的设计与实现

    本实验的主题是“多线程Web服务器的设计与实现”,这涉及到并发处理和网络通信的核心概念。下面将详细讨论相关知识点。 1. **多线程**:多线程是指在一个程序中可以同时执行多个独立的线程。在Web服务器中,多线程...

    Java NIO英文高清原版

    6. **多路复用器(Multiplexing)**:Java NIO的选择器实现了I/O多路复用,即单个线程可以同时处理多个连接,这在处理大量并发连接时非常有用。 7. **管道(Pipe)**:管道是两个线程间进行单向数据传输的通道。一...

    Java多线程下载器

    Java多线程下载器是一种利用Java编程语言实现的高效文件下载工具,它通过将大文件分割成多个部分并同时下载,显著提高了下载速度。在Java中实现多线程下载器涉及许多关键概念和技术,包括线程、并发控制、网络I/O...

    Java Socket实例(服务器多线程)

    要查看具体的代码实现,你需要检查`src`目录下的文件,通常在`java`或`src/main/java`这样的路径下,找到相关的类文件,比如`ServerSocketThread.java`或其他类似的名字,那里会有实现多线程服务器的代码。

    JavaNIO服务器实例Java开发Java经验技巧共6页

    本资料"JavaNIO服务器实例Java开发Java经验技巧共6页"可能是某个Java开发者或讲师分享的一份关于如何在Java中构建NIO服务器的教程,涵盖了6个关键页面的内容。尽管具体的细节无法在此直接提供,但我们可以根据Java ...

    多线程web服务器 附实验报告 java

    在IT领域,尤其是在服务器开发中,多线程技术扮演着至关重要的角色,特别是在构建高性能的Web服务器时。本文将深入探讨多线程Web服务器的概念、Java中的Socket编程以及如何通过实现Runnable接口来创建多线程。 多...

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

    ### 基于时间的NIO多线程服务器——深入解析与关键技术点 #### 引言 在服务器端编程领域,随着互联网应用的不断发展,如何高效处理大量的并发连接成为了一个重要议题。Java NIO(非阻塞I/O)作为一种先进的I/O处理...

    高吞吐高并发Java NIO服务的架构(NIO架构及应用之一)

    4. **文件锁(FileLock)**:在多线程环境中,文件锁用于控制对文件的并发访问。Java NIO提供了对文件锁的支持,可以在读写文件时确保数据的一致性。 在实际应用中,构建高吞吐、高并发的NIO服务通常涉及以下步骤:...

    java多线程下载器

    通过理解以上技术点,开发者可以构建一个功能完备的Java多线程下载器,提供高效且用户友好的文件下载体验。在实际开发中,还应考虑优化代码性能、内存管理以及如何处理各种网络和系统环境的差异。

    Java NIO Socket基本

    NIO在实际开发中常用于构建高性能的网络服务器,如Tomcat、Netty等框架就大量使用了NIO技术。通过合理使用NIO,开发者可以编写出更加高效、可扩展的服务端代码。了解和掌握Java NIO对于提升Java程序员在服务器端编程...

    java多线程下载图片

    综上所述,Java多线程下载图片涉及到的知识点包括线程的创建与管理、并发控制、异常处理、IO操作、线程同步、资源管理以及用户界面的更新等,这些都是构建高效、健壮的多线程应用所必需的技能。

    java多线程数据流发送信息

    在这个场景中,我们将深入探讨如何使用Java的多线程技术和TCP来实现高效的数据流发送。 首先,我们要理解Java中的线程。线程是程序执行的最小单元,一个进程中可以有多个线程并发执行,这样可以提高程序的执行效率...

Global site tag (gtag.js) - Google Analytics