lightGBMlightGBM06-15 22:34

tomcat 7下spring 4.x mvc集成websocket以及sockjs完全参考指南(含nginx/https支持)

之所以sockjs会存在,说得不好听点,就是因为微软是个流氓,现在使用windows 7的系统仍然有近半,而windows 7默认自带的是ie 8,有些会自动更新到ie 9,但是大部分非IT用户其实都不愿意或者不会升级(通常我们做IT的认为很简单的事情,在其他行业的人来看,那就是天书,不要觉得不可能,现实已如此)。

现在言归正传,这里完整的讲下在spring 4.x集成sockjs,以及运行在tomcat 7下时的一些额外注意事项。

spring websocket依赖jar:

复制代码

<dependency>
            <groupId>javax.websocket</groupId>
            <artifactId>javax.websocket-api</artifactId>
            <version>1.1</version>
            <scope>provided</scope> <!-- 注意,scope必须为provided,否则runtime会冲突,如果使用tomcat 8,还需要将TOMCAT_HOME/lib下的javax.websocket-api.jar一并删除 -->
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-websocket</artifactId>
            <version>4.2.8.RELEASE</version>
        </dependency>

复制代码

除非使用STOMP协议,否则不需要依赖spring-messaging。

spring通过两种模式支持websocket,一种是通过原生websocket规范的ws://协议访问(个人认为如果确定只用标准websocket访问,还不如tomcat升级到8.x(tomcat 8原生支持JSR 356注解),spring的大量封装毕竟增加了不少额外负载);另一种则是通过sockjs(也就是js)访问,两者目前暂时无法做到兼容。

先完整说明第一种:

1、搭建spring mvc环境,这一点假设读者已知;

2、pom.xml中引入上面两个jar包;

3、spring支持websocket总共分为四个小步骤,handler、interceptor、config、web.xml,基本上可以认为spring mvc的翻版。

3.1、创建WebSocketHandler,spring支持两种方式,一种是实现org.springframework.web.socket.WebSocketHandler接口,另外一种则是继承TextWebSocketHandler或BinaryWebSocketHandler(现在大部分模板式框架或者插件通常都是在提供了API的基础上提供了抽象类,把一些能统一的工作提前预置了,以便应用只需要关心业务,我们自己公司的中间件框架很多也是用这个模式实现了)。

/**
 * 
 */
package com.ld.net.spider.demo.ws;

/**
 * @author zhjh256@163.com
 * {@link} http://www.cnblogs.com/zhjh256
 */
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;

public class DemoWSHandler implements WebSocketHandler {  
  
    @Override  
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {  
        System.out.println("connect to the websocket success......");  
        session.sendMessage(new TextMessage("Server:connected OK!"));  
    }  
  
    @Override  
    public void handleMessage(WebSocketSession wss, WebSocketMessage<?> wsm) throws Exception {  
        TextMessage returnMessage = new TextMessage(wsm.getPayload()  
                + " received at server");  
        System.out.println(wss.getHandshakeHeaders().getFirst("Cookie"));  
        wss.sendMessage(returnMessage);  
    }  
  
    @Override
    public void handleTransportError(WebSocketSession wss, Throwable thrwbl) throws Exception {  
        if(wss.isOpen()){  
            wss.close();  
        }  
       System.out.println("websocket connection closed......");  
    }  
  
    @Override  
    public void afterConnectionClosed(WebSocketSession wss, CloseStatus cs) throws Exception {  
        System.out.println("websocket connection closed......");  
    }  
  
    @Override  
    public boolean supportsPartialMessages() {  
        return false;  
    }
}

3.2、创建拦截器

/**
 * 
 */
package com.ld.net.spider.demo.ws;

import java.util.Map;

import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;

/**
 * @author zhjh256@163.com 
 * {@link} http://www.cnblogs.com/zhjh256
 */
public class HandshakeInterceptor extends HttpSessionHandshakeInterceptor {

