上一篇博客《本身动手实现简单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
快乐源于分享。异步
此博客乃做者原创, 转载请注明出处