JAVA WEB开发实例—NIO开发实例

对于NIO的概念网上有好多文档式的解释,但作为读者不是很好理解,因此我想用个贴近生活的例子介绍下。
一、首先他的设计模型叫反应器模型(Reactor)
   有这么个例子:
   一个饭店刚开业,老板请了3个服务员负责等待顾客的点单下菜,因为客户人数不多,服务员可以胜任,但是后来饭店火了,顾客变得好多,这3个服务员忙不过来了,老板就想有没有什么方法,及不需要新招员工(成本控制)又可以不让顾客长时间等待(线程堵塞),灵光一现,老板就想到了 想给顾客看菜单,等到他们选好了,再让服务员去完成下单工作,这样就大大提高了顾客的接待效率。
     这个先看菜单,在点菜的过程就是反应器模型。

二、NIO中最重要的3个组件及其概念

1.Channel 通道

2.Buffer 缓冲区

3.Selector 选择器

其中Channel对应咱们接触最多的流,Buffer(缓存)不是什么新东西,Selector是因为nio可以使用同步的非堵塞模式才加入的东西。

以前的流总是堵塞的,一个线程只要对它进行操作,其它操作就会被堵塞,也就相当于一个没有阀门的水管,你伸手接水的时候,不管水到了没有,你就都只能耗在接水(流)上(线程堵塞)。

nio的Channel的加入,相当于增加了水龙头(有阀门),虽然一个时刻也是只能接一个水管的水,但依赖轮换策略(循环),在水量不大的时候,各个水管里流出来的水,都可以被处理接纳,但是还有个关键之处就是增加了一个接水工,也就是Selector(选择器),他负责协调,也就是看哪根水管有水了(就绪状态),在当前水管的水接到一定程度的时候,就切换一下:临时关上当前水龙头,试着打开另一个水龙头(看看有没有水)。

当其他人需要用水的时候,不是直接去接水,而是事前提了一个水桶给接水工,这个水桶就是Buffer。也就是,其他人虽然也可能要等,但不会在现场等,而是回家等,可以做其它事去,水接满了,接水工会通知他们。

好了我想经过上面2个例子应该可以对NIO的概念有个比较清晰的理解了吧,下面我们来用具体的代码来实例开发(代码有详细注解说明)

服务端

package demo;

public class NIODemo_Server {
    private static int server_port=9999;//服务器端口
    private static  ServerHandler serverhandler;//服务端处理类
    public static void start(){
        Start(server_port);
    }

