自己动手实现简单web容器二

    上一篇博客《自己动手实现简单web容器一》,留下了一些问题,今天我们来解决并行访问的问题,即多个用户同时访问,我们的web服务器能给出响应,而不至于阻塞住。现代计算机越来越好,CPU核心数也越来越多,为了更高效的利用CPU,Java提供多线程的编程方式,下面我们就用多线程的方式解决这个问题。

    1、写一个HandleRequestThread类,实现Runnable接口,重写run方法,便实现了一个线程类。代码如下:

public class HandleRequestThread implements Runnable {

	private static final StringBuilder responseContent = new StringBuilder();

	static {
		responseContent.append("HTTP/1.1 200 OK");
		responseContent.append("Content-Type: text/html; charset=utf-8");
		responseContent.append("Content-Length:%s");
		responseContent.append("Date:%s");
		responseContent.append("Server:This is a simulation web container");
	}

	private Socket accept;

	public HandleRequestThread(Socket accept) {
		this.accept = accept;
	}

	@Override
	public void run() {
		try {
			// 获取请求中的流
			BufferedReader br = new BufferedReader(new InputStreamReader(accept.getInputStream()));
			// 只读取第一行数据
			String request = br.readLine();
			String result = "";
			if (!"".equals(request) && null != request) {
				String urlAndPrams = request.split(" ")[1];
				if (urlAndPrams.indexOf("/login") != -1) {
					// 获取用户传过来的用户名密码
					String[] params = urlAndPrams.split("\\?")[1].split("&");
					String username = params[0].split("=").length < 2 ? null : params[0].split("=")[1];
					String password = params[1].split("=").length < 2 ? null : params[1].split("=")[1];
					if ("admin".equals(username) && "admin".equals(password)) {
						result = "login success";
					} else {
						result = "username or password error";
					}
				} else {
					// 调用前1面定义的获取登录页方法,获取已经写好的登录页
					result = getLoginPage();
				}
			} else {
				// 调用前面定义的获取登录页方法,获取已经写好的登录页
				result = getLoginPage();
			}
			// 拿到该请求对应Socket的输出流,准备向浏览器写数据了
			PrintWriter printWriter = new PrintWriter(accept.getOutputStream());
			// 把文件长度、日期填回Response字符串中
			printWriter.println(String.format(responseContent.toString(), result.length(), new Date()));
			// 必须有个换行,换行之后才能向浏览器端写要传回的数据(HTTP协议)
			printWriter.println();
			// 写页面数据
			printWriter.println(result);
			printWriter.flush();
			printWriter.close();
			accept.close();
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	private String getLoginPage() {
		// 用于存储从文件读出的文件
		StringBuilder sb = new StringBuilder();
		try {
			// 声明一个BufferedReader准备读文件
			BufferedReader htmlReader = new BufferedReader(new InputStreamReader(
					new FileInputStream(new File(System.getProperty("user.dir") + "/webapp/login.html"))));
			String html = null;
			// 按行读取,直到最后一行结束
			while ((html = htmlReader.readLine()) != null) {
				sb.append(html);
			}
			// 关闭
			htmlReader.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		// 以字符串形式返回读到的HTML页面
		return sb.toString();
	}
}

    2、启动web 服务

public class WebServer {
	public static void main(String[] args) {
		try {
			// 监听在8080端口
			ServerSocket serverSocket = new ServerSocket(8080);
			while (true) {
				Socket accept = serverSocket.accept();
				// 接收到客户端请求,新起一个线程来处理该请求
				new Thread(new HandleRequestThread(accept)).start();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

    以上代码已经可以实现多用户并行访问了,视乎完美解决了问题,假设现在有1000个用户同时访问,那么意味着要创建1000个线程,那么CPU和内存资源肯定吃紧,那么有什么办法优化呢?答案是线程池,预先创建一批线程在池子里,请求来了就处理,线程数不够,请求排队(当然线程池的实现是很复杂的,不是本文讨论的重点)。

    废话不多说,上代码。

public class WebServer {
	public static void main(String[] args) {
		try {
			// 创建一个定长为10的线程池
			ExecutorService pool = Executors.newFixedThreadPool(10);
			// 监听在8080端口
			ServerSocket serverSocket = new ServerSocket(8080);
			while (true) {
				Socket accept = serverSocket.accept();
				// 接收到客户端请求,新起一个线程来处理该请求
				pool.execute(new HandleRequestThread(accept));
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

    用线程池,不仅可以节省线程创建和销毁的开销,还可以稳定线程的数量,不至于撑爆CPU和内存,这里不得不提一下Executors,Executors提供四种线程池创建的静态方法:newCachedThreadPool、newFixedThreadPool、newScheduledThreadPool、newSingleThreadExecutor,这个不是本文讨论的重点,我们只说我们用到的定长线程池,分析一下jdk源码

public static ExecutorService newFixedThreadPool(int nThreads) {
   return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
}

ThreadPoolExecutor是JDK默认线程池的实现,这里corePoolSize和maximumPoolSize相等,keepAliveTime表示不保存空闲线程,这个参数可以 根据请求的密集程度来调整,TimeUnit线程存活时间单位,BlockingQueue线程池满,选用的队列,这里我们是不限长队列,LinkedBlockingQueue是FIFO队列。具体线程池的用法将在后面的文章中详细讨论。

后记

    至此,我们用伪异步的方式,对我们的web服务器进行了改造,实现了请求的并行,但是这个方式并非最高效,那么还有什么高效的方式呢?

快乐源于分享。

   此博客乃作者原创, 转载请注明出处

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值