    @Override
    public boolean beforeHandshake(ServerHttpRequest request,
            ServerHttpResponse response, WebSocketHandler wsHandler,
            Map<String, Object> attributes) throws Exception {

        // 解决The extension [x-webkit-deflate-frame] is not supported问题
        if (request.getHeaders().containsKey("Sec-WebSocket-Extensions")) {
            request.getHeaders().set("Sec-WebSocket-Extensions",
                    "permessage-deflate");
        }

        System.out.println("Before Handshake");
        return super.beforeHandshake(request, response, wsHandler, attributes);
    }

    @Override
    public void afterHandshake(ServerHttpRequest request,
            ServerHttpResponse response, WebSocketHandler wsHandler,
            Exception ex) {
        System.out.println("After Handshake");
        super.afterHandshake(request, response, wsHandler, ex);
    }
}

3.3、bean配置

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:mongo="http://www.springframework.org/schema/data/mongo"
xmlns:cache="http://www.springframework.org/schema/cache"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:amq="http://activemq.apache.org/schema/core"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xmlns:jms="http://www.springframework.org/schema/jms"
xmlns:util="http://www.springframework.org/schema/util"   
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket
http://www.springframework.org/schema/websocket/spring-websocket.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/data/mongo       
http://www.springframework.org/schema/data/mongo/spring-mongo-1.0.xsd   
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd
http://activemq.apache.org/schema/core 
http://activemq.apache.org/schema/core/activemq-core.xsd
http://www.springframework.org/schema/jms 
http://www.springframework.org/schema/jms/spring-jms.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
    <websocket:handlers allowed-origins="*">
        <websocket:mapping path="/springws/websocket.ws" handler="demoWSHandler"/>
         <websocket:handshake-interceptors>
            <bean class="com.ld.net.spider.demo.ws.HandshakeInterceptor"/>
        </websocket:handshake-interceptors>
    </websocket:handlers>

    <bean id="demoWSHandler" class="com.ld.net.spider.demo.ws.DemoWSHandler"/>

上述需要注意的是,1、spring javadoc的说明是默认情况下,允许所有来源访问,但我们跑下来发现不配置allowed-origins的话总是报403错误。

2、sockjs是不允许有后缀的,否则将无法匹配,后面会专门讲到。

3.4、web.xml配置

