Tomcat 专题

  • Tomcat 专题二
    • 内容
    • 5.Web 应用配置
      • 5.1 ServletContext 初始化参数
      • 5.2 会话配置
      • 5.3 Servlet配置
      • 5.4 Listener配置
      • 5.5 Filter配置
      • 5.6 欢迎页面配置
      • 5.7 错误页面配置
    • 6.Tomcat 管理配置
      • 6.1 host-manager
      • 6.2 manager
    • 7.JVM 配置
      • 7.1 JVM内存模型图
      • 7.2 JVM配置选项
    • 8. Tomcat 集群
      • 8.1 简介
      • 8.2 环境准备
        • 8.2.2 准备Tomcat
        • 8.2.3 安装配置Nginx
      • 8.3 负载均衡策略
      • 8.4 Session共享方案
        • 8.4.1 ip_hash 策略
        • 8.4.2 Session复制
        • 8.4.3 SSO-单点登录
    • 9. Tomcat 安全
      • 9.1 配置安全
      • 9.2 应用安全
      • 9.3 传输安全
        • 9.3.1 HTTPS介绍
        • 9.3.2 Tomcat支持HTTPS
    • 10. Tomcat 性能调优
      • 10.1 Tomcat 性能测试
        • 10.1.1 ApacheBench
      • 10.2 Tomcat 性能优化
        • 10.2.1 JVM参数调优
        • 10.2.2 Tomcat 配置调优
    • 11. Tomcat 附加功能
      • 11.1 WebSocket
        • 11.1.1 WebSocket介绍
        • 11.2.2 Tomcat的 Websocket
        • 11.2.3 WebSocket DEMO案例
          • 11.2.3.1 需求
          • 11.2.3.2 实现流程
          • 11.2.3.3 消息格式
          • 11.2.3.4 功能实现

Tomcat 专题二

内容

序号 第一部分 第二部分
1 Tomcat 基础 Web 应用配置
2 Tomcat 架构 Tomcat管理配置
3 Jasper JVM配置
4 Tomcat 服务器配置 Tomcat集群
5 Tomcat安全
6 Tomcat性能调优
7 Tomcat附加功能

5.Web 应用配置

web.xml 是web应用的描述文件, 它支持的元素及属性来自于Servlet 规范定义 。 在Tomcat 中, Web 应用的描述信息包括 tomcat/conf/web.xml 中默认配置 以及 Web 应用 WEB-INF/web.xml 下的定制配置。

[外链图片转存失败(img-WOyxGeJw-1569219073613)(assets/1561301479032.png)]

5.1 ServletContext 初始化参数

我们可以通过 添加ServletContext 初始化参数,它配置了一个键值对,这样我们可以在应用程序中使用 javax.servlet.ServletContext.getInitParameter()方法获取参数。

<context-param><param-name>contextConfigLocation</param-name><param-value>classpath:applicationContext-*.xml</param-value><description>Spring Config File Location</description>
</context-param>

5.2 会话配置

用于配置Web应用会话,包括 超时时间、Cookie配置以及会话追踪模式。它将覆盖server.xml 和 context.xml 中的配置。

  <session-config><session-timeout>30</session-timeout><cookie-config><name>JESSIONID</name><domain>www.itcast.cn</domain><path>/</path><comment>Session Cookie</comment><http-only>true</http-only><secure>false</secure><max-age>3600</max-age></cookie-config><tracking-mode>COOKIE</tracking-mode></session-config>

配置解析:

1) session-timeout : 会话超时时间,单位 分钟2) cookie-config: 用于配置会话追踪Cookiename:Cookie的名称domain:Cookie的域名path:Cookie的路径comment:注释http-only:cookie只能通过HTTP方式进行访问,JS无法读取或修改,此项可以增加网站访问的安全性。secure:此cookie只能通过HTTPS连接传递到服务器,而HTTP 连接则不会传递该信息。注意是从浏览器传递到服务器,服务器端的Cookie对象不受此项影响。max-age:以秒为单位表示cookie的生存期,默认为-1表示是会话Cookie,浏览器关闭时就会消失。3) tracking-mode :用于配置会话追踪模式,Servlet3.0版本中支持的追踪模式:COOKIE、URL、SSLA. COOKIE : 通过HTTP Cookie 追踪会话是最常用的会话追踪机制, 而且Servlet规范也要求所有的Servlet规范都需要支持Cookie追踪。B. URL : URL重写是最基本的会话追踪机制。当客户端不支持Cookie时,可以采用URL重写的方式。当采用URL追踪模式时,请求路径需要包含会话标识信息,Servlet容器会根据路径中的会话标识设置请求的会话信息。如: http://www.myserver.com/user/index.html;jessionid=1234567890。C. SSL : 对于SSL请求, 通过SSL会话标识确定请求会话标识。

5.3 Servlet配置

Servlet 的配置主要是两部分, servlet 和 servlet-mapping :

