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

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();

一次完整的交互完成。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值