• 需求

    • 支持浏览器客户端接入

    • 根据请求的资源路径响应正确的结果

    • 支持访问静态资源

    • 支持访问动态资源

    • 当资源不存在时响应404提示

    • 当发生异常时提示500错误

    • 为保证服务器安全稳定,服务器端不可无限开启新线程

  • 思路

    • 启动ServerSocket,监听指定端口

    • 等待客户端接入,将接入的客户端交给线程池去处理,主线程继续监听客户端接入

    • 静态资源:从指定的静态资源路径去查找文件,将文件转换为字节,写入输出流

    • 动态资源:从类路径下查找响应的Servlet,调用Servlet的service处理程序,将返回值写入输出流

    • 当请求的资源不存在,将404.html文件写入输出流

    • 当发生异常,将500.html文件写入输出流

在实现HTTP服务器之前,我们需要先来了解一下HTTP的报文结构。

# HTTP报文结构

  • 可参考

    • https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Messages

  • Request与Response的报文结构

  • Request的报文结构

  • 所以按照约定的报文格式进行消息的解析与发送即可~

# 资源准备

  • 静态资源

  • 动态资源,Servlet

    • Servlet规范

/*** Servlet规范接口** @author futao* @date 2020/7/6*/
public interface Servlet {/*** 业务处理程序** @return 响应*/Object service();
}
  • Servlet服务端小程序

/*** 返回字符串** @author futao* @date 2020/7/6*/
public class HelloServlet implements Servlet {@Overridepublic Object service() {return "greet from dynamic server...";}
}--------------------------------------------------------
--------------------------------------------------------/*** 返回list集合** @author futao* @date 2020/7/6*/
public class UserListServlet implements Servlet {@Overridepublic Object service() {ArrayList<User> users = new ArrayList<>();for (int i = 0; i < 10; i++) {users.add(User.builder().name("喜欢天文的pony站长").age(i).address("浙江杭州").build());}return users;}@Getter@Setter@Builderstatic class User {private String name;private int age;private String address;}
}--------------------------------------------------------
--------------------------------------------------------/*** 返回当前时间** @author futao* @date 2020/7/6*/
public class CurTimeServlet implements Servlet {private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");@Overridepublic Object service() {return "当前时间为: " + DATE_TIME_FORMATTER.format(LocalDateTime.now(ZoneOffset.ofHours(8)));}
}--------------------------------------------------------
--------------------------------------------------------/*** 模拟异常的Servlet** @author futao* @date 2020/7/6*/
public class ExceptionServlet implements Servlet {@Overridepublic Object service() {throw new RuntimeException("发生了异常");}
}

# 服务器代码编写

  • 核心代码:


