java并发原理实战(14)--自己实现简易web服务器

墨蓝 2023-07-03 08:13 99阅读 0赞

简易web服务器

  • 0.web服务器知识储备
  • 1.简易web服务器-版本1
  • 2.简易web服务器版本2-多线程版
  • 3.1简易web服务器版本3-访问图片资源
  • 3.2简易web服务器版本3-访问外链地址测试
  • 4.简易web服务器-版本4-连接池版

0.web服务器知识储备

web服务器知识储备,了解网络编程,其实就是java的socket。如果实现网络请求,也就是服务端能够接收客户端的请求,然后服务端再对客户端进行响应。响应的话需要按照http的请求格式,浏览器才能识别显示。
http的格式推荐文章
HTTP的请求包括:请求行(request line)、请求头部(header)、空行 和 请求数据 四个部分组成。

Http请求消息结构
抓包的request结构如下:

GET /mix/76.html?name=kelvin&password=123456 HTTP/1.1
Host: www.fishbay.cn
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,/;q=0.8
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
1.请求行
GET为请求类型,/mix/76.html?name=kelvin&password=123456为要访问的资源,HTTP/1.1是协议版本
2.请求头部
从第二行起为请求头部,Host指出请求的目的地(主机域名);User-Agent是客户端的信息,它是检测浏览器类型的重要信息,由浏览器定义,并且在每个请求中自动发送。
3.空行
请求头后面必须有一个空行
4.请求数据
请求的数据也叫请求体,可以添加任意的其它数据。这个例子的请求体为空
Response
一般情况下,服务器收到客户端的请求后,就会有一个HTTP的响应消息,HTTP响应也由4部分组成,分别是:状态行、响应头、空行 和 响应体。

在这里插入图片描述

1.简易web服务器-版本1

之前的javaweb项目中,一般把项目放在tomcat的webapps目录或者直接放在ROOT根目录下,我们在浏览器输入服务器的地址和tomcat的端口号等信息就可以访问到服务器上的资源了。
现在我们要实现类似tomcat的web服务器功能:

假设我们本地就是服务器,现在就实现在浏览器输入个地址,访问到本地的某个盘符的资源的功能:

  • 首先
    在e盘建个webroot目录,我们的资源都放在这个目录下。
    在这个文件下写个index.html:

    <!DOCTYPE html>




    index.html


    hello world

  • 写web服务端代码:

    import java.io.*;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.Date;

    public class HttpServer {

    1. public static void main(String[] args) throws IOException {
    2. //启动服务器,监听8888端口
    3. ServerSocket server = new ServerSocket(8888);
    4. while (!Thread.interrupted()) {
    5. //不停的接收客户端请求
    6. Socket client = server.accept();
    7. //获取输入流
    8. InputStream ins = client.getInputStream();
    9. OutputStream out = client.getOutputStream();

    // int len = 0;
    // byte[] b = new byte[1024];
    // while ((len = ins.read(b)) != -1) {
    // System.out.println(new String(b, 0, len));
    // }

    1. BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream()));
    2. String line = reader.readLine();
    3. System.out.println(line);
    4. //给用户(客户端)响应
    5. FileInputStream i = new FileInputStream("e:\\webroot\\index.html");
    6. PrintWriter pw = new PrintWriter(out);
    7. BufferedReader fr = new BufferedReader(new InputStreamReader(i, "UTF-8"));
    8. String c = null;
    9. pw.println("HTTP/1.1 200 OK");
    10. pw.println("Content-Type: text/html;charset=utf-8");
    11. pw.println("Content-Length: " + i.available());
    12. pw.println("Server: hello");
    13. pw.println("Date: " + new Date());
    14. pw.println("");
    15. pw.flush();
    16. while ((c = fr.readLine()) != null) {
    17. pw.println(c);
    18. }
    19. pw.flush();
    20. fr.close();
    21. pw.close();
    22. reader.close();
    23. client.close();
    24. }
    25. server.close();
    26. }

    }

关键的代码:需要按照http格式响应,客户端才能识别。

  1. pw.println("HTTP/1.1 200 OK");
  2. pw.println("Content-Type: text/html;charset=utf-8");
  3. pw.println("Content-Length: " + i.available());
  4. pw.println("Server: hello");
  5. pw.println("Date: " + new Date());
  6. pw.println("");
  7. pw.flush();
  • 运行代码,这样我们就在服务端开放了8888端口,客户端请求去就可以了
  • 在浏览器输入 http://localhost:8888/ 查看能否访问到index.html
    在这里插入图片描述
    结果: 我们访问到了本地的index.html资源。

