【网络】长连接、Socket、tomcat四个容器

socket与长连接短连接

一、长连接/短连接

①TCP连接

要想明白Socket连接,先要明白TCP连接。手机能够使用联网功能是因为手机底层实现了TCP/IP协议,可以使手机终端通过无线网络建立TCP连接。TCP协议可以对上层网络提供接口,使上层网络数据的传输建立在“无差别”的网络之上

建立起一个TCP连接需要经过“三次握手”:

  • 第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
  • 第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
  • 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。

握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。断开连接时服务器和客户端均可以主动发起断开TCP连接的请求,断开过程需要经过“四次握手”(过程就不细写了,就是服务器和客户端交互,最终确定断开)

②HTTP连接

HTTP协议即超文本传送协议(Hypertext Transfer Protocol ),是Web联网的基础,也是手机联网常用的协议之一,HTTP协议是建立在TCP协议之上的一种应用。

HTTP连接最显著的特点是客户端发送的每次请求都需要服务器回送响应,在请求结束后,会主动释放连接。从建立连接到关闭连接的过程称为“一次连接”。

  • 1)在HTTP 1.0中,客户端的每次请求都要求建立一次单独的连接,在处理完本次请求后,就自动释放连接。
  • 2)在HTTP 1.1中则可以在一次连接中处理多个请求,并且多个请求可以重叠进行,不需要等待一个请求结束后再发送下一个请求。尽管HTTP1.1开始支持持久连接,但仍无法保证始终连接。而Socket连接一旦建立TCP三次握手,除非一方主动断开,否则连接状态一直保持。

由于HTTP在每次请求结束后都会主动释放连接,因此HTTP连接是一种“短连接”,要保持客户端程序的在线状态,需要不断地向服务器发起连接请求。通常的做法是即时不需要获得任何数据,客户端也保持每隔一段固定的时间向服务器发送一次“保持连接”的请求,服务器在收到该请求后对客户端进行回复,表明知道客户端“在线”。若服务器长时间无法收到客户端的请求,则认为客户端“下线”,若客户端长时间无法收到服务器的回复,则认为网络已经断开。

二、Socket

HTTP协议:简单对象访问协议,对应于应用层 ,HTTP协议是基于TCP连接的

TCP协议: 对应于传输层

IP协议: 对应于网络层 TCP/IP是传输层协议,主要解决数据如何在网络中传输;而HTTP是应用层协议,主要解决如何包装数据。

在这里插入图片描述

Socket是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API),通过Socket,我们才能使用TCP/IP协议。

  • HTTP连接:http连接就是所谓的短连接,即客户端向服务器端发送一次请求,服务器端响应后连接即会断掉;
  • socket连接:socket连接就是所谓的长连接,理论上客户端和服务器端一旦建立起连接将不会主动断掉;但是由于各种环境因素可能会是连接断开,比如说:服务器端或客户端主机down了,网络故障,或者两者之间长时间没有数据传输,网络防火墙可能会断开该连接以释放网络资源。所以当一个socket连接中没有数据的传输,那么为了维持连接需要发送心跳消息~~具体心跳消息格式是开发者自己定义的

3.1套接字(socket)概念

套接字(socket)是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议、本地主机的IP地址、本地进程的协议端口、远地主机的IP地址、远地进程的协议端口。

现在我们了解到TCP/IP只是一个协议栈,就像操作系统的运行机制一样,必须要具体实现,同时还要提供对外的操作接口。就像操作系统会提供标准的编程接口,比如Win32编程接口一样,TCP/IP也必须对外提供编程接口,这就是Socket。现在我们知道,Socket跟TCP/IP并没有必然的联系。Socket编程接口在设计的时候,就希望也能适应其他的网络协议。所以,Socket的出现只是可以更方便的使用TCP/IP协议栈而已,其对TCP/IP进行了抽象,形成了几个最基本的函数接口。比如create,listen,accept,connect,read和write等等,不同语言都有对应的建立Socket服务端和客户端的库。

https://blog.csdn.net/mccand1234/article/details/91346202

长连接:

所谓长连接,指在一个TCP连接上可以连续发送多个数据包,在TCP连接保持期间,如果没有数据包发送,需要双方发检测包以维持此连接(心跳包),一般需要自己做在线维持。 短连接是指通信双方有数据交互时,就建立一个TCP连接,数据发送完成后,则断开此TCP连接。比如Http的,只是连接、请求、关闭,过程时间较短,服务器若是一段时间内没有收到请求即可关闭连接。其实长连接是相对于通常的短连接而说的,也就是长时间保持客户端与服务端的连接状态。

  • 通常的短连接操作步骤是:连接→数据传输→关闭连接;
  • 而长连接通常就是:连接→数据传输→保持连接(心跳)→数据传输→保持连接(心跳)→……→关闭连接;

什么时候用长连接,短连接:

  • 长连接多用于操作频繁,点对点的通讯,而且连接数不能太多情况,。每个TCP连接都需要三步握手,这需要时间,如果每个操作都是先连接,再操作的话那么处理 速度会降低很多,所以每个操作完后都不断开,次处理时直接发送数据包就OK了,不用建立TCP连接。例如:数据库的连接用长连接, 如果用短连接频繁的通信会造成Socket错误,而且频繁的Socket创建也是对资源的浪费。

