• 使用Socket实现HttpServer(二)


    使用Socket实现HttpServer(二)

    前面我们使用 Socket 实现了一个简易的 HttpServer,接下来我们将对我们的服务器进行优化:

    • 面向对象的封装
    • 优化线程模型(引入多线程)
    • Request/Response 对象抽象

    Step1(面向对象的封装)

    对我们之前所写的 HttpServer 进行面向对象封装。

    主要封装了 listen() 和 accept() 方法。

    package com.fengsir.network;
    
    import java.io.*;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.function.Function;
    
    /**
     * @Author FengZeng
     * @Date 2022-01-24 13:35
     * @Description TODO
     */
    public class Step1Server {
    
      ServerSocket socketServer;
      Function<String, String> handler;
    
    
      public Step1Server(Function<String, String> handler) {
        this.handler = handler;
      }
    
      /**
       * @param port listen port
       * @throws IOException
       */
      public void listen(int port) throws IOException {
        socketServer = new ServerSocket(port);
    
        while (true) {
          accept();
        }
      }
    
      private void accept() throws IOException {
        Socket socket = socketServer.accept();
        System.out.println("a socket created");
    
        // 拿到 socket 的请求内容,也就是 inputStream,封装成 bufferedReader,方便读取
        InputStream inputStream = socket.getInputStream();
        BufferedReader bfReader = new BufferedReader(new InputStreamReader(inputStream));
    
        // 按行读取,把内容放到 stringBuilder 中
        StringBuilder requestBuilder = new StringBuilder();
        String line = "";
        // 由于有时候 readLine() 会读取到 null,就会报错 nullException,
        // 所以在这里进行了一点修改
        while (true) {
          line = bfReader.readLine();
          if (line == null || line.isBlank()) {
            break;
          }
          requestBuilder.append(line);
        }
        // 打印请求内容
        String request = requestBuilder.toString();
        System.out.println(request);
    
        // 封装 response
        BufferedWriter bfWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        String response = this.handler.apply(request);
        bfWriter.write(response);
        // 一定要 flush
        bfWriter.flush();
        socket.close();
      }
    
    
      public static void main(String[] args) throws IOException {
        Step1Server step1Server = new Step1Server(res->{
          return "HTTP/1.1 200 ok\n\nGood!\n";
        });
        step1Server.listen(8000);
    
      }
    
    }
    

    到这里也没有什么太大的问题,不过我们的服务器还是单线程的,如果只是单纯的返回一个字符串,我们的架构也没有问题,如果此时每个请求会去做别的处理,比如去查一下数据库,那么我们的服务器性能就很低了,还会出现服务器拒绝的错误(因为操作系统的 pending Queue 已经满了),所以我们需要进一步优化。

    Step2(引入多线程)

    这里引入了多线程,不再是一个线程单独在跑了,试了一下 Jemeter 的压测,10000个线程的并发就会把内存拉得很高,主要还是因为创建了线程后 Java 的垃圾回收还没有启动,所以现在一般不会用这种方式去创建线程,后面会引入线程池来优化。

    package com.fengsir.network;
    
    import java.io.*;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.function.Function;
    
    /**
     * @Author FengZeng
     * @Date 2022-01-24 14:01
     * @Description TODO
     */
    public class Step2Server {
    
      ServerSocket socketServer;
      Function<String, String> handler;
    
    
      public Step2Server(Function<String, String> handler) {
        this.handler = handler;
      }
    
      /**
       * @param port listen port
       * @throws IOException
       */
      public void listen(int port) throws IOException {
        socketServer = new ServerSocket(port);
    
        while (true) {
          accept();
        }
      }
    
      private void accept() throws IOException {
        // 如果不把 accept 提到这里,那么上面的 while循环会一直创建线程
        // 这也体现了 accept() 是 blocking 的
        Socket socket = socketServer.accept();
        System.out.println("a socket created");
        new Thread(()->{
          try {
            handler(socket);
          } catch (IOException e) {
            e.printStackTrace();
          }
        }).start();
      }
    
      private void handler(Socket socket) throws IOException {
    
        // 拿到 socket 的请求内容,也就是 inputStream,封装成 bufferedReader,方便读取
        InputStream inputStream = socket.getInputStream();
        BufferedReader bfReader = new BufferedReader(new InputStreamReader(inputStream));
    
        // 按行读取,把内容放到 stringBuilder 中
        StringBuilder requestBuilder = new StringBuilder();
        String line = "";
        while (true) {
          line = bfReader.readLine();
          if (line == null || line.isBlank()) {
            break;
          }
          requestBuilder.append(line);
        }
        // 打印请求内容
        String request = requestBuilder.toString();
        System.out.println(request);
    
        // 封装 response
        BufferedWriter bfWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        String response = this.handler.apply(request);
        bfWriter.write(response);
        // 一定要 flush
        bfWriter.flush();
        socket.close();
      }
    
    
      public static void main(String[] args) throws IOException {
        Step2Server step2Server = new Step2Server(res->{
          return "HTTP/1.1 200 ok\n\nGood!\n";
        });
        step2Server.listen(8000);
    
      }
    }
    

    Step3(封装Request/Response)

    由于 step2 中,我们使用的是 Function 对象来实现的请求和响应,我们进一步抽象封装,创建 IHandlerInterface 接口:

    package com.fengsir.network.step3;
    
    import java.io.IOException;
    
    /**
     * @Author FengZeng
     * @Date 2022-01-24 14:26
     * @Description TODO
     */
    @FunctionalInterface
    public interface IHandlerInterface {
      /**
       * 处理响应
       * @param request request
       * @param response response
       * @throws IOException
       */
      void handler(Request request, Response response) throws IOException;
    }
    

    然后我们创建 Request 类:

    package com.fengsir.network.step3;
    
    import org.apache.commons.httpclient.HttpParser;
    
    import java.io.*;
    import java.net.Socket;
    import java.util.HashMap;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    /**
     * @Author FengZeng
     * @Date 2022-01-24 14:27
     * @Description TODO
     */
    public class Request {
      // 使用正则表达式提取请求的方法
      static Pattern methodRegex = Pattern.compile("(GET|PUT|POST|DELETE|OPTIONS|TARCE|HEAD)");
      
      private final String body;
      private final String method;
      private final HashMap<String, String> headers;
    
      public String getBody() {
        return body;
      }
    
      public String getMethod() {
        return method;
      }
    
      public HashMap<String, String> getHeaders() {
        return headers;
      }
    
      public Request(Socket socket) throws IOException {
    
        // 这是 DataInputStream 和 InputStream 的区别
        // DataInputStream -> primitives(char,float)
        // InputStream -> bytes
        DataInputStream iptStream = new DataInputStream(socket.getInputStream());
        BufferedReader bfReader = new BufferedReader(new InputStreamReader(iptStream));
    
        // 从第一行读取请求的方法,HttpParser 是我引入的 commons-httpClient 包中的对象
        String methodLine = HttpParser.readLine(iptStream,"UTF-8");
        Matcher matcher = methodRegex.matcher(methodLine);
        matcher.find();
        String method = matcher.group();
    
        // 解析请求头
        // Content-Type: xxxx
        var headers = HttpParser.parseHeaders(iptStream, "UTF-8");
        HashMap<String, String> headMap = new HashMap<>();
        for (var h : headers) {
          headMap.put(h.getName(), h.getValue());
        }
    
        // 解析请求体
        var bufferReader = new BufferedReader(new InputStreamReader(iptStream));
        var body = new StringBuilder();
    
        char[] buffer = new char[1024];
        while (iptStream.available() > 0) {
          bufferReader.read(buffer);
          body.append(buffer);
        }
    
        this.body = body.toString();
        this.method = method;
        this.headers = headMap;
    
      }
    }
    

    创建 Response 类:

    package com.fengsir.network.step3;
    
    import java.io.BufferedWriter;
    import java.io.IOException;
    import java.io.OutputStreamWriter;
    import java.net.Socket;
    import java.util.HashMap;
    
    /**
     * @Author FengZeng
     * @Date 2022-01-24 14:27
     * @Description TODO
     */
    public class Response {
      Socket socket;
      private int status;
      static HashMap<Integer, String> codeMap;
      public Response(Socket socket) {
        this.socket = socket;
        if (codeMap == null) {
          codeMap = new HashMap<>();
          codeMap.put(200, "ok");
        }
      }
    
      /**
       * http 标准响应
       * @param msg message
       * @throws IOException
       */
      public void send(String msg) throws IOException {
        this.status = 200;
        var resp = "HTTP/1.1 " + this.status + " " + codeMap.get(this.status) + "\n";
        resp += "\n";
        resp += msg;
        this.sendRaw(resp);
      }
    
      /**
       * 发送原始的响应
       * @param msg message
       * @throws IOException
       */
      public void sendRaw(String msg) throws IOException {
        var bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        bufferedWriter.write(msg);
        bufferedWriter.flush();
        bufferedWriter.close();
      }
    
    
    }
    
    

    到此,我们再重构一下主函数:

    package com.fengsir.network.step3;
    
    import java.io.IOException;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    /**
     * @Author FengZeng
     * @Date 2022-01-24 14:28
     * @Description TODO
     */
    public class Step3Server {
      ServerSocket serverSocket;
      IHandlerInterface httpHandler;
    
      public Step3Server(IHandlerInterface httpHandler) {
        this.httpHandler = httpHandler;
      }
      
      public void listen(int port) throws IOException {
        serverSocket = new ServerSocket(port);
        while (true) {
          this.accept();
        }
      }
    
      private void accept() throws IOException {
        Socket socket = serverSocket.accept();
        new Thread(() -> {
          try {
            this.handler(socket);
          } catch (IOException e) {
            e.printStackTrace();
          }
        }).start();
      }
    
      private void handler(Socket socket) throws IOException {
        Request request = new Request(socket);
        Response response = new Response(socket);
        this.httpHandler.handler(request, response);
      }
    
      public static void main(String[] args) throws IOException {
        var server = new Step3Server((req,resp)->{
          System.out.println(req.getHeaders());
          resp.send("Greetings\n");
        });
        server.listen(8000);
    
      }
    
    
    }
    

    可以看到,我们现在的主函数已经很简短了,其实大多数的框架都是从这么一个过程过来的,所以抽象,封装,重构能力是很重要的,优秀的程序员在coding的时候,都会去往这几个方面去想,下一章我打算引入NIO继续优化我们的 Http Server

  • 相关阅读:
    使用JS实现复制粘贴功能
    前端向后端发送请求(FormData),你们不要吐槽我,有的时候我也不想写注释
    最全面的数组去重详细解析
    查找字符串数组中的最长公共前缀
    最简单的让多行表格滚动方法
    送给vue初学者的 vue.js技巧
    git 和码云的上传文件代码操作
    常用模块 二
    深拷贝与浅拷贝
    常用模块升级
  • 原文地址:https://www.cnblogs.com/Fzeng/p/15839618.html
Copyright © 2020-2023  润新知