但是这里有个问题,只能请求一次,所以我们要进行优化,变成多线程版本。

2.简易web服务器版本2-多线程版

这里我们修改服务器端的代码

  • 抽出一个单独响应客户端的线程类

    mport java.io.*;
    import java.net.Socket;
    import java.util.Date;

    public class ServerThread implements Runnable {

    1. private Socket client;
    2. private InputStream ins;
    3. private OutputStream out;
    4. private PrintWriter pw;
    5. private BufferedReader br;
    6. private void init() {
    7. try {
    8. ins = client.getInputStream();
    9. out = client.getOutputStream();
    10. } catch (IOException e) {
    11. e.printStackTrace();
    12. }
    13. }
    14. public ServerThread(Socket client) {
    15. this.client = client;
    16. init();
    17. }
  1. @Override
  2. public void run() {
  3. try {
  4. go();
  5. } catch (IOException e) {
  6. e.printStackTrace();
  7. }
  8. }
  9. private void go() throws IOException {
  10. BufferedReader reader = new BufferedReader(new InputStreamReader(ins));
  11. String line = reader.readLine();
  12. System.out.println(line);
  13. //给用户响应
  14. FileInputStream i = new FileInputStream("e:\\webroot\\index.html");
  15. PrintWriter pw = new PrintWriter(out);
  16. BufferedReader fr = new BufferedReader(new InputStreamReader(i, "UTF-8"));
  17. String c = null;
  18. pw.println("HTTP/1.1 200 OK");
  19. pw.println("Content-Type: text/html;charset=utf-8");
  20. pw.println("Content-Length: " + i.available());
  21. pw.println("Server: hello");
  22. pw.println("Date: " + new Date());
  23. pw.println("");
  24. pw.flush();
  25. while ((c = fr.readLine()) != null) {
  26. pw.println(c);
  27. }
  28. pw.flush();
  29. fr.close();
  30. pw.close();
  31. reader.close();
  32. client.close();
  33. }
  34. }
  • 服务端的主类

    import java.io.*;
    import java.net.ServerSocket;
    import java.net.Socket;
    public class HttpServer2 {

    1. public static void main(String[] args) throws IOException {
    2. //启动服务器,监听8888端口
    3. ServerSocket server = new ServerSocket(8888);
    4. while (!Thread.interrupted()) {
    5. //不停的接收客户端请求
    6. Socket client = server.accept();
    7. new Thread(new ServerThread(client)).start();
    8. }
    9. server.close();
    10. }

    }

这样多线程版已完成,测试
打开多个浏览器窗口,都可以访问到index.html的资源:
在这里插入图片描述

3.1简易web服务器版本3-访问图片资源

上面的index.html资源中,只有文字,如果我们修改下

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8">
  5. <title>index.html</title>
  6. </head>
  7. <body>
  8. hello world
  9. <br>
  10. <img src="1.jpg">
  11. </body>
  12. </html>

在这里插入图片描述
1.jpg图片如下:
在这里插入图片描述
现在修改代码,能够访问到图片,这样我们不能通过

  1. PrintWriter pw = new PrintWriter(out);
  2. while ((c = fr.readLine()) != null) {
  3. pw.println(c);
  4. }

这段代码了,因为,这个只能读取文本,我们只能使用inputstream来读取二进制。同时,响应的时候content-type的响应头也不能固定为text/html了,要根据请求的资源动态修改,比如jpg的图片,后缀就应该是image/jpeg。
我们可以定义个缓存map进行动态赋值。

  1. private static Map<String, String> contentMap = new HashMap<>();
  2. static {
  3. contentMap.put("html", "text/html;charset=utf-8");
  4. contentMap.put("jpg", "image/jpeg");
  5. }