<servlet><servlet-name>myServlet</servlet-name><servlet-class>cn.itcast.web.MyServlet</servlet-class><init-param><param-name>fileName</param-name><param-value>init.conf</param-value></init-param><load-on-startup>1</load-on-startup><enabled>true</enabled>
</servlet><servlet-mapping><servlet-name>myServlet</servlet-name><url-pattern>*.do</url-pattern><url-pattern>/myservet/*</url-pattern>
</servlet-mapping>

配置说明:

1) servlet-name : 指定servlet的名称, 该属性在web.xml中唯一。2) servlet-class : 用于指定servlet类名3) init-param: 用于指定servlet的初始化参数, 在应用中可以通过 HttpServlet.getInitParameter 获取。 4) load-on-startup: 用于控制在Web应用启动时,Servlet的加载顺序。 值小于0,web应用启动时,不加载该servlet, 第一次访问时加载。5) enabled: true , false 。 若为false ,表示Servlet不处理任何请求。6) url-pattern: 用于指定URL表达式,一个 servlet-mapping可以同时配置多个 url-pattern。

Servlet 中文件上传配置:

<servlet><servlet-name>uploadServlet</servlet-name><servlet-class>cn.itcast.web.UploadServlet</servlet-class><multipart-config><location>C://path</location><max-file-size>10485760</max-file-size><max-request-size>10485760</max-request-size><file-size-threshold>0</file-size-threshold></multipart-config>
</servlet>

配置说明:

1) location:存放生成的文件地址。2) max-file-size:允许上传的文件最大值。 默认值为-1, 表示没有限制。3) max-request-size:针对该 multi/form-data 请求的最大数量,默认值为-1, 表示无限制。4) file-size-threshold:当数量量大于该值时, 内容会被写入文件。

5.4 Listener配置

Listener用于监听servlet中的事件,例如context、request、session对象的创建、修改、删除,并触发响应事件。Listener是观察者模式的实现,在servlet中主要用于对context、request、session对象的生命周期进行监控。在servlet2.5规范中共定义了8中Listener。在启动时,ServletContextListener 的执行顺序与web.xml 中的配置顺序一致, 停止时执行顺序相反。

<listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

5.5 Filter配置

filter 用于配置web应用过滤器, 用来过滤资源请求及响应。 经常用于认证、日志、加密、数据转换等操作, 配置如下:

<filter><filter-name>myFilter</filter-name><filter-class>cn.itcast.web.MyFilter</filter-class><async-supported>true</async-supported><init-param><param-name>language</param-name><param-value>CN</param-value></init-param>
</filter><filter-mapping><filter-name>myFilter</filter-name><url-pattern>/*</url-pattern>
</filter-mapping>

配置说明:

1) filter-name: 用于指定过滤器名称,在web.xml中,过滤器名称必须唯一。2) filter-class : 过滤器的全限定类名, 该类必须实现Filter接口。3) async-supported: 该过滤器是否支持异步4) init-param :用于配置Filter的初始化参数, 可以配置多个, 可以通过FilterConfig.getInitParameter获取5) url-pattern: 指定该过滤器需要拦截的URL。

5.6 欢迎页面配置

welcome-file-list 用于指定web应用的欢迎文件列表。

<welcome-file-list><welcome-file>index.html</welcome-file><welcome-file>index.htm</welcome-file><welcome-file>index.jsp</welcome-file>
</welcome-file-list>

尝试请求的顺序,从上到下。

5.7 错误页面配置

error-page 用于配置Web应用访问异常时定向到的页面,支持HTTP响应码和异常类两种形式。

<error-page><error-code>404</error-code><location>/404.html</location>
</error-page>
<error-page><error-code>500</error-code><location>/500.html</location>
</error-page>
<error-page><exception-type>java.lang.Exception</exception-type><location>/error.jsp</location>
</error-page>

6.Tomcat 管理配置

从早期的Tomcat版本开始,就提供了Web版的管理控制台,他们是两个独立的Web应用,位于webapps目录下。Tomcat 提供的管理应用有用于管理的Host的host-manager和用于管理Web应用的manager。

6.1 host-manager

Tomcat启动之后,可以通过 http://localhost:8080/host-manager/html 访问该Web应用。 host-manager 默认添加了访问权限控制,当打开网址时,需要输入用户名和密码(conf/tomcat-users.xml中配置) 。所以要想访问该页面,需要在conf/tomcat-users.xml 中配置,并分配对应的角色:

1) admin-gui:用于控制页面访问权限

2) admin-script:用于控制以简单文本的形式进行访问

配置如下:

<role rolename="admin-gui"/>
<role rolename="admin-script"/>
<user username="itcast" password="itcast" roles="admin-script,admin-gui"/>

登录:

[外链图片转存失败(img-IELWH3kH-1569219073614)(assets/1561901585518.png)]

界面:

[外链图片转存失败(img-PF7FikUu-1569219073614)(assets/1561905514316.png)]

6.2 manager

manager的访问地址为 http://localhost:8080/manager, 同样, manager也添加了页面访问控制,因此我们需要为登录用户分配角色为:

<role rolename="manager-gui"/>
<role rolename="manager-script"/>
<user username="itcast" password="itcast" roles="admin-script,admin-gui,manager-gui,manager-script"/>

界面:

[外链图片转存失败(img-RBO5OO8W-1569219073615)(assets/1561906890838.png)]

Server Status

[外链图片转存失败(img-f0PhlT9G-1569219073616)(assets/1561906977299.png)]

[外链图片转存失败(img-pol971HQ-1569219073616)(assets/1561907023825.png)]

7.JVM 配置

最常见的JVM配置当属内存分配,因为在绝大多数情况下,JVM默认分配的内存可能不能够满足我们的需求,特别是在生产环境,此时需要手动修改Tomcat启动时的内存参数分配。

7.1 JVM内存模型图

[外链图片转存失败(img-Ia0wwnCZ-1569219073617)(assets/1562240349924.png)]

7.2 JVM配置选项

windows 平台(catalina.bat):

set JAVA_OPTS=-server  -Xms2048m  -Xmx2048m  -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m -XX:SurvivorRatio=8

linux 平台(catalina.sh):

JAVA_OPTS="-server -Xms1024m -Xmx2048m -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m  -XX:SurvivorRatio=8"

参数说明 :

序号 参数 含义
1 -Xms 堆内存的初始大小
2 -Xmx 堆内存的最大大小
3 -Xmn 新生代的内存大小,官方建议是整个堆得3/8。
4 -XX:MetaspaceSize 元空间内存初始大小, 在JDK1.8版本之前配置为 -XX:PermSize(永久代)
5 -XX:MaxMetaspaceSize 元空间内存最大大小, 在JDK1.8版本之前配置为 -XX:MaxPermSize(永久代)
6 -XX:InitialCodeCacheSize
-XX:ReservedCodeCacheSize
代码缓存区大小
7 -XX:NewRatio 设置新生代和老年代的相对大小比例。这种方式的优点是新生代大小会随着整个堆大小动态扩展。如 -XX:NewRatio=3 指定老年代 / 新生代为 3/1。 老年代占堆大小的 3/4,新生代占 1/4 。
8 -XX:SurvivorRatio 指定伊甸园区 (Eden) 与幸存区大小比例。如 -XX:SurvivorRatio=10 表示伊甸园区 (Eden) 是 幸存区 To 大小的 10 倍 (也是幸存区 From 的 10 倍)。 所以, 伊甸园区 (Eden) 占新生代大小的 10/12, 幸存区 From 和幸存区 To 每个占新生代的 1/12 。 注意, 两个幸存区永远是一样大的。

配置之后, 重新启动Tomcat ,访问 :

[外链图片转存失败(img-ULnNbru2-1569219073617)(assets/1562240180947.png)]

8. Tomcat 集群

8.1 简介

由于单台Tomcat的承载能力是有限的,当我们的业务系统用户量比较大,请求压力比较大时,单台Tomcat是扛不住的,这个时候,就需要搭建Tomcat的集群,而目前比较流程的做法就是通过Nginx来实现Tomcat集群的负载均衡。

[外链图片转存失败(img-GRWqV40k-1569219073618)(assets/1562746048930.png)]

8.2 环境准备

8.2.2 准备Tomcat

在服务器上, 安装两台tomcat, 然后分别改Tomcat服务器的端口号 :

8005 ---------> 8015 ---------> 80258080 ---------> 8888 ---------> 9999 8009 ---------> 8019 ---------> 8029

8.2.3 安装配置Nginx

在当前服务器上 , 安装Nginx , 然后再配置Nginx, 配置nginx.conf :

upstream serverpool{server localhost:8888;server localhost:9999;
}server {listen       99;server_name localhost;location / {    proxy_pass http://serverpool/;      }
}

8.3 负载均衡策略

1). 轮询

最基本的配置方法,它是upstream模块默认的负载均衡默认策略。每个请求会按时间顺序逐一分配到不同的后端服务器。

upstream serverpool{server localhost:8888;server localhost:9999;
}

参数说明:

参数 描述
fail_timeout 与max_fails结合使用
max_fails 设置在fail_timeout参数设置的时间内最大失败次数,如果在这个时间内,所有针对该服务器的请求都失败了,那么认为该服务器会被认为是停机了
fail_time 服务器会被认为停机的时间长度,默认为10s
backup 标记该服务器为备用服务器。当主服务器停止时,请求会被发送到它这里
down 标记服务器永久停机了

2). weight权重

权重方式,在轮询策略的基础上指定轮询的几率。

upstream serverpool{server localhost:8888 weight=3;server localhost:9999 weight=1;
}

weight参数用于指定轮询几率,weight的默认值为1;weight的数值与访问比率成正比,比如8888服务器上的服务被访问的几率为9999服务器的三倍。

此策略比较适合服务器的硬件配置差别比较大的情况。

3). ip_hash

指定负载均衡器按照基于客户端IP的分配方式,这个方法确保了相同的客户端的请求一直发送到相同的服务器,以保证session会话。这样每个访客都固定访问一个后端服务器,可以解决session不能跨服务器的问题。

upstream serverpool{ip_hash;server 192.168.192.133:8080;server 192.168.192.137:8080;
}

8.4 Session共享方案

在Tomcat集群中,如果应用需要用户进行登录,那么这个时候,用于tomcat做了负载均衡,则用户登录并访问应用系统时,就会出现问题 。

[外链图片转存失败(img-1ueKQnQG-1569219073618)(assets/1562748611975.png)]

解决上述问题, 有以下几种方案:

8.4.1 ip_hash 策略

一个用户发起的请求,只会请求到tomcat1上进行操作,另一个用户发起的请求只在tomcat2上进行操作 。那么这个时候,同一个用户发起的请求,都会通过nginx的ip_hash策略,将请求转发到其中的一台Tomcat上。

8.4.2 Session复制

在servlet_demo01 工程中 , 制作session.jsp页面,分别将工程存放在两台 tomcat 的 webapps/ 目录下:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>Title</title>
</head>
<body>TOMCAT - 9999 : <br/>sessionID : <%= session.getId()%><br/><%Object loginUser = session.getAttribute("loginUser");if(loginUser != null && loginUser.toString().length()>0){out.println("session 有值, loginUser = " + loginUser);}else{session.setAttribute("loginUser","ITCAST");out.println("session 没有值");}%></body>
</html>

通过nginx访问 , http://localhost:99/demo01/session.jsp ,访问到的两台Tomcat出现的sessionID是不一样的:

[外链图片转存失败(img-fe2s6MJl-1569219073619)(assets/1563597522906.png)]

上述现象,则说明两台Tomcat的Session各是各的,并没有进行同步,这在集群环境下是存在问题的。

Session同步的配置如下:

1) 在Tomcat的conf/server.xml 配置如下:

<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>

2) 在Tomcat部署的应用程序 servlet_demo01 的web.xml 中加入如下配置 :

<distributable/>

3) 配置完毕之后, 再次重启两个 Tomcat服务。

[外链图片转存失败(img-Oputr15q-1569219073619)(assets/1563597364007.png)]

上述方案,适用于较小的集群环境(节点数不超过4个),如果集群的节点数比较多的话,通过这种广播的形式来完成Session的复制,会消耗大量的网络带宽,影响服务的性能。

8.4.3 SSO-单点登录

单点登录(Single Sign On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统,也是用来解决集群环境Session共享的方案之一 。

[外链图片转存失败(img-XmsL8vsA-1569219073620)(assets/1563067214329.png)]

9. Tomcat 安全

9.1 配置安全

1) 删除webapps目录下的所有文件,禁用tomcat管理界面;

2) 注释或删除tomcat-users.xml文件内的所有用户权限;

3) 更改关闭tomcat指令或禁用;

​ tomcat的server.xml中定义了可以直接关闭 Tomcat 实例的管理端口(默认8005)。可以通过 telnet 连接上该端口之后,输入 SHUTDOWN (此为默认关闭指令)即可关闭 Tomcat 实例(注意,此时虽然实例关闭了,但是进程还是存在的)。由于默认关闭Tomcat 的端口和指令都很简单。默认端口为8005,指令为SHUTDOWN 。

方案一:

更改端口号和指令:
<Server port="8456" shutdown="itcast_shut">

方案二:

禁用8005端口:
<Server port="-1" shutdown="SHUTDOWN">

4) 定义错误页面

在webapps/ROOT目录下定义错误页面 404.html,500.html;

然后在tomcat/conf/web.xml中进行配置 , 配置错误页面:

<error-page> <error-code>404</error-code> <location>/404.html</location>
</error-page><error-page> <error-code>500</error-code> <location>/500.html</location>
</error-page>

这样配置之后,用户在访问资源时出现404,500这样的异常,就能看到我们自定义的错误页面,而不会看到异常的堆栈信息,提高了用户体验,也保障了服务的安全性。

9.2 应用安全

在大部分的Web应用中,特别是一些后台应用系统,都会实现自己的安全管理模块(权限模块),用于控制应用系统的安全访问,基本包含两个部分:认证(登录/单点登录)和授权(功能权限、数据权限)两个部分。对于当前的业务系统,可以自己做一套适用于自己业务系统的权限模块,也有很多的应用系统直接使用一些功能完善的安全框架,将其集成到我们的web应用中,如:SpringSecurity、Apache Shiro等。

9.3 传输安全

9.3.1 HTTPS介绍

HTTPS的全称是超文本传输安全协议(Hypertext Transfer Protocol Secure),是一种网络安全传输协议。在HTTP的基础上加入SSL/TLS来进行数据加密,保护交换数据不被泄露、窃取。

SSL 和 TLS 是用于网络通信安全的加密协议,它允许客户端和服务器之间通过安全链接通信。SSL 协议的3个特性:

1) 保密:通过SSL链接传输的数据时加密的。

2) 鉴别:通信双方的身份鉴别,通常是可选的,单至少有一方需要验证。

3) 完整性:传输数据的完整性检查。

从性能角度考虑,加解密是一项计算昂贵的处理,因为尽量不要将整个Web应用采用SSL链接, 实际部署过程中, 选择有必要进行安全加密的页面(存在敏感信息传输的页面)采用SSL通信。

HTTPS和HTTP的区别主要为以下四点:

1) HTTPS协议需要到证书颁发机构CA申请SSL证书, 然后与域名进行绑定,HTTP不用申请证书;

2) HTTP是超文本传输协议,属于应用层信息传输,HTTPS 则是具有SSL加密传安全性传输协议,对数据的传输进行加密,相当于HTTP的升级版;

3) HTTP和HTTPS使用的是完全不同的连接方式,用的端口也不一样,前者是8080,后者是8443。

4) HTTP的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比HTTP协议安全。

HTTPS协议优势:

1) 提高网站排名,有利于SEO。谷歌已经公开声明两个网站在搜索结果方面相同,如果一个网站启用了SSL,它可能会获得略高于没有SSL网站的等级,而且百度也表明对安装了SSL的网站表示友好。因此,网站上的内容中启用SSL都有明显的SEO优势。

2) 隐私信息加密,防止流量劫持。特别是涉及到隐私信息的网站,互联网大型的数据泄露的事件频发发生,网站进行信息加密势在必行。

3) 浏览器受信任。 自从各大主流浏览器大力支持HTTPS协议之后,访问HTTP的网站都会提示“不安全”的警告信息。

9.3.2 Tomcat支持HTTPS

1) 生成秘钥库文件。

keytool -genkey -alias tomcat -keyalg RSA -keystore tomcatkey.keystore

[外链图片转存失败(img-anfjmSOL-1569219073621)(assets/1562251350597.png)]

输入对应的密钥库密码, 秘钥密码等信息之后,会在当前文件夹中出现一个秘钥库文件:tomcatkey.keystore

2) 将秘钥库文件 tomcatkey.keystore 复制到tomcat/conf 目录下。

3) 配置tomcat/conf/server.xml

<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"maxThreads="150" schema="https" secure="true" SSLEnabled="true"><SSLHostConfig certificateVerification="false"><Certificate certificateKeystoreFile="D:/DevelopProgramFile/apache-tomcat-8.5.42-windows-x64/apache-tomcat-8.5.42/conf/tomcatkey.keystore" certificateKeystorePassword="itcast"  type="RSA" /></SSLHostConfig>
</Connector>

4)访问Tomcat ,使用https协议。

[外链图片转存失败(img-nbXH863n-1569219073621)(assets/1562391059290.png)]

10. Tomcat 性能调优

10.1 Tomcat 性能测试

对于系统性能,用户最直观的感受就是系统的加载和操作时间,即用户执行某项操作的耗时。从更为专业的角度上讲,性能测试可以从以下两个指标量化。

1). 响应时间:如上所述,为执行某个操作的耗时。大多数情况下,我们需要针对同一个操作测试多次,以获取操作的平均响应时间。

2). 吞吐量:即在给定的时间内,系统支持的事务数量,计算单位为 TPS。

通常情况下,我们需要借助于一些自动化工具来进行性能测试,因为手动模拟大量用户的并发访问几乎是不可行的,而且现在市面上也有很多的性能测试工具可以使用,如:ApacheBench、ApacheJMeter、WCAT、WebPolygraph、LoadRunner。

我们课程上主要介绍两款免费的工具:ApacheBench。

10.1.1 ApacheBench

ApacheBench(ab)是一款ApacheServer基准的测试工具,用户测试Apache Server的服务能力(每秒处理请求数),它不仅可以用户Apache的测试,还可以用于测试Tomcat、Nginx、lighthttp、IIS等服务器。

1) 安装

yum install httpd-tools

2) 查看版本号

ab -V

[外链图片转存失败(img-fInEY7rN-1569219073622)(assets/1563106022944.png)]

3) 部署war包, 准备环境

A. 在Linux系统上安装Tomcat上传 : alt + p -------> put D:/apache-tomcat-8.5.42.tar.gz解压 : tar -zxvf apache-tomcat-8.5.42.tar.gz -C /usr/local修改端口号:8005 , 8080 , 8009B. 将资料中的war包上传至Tomcat的webapps下上传: alt + p ---------> put  D:/ROOT.war启动Tomcat解压C. 导入SQL脚本 , 准备环境

4) 测试性能

ab -n 1000 -c 100 -p data.json -T application/json http://localhost:9000/course/search.do?page=1&pageSize=10

参数说明

参数 含义描述
-n 在测试会话中所执行的请求个数,默认只执行一次请求
-c 一次产生的请求个数,默认一次一个
-p 包含了需要POST的数据文件
-t 测试所进行的最大秒数,默认没有时间限制
-T POST数据所需要使用的Content-Type头信息
-v 设置显示信息的详细程度
-w 以HTML表的格式输出结果,默认是白色背景的两列宽度的一张表

结果说明

指标 含义
Server Software 服务器软件
Server Hostname 主机名
Server Port 端口号
Document Path 测试的页面
Document Length 测试的页面大小
Concurrency Level 并发数
Time taken for tests 整个测试持续的时间
Complete requests 完成的请求数量
Failed requests 失败的请求数量,这里的失败是指请求的连接服务器、发送数据、接收数据等环节发生异常,以及无响应后超时的情况。
Write errors 输出错误数量
Total transferred 整个场景中的网络传输量,表示所有请求的响应数据长度总和,包括每个http响应数据的头信息和正文数据的长度。
HTML transferred 整个场景中的HTML内容传输量,表示所有请求的响应数据中正文数据的总和
Requests per second 每秒钟平均处理的请求数(相当于 LR 中的 每秒事务数)这便是我们重点关注的吞吐率,它等于:Complete requests / Time taken for tests
Time per request 每个线程处理请求平均消耗时间(相当于 LR 中的 平均事务响应时间)用户平均请求等待时间
Transfer rate 平均每秒网络上的流量
Percentage of the requests served within a certain time (ms) 指定时间里,执行的请求百分比

重要指标

参数 指标说明
Requests per second 吞吐率:服务器并发处理能力的量化描述,单位是reqs/s,指的是在某个并发用户数下单位时间内处理的请求数。某个并发用户数下单位时间内能处理的最大请求数,称之为最大吞吐率。
这个数值表示当前机器的整体性能,值越大越好。
Time per request 用户平均请求等待时间:从用户角度看,完成一个请求所需要的时间
Time per request:across all concurrent requests 服务器平均请求等待时间:服务器完成一个请求的时间
Concurrency Level 并发用户数

10.2 Tomcat 性能优化

10.2.1 JVM参数调优

Tomcat是一款Java应用,那么JVM的配置便与其运行性能密切相关,而JVM优化的重点则集中在内存分配和GC策略的调整上,因为内存会直接影响服务的运行效率和吞吐量, JVM垃圾回收机制则会不同程度地导致程序运行中断。可以根据应用程序的特点,选择不同的垃圾回收策略,调整JVM垃圾回收策略,可以极大减少垃圾回收次数,提升垃圾回收效率,改善程序运行性能。

1) JVM内存参数

参数 参数作用 优化建议
-server 启动Server,以服务端模式运行 服务端模式建议开启
-Xms 最小堆内存 建议与-Xmx设置相同
-Xmx 最大堆内存 建议设置为可用内存的80%
-XX:MetaspaceSize 元空间初始值
-XX:MaxMetaspaceSize 元空间最大内存 默认无限
-XX:MaxNewSize 新生代最大内存 默认16M
-XX:NewRatio 年轻代和老年代大小比值,取值为整数,默认为2 不建议修改
-XX:SurvivorRatio Eden区与Survivor区大小的比值,取值为整数,默认为8 不建议修改
JAVA_OPTS="-server -Xms2048m -Xmx2048m -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m  -XX:SurvivorRatio=8"
jmap -heap 51421

2) GC策略

JVM垃圾回收性能有以下两个主要的指标:

  • 吞吐量:工作时间(排除GC时间)占总时间的百分比, 工作时间并不仅是程序运行的时间,还包含内存分配时间。
  • 暂停时间:测试时间段内,由垃圾回收导致的应用程序停止响应次数/时间。

在Sun公司推出的HotSpotJVM中, 包含以下几种不同类型的垃圾收集器:

垃圾收集器 含义说明
串行收集器
(Serial Collector)
采用单线程执行所有的垃圾回收工作, 适用于单核CPU服务器,无法利用多核硬件的优势
并行收集器
(Parallel Collector)
又称为吞吐量收集器, 以并行的方式执行年轻代的垃圾回收, 该方式可以显著降低垃圾回收的开销(指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态)。适用于多处理器或多线程硬件上运行的数据量较大的应用
并发收集器
(Concurrent Collector)
以并发的方式执行大部分垃圾回收工作,以缩短垃圾回收的暂停时间。适用于那些响应时间优先于吞吐量的应用, 因为该收集器虽然最小化了暂停时间(指用户线程与垃圾收集线程同时执行,但不一定是并行的,可能会交替进行), 但是会降低应用程序的性能
CMS收集器
(Concurrent Mark Sweep Collector)
并发标记清除收集器, 适用于那些更愿意缩短垃圾回收暂停时间并且负担的起与垃圾回收共享处理器资源的应用
G1收集器
(Garbage-First Garbage Collector)
适用于大容量内存的多核服务器, 可以在满足垃圾回收暂停时间目标的同时, 以最大可能性实现高吞吐量(JDK1.7之后)

不同的应用程序, 对于垃圾回收会有不同的需求。 JVM 会根据运行的平台、服务器资源配置情况选择合适的垃圾收集器、堆内存大小及运行时编译器。如无法满足需求, 参考以下准则:

A. 程序数据量较小,选择串行收集器。

B. 应用运行在单核处理器上且没有暂停时间要求, 可交由JVM自行选择或选择串行收集器。

C. 如果考虑应用程序的峰值性能, 没有暂停时间要求, 可以选择并行收集器。

D. 如果应用程序的响应时间比整体吞吐量更重要, 可以选择并发收集器。

[外链图片转存失败(img-Z6EciUq9-1569219073622)(assets/1563871672547.png)]

查看Tomcat中的默认的垃圾收集器:

1). 在tomcat/bin/catalina.sh的配置中, 加入如下配置

JAVA_OPTS=" -Djava.rmi.server.hostname=192.168.192.138 -Dcom.sun.management.jmxremote.port=8999 -Dcom.sun.management.jmxremote.rmi.port=8999 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false"

2). 打开 jconsole , 查看远程的tomcat的概要信息

连接远程tomcat

[外链图片转存失败(img-Q4wF7LCW-1569219073623)(assets/1563636157038.png)]

GC参数:

参数 描述
-XX:+UseSerialGC 启用串行收集器
-XX:+UseParallelGC 启用并行垃圾收集器,配置了该选项,那么 -XX:+UseParallelOldGC默认启用
-XX:+UseParallelOldGC FullGC 采用并行收集,默认禁用。如果设置了 -XX:+UseParallelGC则自动启用
-XX:+UseParNewGC 年轻代采用并行收集器,如果设置了 -XX:+UseConcMarkSweepGC选项,自动启用
-XX:ParallelGCThreads 年轻代及老年代垃圾回收使用的线程数。默认值依赖于JVM使用的CPU个数
-XX:+UseConcMarkSweepGC 对于老年代,启用CMS垃圾收集器。 当并行收集器无法满足应用的延迟需求是,推荐使用CMS或G1收集器。
启用该选项后, -XX:+UseParNewGC 自动启用。
-XX:+UseG1GC 启用G1收集器。 G1是服务器类型的收集器, 用于多核、大内存的机器。它在保持高吞吐量的情况下,高概率满足GC暂停时间的目标。

我们也可以在测试的时候,将JVM参数调整之后,将GC的信息打印出来,便于为我们进行参数调整提供依据,具体参数如下:

选项 描述
-XX:+PrintGC 打印每次GC的信息
-XX:+PrintGCApplicationConcurrentTime 打印最后一次暂停之后所经过的时间, 即响应并发执行的时间
-XX:+PrintGCApplicationStoppedTime 打印GC时应用暂停时间
-XX:+PrintGCDateStamps 打印每次GC的日期戳
-XX:+PrintGCDetails 打印每次GC的详细信息
-XX:+PrintGCTaskTimeStamps 打印每个GC工作线程任务的时间戳
-XX:+PrintGCTimeStamps 打印每次GC的时间戳

在bin/catalina.sh的脚本中 , 追加如下配置 :

JAVA_OPTS="-XX:+UseConcMarkSweepGC  -XX:+PrintGCDetails"

10.2.2 Tomcat 配置调优

调整tomcat/conf/server.xml 中关于链接器的配置可以提升应用服务器的性能。

参数 说明
maxConnections 最大连接数,当到达该值后,服务器接收但不会处理更多的请求, 额外的请求将会阻塞直到连接数低于maxConnections 。可通过ulimit -a 查看服务器限制。对于CPU要求更高(计算型)时,建议不要配置过大; 对于CPU要求不是特别高时,建议配置在2000左右(受服务器性能影响)。 当然这个需要服务器硬件的支持
maxThreads 最大线程数,需要根据服务器的硬件情况,进行一个合理的设置
acceptCount 最大排队等待数,当服务器接收的请求数量到达maxConnections ,此时Tomcat会将后面的请求,存放在任务队列中进行排序, acceptCount指的就是任务队列中排队等待的请求数 。 一台Tomcat的最大的请求处理数量,是maxConnections+acceptCount。

11. Tomcat 附加功能

11.1 WebSocket

11.1.1 WebSocket介绍

WebSocket是HTML5新增的协议,它的目的是在浏览器和服务器之间建立一个不受限的双向通信的通道,比如说,服务器可以在任意时刻发送消息给浏览器。

为什么传统的HTTP协议不能做到WebSocket实现的功能?这是因为HTTP协议是一个请求-响应协议,请求必须先由浏览器发给服务器,服务器才能响应这个请求,再把数据发送给浏览器。换句话说,浏览器不主动请求,服务器是没法主动发数据给浏览器的。

这样一来,要在浏览器中搞一个实时聊天,或者在线多人游戏的话就没法实现了,只能借助Flash这些插件。也有人说,HTTP协议其实也能实现啊,比如用轮询或者Comet。轮询是指浏览器通过JavaScript启动一个定时器,然后以固定的间隔给服务器发请求,询问服务器有没有新消息。这个机制的缺点一是实时性不够,二是频繁的请求会给服务器带来极大的压力。

Comet本质上也是轮询,但是在没有消息的情况下,服务器先拖一段时间,等到有消息了再回复。这个机制暂时地解决了实时性问题,但是它带来了新的问题:以多线程模式运行的服务器会让大部分线程大部分时间都处于挂起状态,极大地浪费服务器资源。另外,一个HTTP连接在长时间没有数据传输的情况下,链路上的任何一个网关都可能关闭这个连接,而网关是我们不可控的,这就要求Comet连接必须定期发一些ping数据表示连接“正常工作”。

[外链图片转存失败(img-VLbnJP5n-1569219073624)(assets/1563864481513.png)]

以上两种机制都治标不治本,所以,HTML5推出了WebSocket标准,让浏览器和服务器之间可以建立无限制的全双工通信,任何一方都可以主动发消息给对方。WebSocket并不是全新的协议,而是利用了HTTP协议来建立连接。我们来看看WebSocket连接是如何创建的。

[外链图片转存失败(img-lPmOXetB-1569219073624)(assets/1563701565641.png)]

首先,WebSocket连接必须由浏览器发起,因为请求协议是一个标准的HTTP请求,格式如下:

[外链图片转存失败(img-QbvZ6e4H-1569219073625)(assets/1563455301750.png)]

该请求和普通的HTTP请求有几点不同:

  1. GET请求的地址不是类似 http://,而是以 ws:// 开头的地址;

  2. 请求头 Connection: Upgrade 和 请求头 Upgrade: websocket 表示这个连接将要被转换为 WebSocket 连接;

  3. Sec-WebSocket-Key 是用于标识这个连接, 是一个BASE64编码的密文, 要求服务端响应一个对应加密的Sec-WebSocket-Accept头信息作为应答;

  4. Sec-WebSocket-Version 指定了WebSocket的协议版本;

  5. HTTP101 状态码表明服务端已经识别并切换为WebSocket协议 , Sec-WebSocket-Accept是服务端与客户端一致的秘钥计算出来的信息。

11.2.2 Tomcat的 Websocket

Tomcat的7.0.5 版本开始支持WebSocket,并且实现了Java WebSocket规范(JSR356), 而在7.0.5版本之前(7.0.2之后)则采用自定义API, 即WebSocketServlet实现。

Java WebSocket应用由一系列的WebSocketEndpoint组成。Endpoint 是一个java对象,代表WebSocket链接的一端,对于服务端,我们可以视为处理具体WebSocket消息的接口, 就像Servlet之与http请求一样。

我们可以通过两种方式定义Endpoint:

1). 第一种是编程式, 即继承类 javax.websocket.Endpoint并实现其方法。

2). 第二种是注解式, 即定义一个POJO, 并添加 @ServerEndpoint相关注解。

Endpoint实例在WebSocket握手时创建,并在客户端与服务端链接过程中有效,最后在链接关闭时结束。在Endpoint接口中明确定义了与其生命周期相关的方法, 规范实现者确保生命周期的各个阶段调用实例的相关方法。生命周期方法如下:

方法 含义描述 注解
onClose 当会话关闭时调用。 @OnClose
onOpen 当开启一个新的会话时调用, 该方法是客户端与服务端握手成功后调用的方法。 @OnOpen
onError 当连接过程中异常时调用。 @OnError

通过为Session添加MessageHandler消息处理器来接收消息,当采用注解方式定义Endpoint时,我们还可以通过 @OnMessage 注解指定接收消息的方法。发送消息则由RemoteEndpoint 完成, 其实例由Session维护, 根据使用情况, 我们可以通过Session.getBasicRemote获取同步消息发送的实例 , 然后调用其sendXxx()方法就可以发送消息, 可以通过Session.getAsyncRemote 获取异步消息发送实例。

11.2.3 WebSocket DEMO案例

11.2.3.1 需求

通过 websocket 实现一个简易的聊天室功能 ;

1). 登录聊天室

[外链图片转存失败(img-cWZymTXK-1569219073625)(assets/1563700498341.png)]

  1. 登陆之后,进入聊天界面进行聊天

用户 Deng 的界面:

[外链图片转存失败(img-YZ1GkXEE-1569219073626)(assets/1563700629696.png)]

用户 ICAST 的界面 :

[外链图片转存失败(img-qDU6M8Ek-1569219073626)(assets/1563700687162.png)]

11.2.3.2 实现流程

[外链图片转存失败(img-jkvIZksX-1569219073626)(assets/1563725111100.png)]

11.2.3.3 消息格式

客户端–>服务端 : {“fromName”:“Deng”,“toName”:“HEIMA”,“content”:“约会呀”}

服务端–>客户端 :

​ ①. 如果type为user , 则说明返回的是用户列表

​ {“data”:“HEIMA,Deng,ITCAST”,“toName”:"",“fromName”:"",“type”:“user”}

​ ②. 如果type为message , 则说明返回的是消息内容

​ {“data”:“你好”,“toName”:“HEIMA”,“fromName”:“Deng”,“type”:“message”}

11.2.3.4 功能实现

1) 创建项目, 导入项目依赖。

[外链图片转存失败(img-Nb1bb1in-1569219073627)(assets/1563684249455.png)]

  1. 引入静态资源文件。

[外链图片转存失败(img-C2DXaMMx-1569219073627)(assets/1563684308295.png)]

[外链图片转存失败(img-LowipFo8-1569219073627)(assets/1563684579721.png)]

3) 定义一个登陆的servlet

获取页面传递的用户名和密码 , 只要传递的聊天室密码为123456 , 则认为正确, 允许登录 .

@WebServlet(name = "loginServlet",urlPatterns = {"/login"})
public class LoginServlet  extends HttpServlet {private static final String PASSWORD = "123456";@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {doPost(req,resp);}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.setCharacterEncoding("UTF-8");String username = req.getParameter("username");String password = req.getParameter("password");Map resultMap = new HashMap();if(password != null && password.equals(PASSWORD)){req.getSession().setAttribute("username",username);resultMap.put("success",true);resultMap.put("message","登录成功");}else{resultMap.put("success",false);resultMap.put("message","登录失败");}resp.getWriter().write(JSON.toJSONString(resultMap));}}

4) 定义配置类,便于在WS中获取HttpSession

/*** 获取HttpSession对象的配置类*/
public class GetHttpSessionConfigurator extends ServerEndpointConfig.Configurator {/*** 获取到HttpSession , 并将其存储在 ServerEndpointConfig对象中.* @param config* @param request* @param response*/@Overridepublic void modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response) {HttpSession httpSession = (HttpSession) request.getHttpSession();config.getUserProperties().put(HttpSession.class.getName(), httpSession);}}

