目录
HTTP属于 TCP/IP模型中的应用层协议,HTTP长连接和HTTP短连接,指的是传输层的TCP连接是否被多次使用。
一般来说,用户通过浏览器输入URL回车,浏览器会通过DNS解析域名得到服务器的IP地址,然后通过解析出来的 IP和URL中的端口(默认为 80)发起建立 TCP连接请求,通过三次握手之后,建立TCP连接。
默认情况下,HTTP的1.0版本协议中,HTTP在每次请求结束后都会主动释放TCP连 接,因此HTTP连接是一种“短连接”。客户端与服务端通过HTTP短连接的交互过程,具体如下图所示。

在高并发场景使用HTTP“短连接”通信,会出现两个问题:
(1)性能较差:传输层的TCP连接不会复用,每一次请求,都需要建立和拆除一次 TCP连接,也即是说,每次请求均需要TCP三次握手建立连接,TCP四次挥手关闭连接,性 能较差。
(2)很容易出现端口被占满:在主动断开方,系统会出现大量的TIME_WAIT状态的 TCP连接,只有等2个MSL后,TCP连接才会进行关闭掉,在高并发场景中,如果服务器主动断开连接,则很容易发生端口耗尽。当然,如果连接被设置了SO_RESUSEADDR特性,其端口可能被其他连接复用,尽管如此,还是会存在不少的约束条件影响到端口复用。出于以上两个原因,在高并发场景使用HTTP“短连接”进行通信肯定是不行的。
HTTP长连接,也叫HTTP持久连接,指的是TCP连接建立后,该传输层连接不再进行释放,供应用层反复使用。客户端与服务端通过HTTP长连接的交互过程,具体如下图所示:

HTTP长连接的特点是:
(1)性能较高,不需要重复建立TCP连接或者关闭TCP连接;
(2)TCP数据传输连接基本上不会出现CLOSE_WAIT和TIME_WAIT的问题,系统资源的使用效率会大大提升。 HTTP长连接也有缺点:一般需要一个连接池来对可供复用的TCP长连接进行管理和监测。常见的数据库连接池、HTTP连接池,本质上都属于TCP连接池。
HTTP/1.1默认使用长连接而不是短连接,除非显式关闭TCP连接。如果要显式关闭连 接,需要在HTTP报文首部加上“Connection:Close”请求头,也就是说在HTTP/1.1协议 中,默认情况下,所有的TCP连接都可以进行复用的。
当然,不发送“Connection:Close”请求头,不意味着服务器承诺TCP连接永远保持打开。空闲的TCP连接也可以被客户端与服务端关闭。
本节对主流的反向代理服务器Nginx和应用服务器Tomcat的服务端长连接配置进行介绍。
生产环境所用的Java应用服务器不一定是Tomcat,可能是JBoss、Jetty或者其他的应用 服务器。无论使用哪一种服务器,其HTTP长连接配置的原理是类似的,所以,这里以 Tomcat为例进行应用服务器的长连接配置介绍。服务器端Tomcat的长连接配置,主要分为两种场景:
(1)独立部署的Tomcat
在传统的Nginx+Tomcat架构的Web应用中,一般使用独立部署的Tomcat作为Web服务器。
(2)内嵌部署的Tomcat
在目前主流的Spring Boot应用,一般使用内嵌的Tomcat作为Web服务器。
以上两种细分场景的Tomcat使用,具体如下图所示:

图:Tomcat使用的两种细分场景
1. 独立部署 Tomcat 的长连接配置
针对于细分场景一中的独立部署Tomcat,其长连接配置是通过修改Tomcat配置文件中 Connector(连接器)的配置完成的。一个使用HTTP长连接的Connector连接器的配置示例大致如下(Tomcat版本假定8.0或以上):
<Connector port="8080" redirectPort="8443"
protocol="org.apache.coyote.http11.Http11NioProtocol"
connectionTimeout="20000"
URIEncoding="UTF-8"
keepAliveTimeout="15000"
maxKeepAliveRequests="-1"
maxConnections="3000"
maxThreads="1000"
maxIdleTime="300000"
minSpareThreads="200"
acceptCount=”100″
enableLookups="false" />
对以上配置示例中用到的三个长连接配置选项,介绍如下:
(1)keepAliveTimeout
此选项为TCP连接保持时长,单位为毫秒。表示在下次请求过来之前,该连接将被Tomcat保持多久。在keepAliveTimeout时间范围内,假如客户端不断有新的请求过来,则该连接将一直被保持。KeepAliveTimeout选项决定一个不活跃的连接能保持多少时间。
(2)maxKeepAliveRequests
此选项表示长连接最大支持的请求数。超过该请求数的连接将被关闭,关闭的时候 Tomcat会返回一个带“Connection: close”响应头的给客户端。 当maxKeepAliveRequests的值为-1时,表示没有最大请求数限制;如果其值被设置为 1,将会禁用掉HTTP长连接。
默认情况下Tomcat是使用长连接的,如果要关闭长连接,只要将maxKeepAliveRequests设置为1即可。
(3)maxConnections
Tomcat在任意时刻能接收和处理的最大连接数。如果其值被设置为-1,则连接数不受限制。由于Linux的内核默认限制了单进程最大打开文件句柄数为1024,因此,如果此配置 项的值超过1024,则相应的需要对Linux系统的单进程最大打开文件句柄数限制进行修改。
以上是对Tomcat的HTTP长连接配置选项的介绍。总的来说,使用长连接能提高服务性 能,不过,如果使用不当,也会带来一些不利的结果。
使用长连接意味着,一个TCP连接在当前请求结束后,如果没有新的请求到来, Socket连接不会立马释放,而是等keepAliveTimeout到期之后才被释放,如果一个高负载的 Tomcat服务器建立的很多长连接,将无法继续建立新的连接,无法为新的客户端提供服 务。所以,对于Tomcat长连接的配置需要慎重,错误的参数可能导致严重的性能问题,需要根据具体的负载,配置合适的KeepAliveTimeout和MaxKeepAliveRequests的选项值。
2. 内嵌式部署 Tomcat 的长连接配置
针对于细分场景二中的内嵌式Tomcat,其长连接配置可以通过一个自动配置类完成。 在自动配置类中,可以配置一个TomcatServletWebServerFactory容器工厂Bean实例, SpringBoot将通过该工厂实例,在运行时获取内嵌式Tomcat容器实例。在容器工厂配置代 码中,可以对Tomcat的Connector的三个长连接相关属性进行具体的配置。
一段简单的定制化TomcatServletWebServerFactory容器工厂的配置代码大致如下:
package com.crazymaker.springcloud.standard.config;
//....省略 import
@Configuration
@ConditionalOnClass({Connector.class})
public class TomcatConfig<HttpConnectionProperties> {
@Autowired
private HttpConnectionProperties httpConnectionProperties;
@Bean
public TomcatServletWebServerFactory
createEmbeddedServletContainerFactory() {
TomcatServletWebServerFactory tomcatFactory =
new TomcatServletWebServerFactory();
//增加连接器的定制配置
tomcatFactory.addConnectorCustomizers(connector -> {
Http11NioProtocol protocol =
(Http11NioProtocol) connector.getProtocolHandler();
// 定制 keepAliveTimeout,确定下次请求过来之前 Socket 连接保持多久
// 设置 600 秒内没有请求则服务端自动断开 socket 连接
protocol.setKeepAliveTimeout(600000);
// 当客户端发送超过 10000 个请求,强制关闭掉 socket 连接
protocol.setMaxKeepAliveRequests(1000);
//设置最大连接数
protocol.setMaxConnections(3000);
//...省略其他配置
});
return tomcatFactory;
}
}
以上示例是SpringBoot2.0.8中的内嵌式Tomcat长连接配置,具体的三个配置选项的语义和独立Tomcat的配置是相同的,仅仅是形式上的不同。
无论在传统的Nginx+Tomcat架构中,还是在目前主流的Nginx+SpringCloud架构中,反 向代理Nginx都承担了两种角色:对于下游客户端来说Nginx承担了服务端角色,对于上游 的WEB服务来说Nginx承担了客户端角色。Nginx承担的两种角色,具体如下图所示:

