java socket实现web服务器

需要掌握:HTTP协议

提出问题

socket的接收请求是 阻塞 的,需要处理完一个请求才能处理下一个请求,所以在面对高并发的服务请求时,性能就会很差。

如下图,当 client 1、client 2 发来请求时,Server 先处理 client 1,处理完成后,再处理 client 2。

理解:相当于食堂只开一个打饭窗口,当学生多时,就需要排队,前一个打饭没结束,后面人都要 一直等待

分析

使用多线程,接收到一个请求,就创建一个线程处理,这样就 不会阻塞 了,可以提升性能

多线程的缺点

当 请求很多 的时候,就会 创建大量的线程,维护线程需要资源的消耗(JAVA中每个线程都需要占用内存),线程之间的切换也需要消耗性能。而且系统创建线程的数量也是有限的,所以当高并发时,会直接把系统拖垮。

实现1

使用 socket、多线程是线程

提示: 需要用多线程实现,否则使用浏览器接收不到内容。

public class WebServer {
    public static int PORT=8080;
    public static void main(String[] args) {
        try {
            ServerSocket ss=new ServerSocket(PORT);
            System.out.println("启动成功,端口:"+PORT);
            while(true) {
                Socket s=ss.accept();
                new Thread(new Handle(s)).start();
            }
//            ss.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

处理操作类

需要在当前工程根目录下创建 WebRoot 文件夹,然后在该文件夹下创建 .html 网页、图片等

package socket;

import java.io.*;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

public class Handle implements Runnable{
    private Socket s;
    private static final int BUFF_SIZE=1024*8;
    private static final String WEB_ROOT="WebRoot";
    private static final String INDEX_HTML="index.html";
    private static final List<String> HTML_SUFFIX=new ArrayList<>();
    private static final List<String> IMG_SUFFIX=new ArrayList<>();

    public Handle(Socket s) {
        this.s = s;
        HTML_SUFFIX.add("html");
        HTML_SUFFIX.add("htm");

        IMG_SUFFIX.add("png");
        IMG_SUFFIX.add("jpg");
        IMG_SUFFIX.add("jpeg");
        IMG_SUFFIX.add("gif");
    }

    @Override
    public void run() {
        System.out.println(String.format("客户端:%s:%d 连接到服务器", s.getInetAddress().getHostAddress(),s.getPort()));

        List<String> heads=new ArrayList<String>();
        try {
            InputStream is = s.getInputStream();
            OutputStream os=s.getOutputStream();

            //接收数据
            BufferedReader br=new BufferedReader(new InputStreamReader(is));
            String line=br.readLine();
            while(line!=null){
                //使用BufferedReader,最后2个\r\n解析成一个空字符串
                if("".equals(line)){
                    break;
                }
                heads.add(line);
                line=br.readLine();
            }

            System.out.println("请求头:");
            for(String str:heads){
                System.out.println(str);
            }

            String fileName=heads.get(0).split(" ")[1];
            if("/".equals(fileName)){
                fileName=INDEX_HTML;
            }
            sendFile(os,fileName);

            s.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void sendFile(OutputStream os,String fileName) throws IOException {
        byte[] buff=new byte[BUFF_SIZE];
        os.write("HTTP/1.1 200 OK\r\n".getBytes());

        String suffix=fileName.substring(fileName.lastIndexOf(".")+1,fileName.length()).toLowerCase();
        if(HTML_SUFFIX.contains(suffix)){
            os.write("Content-Type: text/html;charset=utf-8\r\n".getBytes());
        }else if(IMG_SUFFIX.contains(suffix)){
            os.write(String.format("content-type: image/%s\r\n",suffix).getBytes());
        }else{
            os.write("Content-Type: text/html;charset=utf-8\r\n".getBytes());
        }
        os.write("Server: houlong server/1.1\r\n".getBytes());

        os.write("\r\n".getBytes());

        File file = new File(WEB_ROOT, fileName);
        FileInputStream fis = new FileInputStream(file);
        int len = fis.read(buff);
        while (len != -1) {
            os.write(buff, 0, len);
            len = fis.read(buff);
        }
        os.write("\r\n\r\n".getBytes());
        os.flush();
    }
}

测试

需要在当前工程根目录下创建 WebRoot 文件夹,然后在该文件夹下创建 index.html 文件,内容如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
hello world!
</body>
</html>

打开浏览器,访问:http://localhost:8080/ ,就能访问到网页

实现2

使用 ThreadPoolExecutor 线程池

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class WebServer2 {
    public static int PORT=8080;
    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(100, 100,
                60L, TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>(200));
        try {
            ServerSocket ss=new ServerSocket(PORT);
            System.out.println("启动成功,端口:"+PORT);
            while(true) {
                Socket s=ss.accept();
                pool.execute(new Handle(s));
            }
//            ss.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

处理操作类

同上


原文出处:/show_1EF62kIdybNa.html