HTTP连接与Socket连接的区别:

  • HTTP是短连接,Socket(基于TCP协议的)是长连接。尽管HTTP1.1开始支持持久连接,但仍无法保证始终连接。而Socket连接一旦建立TCP三次握手,除非一方主动断开,否则连接状态一直保持。
  • HTTP连接服务端无法主动发消息,Socket连接双方请求的发送先后限制。这点就比较重要了,因为它将决定二者分别适合应用在什么场景下。HTTP采用“请求-响应”机制,在客户端还没发送消息给服务端前,服务端无法推送消息给客户端。必须满足客户端发送消息在前,服务端回复在后。Socket连接双方类似peer2peer的关系,一方随时可以向另一方喊话。

3.2 建立socket连接

建立Socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket,另一个运行于服务器端,称为ServerSocket

套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。

  • 服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。
  • 客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。
  • 连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。

*4、SOCKET连接与TCP连接*

创建Socket连接时,可以指定使用的传输层协议,Socket可以支持不同的传输层协议(TCP或UDP),当使用TCP协议进行连接时,该Socket连接就是一个TCP连接(可以对应多个)。

*5、Socket连接与HTTP连接*

由于通常情况下Socket连接就是TCP连接,因此Socket连接一旦建立,通信双方即可开始相互发送数据内容,直到双方连接断开。但在实际网络应用中,客户端到服务器之间的通信往往需要穿越多个中间节点,例如路由器、网关、防火墙等,大部分防火墙默认会关闭长时间处于非活跃状态的连接而导致 Socket 连接断连,因此需要通过轮询告诉网络,该连接处于活跃状态。

而HTTP连接使用的是“请求—响应”的方式,不仅在请求时需要先建立连接,而且需要客户端向服务器发出请求后,服务器端才能回复数据。

很多情况下,需要服务器端主动向客户端推送数据,保持客户端与服务器数据的实时与同步。此时若双方建立的是Socket连接,服务器就可以直接将数据传送给客户端;若双方建立的是HTTP连接,则服务器需要等到客户端发送一次请求后才能将数据传回给客户端,因此,客户端定时向服务器端发送连接请求,不仅可以保持在线,同时也是在“询问”服务器是否有新的数据,如果有就将数据传给客户端。

socket
我们知道两个进程如果需要进行通讯最基本的一个前提能能够唯一的标示一个进程,在本地进程通讯中我们可以使用PID来唯一标示一个进程,但PID只在本地唯一,网络中的两个进程PID冲突几率很大,这时候我们需要另辟它径了,我们知道IP层的ip地址可以唯一标示主机,而TCP层协议和端口号可以唯一标示主机的一个进程,这样我们可以利用ip地址+协议+端口号唯一标示网络中的一个进程。

能够唯一标示网络中的进程后,它们就可以利用socket进行通信了,什么是socket呢?我们经常把socket翻译为套接字,socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。

socket起源于UNIX,在Unix一切皆文件哲学的思想下,socket是一种”打开—读/写—关闭”模式的实现,服务器和客户端各自维护一个”文件”,在建立连接打开后,可以向自己文件写入内容供对方读取或者读取对方内容,通讯结束时关闭文件。

socket通信流程:

socket是”打开—读/写—关闭”模式的实现,以使用TCP协议通讯的socket为例,其交互流程大概是这样子的

  • 服务器根据地址类型(ipv4,ipv6)、socket类型、协议创建socket
  • 服务器为socket绑定ip地址和端口号
  • 服务器socket监听端口号请求,随时准备接收客户端发来的连接,这时候服务器的socket并没有被打开
  • 客户端创建socket
  • 客户端打开socket,根据服务器ip地址和端口号试图连接服务器socket
  • 服务器socket接收到客户端socket请求,被动打开,开始接收客户端请求,直到客户端返回连接信息。这时候socket进入阻塞状态,所谓阻塞即accept()方法一直到客户端返回连接信息后才返回,开始接收下一个客户端谅解请求
  • 客户端连接成功,向服务器发送连接状态信息
  • 服务器accept方法返回,连接成功
  • 客户端向socket写入信息
  • 服务器读取信息
  • 客户端关闭
  • 服务器端关闭

浏览器对同一 Host 建立 TCP 连接到数量有没有限制?

假设我们还处在 HTTP/1.1 时代,那个时候没有多路传输,当浏览器拿到一个有几十张图片的网页该怎么办呢?肯定不能只开一个 TCP 连接顺序下载,那样用户肯定等的很难受,但是如果每个图片都开一个 TCP 连接发 HTTP 请求,那电脑或者服务器都可能受不了,要是有 1000 张图片的话总不能开 1000 个TCP 连接吧,你的电脑同意 NAT 也不一定会同意。

所以答案是:有。Chrome最多允许对同一个 Host 建立六个 TCP 连接。不同的浏览器有一些区别。

