gball个人知识库
首页
基础组件
基础知识
算法&设计模式
  • 操作手册
  • 数据库
  • 极客时间
  • 每日随笔
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
  • 画图工具 (opens new window)
关于
  • 网盘 (opens new window)
  • 分类
  • 标签
  • 归档
项目
GitHub (opens new window)

ggball

后端界的小学生
首页
基础组件
基础知识
算法&设计模式
  • 操作手册
  • 数据库
  • 极客时间
  • 每日随笔
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
  • 画图工具 (opens new window)
关于
  • 网盘 (opens new window)
  • 分类
  • 标签
  • 归档
项目
GitHub (opens new window)
  • 面试

  • 数据库

  • linux

  • node

  • tensorFlow

  • 基础组件

    • mybatis

    • spring

    • 消息队列

    • springboot

    • tomcat如何工作

      • 简单实现web服务器
        • 超文本传输协议(HTTP)
          • http请求
          • http响应
        • socket(套接字)
        • request
        • response
      • 建立servlet容器
    • elasticsearch

  • 基础知识

  • 算法与设计模式

  • 分布式

  • 疑难杂症

  • go学习之旅

  • 极客时间

  • 知识库
  • 基础组件
  • tomcat如何工作
ggball
2021-10-08

简单实现web服务器

# 实现简单的web服务器

既然需要手动实现一个web服务器,那么先要了解什么是web服务器?

web服务器:严格意义上Web服务器只负责处理HTTP协议,只能发送静态页面的内容。而JSP,ASP,PHP等动态内容需要通过CGI、FastCGI、ISAPI等接口交给其他程序去处理。这个其他程序就是应用服务器。比如Web服务器包括Nginx,Apache,IIS等。而应用服务器包括WebLogic,JBoss等。应用服务器一般也支持HTTP协议,因此界限没这么清晰。但是应用服务器的HTTP协议部分仅仅是支持,一般不会做特别优化,所以很少有见Tomcat直接暴露给外面,而是和Nginx、Apache等配合,只让Tomcat处理JSP和Servlet部分

现在我们知道了,web服务器只实现,处理http协议,发送静态页面。

# 超文本传输协议(HTTP)

HTTP 是一种协议,允许 web 服务器和浏览器通过互联网进行来发送和接受数据。它是一种请求和响应协议。客户端请求一个文件而服务器响应请求。HTTP 使用可靠的 TCP 连接--TCP 默认使用 80 端口。第一个 HTTP 版是 HTTP/0.9,然后被 HTTP/1.0 所替代。

# http请求

# http响应

image-20210726153017803

这里主要的流程,如何将监听端口,请求转换成request对象,响应如何转换成reponse,这里就要介绍socket

# socket(套接字)

常用的几个方法

