其实这个是我自己对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的多路复用时,非阻塞模式是最有用的。向选择器
注册某个通道前,必须将该通道置于非阻塞模式,并且在注销之前可能无法返回到阻塞模式。