/*** 基于BIO实现的静态 and 动态服务器** @author futao* @date 2020/7/6*/
public class BIODynamicServer {private static final Logger logger = LoggerFactory.getLogger(BIODynamicServer.class);/*** 用于处理客户端接入的线程池*/private static final ExecutorService THREAD_POOL = Executors.newFixedThreadPool(10);/*** 静态资源路径*/private static final String STATIC_RESOURCE_PATH = System.getProperty("user.dir") + "/practice/src/main/resources/pages/";/*** Servlet的类路径*/private static final String DYNAMIC_RESOURCE_CLASS_PATH = "com.futao.practice.chatroom.bio.v6server.servlet.";/*** Servlet后缀*/private static final String SERVLET_SUFFIX = "Servlet";/*** Servlet缓存*/private static final Map<String, Servlet> SERVLET_MAP = new HashMap<>();/*** 默认页面*/private static final String DEFAULT_PAGE = STATIC_RESOURCE_PATH + "index.html";/*** 响应的基础信息*/public static final String BASIC_RESPONSE = "HTTP/1.1 200 OK\r\n" +"Content-Type: text/html;charset=utf-8\r\n" +"Vary: Accept-Encoding\r\n";/*** 回车换行符*/private static final String carriageReturn = "\r\n";public void start() {ServerSocket serverSocket = null;try {serverSocket = new ServerSocket(Constants.SERVER_PORT);logger.debug("========== 基于BIO实现的服务器,开始提供服务 ==========");while (true) {Socket socket = serverSocket.accept();THREAD_POOL.execute(() -> {try {InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream();byte[] bytes = new byte[1024];int curByteLength = inputStream.read(bytes);byte[] dest = new byte[curByteLength];System.arraycopy(bytes, 0, dest, 0, curByteLength);//请求报文String request = new String(dest);logger.info("接收到客户端的数据:\n{}\n{}", request, StringUtils.repeat("=", 50));// 解析请求地址String requestUri = BIODynamicServer.getRequestUri(request);// 静态资源处理器boolean staticHandler = staticHandler(requestUri, outputStream);if (!staticHandler) {//动态资源处理器if (!dynamicHandler(requestUri, outputStream)) {//动态资源不存在,响应404logger.debug("资源[{}]不存在,响应404", requestUri);staticHandler("404.html", outputStream);}}} catch (IOException e) {e.printStackTrace();}});}} catch (IOException e) {e.printStackTrace();}}
}
  • 解析请求地址

    /*** 获取请求的资源地址** @param request* @return*/private static String getRequestUri(String request) {//GET /index.html HTTP/1.1int firstBlank = request.indexOf(" ");String excludeMethod = request.substring(firstBlank + 2);return excludeMethod.substring(0, excludeMethod.indexOf(" "));}
  • 处理静态资源

    /*** 静态资源处理器** @return*/public boolean staticHandler(String page, OutputStream outputStream) throws IOException {//资源的绝对路径String filePath = BIODynamicServer.STATIC_RESOURCE_PATH + page;boolean fileExist = false;File file = new File(filePath);if (file.exists() && file.isFile()) {logger.debug("静态资源[{}]存在", page);fileExist = true;//读取文件内容byte[] bytes = Files.readAllBytes(Paths.get(filePath));//写入响应BIODynamicServer.writeResponse(outputStream, bytes);}return fileExist;}
  • 处理动态资源

/*** 动态资源处理器** @param requestUri   请求资源名* @param outputStream 输出流* @return* @throws IOException*/private boolean dynamicHandler(String requestUri, OutputStream outputStream) throws IOException {//Servlet是否存在boolean servletExist = false;//ServletServlet servletInstance = null;//从缓存中取Servlet servlet = SERVLET_MAP.get(requestUri);if (servlet == null) {//缓存中不存在try {//反射获取ClassClass<Servlet> aClass = (Class<Servlet>) Class.forName(BIODynamicServer.DYNAMIC_RESOURCE_CLASS_PATH + requestUri + BIODynamicServer.SERVLET_SUFFIX);//创建Servlet对象servletInstance = aClass.newInstance();//缓存SERVLET_MAP.put(requestUri, servletInstance);servletExist = true;logger.debug("动态资源[{}]存在", requestUri);} catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {return false;}} else {//缓存中存在servletInstance = servlet;servletExist = true;logger.debug("动态资源[{}]存在", requestUri);}//执行业务逻辑try {Object result = servletInstance.service();String resp = JSON.toJSONString(result, SerializerFeature.PrettyFormat);//结果写入输出流BIODynamicServer.writeResponse(outputStream, resp.getBytes(Constants.CHARSET));} catch (Exception e) {//响应500staticHandler("500.html", outputStream);}return servletExist;}
  • 将结果写入输出流

    /*** 写入响应** @param outputStream 输出流* @param content      内容* @throws IOException*/private static void writeResponse(OutputStream outputStream, byte[] content) throws IOException {//写入基础响应头outputStream.write(BASIC_RESPONSE.getBytes(Constants.CHARSET));//写入服务器信息outputStream.write(("Server: futaoServer/1.1" + BIODynamicServer.carriageReturn).getBytes(Constants.CHARSET));//写入传输的正文内容大小outputStream.write(("content-length: " + content.length + BIODynamicServer.carriageReturn).getBytes(Constants.CHARSET));//响应头与响应体之间需要空一行outputStream.write(BIODynamicServer.carriageReturn.getBytes(Constants.CHARSET));//写入响应正文outputStream.write(content);outputStream.flush();}
  • 测试

    • index.html

    • 静态资源

image.png
  • 动态资源

g1.gif
  • 异常情况

# 源代码

  • 源代码都给你了你还不看看?

  • https://github.com/FutaoSmile/learn-IO/tree/master/practice/src/main/java/com/futao/practice/chatroom/bio/v6server

特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下:

长按订阅更多精彩▼如有收获,点个在看,诚挚感谢

【BIO】基于BIO实现简单动态HTTP服务器相关推荐

  1. 基于epoll实现简单的web服务器

    1. 简介 epoll 是 Linux 平台下特有的一种 I/O 复用模型实现,于 2002 年在 Linux kernel 2.5.44 中被引入.在 epoll 之前,Unix/Linux 平台下 ...

  2. 基于epoll的简单的http服务器

    http://blog.csdn.net/fangjian1204/article/details/34415651 http服务器已经可以处理并发连接,支持多个客户端并发访问,每个连接可以持续读写数 ...

  3. 基于bio手写实现简单的rpc

    基于bio手写实现简单的rpc 1.bio基础知识 Java BIO:传统的网络通讯模型,就是BIO,同步阻塞IO, 其实就是服务端创建一个ServerSocket, 然后就是客户端用一个Socket ...

  4. BIO、NIO、AIO 详解和基于BIO模式下即时通信

    第一章 BIO.NIO.AIO课程介绍 身边同学写的,没发布出来,我算是转载 1.1 课程说明 ​ 在Java的软件设计开发中,通信架构是不可避免的,我们在进行不同系统或者不同进程之间的数据交互,或者 ...

  5. Mina NIO与BIO了解及Telnet简单测试-Getting Started

    原文地址:Mina user guide getting started 1.1.1. NIO回顾 1.1.1.1. NIO回顾 NIO API是java 1.4引入的,已经被大多数应用系统所采用.N ...

  6. C++基于TCP/IP简单的客户端、服务器通信程序实例

    本篇文章实现了一个基于TCP 的一个非常简单的客户/服务器通信程序实例.该程序中通讯协议使用的是面向连接的TCP协议SOCK_STREAM, 服务器的ip地址为本地地址即: 127.0.0.1,端口号 ...

  7. 基于 Java NIO 实现简单的 HTTP 服务器

    1.简介 本文是上一篇文章实践篇,在上一篇文章中,我分析了选择器 Selector 的原理.本篇文章,我们来说说 Selector 的应用,如标题所示,这里我基于 Java NIO 实现了一个简单的 ...

  8. Caddy - Web服务器的新秀 高性能 配置简单 动态代理 负载均衡

    Caddy - Web服务器的新秀 高性能 配置简单 动态代理 负载均衡 据Netcraft今年5月份放出了一篇统计文章中,Apache.Nginx和微软的IIS分别以45.61%.20.22%和7. ...

  9. python 做网站用服务器_Python实现简单的Web服务器 Part2—支持动态网站

    1. 什么是CGI? CGI即通用网关接口(Common Gateway Interface),是外部应用程序(CGI程序)与Web服务器之间的接口标准,是在CGI程序和Web服务器之间传递信息的规程 ...

最新文章

  1. flask使用SQLAlchemy操作mysql
  2. VM结构、GC工作机制详解
  3. [PHP] 编写爬虫获取淘宝网上所有的商品分类以及关键属性 销售属性 非关键属性数据...
  4. jsessionid每次请求都在变_为什么每次沟通都变争吵,学会这4条沟通原则,解决90%的沟通问题...
  5. 小程序入门学习12--云函数与数据库01
  6. 0基础学python有多难-对于几乎是零基础的人,直接学 Python 编程合适吗?
  7. 正则表达式判断是否为数字
  8. 写给前端的区块链开发入门指南:零基础开发基于以太坊智能合约的 ICO DApp
  9. 面试时应该问公司一些什么问题?
  10. 【渝粤题库】广东开放大学 服务质量管理 形成性考核
  11. 蒲丰投针结果_Scratch3.0模拟布丰投针,求π的近似值#寻找真知派#
  12. 深入了解物联网,这几个物联网技术了解吗?
  13. 我的世界在自建的服务器中装mod,《我的世界》中国版服务器MOD添加教程
  14. 特征工程(三) 数据标准化和归一化
  15. win7如何更改计算机管理员用户名和密码,win7系统下修改administrator管理员账户密码的设置方法?...
  16. VuePress搭建博客教程(六)、vuepress配置head和自定义样式
  17. 解决maven工程的properties文件内容呈灰色
  18. IIS打开aspx文件
  19. linux怎么运行dat文件,dat文件扩展名,dat文件怎么打开?
  20. strongswan简单介绍

热门文章

  1. 关于学习Python的一点学习总结(4->成员资格->list->列表操作)
  2. szucodeforce训练1081C组合数学lucas定理,div2 627的D dfs +剪枝优化,697D Puzzles{dfs序+概率}
  3. 一文让你完全弄懂回归问题、激活函数、梯度下降和神经元模型实战《繁凡的深度学习笔记》第 2 章 回归问题与神经元模型(DL笔记整理系列)
  4. 【学习笔记】树上启发式合并
  5. 模板 - O(n)预处理,O(1)查询gcd
  6. 从数组中取出m个数相加的结果(组合)
  7. 论新时代软件测试人员的工作之道(二)之让评审更有意义
  8. 搜索插入位置的golang实现
  9. MongoDB(4.0)分片——大数据的处理之道
  10. Express4.x api 翻译(draft)