JAVA简易WEB服务器(五)

简介:

在之前的几篇博客中,我们的服务器已经具备雏形了,我们还需要继续对其进行优化,在《JAVA简易WEB服务器(三)》中,我们启动服务器的方法如下:

 /**
  * 启动服务器
  */
 public synchronized void start()
 {
    try
    {
       serverSocket = new ServerSocket(port);
       LOG.info("server init success.");
    }
    catch (IOException e)
    {
       LOG.log(Level.SEVERE, e.getMessage(), e);
    }
    new Thread()
    {
       public void run()
       {
          while (!isStop())
          {
             Socket socket;
             try
             {
                socket = serverSocket.accept();
                handleRequest(socket);
             }
             catch (IOException e)
             {
                LOG.log(Level.SEVERE, e.getMessage(), e);
             }
          }
       };
    }.start();
 }

我们是在一个线程中启动我们的服务器,避免阻塞主线程,但是这样,依然存在的问题是,我们的服务器一次只能处理一个请求,这样肯定是不合理的,所以在这一篇博客中,我们将使用多线程对服务端进行优化。

进一步的优化方式:使用多线程,我们可以在接收到请求后开辟新线程进行处理,代码如下:

/**
  * 启动服务器
  */
 public synchronized void start2()
 {
    try
    {
       serverSocket = new ServerSocket(port);
       LOG.info("server init success.");
    }
    catch (IOException e)
    {
       LOG.log(Level.SEVERE, e.getMessage(), e);
    }
    new Thread()
    {
       public void run()
       {
          while (!isStop())
          {
              try
             {
                final Socket socket = serverSocket.accept();
                new Thread()
                {
                   public void run()
                   {
                      try
                      {
                         handleRequest(socket);
                      }
                      catch (IOException e)
                      {
                        LOG.log(Level.SEVERE, e.getMessage(), e);
                     }
                  };
               }.start();
            }
            catch (IOException e)
            {
               LOG.log(Level.SEVERE, e.getMessage(), e);
            }
         }
      };
   }.start();
}

这里,一旦TCP连接建立之后,将会创建一个新的线程来处理新的请求,在新的线程中执行handleRequest方法。
通过创建新的线程,程序可以继续接受新的TCP连接,且这些请求可以并行的处理。
在每个请求一个线程实现中,创建一个线程(和后续的销毁)开销是非常昂贵的,因为JVM和操作系统都需要分配资源。另外,上面的实现还有一个问题,即创建的线程数是不可控的,这将可能导致系统资源被迅速耗尽。
所以我们应该继续寻找解决方案:线程池
使用线程池可以更好的管理我们创建的线程,JDK为我们提供了几种默认的线程池实现,当线程池满后,后续的请求我们是用丢弃的策略,所以在这里面们使用自定义的线程池,完整的HQHttpServer代码如下:

package com.gujin.server;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;

import com.gujin.server.utils.HQClose;

/**
 * 服务端
 * 
 * @author jianggujin
 * 
 */
public abstract class HQHttpServer implements HQHttpServerLog
{
   /** 端口号 **/
   private int port = 80;
   /** 服务套接字 **/
   private ServerSocket serverSocket = null;
   private int poolSize = 4;
   private int capacity = 16;

   /**
    * 默认构造方法
    */
   public HQHttpServer()
   {
   }

   /**
    * 构造方法
    * 
    * @param port
    */
   public HQHttpServer(int port)
   {
      this.port = port;
   }

   public int getPoolSize()
   {
      return poolSize;
   }

   public void setPoolSize(int poolSize)
   {
      this.poolSize = poolSize;
   }

   public int getCapacity()
   {
      return capacity;
   }

   public void setCapacity(int capacity)
   {
      this.capacity = capacity;
   }

   /**
    * 异步启动服务器
    */
   public void startAsyn()
   {
      new Thread()
      {
         public void run()
         {
            start();
         };
      }.start();
   }

   /**
    * 启动服务器
    * 
    * @throws IOException
    */
   public synchronized void start() throws IOException
   {
      if (isStop())
      {
         serverSocket = new ServerSocket(port);
         LOG.info("server init success.");
         ExecutorService executorService = newBoundedFixedPool(poolSize,
               poolSize);
         while (!isStop())
         {
            Socket socket = serverSocket.accept();
            LOG.info("accept client...");
            executorService.submit(new HQSocketRunnable(socket));
         }
      }
   }

   /**
    * 处理请求
    * 
    * @param socket
    * @throws IOException
    */
   protected void handleRequest(Socket socket) throws IOException
   {
      HQRequest request = new HQRequest(socket);
      request.execute();
      HQResponse response = new HQResponse(socket);
      try
      {
         response.setCookieHandler(request.getCookieHandler().clone());
      }
      catch (CloneNotSupportedException e)
      {
         LOG.log(Level.SEVERE, e.getMessage(), e);
      }
      handleRequest(request, response);
      response.response();
      HQClose.safeClose(socket);
   }

   /**
    * 处理请求
    * 
    * @param request
    * @param response
    * @throws IOException
    */
   public abstract void handleRequest(HQRequest request, HQResponse response)
         throws IOException;