Nginx承担服务端角色时的长连接,主要通过keepalive_timeout和keepalive_requests两个指令完成相关设置。一段简单的Nginx承担服务端角色时的长连接配置代码,大致如下
#...
http{
include mime.types;
default_type application/octet-stream;
#长连接保持时长
keepalive_timeout 65s;
#长连接最大处理请求数
keepalive_requests 1000;
#...
server{
listen 80;
server_name openresty localhost;
#长连接保持时长
keepalive_timeout 10s;
#长连接最大处理请求数
keepalive_requests 10;
location/{
root html;
index index.html index.htm;
}
#...
}
}
对以上配置代码中涉及的两个长连接相关指令,具体介绍如下:
(1) keepalive_requests
此指令设置同一个长连接可以处理的最大请求数,请求数超过此值,长连接将关闭。 其格式如下:
语法:keepalive_requests number
默认值:keepalive_requests 100
上下文:http、server、location
keepalive_requests指令用于设置一个长连接上可以服务的最大请求数量,当最大请求 数量达到时,长连接将被关闭,Nginx中其默认值是100。一个长连接建立之后,Nginx就会为这个连接设置一个计数器,记录这个长连接上已经接收并处理的客户端请求的数量。如果达到这个参数设置的最大值时,则Nginx会强行关闭这个长连接,逼迫客户端不得不重新建立新的长连接。
(2)keepalive_timeout
此指令用于设置长连接的空闲保持时长,表示在下次请求过来之前,该连接将被Nginx 保持多久。在keepalive_timeout时间范围内,假如客户端不断有新的请求过来,则该连接将 一直被保持。
语法:keepalive_timeout timeout [header_timeout];
默认值:keepalive_timeout 60s;
上下文:http、server、location
keepalive_timeout指令的第一个参数,用于设置客户端的长连接在服务器端保持的最长 时间(默认60秒),如果值设置为0,会禁用HTTP长连接。对于一些并发量较高的内部服务器通讯的场景,其值可以适当加大,比如增加到120秒甚至300秒。
keepalive_timeout指令的第二个参数,是一个可选参数,其作用为HTTP响应报文增加 一个“Keep-Alive: timeout=time”头部选项,用于告知客户端长连接的保持时间,通常可 以不用设置。该响应头可以被Mozilla 浏览器识别和处理,Mozilla浏览器会在timeout空闲 时间之后,关闭TCP长连接;而MSIE浏览器则在大约60秒后会关闭长连接。
在进行服务端长连接设置时,keepalive_timeout和keepalive_requests的值,并不是越大越好,而是要根据具体场景而定。
场景一:单个客户端的 HTTP 请求数较少时
比如在客户端是普通用户时,客户端是网页浏览器,当用户通过浏览器在访问服务端 时,其单个用户的请求数是比较有限的,1分钟之内所发出的请求数之多在百位数左右。在这种场景下,如果Nginx的服务端长连接设置如下:
#长连接保持时长
keepalive_timeout 65s;
#长连接最大处理请求数
keepalive_requests 1000;
上述设置会导致大量的长连接由于请求数达不到1000,一直在空闲等待,需要等到65秒结束 之后才被关闭,造成服务器资源的浪费。所以,需要减少长连接最大处理请求数和长连接 保持时长,初步优化后的配置大致如下:
#长连接保持时长
keepalive_timeout 10s;
#长连接最大处理请求数
keepalive_requests 100;
但是,如果配置得极端,将长连接最大处理请求数减小得太多,可能会导致另外的问 题。比如,将长连接最大处理请求数减到10,其配置如下:
#长连接保持时长
keepalive_timeout 10s;
#长连接最大处理请求数
keepalive_requests 10;
当QPS=10000时,假定一共100个用户,单个客户端每秒发出100个请求。由于以上配 置中每个连接只能最多处理10次请求,单个客户端每秒发出100个请求相当于每个用户需要10个连接,在总体100个用户的情况下,意味着平均每秒钟就会有1000个长连接将被Nginx主动关闭。在这个情况下,了解前面介绍的TCP连接四次挥手知识的读者就会知道,服务端Nginx就会有大量的TIME_WAIT的Socket连接。
所以,keepalive_requests的值,不能比单个客户端在keepalive_timeout时间范围的实际请求数少太多,如果少太多,在QPS较高的场景,会出现大量连接被服务端主动关闭而出现大量TIME_WAIT连接。
当然,keepalive_requests的值,也不能比单个客户端在keepalive_timeout时间范围实际请求数多太多,这样会导致大量的TCP长连接出现空闲等待。
总体而言,keepalive_requests的值与单客户端在keepalive_timeout时间范围的实际请求 数量,要做到基本的匹配。
场景二:单个客户端的请求数较多时
比如在客户端不是普通用户,而是下游的代理服务器。在这种场景下,客户端数量是 很少的,而单个客户端与服务器之间的请求数是非常多的。
这种场景的设置比较简单,可以尽可能的对长连接进行复用,keepalive_requests值可 以设置偏大,示例的配置如下:
#长连接保持时长
keepalive_timeout 65s;
#长连接最大处理请求数
keepalive_requests 100000;
当然,在此场景中,选项keepalive_timeout可以配置一个较大的值。但是,对于Nginx 来说,不能对单个连接的处理请求数不做限制,必须定期关闭连接,才能释放每个连接的所分配的内存。由于使用过大请求数可能会导致内存占用过度,因此不建议为 keepalive_requests设置太大的值,当然更不能不做keepalive_requests设置。
无论是Nginx、Tomcat还是其他的服务器,有关服务端长连接的设置,其原理是类似 的,仅仅是具体参数的命名规则不同,或者是配置形式稍微有点不同。
参考资料:《Java高并发核心编程 卷1:NIO、Netty、Redis、ZooKeeper》
是的,我知道最好使用webmock,但我想知道如何在RSpec中模拟此方法:defmethod_to_testurl=URI.parseurireq=Net::HTTP::Post.newurl.pathres=Net::HTTP.start(url.host,url.port)do|http|http.requestreq,foo:1endresend这是RSpec:let(:uri){'http://example.com'}specify'HTTPcall'dohttp=mock:httpNet::HTTP.stub!(:start).and_yieldhttphttp.shou
我正在使用Sequel构建一个愿望list系统。我有一个wishlists和itemstable和一个items_wishlists连接表(该名称是续集选择的名称)。items_wishlists表还有一个用于facebookid的额外列(因此我可以存储opengraph操作),这是一个NOTNULL列。我还有Wishlist和Item具有续集many_to_many关联的模型已建立。Wishlist类也有:selectmany_to_many关联的选项设置为select:[:items.*,:items_wishlists__facebook_action_id].有没有一种方法可以
我使用的是Firefox版本36.0.1和Selenium-Webdrivergem版本2.45.0。我能够创建Firefox实例,但无法使用脚本继续进行进一步的操作无法在60秒内获得稳定的Firefox连接(127.0.0.1:7055)错误。有人能帮帮我吗? 最佳答案 我遇到了同样的问题。降级到firefoxv33后一切正常。您可以找到旧版本here 关于ruby-无法在60秒内获得稳定的Firefox连接(127.0.0.1:7055),我们在StackOverflow上找到一个类
我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur
1.错误信息:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:requestcanceledwhilewaitingforconnection(Client.Timeoutexceededwhileawaitingheaders)或者:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:TLShandshaketimeout2.报错原因:docker使用的镜像网址默认为国外,下载容易超时,需要修改成国内镜像地址(首先阿里
Rails中有没有一种方法可以提取与路由关联的HTTP动词?例如,给定这样的路线:将“users”匹配到:“users#show”,通过:[:get,:post]我能实现这样的目标吗?users_path.respond_to?(:get)(显然#respond_to不是正确的方法)我最接近的是通过执行以下操作,但它似乎并不令人满意。Rails.application.routes.routes.named_routes["users"].constraints[:request_method]#=>/^GET$/对于上下文,我有一个设置cookie然后执行redirect_to:ba
我正在使用Heroku(heroku.com)来部署我的Rails应用程序,并且正在构建一个iPhone客户端来与之交互。我的目的是将手机的唯一设备标识符作为HTTPheader传递给应用程序以进行身份验证。当我在本地测试时,我的header通过得很好,但在Heroku上它似乎去掉了我的自定义header。我用ruby脚本验证:url=URI.parse('http://#{myapp}.heroku.com/')#url=URI.parse('http://localhost:3000/')req=Net::HTTP::Post.new(url.path)#boguspara
require"socket"server="irc.rizon.net"port="6667"nick="RubyIRCBot"channel="#0x40"s=TCPSocket.open(server,port)s.print("USERTesting",0)s.print("NICK#{nick}",0)s.print("JOIN#{channel}",0)这个IRC机器人没有连接到IRC服务器,我做错了什么? 最佳答案 失败并显示此消息::irc.shakeababy.net461*USER:Notenoughparame
我试图在我的网站上实现使用Facebook登录功能,但在尝试从Facebook取回访问token时遇到障碍。这是我的代码:ifparams[:error_reason]=="user_denied"thenflash[:error]="TologinwithFacebook,youmustclick'Allow'toletthesiteaccessyourinformation"redirect_to:loginelsifparams[:code]thentoken_uri=URI.parse("https://graph.facebook.com/oauth/access_token
考虑一下:现在这些情况:#output:http://domain.com/?foo=1&bar=2#output:http://domain.com/?foo=1&bar=2#output:http://domain.com/?foo=1&bar=2#output:http://domain.com/?foo=1&bar=2我需要用其他字符串输出URL。我如何保证&符号不会被转义?由于我无法控制的原因,我无法发送&。求助!把我的头发拉到这里:\编辑:为了澄清,我实际上有一个像这样的数组:@images=[{:id=>"fooid",:url=>"http://