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

}

结果如下
这里写图片描述

Java Web开发实例大全(提高卷)筛选、汇集了Java Web开发从基础知识到高级应用各个层面的大量实例及源代码,共有600个左右,每个实例及源代码按实例说明、关键技术、设计过程、详尽注释、秘笈心法的顺序进行了分析解读。 全书分为7篇23章,包括流行组件应用、数据库应用、图表统计、Ajax框架应用、流行框架、网站安全与架构模式、综合应用等。重点内容有操作XML文件、发送与接收邮件、数据库操作技术、SQL语句应用技术、复杂查询技术、数据库高级应用、JFreeChart绘图基础、基础图表技术、扩展图表技术、基于Cewolf组件的图表编程、Prototype框架、jQuery框架、Dojo框架、Struts2框架应用、Struts2框架标签应用、Hibernate框架基础、Hibernate高级话题、Spring框架基础、Spring的 Web MVC 框架、网站性能优化与安全策略、设计模式与架构、网站设计与网页配色、Java Web典型项目开发案例等。配书光盘附带了实例的源程序。 《Java Web开发实例大全(提高卷)》既适合Java Web程序员参考和查阅,也适合Java Web初学者,如高校学生、软件开发培训学员及相关求职人员学习、练习、速查使用。 目录 第1篇 流行组件应用篇 第1章 操作XML文件 第2章 发送与接收邮件 第2篇 数据库应用篇 第3章 数据库操作技术 第4章 SQL语句应用技术 第5章 复杂查询技术 第6章 数据库高级应用 第3篇 图表统计篇 第7章 JFreeChart绘图基础 第8章 基础图表技术 第9章 扩展图表技术 第10章 基于Cewolf组件的图表编程 第4篇 Ajax框架应用篇 第11章 Prototype框架 第12章 jQuery框架 第13章 Dojo框架 第5篇 流行框架篇 第14章 Struts2框架应用 第15章 Struts2框架标签应用 第16章 Hibernate框架基础 第17章 Hibernate高级话题 第18章 Spring框架基础 第19章 Spring的Web MVC框架 第6篇 网站安全与架构模式篇 第20章 网站性能优化与安全策略 第21章 设计模式与架构 第7篇 综合应用篇 第22章 网站设计与网页配色 第23章 Java Web典型项目开发案例
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值