前几天初步接触了解了NIO,发现性能方面完爆BIO。所以决定将以前一个项目的服务端改形成NIO。可是NIO学习难度比BIO大,所以在网上查找相关资料,而后将我本身理解并简化的NIO服务器搭建思路在这里作个记录分享。所以会有一些错误和忽略的地方,若要更加详细请点击下方的原文。html
思路来源:《Java NIO文档》非阻塞式服务器,原文:Java NIO Tutorial.java
NIO相关的这里就不作相关介绍,毕竟不能算真正的教程。服务器
大体思路以下:socket
整个系统主要为两个线程:AccepterThread,ProcessorThread。分别用于接收成功链接的Socket对象以及对消息的接收处理。函数
两个线程之间使用队列通讯,用于传递Socket对象性能
在消息的处理中又对Channel中的操做分为读操做和写操做,这两个操做分别创建两个不一样的类用来执行,原做者对此的解释是为了防止读取数据的时候数据缺失以及写数据的时候未彻底写入。学习
读写操做分别又两个对应的Selector对其进行监听。this
对请求的接收就和通常的ServerSocket同样,while循环直到有请求进来,这里原文将准备好的SocketChannel包装进另外编写的Socket类中,这个Socket类相关定义以下:spa
public class Socket {
public long socketId;
public SocketChannel socketChannel;
public MessageReader reader;
public MessageWriter writer;
public Socket(SocketChannel channel) {
this.socketChannel = channel;
}
public int read(Bytebuffer buffer) {
//读方法实现
}
public int write(Bytebuffer buffer) {
//写方法实现
}
}
复制代码
这里将最重要的读写操做进行包装,而且使用本身的消息读/写器来对Channel中的字节进行读写操做。线程
Accepter在包装好Socket对象后将该对象add进队列中。
在Processor中,存在一个while(true)循环,该循环遍历链接AccepterThread的队列,一旦队列中有对象,则进行数据的处理操做。
Processor中存在两个Selector,一个负责读,另外一个负责写。
在Accepter中包装的Socket对象只有SocketChannel,所以在进行信息的读写以前要将对应的读写器以及SocketId装入Socket对象中。这里原做者对此的操做要规范复杂的多,翻译的原文是:
一个Message Reader必定知足特定的协议。Message Reader须要知道它尝试读取的消息的消息格式。若是咱们的服务器能够经过协议来复用,那它须要有可以插入Message Reader实现的功能 – 可能经过接收一个Message Reader工厂做为配置参数。
在这里进行包装的好处是能给每一个SocketChannel都有读/写器,甚至可让每一个SocketChannel的读写器不一样。
再将信息包装好后将Socket中的SocketChannel注册进读Selector中,同时将Socket对象做为附件装入。
等待消息。
当消息到达后触发Selector,将读就绪的SocketChannel获取出来。其中的附件是包装了的Socket对象,里面有这个SocketChannel对应的读写器。将信息完整读取后就是对信息的处理了,在处理这里采用的是函数处理。即在程序的入口处就将处理程序设定完毕,而后将对应的函数传递给Processor,这样当咱们须要对处理处理进行修改的时候不须要进入程序的主体,而只要在入口处修改便可。同时读写之间也是采用队列进行通讯。信息处理完成后若是须要返回相关细心或者要将信息发送给其余SocketChannel,就能够将该操做offer进写队列当中。
对信息处理完成若是要进行写操做,就将SocketChannel注册进写Selector中。这里一开始以为为何不直接从队列中获取到对象就就进行写操做,仔细考虑后发现没法保证这个SocketChannel在执行写的时候Client是没有数据发送过来的,所以要将该SocketChannel注册进写Selector中保证在写就绪状态进行数据写入。 同时在写Selector中也不能保证全部都是对于流程来讲能够写就绪的,毕竟空闲的时候就处于写就绪状态,可是此时服务器根本不会主动发送信息给客户端,所以要检测全部在写Selector中注册的SocketChannel中的Message是否写入完毕,若是写入完毕则将其移除Selector若是没有则写入。
只要在获取到包装好的Socket对象后将其保存在Hash表中则能够随时根据Socket的Id获取到对应的SocketChannel而后根据将要进行的操做将该Socket添加到对应的队列中等待注册。