浏览器访问的时候,可以看到打印的line为:
在这里插入图片描述
这其实就是http请求格式的第一行:
在这里插入图片描述
我们用空格分割,索引为1的就是资源,我们可以得到资源的后缀,给content-type动态赋值,如果直接就是\。我们可以手动改成index.html:

  1. BufferedReader reader = new BufferedReader(new InputStreamReader(ins));
  2. String line = reader.readLine().split(" ")[1].replace("/","\\");
  3. if (line.equals("\\")){
  4. line += "index.html";
  5. }
  6. System.out.println(line);
  • 现在全部的修改如下
    HttpServer2类代码不变,修改ServerThread

    public class ServerThread implements Runnable {

    1. private static final String webroot = "e:\\webroot\\";
    2. private static Map<String, String> contentMap = new HashMap<>();
    3. static {
    4. contentMap.put("html", "text/html;charset=utf-8");
    5. contentMap.put("jpg", "image/jpeg");
    6. }
    7. private Socket client;
    8. private InputStream ins;
    9. private OutputStream out;
  1. private PrintWriter pw;
  2. private BufferedReader br;
  3. private void init() {
  4. try {
  5. ins = client.getInputStream();
  6. out = client.getOutputStream();
  7. } catch (IOException e) {
  8. e.printStackTrace();
  9. }
  10. }
  11. public ServerThread(Socket client) {
  12. this.client = client;
  13. init();
  14. }
  15. @Override
  16. public void run() {
  17. try {
  18. go();
  19. } catch (IOException e) {
  20. e.printStackTrace();
  21. }
  22. }
  23. private void go() throws IOException {
  24. BufferedReader reader = new BufferedReader(new InputStreamReader(ins));
  25. String line = reader.readLine().split(" ")[1].replace("/","\\");
  26. if (line.equals("\\")){
  27. line += "index.html";
  28. }
  29. System.out.println(line);
  30. //给用户响应
  31. PrintWriter pw = new PrintWriter(out);
  32. FileInputStream i = new FileInputStream(webroot + line);
  33. // BufferedReader fr = new BufferedReader(new InputStreamReader(i, "UTF-8"));
  34. String c = null;
  35. pw.println("HTTP/1.1 200 OK");
  36. String s = line.substring(line.lastIndexOf(".")+1);
  37. System.out.println(s);
  38. pw.println("Content-Type: "+contentMap.get(s));
  39. pw.println("Content-Length: " + i.available());
  40. pw.println("Server: hello");
  41. pw.println("Date: " + new Date());
  42. pw.println("");
  43. pw.flush();
  44. byte[] buff = new byte[1024];
  45. int len = 0;
  46. while ((len = i.read(buff)) != -1) {
  47. out.write(buff, 0 , len);
  48. }
  49. pw.flush();
  50. i.close();
  51. pw.close();
  52. reader.close();
  53. client.close();
  54. }
  55. }
  • 启动HttpServer2,开放8888端口:
  • 客户端访问,可看到图片也访问到了
    在这里插入图片描述

3.2简易web服务器版本3-访问外链地址测试

版本3,其实也可以访问到外链地址,我们测试下:
修改index.html

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8">
  5. <title>index.html</title>
  6. </head>
  7. <body>
  8. hello world
  9. <br>
  10. <img src="1.jpg">
  11. <a href="login.html">登陆</a>
  12. </body>
  13. </html>

我们在webroot下新建login.html
在这里插入图片描述login.html

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title></title>
  5. </head>
  6. <body>
  7. this is sign in html
  8. 这是登陆页面奥
  9. </body>
  10. </html>
  • 测试下能否访问到
    在这里插入图片描述
  • 点击登陆链接

在这里插入图片描述
测试没有问题,可以访问到login.html

4.简易web服务器-版本4-连接池版

上面的代码有啥问题吗,其实每次都是重新开个线程,比较消耗资源,我们可以使用连接池进行,管理,这样对以后的线程的监控,管理等都可以有效控制。阿里代码规范中,也有说到,凡是创建线程,必须通过,线程池,现在我们进行优化,其实只修改HttpServer2主类即可。
原来的代码:

  1. import java.io.IOException;
  2. import java.net.ServerSocket;
  3. import java.net.Socket;
  4. public class HttpServer2 {
  5. public static void main(String[] args) throws IOException {
  6. //启动服务器,监听8888端口
  7. ServerSocket server = new ServerSocket(8888);
  8. while (!Thread.interrupted()) {
  9. //不停的接收客户端请求
  10. Socket client = server.accept();
  11. new Thread(new ServerThread(client)).start();
  12. }
  13. server.close();
  14. }
  15. }

修改为:

  1. public class HttpServer2 {
  2. public static void main(String[] args) throws IOException {
  3. ExecutorService pool = Executors.newCachedThreadPool();
  4. //启动服务器,监听8888端口
  5. ServerSocket server = new ServerSocket(8888);
  6. while (!Thread.interrupted()) {
  7. //不停的接收客户端请求
  8. Socket client = server.accept();
  9. pool.execute(new ServerThread(client));
  10. }
  11. server.close();
  12. }
  13. }
  • 启动httpserver2主类
  • 测试
    在这里插入图片描述
    完美,结果没问题。

现在简易的web服务器已经实现了,其实还有需要可以优化的地方。比如httpservet的支持等,tomcat的源码中可以多多去研读。


发表评论

表情:
评论列表 (有 0 条评论,99人围观)

还没有评论,来说两句吧...

相关阅读