    /**
     * 创建一个同步的线程启动服务端
     * 
     * **/
    public static synchronized void  Start(int server_port2) {
        if (null!=serverhandler) {
            serverhandler.shop();
        }
        serverhandler=new ServerHandler(server_port2);

        new Thread(serverhandler, "server").start();;
    }
    public static void main(String[] args) {
        start();
    }
}
package demo;

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 ServerHandler implements Runnable {
    private Selector selector;//NIO中的选择器
    private ServerSocketChannel serverchannel;//通道
    private volatile boolean started;//服务器状态并线程可见


    public ServerHandler(int server_port2) {
        try {
            selector=Selector.open();//1.开启选择器,用于监听、轮查
            serverchannel=ServerSocketChannel.open();//2.开启通道
            serverchannel.configureBlocking(false);//3.调用configureBlocking(),开启非堵塞模式,true:堵塞;false:非堵塞

            //4.通过ServerSocketChannel中的socket()获取一个ServerSocket,然后使用ServerSocket的bind方法为其绑定通讯地址,并设置连接队列长度
            serverchannel.socket().bind(new InetSocketAddress(server_port2),1024);
            //5.将信道注册到选择器上并配置事件类型
            /*  
                SelectionKey.OP_ACCEPT —— 接收连接继续事件,表示服务器监听到了客户连接,服务器可以接收这个连接了
                SelectionKey.OP_CONNECT —— 连接就绪事件,表示客户与服务器的连接已经建立成功
                SelectionKey.OP_READ —— 读就绪事件,表示通道中已经有了可读的数据,可以执行读操作了(通道目前有数据,可以进行读操作了)
                SelectionKey.OP_WRITE —— 写就绪事件,表示已经可以向通道写数据了(通道目前可以用于写操作)
            */
            serverchannel.register(selector, SelectionKey.OP_ACCEPT);

            started=true;//6.完成上述操作后,将服务器状态变为启动
            System.out.println("服务器已启动,端口号"+server_port2);
        } catch (IOException e) {

            e.printStackTrace();
        }
    }

    public void shop() {
        started = false;        
    }

    @Override
    public void run() {
        //通过started来判断服务器是否启动,并在启动的情况下遍历selector
        while (started) {
            try {
                selector.select(1000);//设置选择器的工作周期1s
                Set<SelectionKey> keys=selector.selectedKeys();//使用Set集合获取当前选择器中的主键集合
                Iterator<SelectionKey> it=keys.iterator();//使用迭代器,遍历set集合
                SelectionKey key = null;  //定义key 用于接收遍历后的值
                while (it.hasNext()) {
                    key=it.next();//赋值
                    //稍微提下 在多线程情况下 要删除集合里的元素 需要使用迭代器的remove方法 因为remove方法可以保障从源集合中安全删除对象
                    it.remove();
                    handleInput(key);
                }

            } catch (IOException e) {

                e.printStackTrace();
            }

        }       
        //释放资源  
        if(selector != null)  
            try{  
                selector.close();  
            }catch (Exception e) {  
                e.printStackTrace();  
            } 
    }

    private void handleInput(SelectionKey key)throws IOException {
        //判断key是否有效
        if (key.isValid()) {
            //通过isAcceptable方法判断是否有新的信息进入通道,有着进行下步处理
            if (key.isAcceptable()) {
                ServerSocketChannel ssc=(ServerSocketChannel) key.channel();//创建新的通道用于接收key代表的通道
                SocketChannel sc = ssc.accept(); //从accept中获取连接通道
                sc.configureBlocking(false);//设置非堵塞

                sc.register(selector, SelectionKey.OP_READ);//注册到选择器中,并规定模式为读
            }
            //读取通道信息
            if (key.isReadable()) {
                SocketChannel  sc=(SocketChannel) key.channel();//创建新的通道用于接收key代表的通道
                ByteBuffer bb=ByteBuffer.allocate(1024);//使用静态方法allocate定义一个1M的byte类型的缓存区域,用于保存通道中的数据
                int returnReadByte=sc.read(bb);//读取通道中的数据并返回其字节码
                if (returnReadByte>0) {
                    bb.flip();//pos变为0,从buffer头开始读取数据
                    byte[] b=new byte[bb.remaining()];//使用remaining方法获取buffer中的元素大小,并创建对应大小的byte数组
                    bb.get(b);//将buffer中的数据赋给byte数组中
                    String CilentMsg=new String(b, "utf-8");//编译
                    System.out.println("服务端:接收到的信息是"+CilentMsg);
                    //业务处理...........
                    //...........
                    String result=CilentMsg+"(已处理)";
                    //发送应答消息  
                    doWrite(sc,result);                     
                }
                //链路已经关闭,释放资源  
                else if(returnReadByte<0){  
                    key.cancel();  
                    sc.close();  
                }
            }
        }
    }

    private void doWrite(SocketChannel sc, String result)  throws IOException{       
         byte[] bytes = result.getBytes(); //将返回信息变成字节数组         
         ByteBuffer rebb=ByteBuffer.allocate(bytes.length);//根据数组容量创建ByteBuffer 
         rebb.put(bytes);  //将字节数组复制到缓冲区        
         rebb.flip();   //flip操作        
         sc.write(rebb);  //想通道中发送缓冲区的字节数组  
    }

}

客户端

package demo;



public class NIODemo_Client {
    private static String server_host="127.0.0.1";//服务器地址
    private static int server_port=9999;//服务器端口
    private static ClientHandle clientHandle ;
    public static void start(){
        start(server_host,server_port);
    }

    //启动一个线程用于连接server
    private static synchronized void start(String server_host2, int server_port2) {
        if(clientHandle!=null)clientHandle.shop();
        clientHandle=new ClientHandle(server_host2,server_port2);
        new Thread(clientHandle, "client").start();
    }