5) 定义WebSocket的服务端程序


@ServerEndpoint(value = "/websocket", configurator = GetHttpSessionConfigurator.class)
public class ChatServlet {//该map集合用来存储所有在线用户的实例信息private static final Map<HttpSession, ChatServlet> onlineUsers = new HashMap<HttpSession, ChatServlet>();//记录在线用户数private static int onlineCount = 0;//用户的HttpSessionprivate HttpSession httpSession;//用户的WS的会话信息Sessionprivate Session session;/***  @onOpen :当开启一个新的会话时调用, 该方法是客户端与服务端握手成功后调用的方法。**   为当前Servlet中的Session赋值 , 为HttpSession赋值, 将当前的会话信息, 记录在在线用户集合中 ;**  获取到当前所有在线的用户信息 , 并且给所有的WS客户端推送消息, 在客户端更新好友列表。*  在线用户数增加1**/@OnOpenpublic void onOpen(Session session, EndpointConfig config) {this.session = session;this.httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName());if (httpSession.getAttribute("username") != null) {onlineUsers.put(httpSession, this);}String names = getNames();String content = MessageUtil.getContent(MessageUtil.TYPE_USER, "", "", names);System.out.println("服务端给客户端广播消息: "+ content);broadcastAll(content);addOnlineCount();           //在线数加1System.out.println("有新连接加入!当前在线人数为" + onlineUsers.size());}@OnClosepublic void onClose(Session session, CloseReason closeReason) {//onlineUsers.remove(this);  //从set中删除subOnlineCount();           //在线数减1System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount());}/***  接收客户端传递的消息, 并且根据消息中的toName判定当前消息给那个客户端发送* @param message* @param session* @throws Exception*/@OnMessagepublic void onMessage(String message, Session session) throws Exception {//获取客户端发送的消息,并解析Map<String, String> messageMap = JSON.parseObject(message, Map.class);String fromName = messageMap.get("fromName");    //消息来自人 的userIdString toName = messageMap.get("toName");       //消息发往人的 userIdString mapContent = messageMap.get("content");//判断是否有接收人if (toName == null || toName.isEmpty()) {return;}//如果接收人是 all , 则说明是广播消息if ("all".equals(toName)) {String content = MessageUtil.getContent(MessageUtil.TYPE_MESSAGE, fromName, "all", mapContent);broadcastAll(content);} else {//如果不是all , 则给指定用户推送消息try {String content = MessageUtil.getContent(MessageUtil.TYPE_MESSAGE, fromName, toName, mapContent);System.out.println("服务端给客户端推消息: "+ content);singleChat(fromName, toName, content);} catch (IOException e) {e.printStackTrace();}}System.out.println("来自客户端的消息:" + message);//broadcastAll(message);}private void singleChat(String fromName, String toName, String mapContent) throws IOException {boolean isExit = false;//判定收件人是否存在for (HttpSession key : onlineUsers.keySet()) {if (key.getAttribute("username").equals(toName)) {isExit = true;}}//如果存在则, 发送消息if (isExit) {for (HttpSession key : onlineUsers.keySet()) {if (key.getAttribute("username").equals(fromName) || key.getAttribute("username").equals(toName)) {onlineUsers.get(key).session.getBasicRemote().sendText(mapContent);}}}}//发送广播消息private void broadcastAll(String msg) {for (HttpSession key : onlineUsers.keySet()) {try {onlineUsers.get(key).session.getBasicRemote().sendText(msg);} catch (IOException e) {e.printStackTrace();}}}@OnErrorpublic void onError(Session session, Throwable error) {error.printStackTrace();System.out.println("发生错误");}//获取所有当前在线的用户private String getNames() {String names = "";for (HttpSession key : onlineUsers.keySet()) {String name = (String) key.getAttribute("username");names += name + ",";}String namesTemp = names.substring(0, names.length() - 1);return namesTemp;}public static synchronized int getOnlineCount() {return onlineCount;}public static synchronized void addOnlineCount() {ChatServlet.onlineCount++;}public static synchronized void subOnlineCount() {ChatServlet.onlineCount--;}
}

