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

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>
<html>
<head>
	<meta charset="utf-8">
	<title>index.html</title>
</head>
<body>
	hello world
</body>
</html>
  • 写web服务端代码:
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;

public class HttpServer {
    public static void main(String[] args) throws IOException {
        //启动服务器,监听8888端口
        ServerSocket server = new ServerSocket(8888);
        while (!Thread.interrupted()) {
            //不停的接收客户端请求
            Socket client = server.accept();
            //获取输入流
            InputStream ins = client.getInputStream();
            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));
//            }
            BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream()));
            String line = reader.readLine();
            System.out.println(line);

            //给用户(客户端)响应
            FileInputStream i = new FileInputStream("e:\\webroot\\index.html");
            PrintWriter pw = new PrintWriter(out);
            BufferedReader fr = new BufferedReader(new InputStreamReader(i, "UTF-8"));
            String c = null;
            pw.println("HTTP/1.1 200 OK");
            pw.println("Content-Type: text/html;charset=utf-8");
            pw.println("Content-Length: " + i.available());
            pw.println("Server: hello");
            pw.println("Date: " + new Date());
            pw.println("");
            pw.flush();
            while ((c = fr.readLine()) != null) {
                pw.println(c);
            }
            pw.flush();
            fr.close();
            pw.close();
            reader.close();
            client.close();
        }
        server.close();
    }
}

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

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

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

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

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

扫描二维码关注公众号,回复: 9895910 查看本文章
  • 抽出一个单独响应客户端的线程类
mport java.io.*;
import java.net.Socket;
import java.util.Date;

public class ServerThread implements Runnable {
    private Socket client;
    private InputStream ins;
    private OutputStream out;

    private PrintWriter pw;
    private BufferedReader br;

    private void init() {
        try {
            ins = client.getInputStream();
            out = client.getOutputStream();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public ServerThread(Socket client) {
        this.client = client;
        init();
    }


    @Override
    public void run() {
        try {
            go();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void go() throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(ins));
        String line = reader.readLine();
        System.out.println(line);

        //给用户响应
        FileInputStream i = new FileInputStream("e:\\webroot\\index.html");
        PrintWriter pw = new PrintWriter(out);
        BufferedReader fr = new BufferedReader(new InputStreamReader(i, "UTF-8"));
        String c = null;
        pw.println("HTTP/1.1 200 OK");
        pw.println("Content-Type: text/html;charset=utf-8");
        pw.println("Content-Length: " + i.available());
        pw.println("Server: hello");
        pw.println("Date: " + new Date());
        pw.println("");
        pw.flush();
        while ((c = fr.readLine()) != null) {
            pw.println(c);
        }
        pw.flush();
        fr.close();
        pw.close();
        reader.close();
        client.close();
    }
}

  • 服务端的主类
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class HttpServer2 {
    public static void main(String[] args) throws IOException {
        //启动服务器,监听8888端口
        ServerSocket server = new ServerSocket(8888);
        while (!Thread.interrupted()) {
            //不停的接收客户端请求
            Socket client = server.accept();
             new Thread(new ServerThread(client)).start();
        }
        server.close();
    }
}

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

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

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

<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<title>index.html</title>
</head>
<body>
	hello world
	<br>
	<img src="1.jpg">
</body>
</html>

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

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

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

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

    }

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

 BufferedReader reader = new BufferedReader(new InputStreamReader(ins));
        String line = reader.readLine().split(" ")[1].replace("/","\\");
        if (line.equals("\\")){
            line += "index.html";
        }
        System.out.println(line);
  • 现在全部的修改如下
    HttpServer2类代码不变,修改ServerThread
public class ServerThread implements Runnable {
    private static final String webroot = "e:\\webroot\\";
    private static Map<String, String> contentMap = new HashMap<>();
    static {
        contentMap.put("html", "text/html;charset=utf-8");
        contentMap.put("jpg", "image/jpeg");

    }
    private Socket client;
    private InputStream ins;
    private OutputStream out;


    private PrintWriter pw;
    private BufferedReader br;

    private void init() {
        try {
            ins = client.getInputStream();
            out = client.getOutputStream();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public ServerThread(Socket client) {
        this.client = client;
        init();
    }


    @Override
    public void run() {
        try {
            go();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void go() throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(ins));
        String line = reader.readLine().split(" ")[1].replace("/","\\");
        if (line.equals("\\")){
            line += "index.html";
        }
        System.out.println(line);

        //给用户响应
        PrintWriter pw = new PrintWriter(out);
        FileInputStream i = new FileInputStream(webroot + line);
//        BufferedReader fr = new BufferedReader(new InputStreamReader(i, "UTF-8"));
        String c = null;
        pw.println("HTTP/1.1 200 OK");
        String s = line.substring(line.lastIndexOf(".")+1);
        System.out.println(s);
        pw.println("Content-Type: "+contentMap.get(s));
        pw.println("Content-Length: " + i.available());
        pw.println("Server: hello");
        pw.println("Date: " + new Date());
        pw.println("");
        pw.flush();
        byte[] buff = new byte[1024];
        int len = 0;
        while ((len = i.read(buff)) != -1) {
           out.write(buff, 0 , len);
        }
        pw.flush();
        i.close();
        pw.close();
        reader.close();
        client.close();
    }
}

  • 启动HttpServer2,开放8888端口:
  • 客户端访问,可看到图片也访问到了
    在这里插入图片描述

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

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

<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<title>index.html</title>
</head>
<body>
	hello world
	<br>
	<img src="1.jpg">
	<a href="login.html">登陆</a>
</body>
</html>

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

<!DOCTYPE html>
<html>
<head>
	<title></title>
</head>
<body>
    this is sign in html
    这是登陆页面奥
</body>
</html>
  • 测试下能否访问到
    在这里插入图片描述
  • 点击登陆链接

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

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

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

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class HttpServer2 {
    public static void main(String[] args) throws IOException {
        //启动服务器,监听8888端口
        ServerSocket server = new ServerSocket(8888);
        while (!Thread.interrupted()) {
            //不停的接收客户端请求
            Socket client = server.accept();
             new Thread(new ServerThread(client)).start();
        }
        server.close();
    }
}

修改为:

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

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


发布了246 篇原创文章 · 获赞 29 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/baidu_21349635/article/details/104174431