您的位置 首页 java

Java后端精选技术:Java NIO

以前写过一篇Java Socket的用法,不过觉得介绍的不够细致也不够全面,因此今天想在细谈一下Java NIO,也算是对上一篇博客的补充吧。在以前的博客中提到Java NIO的三个核心部分Buffers、 channel s、Selectors,这里不再赘述三者之间的关系,接下来我们重点看看这三个核心部分。

Buffer

该区域本质是一块可以读写的数据的内存区,这组内存区被包装成NIO Buffer对象,并提供了一组方法,方便访问该块内存。为了更清楚的理解Buffer的工作原理,需要熟悉它的三个属性capacity、position、limit。capacity表示缓冲区大小。而position和limit的含义取决于Buffer处在读模式还是写模式下。在读模式下,position表示开始读的位置,limit表示最后能读的数据位置。在写模式下,position表示当前数据需要写入的位置,最大值为capacity-1。当由写模式切换到读模式时,position=0,limit=position。

抽象类Buffer具体实现类有ByteBuffer、MappedByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer。接下来我们以ByteBuffer为例来了解一下Buffer的具体用法。

View Code

Channel s

Channel充当的其实是搬运工的角色,它负责把数据搬运到Buffer中,也可以从Buffer中把数据搬运出去。具体的实现Channel接口的类有:FileChannel、DatagramChannel、SocketChannel、Server socket Channel。FileChannel从文件中读取数据到缓冲区(已经在Buffer中介绍过了),DatagramChannel能通过UDP读写网络中的数据,SocketChannel能通过TCP读写网络中的数据,ServerSocketChannel可以监听新进来的TCP连接,对每个新进来的连接都会创建一个SocketChannel。如下是利用SocketChannel和ServerSocketChannel实现客户端和服务器端(IP地址192.168.6.42)通信:

客户端程序如下:

Selector

Selector是Java NIO中能够检测到一到多个NIO通道,并能够知晓通道是否为诸如读写时间做好准备的组件。这样就可以实现一个单独的 线程 可以管理多个Channel,从而管理多个网络连接。我们从如下服务器端和客户端的程序介绍Selector吧。

客户端程序如下,首先是一个创建SocketChannel的类如下:

客户端主程序通过调用该类的createSocketChannel()方法创建一个SocketChannel对象,主程序如下:

public class Main {
 public static void main(String[] args){
 try {
 //创建SocketChannel,连接192.168.6.42服务器的 8080端口 
 SocketChannel sc8080 = TestSocketChannel.createSocketChannel("192.168.6.42",8080);
 
 //创建SocketChannel,连接192.168.6.42服务器的8090端口
 SocketChannel sc8090 = TestSocketChannel.createSocketChannel("192.168.6.42",8090);
 
 //创建selector
 Selector selector = Selector.open();
 //向通道注册选择器,并设置selector监听Channel时对读操作感兴趣
 sc8080.register(selector, SelectionKey.OP_READ);
 sc8090.register(selector, SelectionKey.OP_READ);
 //启动线程,监听是否从服务器端有数据传过来
 Thread thread = new Thread(new MyRunnable(selector));
 thread.start();
 //分别向服务器的8080和8090端口发送数据
 sendString(sc8080,"This message is going to send to server 8080 port");
 sendString(sc8090,"This message is going to send to server 8090 port");
 
 } catch (IOException e) {
 // TODO Auto-generated catch block
 e.printStackTrace();
 }
 }
 private static void sendString(SocketChannel sc, String str){
 ByteBuffer buffer = ByteBuffer.wrap(str.getBytes());
 try {
 //将buffer中的数据写入sc通道
 sc.write(buffer);
 } catch (IOException e) {
 // TODO Auto-generated catch block
 e.printStackTrace();
 }
 }
}
class MyRunnable implements Runnable{
 private Selector selector;
 public MyRunnable(Selector s){
 this.selector =s;
 }
 @Override
 public void run() {
 // TODO Auto-generated method stub
 try {
 while(true){
 //阻塞2000ms,判断是否有通道在注册的事件上就绪了,如果有则该返回值就绪通道的个数
 if(selector.select(2000) == 0){
 System.out.println("please waiting.....");
 continue;
 }else{
 //当有通道就绪时,获取SelectionKey,并遍历
  Iterator <SelectionKey> keys = selector.selectedKeys().iterator();
 while(keys.hasNext()){
 SelectionKey key = keys.next();
 //判断通道中是否可读事件就绪了,如果是则isReadable()方法返回TRUE
 if(key.isReadable()){
 SocketChannel socketChannel = (SocketChannel) key.channel();
 //默认服务器端发送的数据都小于1024byte,因此一次可以读完
 ByteBuffer buffer = ByteBuffer.allocate(1024);
 socketChannel.read(buffer); //利用通道将数据读入buffer中
 buffer.flip(); //将buffer切换为读模式
 String receiveString = Charset.forName("UTF-8").newDecoder().decode(buffer). toString ();
 System.out.println(receiveString);
 buffer.clear(); //清空缓冲区buffer
 }
 //设置通道对什么时间感兴趣,该设置是对“读”和“写”感兴趣
 key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
 //移除当前已经处理过的SelectionKey
 keys.remove();
 }
 }
 }
 } catch (IOException e) {
 // TODO Auto-generated catch block
 e.printStackTrace();
 }
 }
 
 

服务器端程序如下:

public class Server {
 private static Selector selector = null;
 public static void main(String[] args){
 
 try {
 //创建一个ServerSocketChannel,监听8080端口,非阻塞模式
 ServerSocketChannel ssc8080 = (new TestServerSocketChannel()).createServerSocketChannel(8080);
 //创建一个ServerSocketChannel,监听8090端口,非阻塞模式
 ServerSocketChannel ssc8090 = (new TestServerSocketChannel()).createServerSocketChannel(8090);
 //创建监听器
 selector = Selector.open();
 //向通道注册监听器
 ssc8080.register(selector, SelectionKey.OP_ACCEPT);
 ssc8090.register(selector, SelectionKey.OP_ACCEPT);
 //开启线程,监听客户端发送过来的数据
 Thread thread = new Thread(new MyRunnable());
 thread.start();
 } catch (IOException e) {
 // TODO Auto-generated catch block
 e.printStackTrace();
 }
 }
 static class MyRunnable implements Runnable {
 @Override
 public void run() {
 // TODO Auto-generated method stub
 while (true) {
 try {
 //阻塞3s后判断selector注册的通道是否有就绪的
 if(selector.select(3000) == 0){
 System.out.println("正在等待请求......");
 continue;
 }else{
 Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
 while (keys.hasNext()) {
 SelectionKey key = keys.next();
 //判断是否有新的连接
 if (key.isAcceptable()) {
 HandleRequest.handleAccept(key);
 } else if (key.isReadable()) { //判断是否有读操作
 HandleRequest.handleRead(key);
 } else if (key.isValid() && key.isWritable()) { //判断是否对写操作感兴趣
 HandleRequest.handleWrite(key);
 }
 keys.remove(); // 移除处理过的键
 }
 }
 } catch (IOException e) {
 // TODO Auto-generated catch block
 e.printStackTrace();
 }
 }
 }
 }
}
 
public class HandleRequest {
 //当有新的连接时
 public static void handleAccept(SelectionKey key){
 try {
 //通过SelectionKey对象key创建SocketChannel对象
 SocketChannel socketChannel = ((ServerSocketChannel)key.channel()).accept();
 //设置socketChannel为非阻塞模式
 socketChannel.configureBlocking(false);
 //向通道注册选择器和感兴趣事件
 socketChannel.register(key.selector(), SelectionKey.OP_READ);
 //输出数据从服务器的哪个端口传入
 System.out.println("receive data from port:" + socketChannel.socket().getLocalPort());
 } catch (IOException e) {
 // TODO Auto-generated catch block
 e.printStackTrace();
 }
 }
 public static void handleRead(SelectionKey key){
 SocketChannel socketChannel = (SocketChannel) key.channel();
 System.out.println("receive data from port:" + socketChannel.socket().getLocalPort());
 ByteBuffer buffer = ByteBuffer.allocate(32);
 try {
 int readBytes = socketChannel.read(buffer);
 //输出数据从哪个客户端地址传入
 System.out.println("receive data from " + socketChannel.socket().getRemoteSocketAddress() + ", the data are ");
 //读取缓冲区中的数据
 while(readBytes != 0){
 buffer.flip();
 String receiveString = Charset.forName("UTF-8").newDecoder().decode(buffer).toString();
 System.out.print(receiveString);
 buffer.clear();
 readBytes = socketChannel.read(buffer);
 }
 //更改通道感兴趣的事件为“读操作”和“写操作”
 key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
 } catch (IOException e) {
 // TODO Auto-generated catch block
 e.printStackTrace();
 }
 }
 public static void handleWrite(SelectionKey key){
 SocketChannel socketChannel = (SocketChannel) key.channel();
 ByteBuffer writeBuffer = ByteBuffer.wrap("This message is from server".getBytes());
 try {
 socketChannel.write(writeBuffer);
 System.out.println("The message is writen in channel");
 key.interestOps(SelectionKey.OP_READ);
 } catch (IOException e) {
 // TODO Auto-generated catch block
 e.printStackTrace();
 }
 }
}

 

文章来源:智云一二三科技

文章标题:Java后端精选技术:Java NIO

文章地址:https://www.zhihuclub.com/183635.shtml

关于作者: 智云科技

热门文章

网站地图