Selector、SelectionKey、ServerSocketChannel、SocketChannel。。。头晕了,一下子理解不来,用socket的模式没法套。
做了个简单的例子:
Server端:
package nio;
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.Set;
public class TestReadServer {
/*缓冲区大小*/
private int BLOCK = 1024*1024*10;
/*接受数据缓冲区*/
private ByteBuffer sendbuffer = ByteBuffer.allocate(BLOCK);
/*发送数据缓冲区*/
private ByteBuffer receivebuffer = ByteBuffer.allocate(BLOCK);
private Selector selector;
public TestReadServer(int port) throws IOException {
// 打开服务器套接字通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 服务器配置为非阻塞
serverSocketChannel.configureBlocking(false);
// 检索与此通道关联的服务器套接字
ServerSocket serverSocket = serverSocketChannel.socket();
// 进行服务的绑定
serverSocket.bind(new InetSocketAddress(port));
// 通过open()方法找到Selector
selector = Selector.open();
// 注册到selector,等待连接
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("Server Start at:"+port);
}
// 监听
private void listen() throws IOException {
while (true) {
// 选择一组键,并且相应的通道已经打开
int select = selector.select();
if(select == 0)continue;
// 返回此选择器的已选择键集。
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
iterator.remove();
handleKey(selectionKey);
}
}
}
// 处理请求
private void handleKey(SelectionKey selectionKey) throws IOException {
// 接受请求
ServerSocketChannel server = null;
SocketChannel client = null;
String receiveText;
int count=0;
// 测试此键的通道是否已准备好接受新的套接字连接。
if (selectionKey.isAcceptable()) {
// 返回为之创建此键的通道。
server = (ServerSocketChannel) selectionKey.channel();
// 接受到此通道套接字的连接。
// 此方法返回的套接字通道(如果有)将处于阻塞模式。
client = server.accept();
// 配置为非阻塞
client.configureBlocking(false);
//往选择器注册该通道上的OP_READ事件
client.register(selector, SelectionKey.OP_READ);
} else if(selectionKey.isReadable()){
client = (SocketChannel) selectionKey.channel();
//读Client发过来的信息
receivebuffer.clear();
count = client.read(receivebuffer);
if (count > 0) {
receiveText = new String( receivebuffer.array(),0,count);
System.out.println("服务器端read客户端数据:"+receiveText);
}
receivebuffer.flip();
//发信息给Client
sendbuffer.clear();
sendbuffer.put("server --> client".getBytes());
sendbuffer.flip();
client.write(sendbuffer);
/*
* 把选择键注销掉。
* 否则Client关闭通道时会发送消息过来,selector会一直select到这个key
* 并执行最后的操作,到时对通道读写就会报异常了。
*/
selectionKey.cancel();
}
}
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
int port = 1234;
TestReadServer server = new TestReadServer(port);
server.listen();
}
}
Client端:
package nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class TestReadClient {
/*缓冲区大小*/
private static int BLOCK = 1024*1024*10;
/*接受数据缓冲区*/
private static ByteBuffer sendbuffer = ByteBuffer.allocate(BLOCK);
/*发送数据缓冲区*/
private static ByteBuffer receivebuffer = ByteBuffer.allocate(BLOCK);
/*服务器端地址*/
private final static InetSocketAddress SERVER_ADDRESS = new InetSocketAddress("localhost", 1234);
public static void main(String[] args) throws IOException {
// 打开socket通道
SocketChannel socketChannel = SocketChannel.open();
// 客户端设置为阻塞方式
socketChannel.configureBlocking(true);
// 连接
socketChannel.connect(SERVER_ADDRESS);
socketChannel.finishConnect();
//往通道写
sendbuffer.clear();
sendbuffer.put("Hello,Server!I'm Client.".getBytes());
sendbuffer.flip();
socketChannel.write(sendbuffer);
String receiveText;
int count=0;
//从通道里读数据
receivebuffer.clear();
count=socketChannel.read(receivebuffer);
if(count>0){
receiveText = new String( receivebuffer.array(),0,count);
System.out.println("客户端接收服务器端数据:"+receiveText);
} else {
System.out.println("read null:");
}
//关闭通道
socketChannel.close();
}
}
Server端打印:
Server Start at:1234
服务器端read客户端数据:Hello,Server!I'm Client.
Client端打印:
客户端接收服务器端数据:server --> client
怎么理解这种Selector和SelectionK呢?
模拟个不是很确切的例子,我(SocketChannel)去客户公司(ServerSocketChannel)那里做交流。到门口后,保安(Selector)发现我没门卡,就过来问我找谁,然后让我登记信息,给了我一张访客卡。这个时候我拿着这张卡就进到里边去转转了。保安的职责之一是检查进出公司的人,每个人每次进出都是一个SelectionKey。
1、客户的公司开门做生意:ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
2、保安上班了:serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);《保安守则》一、碰到第一次造访的客人要留意。
3、看到有人进出门口:int select = selector.select();
4、看看有没有人没门卡,没有的话当做客人:if (selectionKey.isAcceptable()) {}
5、询问客人并登记领访客卡:client = server.accept();
6、我可以进去了:client.register(selector, SelectionKey.OP_READ);
显然,这张访客卡是一张次卡,只能访问一次,不能带出门。保安是让我登记了才放我进去,等和我的客户交流(read、write)后,临走时就把卡收回去了selectionKey.cancel();下次我再访问的话,又要重新登记。
整个Server端建立连接或在明或在暗都是Selector(保安)在操作控制。
而Client这边,我呢:
1、去客户公司:socketChannel.connect(SERVER_ADDRESS);
2、登记信息并领卡:socketChannel.finishConnect();
3、和客户交流:write、read
4、临走时把门卡退给保安:socketChannel.close();
门卡是看得见摸得着的东西,NIO里却看不到,不过作为门禁的凭证,也可以是人脸信息或者视网膜或者指纹什么的。
另外,补充些程序的注释。
1、这是一次简单的客户端与服务端间“请求——响应”的互动。
Server端设置成非阻塞、Client端设置成阻塞。
Server端是接收到请求后,才读写数据的,而读写的是两个缓冲区的数据,互补干扰,用非阻塞没问题。
Client端是先发送消息,等Server响应后再读Server发过来的消息。如果设置成非阻塞,会出现:消息还没发送,就开始去接收Server的消息,就读不到数据了。正常的“请求——响应”也是这样子的,发送请求后阻塞等待响应。
2、通道和套接字,能扯得上想的就是:SocketServerChannel=SocketServer、SocketChannel=Socket了
每个地址端口的SocketChannel是单例的,而SocketServer,都会为每个请求创建一个Socket来处理。
3、Selector.select()方法是一直阻塞着,等待SelectableChannel(SocketServerChannel和SocketChannel的父类)出现变化:是否有新的请求进来,是否有旧连接的数据过来。。。使用Set<SelectionKey> selectionKeys = selector.selectedKeys();返回所有监听到的SelectionKey。SelectionKey这个对象含有监听的事件、所在的Channel和所属Selector。
4、MINA的连接也是按这种“请求——响应”的一次连接。MINA封装Channel就像Tomcat封装Socket一样。
5、也可以把Server端的这段代码注释掉。
client.register(selector, SelectionKey.OP_READ);
} else if(selectionKey.isReadable()){
client = (SocketChannel) selectionKey.channel();
这样一来,就是相当于跟保安打个招呼就进去了。
nio中客户端与服务端的交互步骤大致如下:
1、服务端开始监听建立连接的请求:serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
2、客户端向服务端发起连接请求:socketChannel.connect(SERVER_ADDRESS);
3、请求建立后,服务端监听客户通道上的可读数据:client.register(selector, SelectionKey.OP_READ);
4、客户端写数据:socketChannel.write(sendbuffer);
5、服务端监听到有数据可读就读数据:count = client.read(receivebuffer);
6、服务端往客户通道写数据:client.write(sendbuffer);
7、服务端取消对该客户通道的监听:selectionKey.cancel();
8、客户端接收服务端的数据:count=socketChannel.read(receivebuffer);
9、客户端关闭通道:socketChannel.close();
一次完整的交互完成。