收到的 HTML 如果包含几十个图片标签,这些图片是以什么方式、什么顺序、建立了多少连接、使用什么协议被下载下来的呢?

如果图片都是 HTTPS 连接并且在同一个域名下,那么浏览器在 SSL 握手之后会和服务器商量能不能用 HTTP2,如果能的话就使用Multiplexing 功能在这个连接上进行多路传输。不过也未必会所有挂在这个域名的资源都会使用一个 TCP 连接去获取,但是可以确定的是 Multiplexing 很可能会被用到。

如果发现用不了 HTTP2 呢?或者用不了 HTTPS(现实中的 HTTP2 都是在 HTTPS 上实现的,所以也就是只能使用 HTTP/1.1)。那浏览器就会在一个 HOST 上建立多个 TCP 连接,连接数量的最大限制取决于浏览器设置,这些连接会在空闲的时候被浏览器用来发送新的请求,如果所有的连接都正在发送请求呢?那其他的请求就只能等等了。

Servlet

回忆下Servlet,我们定义类实现HttpServlet,放到tomcat中,即可拦截到请求。对应的req和resp实例是tomcat创建的

public class MyServlet extends HttpServlet{
    
    
    // 重写doGet方法
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp){
    
    
        resp.getWriter().println("hhh");
    }
}

HttpServlet源码如下,service方法会调用get或者post请求

public abstract class HttpServlet extends GenericServlet {
    
    
    // 传入的req和res的类型是ServletRequest的话,转换为HttpServletRequest
    @Override //
    public void service(ServletRequest req, ServletResponse res)
        throws ServletException, IOException {
    
    

        HttpServletRequest  request;
        HttpServletResponse response;

        try {
    
    
            request = (HttpServletRequest) req;
            response = (HttpServletResponse) res;
        } catch (ClassCastException e) {
    
    
            throw new ServletException(lStrings.getString("http.non_http"));
        }
        service(request, response);
    }

    // service方法根据req中的method属性判断是接着往下调用get还是post方法
    protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
    
    

        String method = req.getMethod();

        if (method.equals(METHOD_GET)) {
    
    
            long lastModified = getLastModified(req);
            if (lastModified == -1) {
    
    
                // servlet doesn't support if-modified-since, no reason
                // to go through further expensive logic
                doGet(req, resp);
            } else {
    
    
                long ifModifiedSince;
                try {
    
    
                    ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                } catch (IllegalArgumentException iae) {
    
    
                    // Invalid date header - proceed as if none was set
                    ifModifiedSince = -1;
                }
                if (ifModifiedSince < (lastModified / 1000 * 1000)) {
    
    
                    // If the servlet mod time is later, call doGet()
                    // Round down to the nearest second for a proper compare
                    // A ifModifiedSince of -1 will always be less
                    maybeSetLastModified(resp, lastModified);
                    doGet(req, resp);
                } else {
    
    
                    resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                }
            }

        } else if (method.equals(METHOD_HEAD)) {
    
    
            long lastModified = getLastModified(req);
            maybeSetLastModified(resp, lastModified);
            doHead(req, resp);

        } else if (method.equals(METHOD_POST)) {
    
    
            doPost(req, resp);

        } else if (method.equals(METHOD_PUT)) {
    
    
            doPut(req, resp);

        } else if (method.equals(METHOD_DELETE)) {
    
    
            doDelete(req, resp);

        } else if (method.equals(METHOD_OPTIONS)) {
    
    
            doOptions(req,resp);

        } else if (method.equals(METHOD_TRACE)) {
    
    
            doTrace(req,resp);

        } else {
    
    
            //
            // Note that this means NO servlet supports whatever
            // method was requested, anywhere on this server.
            //

            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[1];
            errArgs[0] = method;
            errMsg = MessageFormat.format(errMsg, errArgs);

            resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
        }
    }

Tomcat

tomcat 主要有 连接器 Connector 和 容器 两大部分,

  • 连接器主要负责建立和解析tcp 连接,具体的业务逻辑有容器处理。
  • Tomcat提供了engine,host,context及wrapper四种容器。这四种容器继承了一个容器基类,因此可以定制化。当然,tomcat也提供了标准实现。

四大容器

Tomcat是servlet容器,定义了Container接口,有4个接口继承了该接口,分别是

