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文件配置

原文地址:https://www.cnblogs.com/xugf/p/9603933.html