在web.xml中增加*.ws映射即可(如果原来不是/*的话),如下:

<servlet-mapping>
        <servlet-name>springMVC</servlet-name>
        <url-pattern>*.ws</url-pattern>
    </servlet-mapping>

上述配置完成之后,就可以通过标准的websocket接口进行访问了,如下所示。

4、websocket客户端

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <title>Web Socket JavaScript Echo Client</title>
  <script src="http://cdn.jsdelivr.net/sockjs/1/sockjs.min.js"></script>
  <script language="javascript" type="text/javascript">
    var echo_websocket;
    function init() {
      output = document.getElementById("output");
    }

    function send_echo() {
      var wsUri = "ws://localhost:28080/springws/websocket.ws";
      writeToScreen("Connecting to " + wsUri);
      echo_websocket = new WebSocket(wsUri);
      echo_websocket.onopen = function (evt) {
        writeToScreen("Connected !");
        doSend(textID.value);
      };
      echo_websocket.onmessage = function (evt) {
        writeToScreen("Received message: " + evt.data);
        echo_websocket.close();
      };
      echo_websocket.onerror = function (evt) {
        writeToScreen(''<span style="color: red;">ERROR:</span> ''
          + evt.data);
        echo_websocket.close();
      };
    }
    function doSend(message) {
      echo_websocket.send(message);
      writeToScreen("Sent message: " + message);
    }
    function writeToScreen(message) {
      var pre = document.createElement("p");
      pre.style.wordWrap = "break-word";
      pre.innerHTML = message;
      output.appendChild(pre);
    }
    window.addEventListener("load", init, false);
  </script>
</head>
<body>
<h1>Echo Server</h1>
<div style="text-align: left;">
  <form action="">
    <input onclick="send_echo()" value="发送socket请求" type="button">
    <input id="textID" name="message" value="Hello World, Web Sockets" type="text">
    <br>
  </form>
</div>
<div id="output"></div>
</body>
</html>

上述前后端均配置完成后,基于标准websocket api的搭建就完成了,试试吧。。

现在再来看下sockjs的配置。

spring对sockjs和websocket支持的差别在于配置,web.xml,以及客户端,服务实现无差别。

3.3需要调整为如下:

<websocket:handlers>
        <websocket:mapping path="/springws/websocket" handler="demoWSHandler"/>
         <websocket:handshake-interceptors>
            <bean class="com.ld.net.spider.demo.ws.HandshakeInterceptor"/>
        </websocket:handshake-interceptors>
        <websocket:sockjs/>
    </websocket:handlers>

    <bean id="demoWSHandler" class="com.ld.net.spider.demo.ws.DemoWSHandler"/>

3.4 一定要有到/xxx/*的映射,简单的可以直接/*,如下所示:

<servlet-mapping>
        <servlet-name>springMVC</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

上述配置完成后,就sockjs直接性的支持而言,就可以没有问题了。

客户端则为如下:

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <title>Web Socket JavaScript Echo Client</title>
  <script src="http://cdn.jsdelivr.net/sockjs/1/sockjs.min.js"></script>
  <script language="javascript" type="text/javascript">
    var echo_websocket;
    function init() {
      output = document.getElementById("output");
    }
    function send_echo() {
      echo_websocket = new SockJS("http://localhost:28080/springws/websocket") ;   //初始化 websocket

      echo_websocket.onopen = function () {
        console.log(''Info: connection opened.'');
      };

      echo_websocket.onmessage = function (event) {
        console.log(''Received: '' + event.data); //处理服务端返回消息
      };

      echo_websocket.onclose = function (event) {
        console.log(''Info: connection closed.'');
        console.log(event);
      };

      ws.send("abcabc");
    }
    
    function doSend(message) {
      echo_websocket.send(message);
      writeToScreen("Sent message: " + message);
    }
    function writeToScreen(message) {
      var pre = document.createElement("p");
      pre.style.wordWrap = "break-word";
      pre.innerHTML = message;
      output.appendChild(pre);
    }
    window.addEventListener("load", init, false);
  </script>
</head>
<body>
<h1>Echo Server</h1>
<div style="text-align: left;">
  <form action="">
    <input onclick="send_echo()" value="send websocket request" type="button">
    <input id="textID" name="message" value="Hello world, Web Sockets" type="text">
    <br>
  </form>
</div>
<div id="output"></div>
</body>
</html>

 

上述配置完成后,如果访问没有CORS异常的话,基于sockjs的websocket就完成了。试试吧。。。

典型错误及原因、解决方法如下:

Error during WebSocket handshake: Unexpected response code: 404
检查web.xml servlet-mapping包含了到websocket路径的映射,比如如果请求不含后缀,就必须包含/*的映射

 

WebSocket connection to ''ws://localhost:8080/springwebsocket/websocket'' failed: Error during WebSocket handshake: Unexpected response code: 403
<websocket:handlers allowed-origins="*">,javadoc说明默认代表所有站点,实际好像并不是,所以需要配置*

 

sockjs启用
启用sockjs后,直接用websocket协议访问会报
html5ws.html:15 WebSocket connection to ''ws://localhost:28080/springws/websocket.ws'' failed: Error during WebSocket handshake: Unexpected response code: 200

 

直接改为sockjs后,会报
XMLHttpRequest cannot load http://localhost:28080/springws/websocket.ws/info?t=1478758042205. No ''Access-Control-Allow-Origin'' header is present on the requested resource. Origin ''http://localhost:63342'' is therefore not allowed access. The response had HTTP status code 404.
需要在web.xml中配置CORS过滤(注意,如果apache有自带的类库,建议直接使用,不要随意听信网上的自己实现过滤器的搞法,这些库一天的运行次数可能就比自己写的运行到淘汰还多,所以几乎常见的问题都不可能遗漏):

使用sockjs还有一点需要注意的是:
因为sockjs会自动在url之后增加/info?t=XXXX等路径,如果这里url-pattern拦截类似于*.ws这种带后缀的就找不到映射,比如想通过sockjs访问地址/springws/websocket.ws,但是sockjs框架会先访问/springws/websocket.ws/info这个地址,但是这个地址又不可被spring框架识别,所以导致不可用。

到此为止,tomcat 7下spring 4.x mvc集成websocket以及sockjs的配置就全部介绍完成。

今天看群里一个消息的时候,提到HA时一台服务器挂掉的问题,这就回到socket的思路了,客户端也得加上个定时的心跳逻辑,万一某台服务器挂了或者断网可以failover并自动重新建立连接。在我们的业务中,可靠性这一点是很关键的。

默认情况下,ws://走的时候http协议,即使主页面是通过https访问,此时会出现连接时异常"[blocked] The page at ''https://localhost:8443/endpoint-wss/index.jsp'' was loaded over HTTPS, but ran insecure content from ''ws://localhost:8080/endpoint-wss/websocket'': this content should also be loaded over HTTPS.Uncaught SecurityError: Failed to construct ''WebSocket'': An insecure WebSocket connection may not be initiated from a page loaded over HTTPS.",此时需要使用如下连接:

websocket:wss://localhost:8080/endpoint-wss/websocket

sockJS:https://localhost:8080/endpoint-wss/socketJS

 

配置nginx支持websocket,默认情况下,nginx不支持自动升级至websocket协议,否则js中会出现连接时异常"Error during WebSocket handshake: Unexpected response code: 400",需在恰当的位置加上如下设置:

server {
    listen 8020;
    location / {
        proxy_pass http://websocket;
        proxy_set_header Host $host:8020; #注意, 原host必须配置, 否则传递给后台的值是websocket,端口如果没有输入的话会是80, 这会导致连接失败
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
    }
}
upstream websocket {
    server 192.168.100.10:8081;
}

经上述调整后,websocket就可以同时支持通过nginx代理的https协议,结合MQ机制,可以做到B端实时推送、B端/C端实时通信。
nginx的https(自动跳转http->https)+nginx+websocket的完整配置可参考http://www.cnblogs.com/zhjh256/p/6262620.html。

===============================================================

补充:

ie下使用sockjs,后台Tomcat会报错:

java.io.IOException: APR error: -730054
	at org.apache.coyote.http11.InternalAprOutputBuffer.writeToSocket(InternalAprOutputBuffer.java:292) ~[tomcat-coyote.jar:8.0.43]
	at org.apache.coyote.http11.InternalAprOutputBuffer.writeToSocket(InternalAprOutputBuffer.java:245) ~[tomcat-coyote.jar:8.0.43]
	at org.apache.coyote.http11.InternalAprOutputBuffer.flushBuffer(InternalAprOutputBuffer.java:214) ~[tomcat-coyote.jar:8.0.43]
	at org.apache.coyote.http11.AbstractOutputBuffer.flush(AbstractOutputBuffer.java:305) ~[tomcat-coyote.jar:8.0.43]
	at org.apache.coyote.http11.AbstractHttp11Processor.action(AbstractHttp11Processor.java:792) ~[tomcat-coyote.jar:8.0.43]
	at org.apache.coyote.Response.action(Response.java:177) [tomcat-coyote.jar:8.0.43]
	at org.apache.catalina.connector.OutputBuffer.doFlush(OutputBuffer.java:352) [catalina.jar:8.0.43]
	at org.apache.catalina.connector.OutputBuffer.flush(OutputBuffer.java:320) [catalina.jar:8.0.43]
	at org.apache.catalina.connector.Response.flushBuffer(Response.java:496) [catalina.jar:8.0.43]
	at org.apache.catalina.connector.ResponseFacade.flushBuffer(ResponseFacade.java:318) [catalina.jar:8.0.43]
	at javax.servlet.ServletResponseWrapper.flushBuffer(ServletResponseWrapper.java:176) [servlet-api.jar:3.1.FR]
	at org.springframework.http.server.ServletServerHttpResponse.flush(ServletServerHttpResponse.java:95) [spring-web-4.2.8.RELEASE.jar:4.2.8.RELEASE]
	at org.springframework.web.socket.sockjs.transport.session.AbstractHttpSockJsSession.writeFrameInternal(AbstractHttpSockJsSession.java:355) [spring-websocket-4.2.8.RELEASE.jar:4.2.8.RELEASE]
	at org.springframework.web.socket.sockjs.transport.session.AbstractSockJsSession.writeFrame(AbstractSockJsSession.java:321) [spring-websocket-4.2.8.RELEASE.jar:4.2.8.RELEASE]
	at org.springframework.web.socket.sockjs.transport.session.AbstractSockJsSession.sendHeartbeat(AbstractSockJsSession.java:254) [spring-websocket-4.2.8.RELEASE.jar:4.2.8.RELEASE]
	at org.springframework.web.socket.sockjs.transport.session.AbstractSockJsSession$HeartbeatTask.run(AbstractSockJsSession.java:450) [spring-websocket-4.2.8.RELEASE.jar:4.2.8.RELEASE]
	at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54) [spring-context-4.2.8.RELEASE.jar:4.2.8.RELEASE]
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [na:1.8.0_121]
	at java.util.concurrent.FutureTask.run(FutureTask.java:266) [na:1.8.0_121]
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180) [na:1.8.0_121]
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) [na:1.8.0_121]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_121]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_121]
	at java.lang.Thread.run(Thread.java:745) [na:1.8.0_121]
21:31:16.794 [SockJsScheduler-1] ERROR o.s.s.s.TaskUtils$LoggingErrorHandler - Unexpected error occurred in scheduled task.
org.springframework.web.socket.sockjs.SockJsTransportFailureException: Failed to write SockJsFrame content=''h''; nested exception is org.apache.catalina.connector.ClientAbortException: java.io.IOException: APR error: -730054
	at org.springframework.web.socket.sockjs.transport.session.AbstractSockJsSession.writeFrame(AbstractSockJsSession.java:338) ~[spring-websocket-4.2.8.RELEASE.jar:4.2.8.RELEASE]
	at org.springframework.web.socket.sockjs.transport.session.AbstractSockJsSession.sendHeartbeat(AbstractSockJsSession.java:254) ~[spring-websocket-4.2.8.RELEASE.jar:4.2.8.RELEASE]
	at org.springframework.web.socket.sockjs.transport.session.AbstractSockJsSession$HeartbeatTask.run(AbstractSockJsSession.java:450) ~[spring-websocket-4.2.8.RELEASE.jar:4.2.8.RELEASE]
	at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54) ~[spring-context-4.2.8.RELEASE.jar:4.2.8.RELEASE]
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [na:1.8.0_121]
	at java.util.concurrent.FutureTask.run(FutureTask.java:266) [na:1.8.0_121]
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180) [na:1.8.0_121]
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) [na:1.8.0_121]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_121]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_121]
	at java.lang.Thread.run(Thread.java:745) [na:1.8.0_121]
Caused by: org.apache.catalina.connector.ClientAbortException: java.io.IOException: APR error: -730054
	at org.apache.catalina.connector.OutputBuffer.doFlush(OutputBuffer.java:356) ~[catalina.jar:8.0.43]
	at org.apache.catalina.connector.OutputBuffer.flush(OutputBuffer.java:320) ~[catalina.jar:8.0.43]
	at org.apache.catalina.connector.Response.flushBuffer(Response.java:496) ~[catalina.jar:8.0.43]
	at org.apache.catalina.connector.ResponseFacade.flushBuffer(ResponseFacade.java:318) ~[catalina.jar:8.0.43]
	at javax.servlet.ServletResponseWrapper.flushBuffer(ServletResponseWrapper.java:176) ~[servlet-api.jar:3.1.FR]
	at org.springframework.http.server.ServletServerHttpResponse.flush(ServletServerHttpResponse.java:95) ~[spring-web-4.2.8.RELEASE.jar:4.2.8.RELEASE]
	at org.springframework.web.socket.sockjs.transport.session.AbstractHttpSockJsSession.writeFrameInternal(AbstractHttpSockJsSession.java:355) ~[spring-websocket-4.2.8.RELEASE.jar:4.2.8.RELEASE]
	at org.springframework.web.socket.sockjs.transport.session.AbstractSockJsSession.writeFrame(AbstractSockJsSession.java:321) ~[spring-websocket-4.2.8.RELEASE.jar:4.2.8.RELEASE]
	... 10 common frames omitted
Caused by: java.io.IOException: APR error: -730054
	at org.apache.coyote.http11.InternalAprOutputBuffer.writeToSocket(InternalAprOutputBuffer.java:292) ~[tomcat-coyote.jar:8.0.43]
	at org.apache.coyote.http11.InternalAprOutputBuffer.writeToSocket(InternalAprOutputBuffer.java:245) ~[tomcat-coyote.jar:8.0.43]
	at org.apache.coyote.http11.InternalAprOutputBuffer.flushBuffer(InternalAprOutputBuffer.java:214) ~[tomcat-coyote.jar:8.0.43]
	at org.apache.coyote.http11.AbstractOutputBuffer.flush(AbstractOutputBuffer.java:305) ~[tomcat-coyote.jar:8.0.43]
	at org.apache.coyote.http11.AbstractHttp11Processor.action(AbstractHttp11Processor.java:792) ~[tomcat-coyote.jar:8.0.43]
	at org.apache.coyote.Response.action(Response.java:177) ~[tomcat-coyote.jar:8.0.43]
	at org.apache.catalina.connector.OutputBuffer.doFlush(OutputBuffer.java:352) ~[catalina.jar:8.0.43]
	... 17 common frames omitted

需要在修改tomcat配置文件:

<Connector port="33011" protocol="org.apache.coyote.http11.Http11NioProtocol"
           connectionTimeout="20000" maxThreads="1000" URIEncoding="UTF-8"  keepAliveTimeout="0"/>

====================================

关于keepAliveTimeout="0":

举例

  1. 某日,你的人品爆发,网站突然来了 1000 个人,那么 tomcat 将消耗 1000 个连接去处理这些请求

  2. 当请求返回后,这 1000 个连接并没有马上销毁,而是全部处于 wait 状态

  3. 这时你的网站又来了 1000 个人(人品持续爆发),但之前那 1000 个连接是无法接受新的请求的(全部处于 wait 状态),所以你现在只有 24 个连接可以使用,so,其他 976 人陷入了漫长的等待中。。。。

  4. 5分钟后,最初的 1000 个连接被逐渐释放了,这时你又有 1000 个连接可以使用了

  5. 无奈那些陷入等待的 976 人已经走的差不多了,就只剩下了 3 个,但是值得高兴的是,因为有了可用连接,他们都可以愉快的连接到你的网站,快乐的玩耍了

故事讲到这里我想你也明白了是什么导致的问题,是因为那些处于 wait 的连接太多,导致可用连接应接不暇。那么,如何优化,答案很简单,禁用 wait ,让连接在返回后立马关闭,成为一个可用连接。

以 tomcat 为例

<Connector port="33011" protocol="org.apache.coyote.http11.Http11NioProtocol"
           connectionTimeout="20000" maxThreads="1000" URIEncoding="UTF-8" keepAliveTimeout="0" />

注意 keepAliveTimeout="0" 即为关键所在,其他 server 都会有相关配置,有兴趣的自己去翻翻文档。

 

官场书屋二维码

000
评论