Context是servlet的list,Host也是。Engine也是,他们是3级父子标签。

  • Engine:tomcat中有实现类StandardEngineengine有四大组件:

    • Cluster: 实现tomcat集群,例如session共享等功能,通过配置server.xml可以实现,对其包含的所有host里的应用有效,该模块是可选的。其实现方式是基于pipeline+valve模式的,有时间会专门整理一个pipeline+valve模式应用系列;

    • Realm: 实现用户权限管理模块,例如用户登录,访问控制等,通过通过配置server.xml可以实现,对其包含的所有host里的应用有效,该模块是可选的;

    • Pipeline: 每个容器对象都有一个pipeline,它不是通过server.xml配置产生的,是必须有的。它就是容器对象实现逻辑操作的骨架,在pipeline上配置不同的valve,当需要调用此容器实现逻辑时,就会按照顺序将此pipeline上的所有valve调用一遍,这里可以参考责任链模式;

    • Valve: 实现具体业务逻辑单元。可以定制化valve(实现特定接口),然后配置在server.xml里。对其包含的所有host里的应用有效。定制化的valve是可选的,但是每个容器有一个缺省的valve,例如engine的StandardEngineValve,是在StandardEngine里自带的,它主要实现了对其子host对象的StandardHostValve的调用,以此类推。

  • Host:host是域名,匹配的域名,默认是localhost,可以隔离应用。可以用appBase指定替代webapps文件夹的文件夹。还有个热部署标签,默认开。tomcat中有实现类StandardHost

  • Context:Context应用,部署应用的方式:war,文件夹,server.xml,conf。war包解压后就是文件夹,文件夹下必须有WEB-INF/子文件夹,WEB-INF/classes存放class,servlet可以放到这里。类上注解@WebServlet可以指定拦截地址。所以我们得去找dispatcherServlet类。tomcat中有实现类StandardContext

    • 描述符的方式可以指定不放在webapps下的文件夹
    • 配置文件里有unpackWARs属性指定默认解压war包
    • Jar包里只有class文件,需要我们配置其他目录
    • War那个会负责遍历.war结尾的文件,不会管.jar
  • Wrapper:Wrapper是context下面的,httpServlet实现类只有一个实例,共用的话请求过来都调用同一个,会并发不安全。为了解决安全,可以实现singleThreadModel接口,单线程模型,此时每个请求都是一个实例。tomcat最多20实例。多出来的请求阻塞。重用会重新初始化。tomcat中有实现类StandardWrapper

    • 同一个context下可能有多个类型servlet实例,所以要用wrapper对servlet分类

Tomcat主类是org.apache.catalina.startup.Bootstrap

主类执行逻辑里会调用描述符部署deployDescriptore,war包deploywars部署,文件夹部署deployDirectoris的方法

四个容器的关系:阀门

Tomcat里还有个组件pipeline,他有个list属性walve阀门。模型像责任链一样。4大容器下面都有管道。比如记录返回日志的阀门,访问指定层次的容器就会经过阀门,可以实现RequestFilterWalve接口自定义阀门。

请求来了之后new Request,request.setHost,req.setContext,req.setWarpper,

比如 在engine级别处理完了之后就会调用getEngine.getPipeline.getFirstWalve.invoke(req)

最后一个servlet可能重用可能new

四大容器都有默认的阀门,负责继续调用下层的getEngine.getPipeline.getFirstWalve.invoke(req)类似的代码。

Wrapper的最后一个阀门交给servlet实例调用方法

class Tomcat{
    
    
    Connector connector;
    List<Servlet> servlet;
}
public class HostConfig implements LifecycleListener {
    
    
    protected void deployApps() {
    
    

        File appBase = host.getAppBaseFile();
        File configBase = host.getConfigBaseFile();
        String[] filteredAppPaths = filterAppPaths(appBase.list());
        // 部署xml,Deploy XML descriptors from configBase
        deployDescriptors(configBase, configBase.list());
        // 部署war包,Deploy WARs
        deployWARs(appBase, filteredAppPaths);
        // 部署文件夹,Deploy expanded folders
        deployDirectories(appBase, filteredAppPaths);

    }

数据,操作系统,通过socket过去数据,endpoint,解析数据,解析成req

tomcat参数

