java NIO 非阻塞嵌套字 Select的基本用法以及modbus RTU通讯实例

版权声明:博客知识产权来源命运的信徒,切勿侵权 https://blog.csdn.net/qq_37591637/article/details/86594236

 为什么需要使用Select ?

socket通讯总共有两种嵌套字(阻塞和非阻塞的),

阻塞式:socket.accpet();socket.read();耗时非常长,长达20多秒,不利于大数据的开发

非阻塞式:耗时短,程序比阻塞式的复杂.


Select (选择器)是Java Nio中能够检测一个到多个通道,并能够知道是否为诸如读写事件做好准备的组件。

(类似于道路管理员)

这样,一个单独的线程就可以管理多个channel,从而管理多个网络连接。


1、Selector 的创建

通过调用Selector.open()方法,创建Selector,代码如下:

Selector    selector = Selector.open();

2、向Selector 注册通道

为了将Channel 和Selector 配合使用,必须将Channel 注册到Selector 上,主要通过SelectableChannel.register()方法实现,代码如下:

//启用通道非阻塞模式。

serverSocketChannel.configBlocking(false);

//Selector 监听Channel 事件类型以及SelectionKey 常量表示。

SelectionKey key = serverSocketChannel.register(selector,SelectionKey.OP_READ)

3、SelectionKey 对象

当向selector 注册channel时,register()方法会返回一个SelectionKey 对象。


4、检测channel中什么事件或操作已经就绪。同时,也可以使用以下四个方法,它们都会返回一个布尔类型:

1、selectionKey.isAcceptable();

2、selectionKey.isConnectable();

3、selectionKey.isReadable();

4、selectionKey.isWritable();



Channel +Selector

从SelectionKey访问Channel和Selector很简单。代码如下:

Channel channel = SelectionKey.channel();

Selector selector = SelectionKey.selector();

 


注意重点:
select()方法返回的int值表示有多少通道已经就绪。亦即,自上次调用select()方法后有多少通道变成就绪状态。如果调用select()方法,因为有一个通道变成就绪状态,返回了1,若再次调用select()方法,如果另一个通道就绪了,它会再次返回1。如果对第一个就绪的channel没有做任何操作,现在就有两个就绪的通道,但在每次select()方法调用之间,只有一个通道就绪了。
SelectedKeys()

一旦调用了select()方法,并且返回值表明有一个或更多个通道就绪了,然后可以通过调用selector的selectedKeys()方法,访问“已选择键集(selected key set)”中的就绪通道。如下所示: Set   selectedKeys  =  selector. selectedKeys()
当像Selector注册Channel时,Channel.register()方法会返回一个SelectionKey 对象。这个对象代表了注册到该Selector的通道。可以通过SelectionKey的selectedKeySet()方法访问这些对象。


modbus RTU通讯实例 

1、在电脑上管理modbus RTU协议的传感器,port端口是6031

2、服务器也就是电脑发送数据给客户机(传感器),然后客户机返回数据给服务器,是一问一答的模式

如果服务器没有发送数据给客户机,那么客户机也不会返回数据给服务器

3、我这里主要是实现服务器的功能!客户机我也不用管的!

4、我的代码每一句都有注解,不知道什么意思的可以看看

