其实这个是我自己对NIO做服务器时的一点见解,要是不太对,望赐教,这个图和数据通信的时分复用图差不多吧!
package org.com.mayi;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
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.Set;
public class SelectorServer
{
private static int DEFAULT_SERVERPORT = 6018;//默认端口
private static int DEFAULT_BUFFERSIZE = 1024;//默认缓冲区大小为1024字节
private ServerSocketChannel channel;
private LinkedList<SocketChannel> clients;
private Selector readSelector;
private ByteBuffer buffer;//字节缓冲区
private int port;
//构造函数,初始化数据
public SelectorServer(int port) throws IOException
{
this.port = port;
this.clients = new LinkedList<SocketChannel>();
this.channel = null;
this.readSelector = Selector.open();//打开选择器
this.buffer = ByteBuffer.allocate(DEFAULT_BUFFERSIZE);
}
// 服务器程序在服务循环中调用sericeClients()方法为已接受的客户服务
public void serviceClients()throws IOException
{
Set<?> keys;
Iterator<?> it;
SelectionKey key;
SocketChannel client;
// 在readSelector上调用select(long timeout)方法,
//timeout - 如果为正,则在等待某个通道准备就绪时最多阻塞 timeout 毫秒;
//如果为零,则无限期地阻塞;必须为非负数
if(readSelector.select(1) > 0)
{
keys = readSelector.selectedKeys(); //返回此选择器的已选择键集
it = keys.iterator();
// 遍历,为每一个客户服务
while(it.hasNext())
{
key = (SelectionKey)it.next();
if(key.isReadable())
{ // 测试此键的通道是否已准备好进行读取。
int bytes;
client = (SocketChannel)key.channel();//返回为之创建此键的通道。
buffer.clear(); // 清空缓冲区中的内容,设置好position,limit,准备接受数据
bytes = client.read(buffer); // 从通道中读数据到缓冲中,返回读取得字节数
if(bytes >= 0)
{
buffer.flip(); // 准备将缓冲中的数据写回到通道中
client.write(buffer); // 数据写回到通道中
}
else if(bytes < 0)
{ // 如果返回小于零的值代表读到了流的末尾
clients.remove(client);
// 通道关闭时,选择键也被取消
client.close();
}
}
}
}
}
// 配置和注册代表客户连接的通道对象
public void registerClient(SocketChannel client) throws IOException
{
client.configureBlocking(false); // 设置非阻塞模式
client.register(readSelector, SelectionKey.OP_READ); //注册到选择器上
clients.add(client); //保存这个通道对象----->为了写完数据时,删掉这个通道。
}
//服务器开始监听端口,提供服务
public void listen() throws IOException
{
ServerSocket socket;
SocketChannel client;
channel = ServerSocketChannel.open(); // 打开通道
socket = channel.socket(); //得到与通道相关的ServerSocket对象
socket.bind(new InetSocketAddress(port), 10);//将ServerSocket绑定在制定的端口上
channel.configureBlocking(false); //配置通道使用非阻塞模式。
try
{
while(true)
{
//与通常的程序不同,这里使用ServerSocketChannel.accpet()接受客户端连接请求,
//而不是在ServerSocket对象上调用accept()。
client = channel.accept(); //接受到此通道套接字的连接。
if(client != null)
{
registerClient(client); // 注册客户信息
}
serviceClients(); // 为以连接的客户服务
}
}
finally
{
socket.close(); // 关闭socket,关闭socket会同时关闭与此socket关联的通道
}
}
public static void main(String[] args) throws IOException
{
System.out.println("服务器启动");
SelectorServer server = new SelectorServer(SelectorServer.DEFAULT_SERVERPORT);
server.listen(); //服务器开始监听端口,提供服务
}
}
基本过程: 服务器启动并初始化(new SelectorServer),服务器开始监听,serversocketchannel接收socket连接,并注册到selector,然后提供serversocket服务。
其中selector 是 SelectableChannel 对象的多路复用器。很有必要了解selector。
serversocketchannel 和socketchannel是实现使用通道传输数据的基础条件
非阻塞模式的优势:
通道channel要么处于阻塞 模式,要么处于非阻塞模式。
在阻塞模式中,每一个 I/O 操作完成之前都会阻塞在这个通道上调用的其他 I/O 操作。
在非阻塞模式中,永远不会阻塞 I/O 操作,并且传输的字节可能少于请求的数量,或者可能根本不传输字节。
新创建的通道总是处于阻塞模式。在结合使用基于选择器selector的多路复用时,非阻塞模式是最有用的。向选择器
注册某个通道前,必须将该通道置于非阻塞模式,并且在注销之前可能无法返回到阻塞模式。