  • KeepAlive:KeepAlive是在HTTP1.1中定义的,用来保持客户机和服务器的长连接,通过减少建立TCP Session的次数来提高性能。常用的配置参数有{KeepAlive, KeepAliveTimeout, MaxKeepAliveRequests}。
  • KeepAliveTimeout:决定一个KeepAlive的连接能保持多少时间,Timeout就尽快shutdown链接,若还有数据必须再建立新的连接了;read()阻塞时间。长连接就是读完最后一次请求的20s后关闭socket
  • MaxKeepAliveRequests:服务多少个请求就shutdown关闭socket连接。
  • maxThreads:tomcat起动的最大线程数,即同时处理的任务个数,默认值为200
    • 每一次HTTP请求到达Web服务,tomcat都会创建一个线程来处理该请求,那么最大线程数决定了Web服务容器可以同时处理多少个请求。maxThreads默认200,肯定建议增加。但是,增加线程是有成本的,更多的线程,不仅仅会带来更多的线程上下文切换成本,而且意味着带来更多的内存消耗。JVM中默认情况下在创建新线程时会分配大小为1M的线程栈,所以,更多的线程异味着需要更多的内存。线程数的经验值为:1核2g内存为200,线程数经验值200;4核8g内存,线程数经验值800。
  • maxConnections:tomcat能够接受的最大连接数,maxConnections 默认值是10000。但是还有个参数acceptCount可以决定连接数的大小
    • 对于Java的阻塞式BIO,默认值是maxthreads的值;如果在BIO模式使用定制的Executor执行器,默认值将是执行器中maxthreads的值。
    • 对于Java 新的NIO模式,一个线程可以同时处理多个连接。
    • 对于windows上APR/native IO模式,maxConnections默认值为8192,这是出于性能原因,如果配置的值不是1024的倍数,maxConnections 的实际值将减少到1024的最大倍数。
    • 如果设置为-1,则禁用maxconnections功能,表示不限制tomcat容器的连接数。
    • maxConnections和accept-count的关系为:当连接数达到最大值maxConnections后,系统会继续接收连接,但不会超过acceptCount的值。
  • acceptCount:当tomcat起动的线程数达到最大时,接受排队的请求个数,默认值为100
    • 情况1:接受一个请求,此时tomcat起动的线程数没有到达maxThreads,tomcat会起动一个线程来处理此请求。
      情况2:接受一个请求,此时tomcat起动的线程数已经到达maxThreads,tomcat会把此请求放入等待队列,等待空闲线程。
      情况3:接受一个请求,此时tomcat起动的线程数已经到达maxThreads,等待队列中的请求个数也达到了acceptCount,此时tomcat会直接拒绝此次请求,返回connection refused
    • 原来tomcat最大连接数取决于maxConnections这个值加上acceptCount这个值,在连接数达到了maxConenctions之后,tomcat仍会保持住连接,但是不处理,等待其它请求处理完毕之后才会处理这个请求。

maxConnections、maxThreads、acceptCount关系

图解:maxConnections、maxThreads、acceptCount关系

用一个形象的比喻,通俗易懂的解释一下tomcat的最大线程数(maxThreads)、最大等待数(acceptCount)和最大连接数(maxConnections)三者之间的关系。

我们可以把tomcat比做一个火锅店,流程是取号、入座、叫服务员,可以做一下三个形象的类比:

(1)acceptCount 最大等待数
可以类比为火锅店的排号处能够容纳排号的最大数量;排号的数量不是无限制的,火锅店的排号到了一定数据量之后,服务往往会说:已经客满。
(2)maxConnections 最大连接数
可以类比为火锅店的大堂的餐桌数量,也就是可以就餐的桌数。如果所有的桌子都已经坐满,则表示餐厅已满,已经达到了服务的数量上线,不能再有顾客进入餐厅了。
(3)maxThreads:最大线程数
可以类比为厨师的个数。每一个厨师,在同一时刻,只能给一张餐桌炒菜,就像极了JVM中的一条线程。

整个就餐的流程,大致如下:
  • (1)取号:如果maxConnections连接数没有满,就不需要取号,因为还有空余的餐桌,直接被大堂服务员领上餐桌,点菜就餐即可。如果 maxConnections 连接数满了,但是取号人数没有达到 acceptCount,则取号成功。如果取号人数已达到acceptCount,则拿号失败,会得到Tomcat的Connection refused connect 的回复信息。
  • (2)上桌:如果有餐桌空出来了,表示maxConnections连接数没有满,排队的人,可以进入大堂上桌就餐。
  • (3)就餐:就餐需要厨师炒菜。厨师的数量,比顾客的数量,肯定会少一些。一个厨师一定需要给多张餐桌炒菜,如果就餐的人越多,厨师也会忙不过来。这时候就可以增加厨师,一增加到上限maxThreads的值,如果还是不够,只能是拖慢每一张餐桌的上菜速度,这种情况,就是大家常见的“上一道菜吃光了,下一道菜还没有上”尴尬场景。

maxConnections、maxThreads、acceptCount关系图如下

在这里插入图片描述

https://blog.csdn.net/quliuwuyiz/article/details/79979031

Tomcat源码

接收到socket之后,有线程池

SocketWrapper wrapper = new SocketWrapper(socket);
// SocketProcessor类实现了Runnable接口:new SocketProcessor(wrapper)
// Executor是线程池,以一个线程处理socket,一个socket连接对应一个线程
getExecutor().execute(new SocketProcessor(wrapper));
// 最大连接数并不是最大请求数,多个http请求对象一个线程。(长连接)
// 最大连接数和最大线程数
while(keepAlive){
    
    
    connection = get("connection");
    if(connection=="close"){
    
    
        keepAlive=false;
    }else if(connection=="keep-alive"){
    
    
        keeplive=true;
    }
    servlet处理;
    响应;
    // 每处理一个请求 // maxKeepAliveRequests  // 获取到socket连接后,只处理几个http请求
    if(socketWrapper.decrementKeepAlive()<=0){
    
    
        keepAlive=false;
    }
}
socket.close();
设置最大请求数是2,发送9个请求,会有6个socket
    12请求是同一个socket,1个socket只能有2个请求,而且是处理完了才处理第二个,处理完第二个就响应close,并一定肯定是共用第二个
    3新建socket
    为了更快获取到数据,会并发,并不会单纯复用socket
    浏览器最多支持6个socket连接

总结

tomcat能支持最大连接数由maxConnections加上acceptCount来决定。同时maxThreads如何设定?

以下部分结论引用自:http://duanfei.iteye.com/blog/1894387
一般的服务器操作都包括两方面:1计算(主要消耗cpu),2等待(io、数据库等)