package cn.com.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
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 ServerSocketChannelDemo {
	static ServerSocketChannel serverSocketChannel;
	static Selector selector = null;
	static SelectionKey key = null;

	public static void main(String[] args) {
		try {
			//1.打开监听通道,这个通道类似于道路
			serverSocketChannel = ServerSocketChannel.open();
			//为这个通道设置阻塞模式-不阻塞
			serverSocketChannel.configureBlocking(false);
			//为这个通道绑定监听的端口号
			serverSocketChannel.socket().bind(new InetSocketAddress(6031));
			//开启选择器 类似于道路管理员
			selector = Selector.open();
			//把通道注册到选择器上面
			serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		while (true) {
			try {
				// select()阻塞到至少有一个通道在你注册的事件上就绪了
				// 如果没有准备好的channel,就在这一直阻塞
				// select(long timeout)和select()一样,除了最长会阻塞timeout毫秒(参数)。
				selector.select();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				break;
			}
			// 返回已经就绪的SelectionKey,然后迭代执行
			Set<SelectionKey> readKeys = selector.selectedKeys();
			for (Iterator<SelectionKey> it = readKeys.iterator(); it.hasNext();) {
				//第一个key返回的通道应该是OP_ACCEPT属性的;
				key = it.next();
				//把当前的移除,
				it.remove();
				SocketChannel client = null;
				try {
					//如果有新的连接
					if (key.isAcceptable()) {
						//获取新的连接
						ServerSocketChannel server = (ServerSocketChannel) key
								.channel();
						//服务器和传感器之间建立连接
						client = server.accept();
						//设置不阻塞模式
						client.configureBlocking(false);
						//开始为这个通道绑定可写事件属性
						client.register(selector, SelectionKey.OP_WRITE);
					} 
					//当新的连接建立以后,如果这个通道有可写的属性,就这个里面执行,
					else if (key.isWritable()) {
						//获取通道,
						client = (SocketChannel) key.channel();
						//开始创建一个静态的缓存区,这个ByteBuffer就是相当于装载数据的大卡车
						ByteBuffer buffer = ByteBuffer.allocate(8);
						System.out.println("开始发送数据....");
						//要发送的数据
						byte[] by = new byte[] { 03, 03, 00, 02, 00, 01, 36, 40 };
						//把数据放在buffer里面
						buffer = ByteBuffer.wrap(by);
						//写数据
						client.write(buffer);
						//这个是为读取数据做准备
						////flip():Buffer有两种模式,写模式和读模式。在写模式下调用flip()之后,Buffer从写模式变成读模式。      
						buffer.flip();
						key.interestOps(SelectionKey.OP_READ);
                        //开启延迟,如果没有延迟就接受不到数据,程序运行的比接受数据的时间还要短
						key.cancel();
						// 延迟几秒以后在执行下面的任务,程序运行的比接受数据的时间还要短
						try {
							Thread.sleep(600);
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
						//创建一个缓存区,接受、发送数据必备的工具
						ByteBuffer readBuff = ByteBuffer.allocate(1024);
						//清理缓存区,不然会接受到以前的信息,不准确
						readBuff.clear();
						//开始读取数据
						int f = client.read(readBuff);
						System.out.println("is kudcdhdjkk:" + f);
						StringBuilder sn = new StringBuilder();
						if (f > 0) {
							//flip():Buffer有两种模式,写模式和读模式。在写模式下调用flip()之后,Buffer从写模式变成读模式。      
							readBuff.flip();
							//新建一个byte类型的数组
							byte[] bytes = new byte[7];
							//把数据放在byte类型的数组里面
							readBuff.get(bytes, 0, f);
							//对数据bytes里面的数据进行16进制的转化
							for (int i = 0; i < bytes.length; i++) {
								int a = bytes[i] & 0xff;
								String w = Integer.toHexString(a);
								if (w.length() == 1) {
									w = "0" + w;
								}
								sn.append(w);
							}
						}
                          //打印数据
						System.out.println("received : " + sn);
					}
				} catch (IOException e) {
					e.printStackTrace();
					key.cancel();
					try {
						key.channel().close();
					} catch (IOException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					}
				}
			}
		}
	}

}

 效果如果


以下是整理后的代码

package cn.com.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
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 ServerSocketChannelDemo {
	static ServerSocketChannel serverSocketChannel;
	static Selector selector = null;
	static SelectionKey key = null;

	public static void init() {
		try {
			// 1.打开监听通道,这个通道类似于道路
			serverSocketChannel = ServerSocketChannel.open();
			// 为这个通道设置阻塞模式-不阻塞
			serverSocketChannel.configureBlocking(false);
			// 为这个通道绑定监听的端口号
			serverSocketChannel.socket().bind(new InetSocketAddress(6031));
			// 开启选择器 类似于道路管理员
			selector = Selector.open();
			// 把通道注册到选择器上面
			serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	public static void main(String[] args) {
		init();
		while (true) {
			try {
				// select()阻塞到至少有一个通道在你注册的事件上就绪了
				// 如果没有准备好的channel,就在这一直阻塞
				// select(long timeout)和select()一样,除了最长会阻塞timeout毫秒(参数)。
				int i=selector.select();
				System.out.println("连接的通道个数:"+i);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				break;
			}
			// 返回已经就绪的SelectionKey,然后迭代执行
			Set<SelectionKey> readKeys = selector.selectedKeys();
			for (Iterator<SelectionKey> it = readKeys.iterator(); it.hasNext();) {
				// 第一个key返回的通道应该是OP_ACCEPT属性的;
				key = it.next();
				// 把当前的移除,
				it.remove();
				SocketChannel client = null;
				try {
					// 如果有新的连接
					if (key.isAcceptable()) {
						// 获取新的连接
						ServerSocketChannel server = (ServerSocketChannel) key
								.channel();
						// 服务器和传感器之间建立连接
						client = server.accept();
						// 设置不阻塞模式
						client.configureBlocking(false);
						// 开始为这个通道绑定可写事件属性
						client.register(selector, SelectionKey.OP_WRITE);
					}
					// 当新的连接建立以后,如果这个通道有可写的属性,就这个里面执行,
					else if (key.isWritable()) {
						// 获取通道,
						client = (SocketChannel) key.channel();
						// 开始创建一个静态的缓存区,这个ByteBuffer就是相当于装载数据的大卡车
						ByteBuffer buffer = ByteBuffer.allocate(8);
						System.out.println("开始发送数据....");
						byte[] by = new byte[] { 03, 03, 00, 02, 00, 01, 36, 40 };
						SocketRead.writeData(buffer, client, by);
						key.interestOps(SelectionKey.OP_READ);
						// 开启延迟,如果没有延迟就接受不到数据,程序运行的比接受数据的时间还要短
						key.cancel();
						// 延迟几秒以后在执行下面的任务,程序运行的比接受数据的时间还要短
						SocketRead.ready(client);
				
					}
				} catch (IOException e) {
					e.printStackTrace();
					key.cancel();
					try {
						key.channel().close();
					} catch (IOException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					}
				}
			}
		}
	}

}
package cn.com.nio;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class SocketRead {
/*author:命运的信徒
 * date:2019/1/22
 * arm:负责读取数据和写入数据
 */
	public static String ready(SocketChannel client){
		try {
			Thread.sleep(600);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		//创建一个缓存区,接受、发送数据必备的工具
		ByteBuffer readBuff = ByteBuffer.allocate(1024);
		//清理缓存区,不然会接受到以前的信息,不准确
		readBuff.clear();
		//开始读取数据
		int f=0;
		try {
			f = client.read(readBuff);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("接受数据的长度是:" + f);
		StringBuilder sn = new StringBuilder();
		if (f > 0) {
			//flip():Buffer有两种模式,写模式和读模式。在写模式下调用flip()之后,Buffer从写模式变成读模式。      
			readBuff.flip();
			//新建一个byte类型的数组
			byte[] bytes = new byte[7];
			//把数据放在byte类型的数组里面
			readBuff.get(bytes, 0, f);
			//对数据bytes里面的数据进行16进制的转化
			for (int i = 0; i < bytes.length; i++) {
				int a = bytes[i] & 0xff;
				String w = Integer.toHexString(a);
				if (w.length() == 1) {
					w = "0" + w;
				}
				sn.append(w);
			}
		}
          //打印数据
		System.out.println("received : " + sn);
		return sn.toString();
	}
	public static void writeData(ByteBuffer buffer,SocketChannel client,byte [] by){		
		//把数据放在buffer里面
		buffer = ByteBuffer.wrap(by);
		//写数据
		try {
			client.write(buffer);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		//这个是为读取数据做准备
		////flip():Buffer有两种模式,写模式和读模式。在写模式下调用flip()之后,Buffer从写模式变成读模式。      
		buffer.flip();
		
	}
}

猜你喜欢

转载自blog.csdn.net/qq_37591637/article/details/86594236
今日推荐