Java NIO | 流程与示例代码

博客引用处(以下内容在原有博客基础上进行补充或更改,谢谢这些大牛的博客指导):
java NIO示例以及流程详解
Java NIO深入理解ServerSocketChannel
并发编程网
Java NIO之Selector(选择器)

NIO执行流程:

第一步:启动server服务器,初始化多路复用器selector、ServerSocketChannel通道、设置通道的模式为非阻塞、注册channel到selector上,并监听accept请求;

第二步:启动server服务器,循环selectionKeys,当有channel准备好时就处理,否则一直循环或者用间隔轮询的方式,比如阻塞1S后再获取selectionKeys;

第三步:启动client端,初始化多路复用器selector、SocketChannel通道,设置通道的模式为非阻塞(这是连接准备就绪,还未连接就绪/连接成功);

第四步:client首先尝试连接server,此时socketChannel.connect(new InetSocketAddress(this.host, this.port)返回false,表示server还没有返回信息,server收到连接请求后,监听到client的接入请求,会初始化一个client通道对象在服务端、并将新接入的client注册到多路复用器Selector上,并应答client;再回到client端,由于client没有及时收到server端的应答(因为是非阻塞式的),所以client会设置监听一个connect请求,socketChannel.register(selector, SelectionKey.OP_CONNECT),当server返回应答信息时,client会收到一个connect请求,key.isConnectable(),如果此时sc.finishConnect()连接完成,client会监听一个read请求,并像server发送数据doWrite(sc),然后server会收到一个read请求,key.isReadable()处理完后返回给client,client也会收到一个读请求,收到server的返回数据,此时,整个交互过程结束;

在这里插入图片描述

服务端:

package nio;
 
import org.springframework.util.StringUtils;
 
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.Date;
import java.util.Iterator;
import java.util.Set;
 
public class ServerSocketChannels implements Runnable {
 
    private  ServerSocketChannel serverSocketChannel;
 
    private  Selector selector;
 
    private volatile boolean stop;
 
 
    public ServerSocketChannels(int port){
 
        try {
            //创建多路复用器selector,工厂方法
            selector = Selector.open();
            //创建ServerSocketChannel,工厂方法
            serverSocketChannel = ServerSocketChannel.open();
            //绑定ip和端口号,默认的IP=127.0.0.1,对连接的请求最大队列长度设置为backlog=1024,如果队列满时收到连接请求,则拒绝连接
            serverSocketChannel.socket().bind(new InetSocketAddress(port), 1024);
            //设置非阻塞方式
            serverSocketChannel.configureBlocking(false);
            //注册serverSocketChannel到selector多路服用器上面,监听accrpt请求
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println("the time is start port = " + port);
        } catch (IOException e) {
            e.printStackTrace();
            //用于非正常退出JVM虚拟机,停掉所有运作 0代表正常退出,非零代表非正常退出
            System.exit(1);
        }
    }
 
    public void stop(){
        this.stop = true;
    }
 
    @Override
    public void run() {
        //如果server没有停止
        while(!stop){
            try {
                //selector.select()会一直阻塞到有一个通道在你注册的事件上就绪了
                //selector.select(1000)会阻塞到1s后然后接着执行,相当于1s轮询检查
                selector.select(1000);
                //找到所有准备接续的key
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> it = selectionKeys.iterator();
                SelectionKey key = null;
               while(it.hasNext()){
                   key = it.next();
                   it.remove();
                   try {
                       //处理准备就绪的key
                       handle(key);
                   }catch (Exception e){
                       if(key != null){
                           //请求取消此键的通道到其选择器的注册
                           key.cancel();
                           //关闭这个通道
                           if(key.channel() != null){
                               key.channel().close();
                           }
                       }
                   }
               }
            } catch (Throwable e) {
                e.printStackTrace();
            }
        }
        if(selector != null){
            try {
                selector.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
 
    public void handle(SelectionKey key) throws IOException {
         //如果key是有效的
          if(key.isValid()){
              //监听到有新客户端的接入请求
              //完成TCP的三次握手,建立物理链路层
              if(key.isAcceptable()){
                  ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                  SocketChannel sc = (SocketChannel) ssc.accept();
                  //设置客户端链路为非阻塞模式
                  sc.configureBlocking(false);
                  //将新接入的客户端注册到多路复用器Selector上
                  sc.register(selector, SelectionKey.OP_READ);
              }
              //监听到客户端的读请求
              if(key.isReadable()){
                  //获得通道对象
                  SocketChannel sc = (SocketChannel) key.channel();
                  ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                  //从channel读数据到缓冲区
                 int readBytes = sc.read(readBuffer);
                 if (readBytes > 0){
                    //Flips this buffer.  The limit is set to the current position and then
                     // the position is set to zero,就是表示要从起始位置开始读取数据
                     readBuffer.flip();
                     //eturns the number of elements between the current position and the  limit.
                     // 要读取的字节长度
                     byte[] bytes = new byte[readBuffer.remaining()];
                     //将缓冲区的数据读到bytes数组
                     readBuffer.get(bytes);
                     String body = new String(bytes, "UTF-8");
                     System.out.println("the time server receive order: " + body);
                     String currenttime = "query time order".equals(body) ? new Date(System.currentTimeMillis()).toString(): "bad order";
                     doWrite(sc, currenttime);
                 }else if(readBytes < 0){
                    key.channel();
                    sc.close();
                  }
              }
          }
    }
 
    public static void doWrite(SocketChannel channel, String response) throws IOException {
        if(!StringUtils.isEmpty(response)){
            byte []  bytes = response.getBytes();
            //分配一个bytes的length长度的ByteBuffer
            ByteBuffer  write = ByteBuffer.allocate(bytes.length);
            //将返回数据写入缓冲区
            write.put(bytes);
            write.flip();
            //将缓冲数据写入渠道,返回给客户端
            channel.write(write);
        }
    }
}
 

服务端启动程序:

package nio;
 
/**
 * 服务端启动程序
 */
public class ServerMain {
 
    public static void main(String[] args) {
        int port = 8010;
        ServerSocketChannels server = new ServerSocketChannels(port);
        new Thread(server,"timeserver-001").start();
    }
}

客户端程序:

package 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.SocketChannel;
import java.util.Iterator;
import java.util.Set;
 
public class TimeClientHandler implements Runnable {
 
    //服务器端的ip
    private String host;
   //服务器端的端口号
    private int port;
   //多路服用选择器
    private Selector selector;
 
    private SocketChannel socketChannel;
 
    private volatile boolean stop;
 
 
    public TimeClientHandler(String host, int port){
        this.host = host == null ? "127.0.0.1": host;
        this.port = port;
        try {
            //初始化一个Selector,工厂方法
            selector = Selector.open();
            //初始化一个SocketChannel,工厂方法
            socketChannel = SocketChannel.open();
            //设置非阻塞模式
            socketChannel.configureBlocking(false);
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }
 
    }
 
 
    /**
     * 首先尝试连接服务端
     * @throws IOException
     */
    public void doConnect() throws IOException {
        //如果连接成功,像多路复用器selector监听读请求
        if(socketChannel.connect(new InetSocketAddress(this.host, this.port))){
         socketChannel.register(selector, SelectionKey.OP_READ);
         //执行写操作,像服务器端发送数据
         doWrite(socketChannel);
        }else {
            //监听连接请求
            socketChannel.register(selector, SelectionKey.OP_CONNECT);
        }
    }
 
 
    public static void doWrite(SocketChannel sc) throws IOException {
      //构造请求消息体
       byte [] bytes = "query time order".getBytes();
      //构造ByteBuffer
       ByteBuffer write = ByteBuffer.allocate(bytes.length);
      //将消息体写入发送缓冲区
        write.put(bytes);
        write.flip();
       //调用channel的发送方法异步发送
        sc.write(write);
       //通过hasRemaining方法对发送结果进行判断,如果消息全部发送成功,则返回true
        if(!write.hasRemaining()){
            System.out.println("send order 2 server successd");
        }
    }
 
    @Override
    public void run() {
        try {
            doConnect();
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }
        while (!stop){
            try {
                selector.select(1000);
                Set<SelectionKey> keys =  selector.selectedKeys();
                Iterator<SelectionKey> its =keys.iterator();
                SelectionKey key = null;
                while (its.hasNext()){
                    key = its.next();
                    its.remove();
                    try {
                        handle(key);
                    }catch (Exception e){
                        if(key != null){
                            key.cancel();
                            if(key.channel() != null){
                                key.channel().close();
                            }
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
                System.exit(1);
            }
        }
    }
 
    public  void handle(SelectionKey key) throws IOException {
        if(key.isValid()){
            SocketChannel sc = (SocketChannel) key.channel();
            if(key.isConnectable()){
                //如果连接成功,监听读请求
               if(sc.finishConnect()){
                  sc.register(this.selector, SelectionKey.OP_READ);
                  //像服务端发送数据
                   doWrite(sc);
               }else{
                   System.exit(1);
               }
            }
            //监听到读请求,从服务器端接受数据
            if(key.isReadable()){
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                int readBytes = sc.read(byteBuffer);
                if(readBytes > 0){
                    byteBuffer.flip();
                    byte []  bytes = new byte[byteBuffer.remaining()];
                    byteBuffer.get(bytes);
                    String body = new String(bytes,"UTF-8");
                    System.out.println("now body is "+ body);
                    stop = true;
 
                }else if(readBytes < 0){
                    key.cancel();
                    sc.close();
                }
 
 
            }
        }
    }
  //释放所有与该多路复用器selector关联的资源
     if(selector != null){
        try {
            selector.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

客户端启动程序:

package nio;
 
 
/**
 * 客户端启动程序
 */
public class ClientMain {
 
    public static void main(String[] args) {
      int port = 8010;
      TimeClientHandler client = new TimeClientHandler("",port);
      new Thread(client,"client-001").start();
    }
}


解释说明:

  • java.nio.Buffer flip()方法:将缓存字节数组的指针设置为数组的开始序列即数组下标0。这样就可以从buffer开头,对该buffer进行遍历(读取)了。
    也就是说调用flip之后,读写指针指到缓存头部,并且设置了最多只能读出之前写入的数据长度(而不是整个缓存的容量大小)。详细:https://www.cnblogs.com/woshijpf/articles/3723364.html
  • backlog参数:
    TCP建立连接是要进行三次握手,但是否完成三次握手后,服务器就处理(accept)呢?
    backlog其实是一个连接队列,在Linux内核2.2之前,backlog大小包括半连接状态和全连接状态两种队列大小。
    半连接状态为:服务器处于Listen状态时收到客户端SYN报文时放入半连接队列中,即SYN queue(服务器端口状态为:SYN_RCVD)。
    全连接状态为:TCP的连接状态从服务器(SYN+ACK)响应客户端后,到客户端的ACK报文到达服务器之前,则一直保留在半连接状态中;当服务器接收到客户端的ACK报文后,该条目将从半连接队列搬到全连接队列尾部,即 accept queue (服务器端口状态为:ESTABLISHED)。
    在Linux内核2.2之后,分离为两个backlog来分别限制半连接(SYN_RCVD状态)队列大小和全连接(ESTABLISHED状态)队列大小。 详细:https://www.cnblogs.com/Orgliny/p/5780796.html
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值