Tomcat核心原理学习总结(二)相关推荐

  1. Mybatis底层原理学习(二):从源码角度分析一次查询操作过程

    在阅读这篇文章之前,建议先阅读一下我之前写的两篇文章,对理解这篇文章很有帮助,特别是Mybatis新手: 写给mybatis小白的入门指南 mybatis底层原理学习(一):SqlSessionFac ...

  2. 逆向工程核心原理学习笔记(十二):分析abex' crackme #1

    程序下载地址:http://t.cn/RX1wpX7 我们首先运行一下,看看提示什么: 我们初步推测,这个程序应该是判断磁盘是否运行在一个CD-ROM上. 为了验证我们的推测,我们拖进OD看一下. 我 ...

  3. 逆向工程核心原理学习笔记(二):字符串检索法查找main函数

    首先就是OD载入我们的程序 然后鼠标右键,如图,找到智能搜索. 然后点击,找到了HelloWord字符串 双击跟进去就是main函数的地址了 这样做的前提是因为我们知道线索(程序在运行时就弹出信息框) ...

  4. 【java并发编程艺术学习】(四)第二章 java并发机制的底层实现原理 学习记录(二) synchronized...

    章节介绍 本章节主要学习 Java SE 1.6 中为了减少获得锁 和 释放锁 时带来的性能消耗 而引入的偏向锁 和 轻量级锁,以及锁的存储结构 和 升级过程. synchronized实现同步的基础 ...

  5. 逆向工程核心原理学习笔记(十四):栈帧1

    栈帧的话,直接截了一些图,大家看一下就好了,理解起来很简单,就是简单的参数转存. 看完之后,我们需要用一个小程序来进一步学习我们的栈帧了. 下载地址:http://t.cn/RaUSglI 代码写法: ...

  6. 逆向工程核心原理学习笔记(七):总结

    首先就是上一节,我们尝试把修改后的代码保存后运行,发现不可以,. 这是由于我们修改的那部分缓冲区造成的. 可执行文件加大再到内存中兵役进程的形式运行并非原封不动的载入内存,而是遵循一定的规则进行,这一 ...

  7. 浏览器工作原理学习(二十一)

    浏览器安全分为三大块:Web页面安全.浏览器网络安全和浏览器系统安全. Web页面安全 什么是同源策略 如果两个URL的协议.域名和端口都相同,我们就称这两个URL同源. 浏览器默认两个相同的源之间是 ...

  8. 逆向工程核心原理学习笔记(十三):分析abex' crackme #1 的延伸:将参数压入栈

    还是上一次的abex' crackme #1,我们用OD附加看一下. 我们发现在调用这个MessageBox函数的时候,用了4个PUSH指令,我们在后面的注释中可以清楚的看到压入参数的内容. 如果我们 ...

  9. 逆向工程核心原理学习笔记(十一):栈

    栈(stack)用途广泛,通常用于存储局部变量.传递函数参数,保存函数返回地址等. 调试程序需要不断查看栈内存,这是很有必要的. 栈是一种数据结构,按照后进先出的原则存储数据. 栈的特征: 一个进程中 ...