  • 第一种极端情况,如果我们的操作是纯粹的计算,那么系统响应时间的主要限制就是cpu的运算能力,此时maxThreads应该尽量设的小,降低同一时间内争抢cpu的线程个数,可以提高计算效率,提高系统的整体处理能力。
  • 第二种极端情况,如果我们的操作纯粹是IO或者数据库,那么响应时间的主要限制就变为等待外部资源,此时maxThreads应该尽量设的大,这样 才能提高同时处理请求的个数,从而提高系统整体的处理能力。此情况下因为tomcat同时处理的请求量会比较大,所以需要关注一下tomcat的虚拟机内 存设置和linux的open file限制。

现实应用中,我们的操作都会包含以上两种类型(计算、等待),所以maxThreads的配置并没有一个最优值,一定要根据具体情况来配置。

最好的做法是:在不断测试的基础上,不断调整、优化,才能得到最合理的配置。

acceptCount的配置,我一般是设置的跟maxThreads一样大,这个值应该是主要根据应用的访问峰值与平均值来权衡配置的。

如果设的较小,可以保证接受的请求较快相应,但是超出的请求可能就直接被拒绝

如果设的较大,可能就会出现大量的请求超时的情况,因为我们系统的处理能力是一定的

调优

Linux查看连接数,并发数

软连接

ln -s /home/ictfmcg/data/photo /var/jtnd/data/photo  

1、查看apache当前并发访问数:

netstat -an | grep ESTABLISHED | wc -l  

对比httpd.conf中MaxClients的数字差距多少。

2、查看有多少个进程数:

ps aux|grep httpd|wc -l  

3、可以使用如下参数查看数据

#ps -ef|grep httpd|wc -l 

1388
  统计httpd进程数,连个请求会启动一个进程,使用于Apache服务器。
  表示Apache能够处理1388个并发请求,这个值Apache可根据负载情况自动调整。

#netstat -nat|grep -i "80"|wc -l  
  4341 
  netstat -an会打印系统当前网络链接状态,而grep -i "80"是用来提取与80端口有关的连接的,wc -l进行连接数统计。 www.2cto.com 
  最终返回的数字就是当前所有80端口的请求总数。 
查看tomcat 并发连接数
#netstat -na|grep ESTABLISHED|wc -l  
  376 
  netstat -an会打印系统当前网络链接状态,而grep ESTABLISHED 提取出已建立连接的信息。 然后wc -l统计。 
  最终返回的数字就是当前所有80端口的已建立连接的总数。
netstat -nat||grep ESTABLISHED|wc  
可查看所有建立连接的详细记录 

查看Apache的并发请求数及其TCP连接状态: 
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}' 

  返回结果示例: 
  LAST_ACK 5 
  SYN_RECV 30 
  ESTABLISHED 1597 
  FIN_WAIT1 51 
  FIN_WAIT2 504 
  TIME_WAIT 1057 
  其中的 
  SYN_RECV表示正在等待处理的请求数; 
  ESTABLISHED表示正常数据传输状态; 
  TIME_WAIT表示处理完毕,等待超时结束的请求数。

配置长连接

https://blog.csdn.net/hotdust/article/details/81057141

为什么要配置长连接

一个普通的请求是从按照下图 1->2->3->4 的顺序。从浏览器到 Nginx,再从 Nginx 到 Tomcat。Tomcat 处理完后,再返回给 Nginx,最后再从 Nginx 返回给浏览器。

+--------------+          +--------------+         +--------------+
|              |    1     |              |   2     |              |
|  浏 览 器     +--------> |    Nginx     +-------> |   Tomcat     |
|              |    4     |              |   3     |              |
|              | <--------+              | <-------+              |
+--------------+          +--------------+         +--------------+

在这个请求过程中,一般从浏览器到 Nginx 是短连接(就是请求回去后就断开连接),而 Nginx 到 Tomcat 可以是短连接 或是 长连接(请求返回后,连接还保持一段时间)。为什么 Nginx 到 Tomcat 之间要设置成长连接呢?因为连接的创建(三次握手)是需要花费一些时间的,如果请求量非常大,不如像连接池一样保存一定的连接,当有请求进来时复用一些连接。而且还能减少 time wait,关于 time wait 请参看:关于 time wait

一般来说,请求方被请求方都可以主动断开连接。请求方断开连接时机比较好判断,如果要请求的内容都完成了,就可以断开连接了。但被请求方的断开连接的时机就不好判断了,因为被请求方不知道请求方会发多少次请求。所以一般被请求方设置的参数相对多一些,例如:长连接在处理多少次请求后断开、长连接在经过多少秒后断开等等。

下面说一下浏览器->Nginx->Tomcat设置长连接的方法。首先说一下 Nginx 的设置方法。

1,Nginx 设置

1. Nginx - 反向代理
nginx.conf:

http {
    ...
    ##
    # 与Client连接的长连接配置
    ##
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#keepalive_requests
    # 设置通过"一个存活长连接"送达的最大请求数(默认是100,建议根据客户端在"keepalive"存活时间内的总请求数来设置)
    # 当送达到单个长连接的请求数超过该值后,该连接就会被关闭。(通过设置为5,验证确实是这样)
    keepalive_requests 8192;

    # http://nginx.org/en/docs/http/ngx_http_core_module.html#keepalive_timeout
    # 第一个参数设置"keep-alive客户端长连接"将在"服务器端"继续打开的超时时间(默认是75秒,
    # 建议根据具体业务要求来,但必须要求所有客户端连接的"Keep-Alive"头信息与该值设置的相同
    # (这里是5分钟),同时与上游服务器(Tomcat)的设置是一样的)
    # 可选的第二个参数设置“Keep-Alive: timeout=time”响应头字段的值。设置第二个参数后,这个时间会实传回给客户端(例如:浏览器)
    keepalive_timeout 300s 300s;

    ...
    include /etc/nginx/web_servers.conf;

}

web_servers.conf:
upstream web_server {
    server 127.0.0.1:8080;

    # http://nginx.org/en/docs/http/ngx_http_upstream_module.html#keepalive
    # 连接到 upstream(也就是 Tomcat)的最大并发空闲keepalive长连接数,也就是最多只能创建
    # 这么多长连接,在这之上再创建连接的话,都是短连接。
    #(默认是未设置,建议与Tomcat Connector中的maxKeepAliveRequests值一样)。
    # 如果并发数大于这个数值的话,根据情况可能会创建一些`短连接`来处理请求。可以使用
    # `netstat -an|grep TIME|awk '{if($4~"10001") print}'|wc -l`命令来查看,
    # 其中`10001`是 Nginx 的端口号,改成自己 Nginx 的端口。
    #
    # 当这个数被超过时,使用"最近最少使用算法(LUR)"来淘汰并关闭连接。
    keepalive 8;
}
server {

    listen 80;
    server_name lihg.com www.lihg.com;
    location / {
        proxy_pass http://web_server;
        ##
        # 与上游服务器(Tomcat)建立keepalive长连接的配置,可参考上面的keepalive链接里的
        # "For HTTP"部分
        ##
        # http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_http_version
        # 设置代理的HTTP协议版本(默认是1.0版本)
        # 使用keepalive连接的话,建议使用1.1版本。
        proxy_http_version 1.1;

        # http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_set_header
        # 允许重新定义或追加字段到传递给代理服务器的请求头信息,默认是close。如果把这个 header 设置成空的话,Nginx 会向 Tomcat 传递`close`,这样 Tomcat 就会在处理完请求后关闭连接(这个部分是推理的,没有实际验证是 Tomcat 关的,还是 Nginx 关的)
        proxy_set_header Connection "";
    }
}

上面每个参数的功能都在注释中介绍了,下面简单总结一下使用上注意的地方:

  • keepalive_timeout 和 keepalive_requests 是影响长连接如何关闭的参数。 如果监控长连接在运行中,一部分的连接被关闭了(也就是没有了),可能是 keepalive_requests 影响的(因为超过了单个长连接的请求最大次数);如果大批量被关闭了,可能是和 keepalive_timeout 有关。
  • upstream 中的 keepalive 是限制对 Tomcat 创建长连接个数的限制。不是不能创建超过这个数的连接,而是创建的长连接数,不能超过这个限制,在这之上可以再创建连接,只不过创建的都是短连接。
  • proxy_http_version 和 proxy_set_header 根据 http 版本(1.0/1.1)设置有所不同,请注意。

关于 Nginx 的设置,可以参考:Alphabetical index of directives

2,Tomcat 设置

conf/server.xml:
    <!-- 
        maxThreads:由此连接器创建的最大请求处理线程数,这决定可同时处理的最大并发请求数(默认为200)
        minSpareThreads:保持运行状态的最小线程数(默认为10)
        acceptCount:接收传入的连接请求的最大队列长度(默认队列长度为100)
        connectionTimeout:在接收一条连接之后,连接器将会等待请求URI行的毫秒数(默认为60000,60秒)
        maxConnections:在任何给定的时间,服务器能接收和处理的最大连接数(NIO的默认值为10000)
        keepAliveTimeout:在关闭这条连接之前,连接器将等待另一个HTTP请求的毫秒数(默认使用connectionTimeout属性值)
        maxKeepAliveRequests:和 Nginx 中的 keepalive_request 属性的功能是一样的(默认为100)
        enableLookups:启用DNS查询(默认是DNS查询被禁用)
        compression:连接器是否启用HTTP/1.1 GZIP压缩,为了节省服务器带宽
        compressionMinSize:指定输出响应数据的最小大小(默认为2048,2KB)
        compressableMimeType:可使用HTTP压缩的文件类型
        server:覆盖HTTP响应的Server头信息
     -->
    <Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol"
               maxThreads="768"
               minSpareThreads="512"
               acceptCount="128"
               connectionTimeout="1000"
               maxConnections="1024"
               keepAliveTimeout="300000"
               maxKeepAliveRequests="768"
               enableLookups="false"
               URIEncoding="utf-8"
               redirectPort="8443"
               compression="on" compressionMinSize="1024" compressableMimeType="text/html,text/xml,text/javascript,text/css,text/plain,application/json,application/xml" server="webserver" />

