• 用NIO实现http协议


    先来看一下本篇博文的目录:

    一:简介Nio

    二:Nio的好处

    三:关于http协议

    四:代码实现

    五:总结

    一:简介Nio

    我们都知道io流,那么NIO是什么呢?本篇博文将会带你一探NIO,NIO的全称叫做New IO,顾名思义也就是全新的IO流,从Java Api 1.4版本开始发行的,nio提供了完全不同的工作体制,java nio的IO模型是同步非阻塞,也就是当用户发起一个IO操作后,边可等待边可返回,但是需要进程不断的询问是否传输完毕,这就引入了不必要的cpu浪费,但是总体它的效率是要高于IO的,它主要有三大核心组件:

    • Channels
    • Buffers
    • Selectors

    channel你可以理解为一个通道,就相当于数据传输的管道,你可以如此抽象的理解。Buffers是一个缓存区,数据可以通过管道到缓冲区,也可以通读缓冲区到channel,Nio的主要实现类为FileChannel、DatagramChannel、SocketChannel、ServerSocketChannel,BUffer的主要实现类为,从中可以看出包含了java的很多基本类型数据,Byte、char、double、Folat、int、long等,ByteBuffer,CharBuffer,DoubleBuffer,FloatBuffer,IntBuffer,LongBuffer,ShortBuffer。而Selector是Nio最关键的一个部分,Selector 允许单线程处理多个 Channel,如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用 Selector 就能极大的提升效率。要使用 Selector,得向 Selector 注册 Channel,然后调用它的 select()方法。这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件,事件的例子有如新连接进来,数据接收等

    二:nio的好处

          传统的socket IO中,需要为每个连接创建一个线程,当并发的连接数量非常巨大时,线程所占用的栈内存和CPU线程上下文切换的开销将非常巨大。使用NIO,不再需要为每个连接创建单独的线程,可以用一个含有限数量线程的线程池,甚至一个线程来为任意数量的连接服务。由于线程数量小于连接数量,所以每个线程进行IO操作时就不能阻塞,如果阻塞的话,有些连接就得不到处理,NIO提供了这种非阻塞的能力。当有任务进程来,可以实现通道注册到Selector上,一个Slector可以管理多个线程,这样就避免了线程的上下文切换,避免了资源的浪费,大大的提升了效率。

    三:关于http协议

       http从1990年开始,目前已经经历3代,最近使用的http1.1,https其实就是实际上的http1.2,当然本篇主要关注的还是http1.1,http协议,我们每天用到的非常多,它是基于服务器和客户端模式的,它在应用层解析内容的,它有点类似于我们的电报,比如在发送之前需要加密报文,发送到目的地需要解析报文,对应的我们网站,其实每次我们点一下链接进行请求就是进行了一次http的request操作,这是对于客户端而言的,这一过程就将会携带有你请求的地址、host主机名、用户的状态,sessionid、cookie等信息(具体我们可以用程序来实现),然后发送到服务器,服务器进行解析,然后给我们返回。学习http协议,我们要记得以下两点:

    1.无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
    2.无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快

    四:代码实现

    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.nio.channels.SelectionKey;
    import java.nio.channels.Selector;
    import java.nio.channels.ServerSocketChannel;
    import java.util.Iterator;
    
    public class Server {
        
        public static void main(String[] args) throws IOException {
            
            ServerSocketChannel ssc = ServerSocketChannel.open();//监听新进来的 TCP连接的通道,打开 ServerSocketChannel
            
            ssc.socket().bind(new InetSocketAddress(8080));//绑定8080端口
            
            ssc.configureBlocking(false);//设置非阻塞模式
            
            Selector selector = Selector.open();//创建选择器
            
            SelectionKey selectionKey = ssc.register(selector, SelectionKey.OP_ACCEPT);//给选择器注册通道
            
            //selectionKey:代表了注册到该 Selector 的通道
            
            while (true) { //监听新进来的连接
                
                int select = selector.select(2000);
                if (select==0) { //如果选择的通道为0,最长会阻塞 timeout毫秒
                    
                    System.out.println("等待请求超时......");
                    
                    continue;
                }
                
                System.out.println("开始处理请求.....");
                
                Iterator<SelectionKey> keyIter = selector.selectedKeys().iterator();//迭代器
                
                while (keyIter.hasNext()) {
                    
                    SelectionKey key = keyIter.next();
                    
                    new Thread(new HttpHandler(key)).run();
                    
                    keyIter.remove();
                    
                }
            }
        }
    }
    package com.wyq.NioToHttp;
    
    import java.io.IOException;
    import java.nio.ByteBuffer;
    import java.nio.channels.SelectionKey;
    import java.nio.channels.ServerSocketChannel;
    import java.nio.channels.SocketChannel;
    import java.nio.charset.Charset;
    
    /**
     * Http处理器器
     * @author Yiron
     *
     */
    public class HttpHandler implements Runnable{
    
    private int buffersize= 1024;//设置缓冲区大小
    
    private String localCharSet="UTF-8"; //设置编码格式
    
    private SelectionKey key;//注册号的通道
    
    public HttpHandler(SelectionKey key) {//把选择键构造进去
    
        this.key = key;
      }
    
    public void handleAccept() throws IOException{
    
    SocketChannel socketChannel=((ServerSocketChannel)key.channel()).accept();
    
    socketChannel.configureBlocking(false);//设置非阻塞模式
    
     socketChannel.register(key.selector(), SelectionKey.OP_READ,ByteBuffer.allocate(buffersize));
            //buffer分配一个缓冲区 大小为1024
    
    }
    
    public void handleRead() {
    
       SocketChannel sc=(SocketChannel)key.channel();// SocketChannel 是一个连接到 TCP 网络套接字的通道
    
       ByteBuffer buffer=(ByteBuffer)key.attachment();//从 SocketChannel读取到的数据将会放到这个 buffer中
    
       buffer.clear();
    
          try {
                if((sc.read(buffer))!=-1) {
    
    buffer.flip();//flip方法将Buffer从写模式切换到读模式
    
     String receive = Charset.forName(localCharSet).newDecoder().decode(buffer).toString();
                    //将此 charset 中的字节解码成 Unicode 字符
                     
                    String[] requestMessage = receive.split("
    ");//接受请求的信息
    
           for (String message : requestMessage) {
    
                if (message.isEmpty()) {//如果是空行说明信息已经结束了
                            
                            break;
                        }
                    }
                    //控制台打印
                        String[] firsetLine = requestMessage[0].split(" ");
                        
                        System.out.println("----控制台输出:-------");
    
                        System.out.println("Method:t"+firsetLine[0]);
    
                        System.out.println("url是:	"+firsetLine[1]);
    
                        System.out.println("Httpversion是:	"+firsetLine[2]);
    
                         System.out.println("-----输出结束-------------");
    
                       //返回客户端
                        StringBuilder sendStr = new StringBuilder();
    
                        sendStr.append("Http/1.1 200 Ok
    ");
    
                        sendStr.append("Content-Type:text/html;charset="+localCharSet+"
    ");
    
                        sendStr.append("
    ");
    
                        sendStr.append("<html><head><title>显示报文</title></head><body>");
    
                        sendStr.append("接受到请求的报文是:+<br>");
    
                       for (String s : requestMessage) {
    
                           sendStr.append(s+"<br/>");
    
                         }
                        sendStr.append("</body></html>");
    
                        buffer=ByteBuffer.wrap(sendStr.toString().getBytes(localCharSet));
    
                        sc.write(buffer);
    
                        sc.close();
                     }else {
                    sc.close();
                }
               } catch (IOException e) {
                
                e.printStackTrace();
            }
          }
        @Override
        public void run() {
    
                try {
                if (key.isAcceptable()) {//接受
    
                    handleAccept();
    
                  }
                if (key.isReadable()) {//开始读
    
                  handleRead();
    
                }
    
             } catch (Exception e) {
    
                  e.printStackTrace();
            }
        }
    }

    打印控制台,输出如下:

    开始处理请求.....
    ----控制台输出:-------
    Method:tGET
    url是: /
    Httpversion是: HTTP/1.1

    因为我们绑定了8080端口,这里进行请求,看浏览器会打印出相关的http信息,具体如下:

    从中可以看出很多信息,比如我们的请求方式为:Get,主机为:localhost,端口为8080,连接状态:存活,接收的语言:中文-英文,cookile信息等等,这代表了我们请求的

    头信息,将会把这些内容发送给浏览器。这就是http协议在request请求前组织的报文,从中我们可以一探http的究竟,这对我们理解客户端-服务器模式更加深刻。有助于我们

    对于web网站的开发。

     五:总结

    本篇博文的主要思想是学习nio的使用方式,并且能够理解http协议,其具体的包含信息的细节,理解nio的好处,关于对同步阻塞模式的理解,如果去通过nio实现http协议,虽然nio在实际中使用的机会并不多,但是还是有助我们加深对管道流的理解,io的实现方式,记得我有次面试的时候,被问到Nio、BiO的区别,当时是很蒙圈的,因为不知道java中还有这么多的流处理方式。我们需要学习关于单选择器处理多线程的方式,还有去思考关于线程效率的问题,这都对我们大有裨益。

  • 相关阅读:
    百度判断手机终端并自动跳转uaredirect.js代码及使用实例
    php 即点即改
    Thinkphp 3.2 去掉index.php
    php获取数组中指定值的下标
    tp5 查询本年、本月、本周的方法
    《数字集成电路静态时序分析基础》笔记⑨
    《数字集成电路静态时序分析基础》笔记⑧
    《数字集成电路静态时序分析基础》笔记⑦
    《数字集成电路静态时序分析基础》笔记⑥
    《数字集成电路静态时序分析基础》笔记⑤
  • 原文地址:https://www.cnblogs.com/wyq178/p/6864328.html
Copyright © 2020-2023  润新知