    //用于业务请求
    public static boolean sendMsg(String msg)throws Exception{
        if(null==msg||"".equals(msg)||" ".equals(msg))return false;
        clientHandle.sendMsg(msg);
        return true;
    } 
    public static void main(String[] args) {
        start();
    }
}
package demo;

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 ClientHandle implements Runnable{
    private String host;//地址
    private int port;//端口
    private SocketChannel clientSocketChannel;//通道
    private Selector clientSelector;//选择器
    private volatile boolean started;//线程可见的客户端状态

    public ClientHandle(String server_host2, int server_port2) {
        this.host = server_host2;  
        this.port = server_port2;  
        try {           
            clientSelector=Selector.open();//开启选择器
            clientSocketChannel=clientSocketChannel.open();//开启通道
            clientSocketChannel.configureBlocking(false);//设为非堵塞模式

            started = true;

        } catch (IOException e) {

            e.printStackTrace();
        }

    }

    public void shop() {
        started = false;  

    }

    @Override
    public void run() {
        try{  
            doConnect();  
        }catch(IOException e){  
            e.printStackTrace();  
            System.exit(1);  
        }  
        //循环遍历selector  
        while(started){  
            try{  
                //无论是否有读写事件发生,selector每隔1s被唤醒一次  
                clientSelector.select(1000);  
                Set<SelectionKey> keys = clientSelector.selectedKeys();  
                Iterator<SelectionKey> it = keys.iterator();  
                SelectionKey key = null;  
                while(it.hasNext()){  
                    key = it.next();  
                    it.remove();  
                    try{  
                        handleInput(key);  
                    }catch(Exception e){  
                        if(key != null){  
                            key.cancel();  
                            if(key.channel() != null){  
                                key.channel().close();  
                            }  
                        }  
                    }  
                }  
            }catch(Exception e){  
                e.printStackTrace();  
                System.exit(1);  
            }  
        }  
        //selector关闭后会自动释放里面管理的资源  
        if(clientSelector != null)  
            try{  
                clientSelector.close();  
            }catch (Exception e) {  
                e.printStackTrace();  
            }  

    }
     private void handleInput(SelectionKey key) throws IOException{  
            if(key.isValid()){  
                SocketChannel sc = (SocketChannel) key.channel();  
                if(key.isConnectable()){  
                    if(sc.finishConnect());  
                    else System.exit(1);  
                }  
                //读消息  
                if(key.isReadable()){  
                    //创建ByteBuffer,并开辟一个1M的缓冲区  
                    ByteBuffer buffer = ByteBuffer.allocate(1024);  
                    //读取请求码流,返回读取到的字节数  
                    int readBytes = sc.read(buffer);  
                    //读取到字节,对字节进行编解码  
                    if(readBytes>0){  
                        //将缓冲区当前的limit设置为position=0,用于后续对缓冲区的读取操作  
                        buffer.flip();  
                        //根据缓冲区可读字节数创建字节数组  
                        byte[] bytes = new byte[buffer.remaining()];  
                        //将缓冲区可读字节数组复制到新建的数组中  
                        buffer.get(bytes);  
                        String result = new String(bytes,"UTF-8");  
                        System.out.println("客户端收到消息:" + result);  
                    }  
                    //没有读取到字节 忽略  
//                else if(readBytes==0);  
                    //链路已经关闭,释放资源  
                    else if(readBytes<0){  
                        key.cancel();  
                        sc.close();  
                    }  
                }  
            }  
        }  
    public void sendMsg(String msg)throws Exception {
        clientSocketChannel.register(clientSelector, SelectionKey.OP_READ);  
        doWrite(clientSocketChannel, msg);  

    }
    private void doConnect() throws IOException{  
        if(clientSocketChannel.connect(new InetSocketAddress(host,port)));  
        else clientSocketChannel.register(clientSelector, SelectionKey.OP_CONNECT);  
    } 

    //异步发送消息  
    private void doWrite(SocketChannel channel,String request) throws IOException{  
        //将消息编码为字节数组  
        byte[] bytes = request.getBytes();  
        //根据数组容量创建ByteBuffer  
        ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);  
        //将字节数组复制到缓冲区  
        writeBuffer.put(bytes);  
        //flip操作  
        writeBuffer.flip();  
        //发送缓冲区的字节数组  
        channel.write(writeBuffer);  
        //****此处不含处理“写半包”的代码  
    } 
}

完成后我们写个测试类

package demo;

import java.util.Scanner;

public class NioTest {
    public static void main(String[] args) throws Exception{
        NIODemo_Server.Start(9999);//启动服务器

        Thread.sleep(1000);//等待

        NIODemo_Client.start();//启动客户端

        while(NIODemo_Client.sendMsg(new Scanner(System.in).nextLine()));
    }

}

结果如下
这里写图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值