这里要注意的是:

  • maxKeepAliveRequests 属性和 Nginx 中的 keepalive_request 属性的功能是一样的,是互相影响的,即达到两边中最小的设置后关闭连接。如果 Tomcat 这边设置的是 5,而 Nginx 那边设置的是 10 的话,Tomcat 到 5 时就会关闭这个长连接。
  • keepAliveTimeout 属性和 Nginx 中的 keepalive_timeout 功能是一样的,也是互相影响的,同上。
  • 如果使用是 Springboot 内嵌 Tomcat 的话,是无法在 application.properties 中设置 maxKeepAliveRequests 属性的。如果使用 Bean 的方式来设置,其它在 Springboot 中无法设置的属性也可以使用此方法来设置。另外,如果是在 application.properties 文件中设置了属性,也在 Bean 声明时设置了属性的话,application.properties 中设置的属性优先生效。
@Bean
public EmbeddedServletContainerFactory servletContainerFactory() {
    
    
    TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory();
    factory.addConnectorCustomizers(connector -> {
    
    
        ((AbstractHttp11Protocol) connector.getProtocolHandler()).setMaxKeepAliveRequests(-1);
        ((AbstractHttp11Protocol) connector.getProtocolHandler()).setConnectionTimeout(20000);
    });
    return factory;
}

关于 Tomcat 的 connector 设置,可以参考:The HTTP Connector
关于 Springboot 设置,可以参考:Appendix A. Common application properties

三、测试

1,测试命令

为了测试长连接设置是否生效,可以使用下面两个命令来测试。

  • ab:用来进行压力测试的命令。例如:ab -c 8 -n 8000 127.0.0.1:10000/crm/,启用 8 个异步处理,发送总共 8000 个请求。
  • netstat:用来查看连接的状态。例如:netstat -an|grep ES|awk '{if($5~10001) print}'|wc -l,查看 Nginx 到 Tomcat 的正在使用连接。如果想要每秒刷新,可以这么做:while [ 1 -eq 1 ] ; do netstat -an|grep ES|awk '{if($5~10001) print}'|wc -l; sleep 1; echo ; done

2,测试准备

为了测试长连接是否起作用,最好把 Tomcat 部分按下面设置:

  • MaxKeepAliveRequests:设置成 -1。这样就不会因为请求个数,造成连接的断开重新创建。用 netstat 查看连接时,看到的就是稳定的状态。
  • ConnectionTimeout:设置成一个比较大的值,例如 60 秒,这样不会因为压测时间长,造成连接的断开重新创建

Nginx 的 keepalive_requests 和 keepalive_timeout 和 Tomcat 上面两个值作用相同,最好设置成一样,以免造成不稳定,而不知道是哪出现的原因。

在按照上面的设置进行测试后,保存长连接可用后,可以修改上面的参数,来查看参数的作用和预想结果是否相同。

3,进行测试

1,启动一个 Terminal,执行while [ 1 -eq 1 ] ; do netstat -an|grep ES|awk '{if($5~10001) print}'|wc -l; sleep 1; echo ; done。这样就可以查看连接创建的个数了。显示如下:

       0
       0
       0
       0

2,再打开一个 Terminal,使用 ab 命令,进行压测:ab -c 8 -n 8000 127.0.0.1:10000/crm/
ab 结果如下:

This is ApacheBench, Version 2.3 <$Revision: 1807734 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 127.0.0.1 (be patient)
Completed 800 requests
Completed 1600 requests
Completed 2400 requests
Completed 3200 requests
Completed 4000 requests
Completed 4800 requests
Completed 5600 requests
Completed 6400 requests
Completed 7200 requests
Completed 8000 requests
Finished 8000 requests


Server Software:        nginx/1.15.1
Server Hostname:        127.0.0.1
Server Port:            10000

Document Path:          /crm/
Document Length:        9 bytes

Concurrency Level:      8
Time taken for tests:   12.685 seconds
Complete requests:      8000
Failed requests:        0
Total transferred:      1304000 bytes
HTML transferred:       72000 bytes
Requests per second:    630.67 [#/sec] (mean)
Time per request:       12.685 [ms] (mean)
Time per request:       1.586 [ms] (mean, across all concurrent requests)
Transfer rate:          100.39 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    1   5.0      0     147
Processing:     1   11  16.4      7     316
Waiting:        0   10  15.8      6     310
Total:          1   12  17.5      8     316

Percentage of the requests served within a certain time (ms)
  50%      8
  66%     12
  75%     15
  80%     16
  90%     23
  95%     30
  98%     45
  99%     78
 100%    316 (longest request)

查看连接状态命令输出如下。从下面输出可以看出,创建了 8 个长连接。连接的个数,应该和 Nginx 中 upstream 中的 keepalive 的参数一致。

       0
       0
       8
       8
       8
       8
       8
       8
       8

3,结过 Nginx 的 keepalive_timeout 时间后,长连接数应该变成 0。

       0
       0
       0

参考

猜你喜欢

转载自blog.csdn.net/hancoder/article/details/110361524
今日推荐