• NIO开发Http服务器(5-完结):HttpServer服务器类


    最近学习了Java NIO技术,觉得不能再去写一些Hello World的学习demo了,而且也不想再像学习IO时那样编写一个控制台(或者带界面)聊天室。我们是做WEB开发的,整天围着tomcatnginx转,所以选择了一个新的方向,就是自己开发一个简单的Http服务器,在总结Java NIO的同时,也加深一下对http协议的理解。

    项目实现了静态资源(htmlcssjs和图片)和简单动态资源的处理,可以实现监听端口、部署目录、资源过期的配置。涉及到了NIO缓冲区、通道和网络编程的核心知识点,还是比较基础的。

    本文是这个系列文章的最后一篇,主要介绍HttpServer类,该类作用是:

    • 打开SelectorServerSocketChannel,根据HttpServerConfig配置启动监听
    • 接收请求连接
    • 开启线程读取请求数据、处理请求

    文章目录:

    NIO开发Http服务器(1):项目下载、打包和部署

    NIO开发Http服务器(2):项目结构

    NIO开发Http服务器(3):核心配置和Request封装

    NIO开发Http服务器(4):Response封装和响应

    NIO开发Http服务器(5-完结):HttpServer服务器类

    Github地址:

    https://github.com/xuguofeng/http-server

    1、启动监听

     1 selector = Selector.open();
     2 
     3 // 打开服务端socket通道
     4 ServerSocketChannel ssc = ServerSocketChannel.open();
     5 // 设置非阻塞
     6 ssc.configureBlocking(false);
     7 // 绑定本地端口
     8 ssc.bind(new InetSocketAddress(config.getServerPort()));
     9 // 把通道注册到Selector
    10 ssc.register(selector, SelectionKey.OP_ACCEPT);

    2、轮询

     1 while (true) {
     2 
     3     int s = selector.select();
     4     // 如果没有就绪的通道直接跳过
     5     if (s <= 0) {
     6         continue;
     7     }
     8     // 获取已经就绪的通道的SelectionKey的集合
     9     Iterator<SelectionKey> i = selector.selectedKeys().iterator();
    10 
    11     while (i.hasNext()) {
    12 
    13         // 获取当前遍历到的SelectionKey
    14         SelectionKey sk = i.next();
    15 
    16         // 可连接状态
    17         if (sk.isValid() && sk.isAcceptable()) {
    18             
    19         } else if (sk.isValid() && sk.isReadable()) {// 可读取状态
    20             
    21         }
    22         i.remove();
    23     }
    24 }

    3、接收和读取请求数据

    接收请求

     1 ServerSocketChannel server = (ServerSocketChannel) sk.channel();
     2 SocketChannel clientChannel;
     3 try {
     4     // 获取客户端channel
     5     clientChannel = server.accept();
     6     // 设置非阻塞
     7     clientChannel.configureBlocking(false);
     8     // 把通道注册到Selector
     9     clientChannel.register(selector, SelectionKey.OP_READ);
    10 } catch (Exception e) {
    11 }

    读取数据

    1 // 获取通道
    2 SocketChannel sChannel = (SocketChannel) sk.channel();
    3 if (socketChannels.get(sChannel.hashCode()) == null) {
    4     socketChannels.put(sChannel.hashCode(), sChannel);
    5     tp.execute(new RequestHandler(sk));
    6 }

    4、请求处理

    这是这个类的核心内容,使用RequestHandler处理请求

    具体实现如下:

    • 从输入通道读取数据,根据配置的解码字符集进行解码
    • 创建Request对象
    • 尝试根据uri获取动态请求处理类,如果是动态请求,就实例化Servlet对象,调用service方法处理请求
    • 输出响应
    • 最后关闭客户端输出通道
     1 SocketChannel sChannel = null;
     2 try {
     3     // 获取通道
     4     sChannel = (SocketChannel) sk.channel();
     5     // 声明保存客户端请求数据的缓冲区
     6     ByteBuffer buf = ByteBuffer.allocate(8192);
     7     // 读取数据并解析为字符串
     8     String requestBody = null;
     9     int len = 0;
    10     if ((len = sChannel.read(buf)) > 0) {
    11         buf.flip();
    12         requestBody = new String(buf.array(), 0, len);
    13         buf.clear();
    14     }
    15     if (requestBody == null) {
    16         return;
    17     }
    18 
    19     // 请求解码
    20     requestBody = URLDecoder.decode(requestBody, config.getRequestCharset());
    21 
    22     // 创建请求对象
    23     Request req = new HttpRequest(requestBody);
    24 
    25     // 关闭输入
    26     sChannel.shutdownInput();
    27 
    28     // 根据uri获取处理请求的Servlet类型
    29     Class<? extends Servlet> servletClass = config.getServlet(req.getRequestURI());
    30 
    31     // 创建响应对象
    32     Response resp = null;
    33 
    34     // 动态请求
    35     if (servletClass != null) {
    36         try {
    37             Servlet servlet = servletClass.newInstance();
    38             resp = new HttpResponse(sChannel);
    39             servlet.service(req, resp);
    40             resp.setResponseCode(ResponseUtil.RESPONSE_CODE_200);
    41         } catch (Exception e) {
    42             resp.setResponseCode(ResponseUtil.RESPONSE_CODE_500);
    43         }
    44     } else {
    45         // 静态请求
    46         resp = new HttpResponse(req, sChannel);
    47     }
    48 
    49     // 测试,添加cookie
    50     if (req.getCookies().isEmpty()) {
    51         Cookie c = new Cookie("sessionId", UUID.randomUUID().toString(), 60000);
    52         resp.addCookie(c);
    53         Cookie c2 = new Cookie("sessionId2", UUID.randomUUID().toString(), 60000);
    54         resp.addCookie(c2);
    55     }
    56 
    57     // 输出响应
    58     resp.response();
    59 
    60 } catch (IOException e) {
    61 } finally {
    62     // 关闭通道
    63     try {
    64         sChannel.finishConnect();
    65         sChannel.close();
    66         socketChannels.remove(sChannel.hashCode());
    67     } catch (IOException e) {
    68     }
    69 }
    View Code

    5、Servlet接口

    处理动态请求的接口,实现类需要在service方法中编写业务处理的程序

    1 public interface Servlet {
    2 
    3     void service(Request request, Response response) throws Exception;
    4 }

    然后在server.properties文件配置

  • 相关阅读:
    动态规划问题
    神经网络学习总结第二天
    神经网络学习第一天总结
    解决Python2.7的UnicodeEncodeError: ‘ascii’ codec can’t encode异常错误
    IntelliJ IDEA 历史版本下载地址
    第九章 数据查询基础
    第八章 用SQL语句操作数据
    第七章 用表组织数据
    第六章 程序数据库集散地:数据库
    linux文件或文件夹常见操作,排查部署在linux上程序问题常用操作
  • 原文地址:https://www.cnblogs.com/xugf/p/9603933.html
Copyright © 2020-2023  润新知