最新文章

  1. PHP成为首个在内核中嵌入加密库的编程语言
  2. java sqlldr导入_使用SQL*LOADER将TXT或CSV数据导入ORACLE(转)
  3. unity节目素材ProceduralMaterial采用
  4. Linux下查看文件内容
  5. Spring线程池服务
  6. oracle11g日志原理,oracle11gRAC之log日志体系
  7. DreamWeaver CS3中的SPRY的自定义验证
  8. 如何JOPtionPane的showConfirmDialog对话框button设置监视器
  9. android与单片机wifi通信原理图,基于单片机的wifi模块原理图分析
  10. 【python 作日期的折线图和柱状图组合图】
  11. android锁屏密码忘了,安卓手机锁屏密码忘记了怎么办
  12. linux invalid argument_Linux命令很熟悉,你知道它们的英文全称和中文解释吗?
  13. 每日3词 2021-03-08 【reference】【print】【coding】
  14. vue输入空格符 以及空字符串在页面不显示或者只显示一个的问题
  15. 【CUDA】判断电脑CUDA和cuDNN是否安装成功(Windows)
  16. TensorFlow实现mnist书写数字分类,出现please use urllib or similar directly错误。
  17. java.sql.SQLException: null, message from server: “Host ‘XXX‘ is not allowed to connect异常(22.11.26)
  18. ClickHouse在苏宁用户画像场景的最佳实践
  19. java 错误1335_安装JAVA的JDK时出现,错误1335? – 手机爱问
  20. day06ViewPager

热门文章

  1. 如何通过DCGAN实现动漫人物图像的自动生成?
  2. 利用CMS漏洞渗透并获取某服务器权限
  3. 『转】山世光导师致报考研究生的一封信
  4. 首席新媒体运营黎想教程:社群搭建及运营实操攻略
  5. RouterOS的基本设置
  6. 对java的粗浅理解
  7. Gitea 无法启动提示 (code=exited, status=203/exec) 错误
  8. map computeIfAbsent 的浅尝辄止
  9. VMware中使用U盘PE系统
  10. [渝粤教育] 温州职业技术学院 纳税筹划 参考 资料