本身动手实现简单web容器二

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

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

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();
	}
}

    二、启动web 服务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队列。具体线程池的用法将在后面的文章中详细讨论。多线程

后记app

    至此,咱们用伪异步的方式,对咱们的web服务器进行了改造,实现了请求的并行,可是这个方式并不是最高效,那么还有什么高效的方式呢?敬请期待《本身动手实现简单web容器三》webapp

快乐源于分享。异步

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

相关文章
相关标签/搜索