Java NIO例子——MINA的最简单模型

来源:互联网 发布:形容程序员的搞笑图片 编辑:程序博客网 时间:2024/05/02 23:22

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()方法找到Selectorselector = 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();//发信息给Clientsendbuffer.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 stubint 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();

一次完整的交互完成。


原创粉丝点击