// 与地址和端口绑定
Socket(InetAddress address, int port)


    /**
     * 与客户端连接
     * Connects this socket to the server.
     *
     * @param   endpoint the {@code SocketAddress}
     * @throws  IOException if an error occurs during the connection
     * @throws  java.nio.channels.IllegalBlockingModeException
     *          if this socket has an associated channel,
     *          and the channel is in non-blocking mode
     * @throws  IllegalArgumentException if endpoint is null or is a
     *          SocketAddress subclass not supported by this socket
     * @since 1.4
     * @spec JSR-51
     */
    public void connect(SocketAddress endpoint) throws IOException {
    connect(endpoint, 0);
}

    /**
    * 返回此套接字的输入流。
    如果此套接字具有关联的通道,则生成的输入流将其所有操作委托给该通道。 如果通道处于非阻塞模式,则输入流的read操作将抛出java.nio.channels.IllegalBlockingModeException 。
    在异常情况下,底层连接可能被远程主机或网络软件破坏(例如在 TCP 连接的情况下重置连接)。 当网络软件检测到断开的连接时,以下内容适用于返回的输入流:-
    网络软件可能会丢弃由套接字缓冲的字节。 可以使用read未被网络软件丢弃的字节。
    如果套接字上没有缓冲的字节,或者所有缓冲的字节都已被read消耗,则对read所有后续调用都将抛出IOException 。
    如果套接字上没有缓冲字节,并且没有使用close关闭套接字,则available将返回0 。
    关闭返回的InputStream将关闭关联的套接字。
    返回:
    用于从此套接字读取字节的输入流。
    抛出:
    IOException – 如果在创建输入流时发生 I/O 错误、套接字关闭、套接字未连接或使用shutdownInput()关闭套接字输入
    */
    public InputStream getInputStream() throws IOException {
        if (isClosed())
            throw new SocketException("Socket is closed");
        if (!isConnected())
            throw new SocketException("Socket is not connected");
        if (isInputShutdown())
            throw new SocketException("Socket input is shutdown");
        InputStream is = null;
        try {
            is = AccessController.doPrivileged(
                new PrivilegedExceptionAction<>() {
                    public InputStream run() throws IOException {
                        return impl.getInputStream();
                    }
                });
        } catch (java.security.PrivilegedActionException e) {
            throw (IOException) e.getException();
        }
        return is;
    }

    /**
    *返回此套接字的输出流。
    如果此套接字具有关联的通道,则生成的输出流将其所有操作委托给该通道。 如果通道处于非阻塞模式,则输出流的write操作将抛出java.nio.channels.IllegalBlockingModeException 。
    关闭返回的OutputStream将关闭关联的套接字。
    返回:
    用于将字节写入此套接字的输出流。
    抛出:
    IOException – 如果在创建输出流时发生 I/O 错误或套接字未连接。
    */
    public OutputStream getOutputStream() throws IOException {
        if (isClosed())
            throw new SocketException("Socket is closed");
        if (!isConnected())
            throw new SocketException("Socket is not connected");
        if (isOutputShutdown())
            throw new SocketException("Socket output is shutdown");
        OutputStream os = null;
        try {
            os = AccessController.doPrivileged(
                new PrivilegedExceptionAction<>() {
                    public OutputStream run() throws IOException {
                        return impl.getOutputStream();
                    }
                });
        } catch (java.security.PrivilegedActionException e) {
            throw (IOException) e.getException();
        }
        return os;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87

Socket提出了他的规范,而要实现客户端与服务端的通信,需要使用他的实现类java.net.ServerSocket 类

接下来,要实现一个简单的web服务器,就是靠httpServer啦。它既负责接受请求,解析请求,也负责将请求的资源响应给浏览器。

public class HttpServer {

    /**
     * WEB_ROOT is the directory where our HTML and other files reside.
     * For this package, WEB_ROOT is the "webroot" directory under the working
     * directory.
     * The working directory is the location in the file system
     * from where the java command was invoked.
     */
    public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "webroot";

    // shutdown command
    private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";

    // the shutdown command received
    private boolean shutdown = false;

    public static void main(String[] args) {
        HttpServer server = new HttpServer();
        // 调用接受请求的方法 
        server.await();
    }

    public void await() {
        ServerSocket serverSocket = null;
        int port = 8080;
        try {
            // 创建一个serverSocket实例
            serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }

        // Loop waiting for a request
        while (!shutdown) {
            Socket socket = null;
            InputStream input = null;
            OutputStream output = null;
            try {
                socket = serverSocket.accept();
                input = socket.getInputStream();
                output = socket.getOutputStream();

                // create Request object and parse
                Request request = new Request(input);
                request.parse();

                // create Response object
                Response response = new Response(output);
                response.setRequest(request);
                // 发静态资源
                response.sendStaticResource();

                // Close the socket
                socket.close();

                //check if the previous URI is a shutdown command
                shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
            } catch (Exception e) {
                e.printStackTrace();
                continue;
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66

我们可以看到,程序启动后,会进入到wait()方法,开始创建serverSocket实例,进入到while里面的accept方法,而accept是阻塞的,只有请求进来时,才会执行,否则会一直阻塞在这。

当一个请求进来时,可以从套接字拿出输入,输出流,输入流可以拿到请求的url,port,headers一些信息,输出流就是帮助我们写入一些东西,然后再将写入的东西响应出去啦。Request request = new Request(input); 这里的request是一个简易版的,主要作用是帮助更好的获取信息,输入流传入进去,会解析uri,那样就不需要自己手动解析uri啦;Response response = new Response(output); 而response 提供了一个发送静态资源的方法,根据WEB_ROOT 提供的路径来查找要返回的资源。

如果浏览器上请求这样的地址127.0.0.1:8080/shutdown ,shutdown = request.getUri().equals(SHUTDOWN_COMMAND); 这句话就会起作用了,使while循环失效,主方法结束,程序结束。

效果图

image-20210812090716298

下面附上request,response的代码

# request

public class Request {

  private InputStream input;
  private String uri;

  public Request(InputStream input) {
    this.input = input;
  }

  public void parse() {
    // Read a set of characters from the socket
    StringBuffer request = new StringBuffer(2048);
    int i;
    byte[] buffer = new byte[2048];
    try {
      i = input.read(buffer);
    }
    catch (IOException e) {
      e.printStackTrace();
      i = -1;
    }
    for (int j=0; j<i; j++) {
      request.append((char) buffer[j]);
    }
    System.out.print(request.toString());
    uri = parseUri(request.toString());
  }

  private String parseUri(String requestString) {
    int index1, index2;
    index1 = requestString.indexOf(' ');
    if (index1 != -1) {
      index2 = requestString.indexOf(' ', index1 + 1);
      if (index2 > index1)
        return requestString.substring(index1 + 1, index2);
    }
    return null;
  }

  public String getUri() {
    return uri;
  }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

# response

public class Response {

  private static final int BUFFER_SIZE = 1024;
  Request request;
  OutputStream output;

  public Response(OutputStream output) {
    this.output = output;
  }

  public void setRequest(Request request) {
    this.request = request;
  }

  public void sendStaticResource() throws IOException {
    byte[] bytes = new byte[BUFFER_SIZE];
    FileInputStream fis = null;
    try {
      File file = new File(HttpServer.WEB_ROOT, request.getUri());
      if (file.exists()) {
        fis = new FileInputStream(file);
        int ch = fis.read(bytes, 0, BUFFER_SIZE);
        while (ch!=-1) {
          output.write(bytes, 0, ch);
          ch = fis.read(bytes, 0, BUFFER_SIZE);
        }
      }
      else {
        // file not found
        String errorMessage = "HTTP/1.1 404 File Not Found\r\n" +
          "Content-Type: text/html\r\n" +
          "Content-Length: 23\r\n" +
          "\r\n" +
          "<h1>File Not Found</h1>";
        output.write(errorMessage.getBytes());
      }
    }
    catch (Exception e) {
      // thrown if cannot instantiate a File object
      System.out.println(e.toString() );
    }
    finally {
      if (fis!=null)
        fis.close();
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

源码地址https://gitee.com/zxqzhuzhu/tomcat-how-to-work-original

如果有需要可以关注公众号,领取 how tomcat works中文版

微信公众号jpg

上次更新: 2025/06/04, 15:06:15
springboot三种系统初始化方式解析
建立servlet容器

← springboot三种系统初始化方式解析 建立servlet容器→

最近更新
01
AIIDE
03-07
02
githubActionCICD实战
03-07
03
windows安装Deep-Live-Cam教程
08-11
更多文章>
Theme by Vdoing
总访问量 次 | 总访客数 人
| Copyright © 2021-2025 ggball | 赣ICP备2021008769号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×

评论

  • 评论 ssss
  • 回复
  • 评论 ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss
  • 回复
  • 评论 ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss
  • 回复
×