   /**
    * 是否停止
    * 
    * @return
    */
   public boolean isStop()
   {
      return serverSocket == null || serverSocket.isClosed();
   }

   /**
    * 停止服务器
    */
   public synchronized void stop()
   {
      if (!isStop())
      {
         HQClose.safeClose(serverSocket);
         serverSocket = null;
      }
   }

   public static ExecutorService newBoundedFixedPool(int nThreads, int capacity)
   {
      return new ThreadPoolExecutor(nThreads, nThreads, 0L,
            TimeUnit.MILLISECONDS, new LinkedBlockingDeque<Runnable>(capacity),
            new ThreadPoolExecutor.DiscardPolicy());
   }

   private class HQSocketRunnable implements Runnable
   {
      private Socket socket = null;

      public HQSocketRunnable(Socket socket)
      {
         this.socket = socket;
      }

      @Override
      public void run()
      {
         try
         {
            handleRequest(socket);
         }
         catch (Exception e)
         {
            LOG.log(Level.SEVERE, e.getMessage(), e);
         }
      }
   }
}
目录
相关文章
|
1月前
|
Java Linux
java读取linux服务器下某文档的内容
java读取linux服务器下某文档的内容
33 3
java读取linux服务器下某文档的内容
|
22天前
|
运维 Java Linux
【运维基础知识】Linux服务器下手写启停Java程序脚本start.sh stop.sh及详细说明
### 启动Java程序脚本 `start.sh` 此脚本用于启动一个Java程序,设置JVM字符集为GBK,最大堆内存为3000M,并将程序的日志输出到`output.log`文件中,同时在后台运行。 ### 停止Java程序脚本 `stop.sh` 此脚本用于停止指定名称的服务(如`QuoteServer`),通过查找并终止该服务的Java进程,输出操作结果以确认是否成功。
29 1
WK
|
5天前
|
安全 Java 编译器
C++和Java哪个更适合开发web网站
在Web开发领域,C++和Java各具优势。C++以其高性能、低级控制和跨平台性著称,适用于需要高吞吐量和低延迟的场景,如实时交易系统和在线游戏服务器。Java则凭借其跨平台性、丰富的生态系统和强大的安全性,广泛应用于企业级Web开发,如企业管理系统和电子商务平台。选择时需根据项目需求和技术储备综合考虑。
WK
9 0
|
28天前
|
分布式计算 资源调度 Hadoop
大数据-01-基础环境搭建 超详细 Hadoop Java 环境变量 3节点云服务器 2C4G XML 集群配置 HDFS Yarn MapRedece
大数据-01-基础环境搭建 超详细 Hadoop Java 环境变量 3节点云服务器 2C4G XML 集群配置 HDFS Yarn MapRedece
61 4
|
28天前
|
Java Shell Maven
Flink-11 Flink Java 3分钟上手 打包Flink 提交任务至服务器执行 JobSubmit Maven打包Ja配置 maven-shade-plugin
Flink-11 Flink Java 3分钟上手 打包Flink 提交任务至服务器执行 JobSubmit Maven打包Ja配置 maven-shade-plugin
88 4
|
30天前
|
Java PHP
PHP作为广受青睐的服务器端脚本语言,在Web开发中占据重要地位。理解其垃圾回收机制有助于开发高效稳定的PHP应用。
【10月更文挑战第1天】PHP作为广受青睐的服务器端脚本语言,在Web开发中占据重要地位。其垃圾回收机制包括引用计数与循环垃圾回收,对提升应用性能和稳定性至关重要。本文通过具体案例分析,详细探讨PHP垃圾回收机制的工作原理,特别是如何解决循环引用问题。在PHP 8中,垃圾回收机制得到进一步优化,提高了效率和准确性。理解这些机制有助于开发高效稳定的PHP应用。
40 3
|
29天前
|
前端开发 Java API
JAVA Web 服务及底层框架原理
【10月更文挑战第1天】Java Web 服务是基于 Java 编程语言用于开发分布式网络应用程序的一种技术。它通常运行在 Web 服务器上,并通过 HTTP 协议与客户端进行通信。
21 1
|
19天前
|
弹性计算 网络安全
阿里云国际OpenAPI多接口快速管理ECS服务器教程
阿里云国际OpenAPI多接口快速管理ECS服务器教程
|
2天前
|
弹性计算
阿里云2核16G服务器多少钱一年?亲测价格查询1个月和1小时收费标准
阿里云2核16G服务器提供多种ECS实例规格,内存型r8i实例1年6折优惠价为1901元,按月收费334.19元,按小时收费0.696221元。更多规格及详细报价请访问阿里云ECS页面。
26 9
|
2天前
|
弹性计算 异构计算
2024年阿里云GPU服务器多少钱1小时?亲测价格查询方法
2024年阿里云GPU服务器每小时收费因实例规格不同而异。可通过阿里云GPU服务器页面选择“按量付费”查看具体价格。例如,NVIDIA A100的gn7e实例为34.742元/小时,NVIDIA A10的gn7i实例为12.710156元/小时。更多详情请访问阿里云官网。
26 2