Java HttpComponents源码阅读2

Java HttpComponents源码阅读1
Java HttpComponents源码阅读2

MainClientExec#exec

MainClientExec作为执行链中的最后一个请求执行器,负责执行实际的请求操作。所以这个方法汇总了大部分连接的处理,下面一步步进行拆解;

public CloseableHttpResponse execute(
        final HttpRoute route,
        final HttpRequestWrapper request,
        final HttpClientContext context,
        final HttpExecutionAware execAware) throws IOException, HttpException {
    ... 
    Object userToken = context.getUserToken();
    // 从连接池中获取http请求句柄
    final ConnectionRequest connRequest = connManager.requestConnection(route, userToken);

connManagerPoolingHttpClientConnectionManager对象,每一个HTTP请求都会调用requestConnection方法来获取一个请求的句柄;

PoolingHttpClientConnectionManager#requestConnection
private final CPool pool;
public ConnectionRequest requestConnection(
            final HttpRoute route,
            final Object state) {
        // pool对象为CPool类型,根据路由从池中租用连接句柄
        final Future<CPoolEntry> future = this.pool.lease(route, state, null);
        return new ConnectionRequest() { ... };
}

根据路由从连接池中租用连接的句柄,从该句柄可以拿到HttpClientConnection对象,注意这里新分配的连接可以以关闭状态返回;state代表着期望的连接状态,如果为null表示新的连接不期望携带任何状态,否则从可用连接池中获取请求时会去寻找连接状态为state的连接;

CPool#lease
public Future<E> lease(final T route, final Object state, 
                       final FutureCallback<E> callback) {
        return new Future<E>() { ... }
}

lease方法返回一个Future句柄,该句柄定义了租用连接的具体细节,这里该句柄已经和路由绑定起来;

继续接MainClientExec#exec

    ...
    if (execAware != null) {
        if (execAware.isAborted()) {
            // 如果请求被中断,取消请求
            connRequest.cancel();
            throw new RequestAbortedException("Request aborted");
        }
        // 通过cas设置cancellable方法
        execAware.setCancellable(connRequest);
    }
    ... 

所有的方法类如HttpGet都是继承HttpExecutionAware,这里把请求取消的处理和请求绑定起来;

    ... 
    final RequestConfig config = context.getRequestConfig();
    final HttpClientConnection managedConn;
    try {
        // 获取配置中的超时时间
        final int timeout = config.getConnectionRequestTimeout();
        // 在一定时间内从句柄接口中获取HttpClientConnection连接
        // 见下面
        managedConn = connRequest.get(timeout > 0 ? timeout : 0, TimeUnit.MILLISECONDS);
    } catch(final InterruptedException interrupted) {
        // 如果遇到线程中断直接抛异常
        Thread.currentThread().interrupt();
        throw new RequestAbortedException("Request aborted", interrupted);
    } catch(final ExecutionException ex) {
        // 试图检索因抛出异常而中止的任务的结果时引发的异常,
        // 主要是和Future有关的,在后面可以看到为什么会发生这个异常
        Throwable cause = ex.getCause();
        if (cause == null) {
            cause = ex;
        }
        throw new RequestAbortedException("Request execution failed", cause);
    }
    ...

根据配置来的超时时间请求HttpClientConnection连接对象;

ConnectionRequest#get
public HttpClientConnection get(
        final long timeout,
        final TimeUnit timeUnit) throws InterruptedException, ExecutionException, ConnectionPoolTimeoutException {
    // 根据route从连接池中获取connection
    // 见下面
    final HttpClientConnection conn = leaseConnection(future, timeout, timeUnit); 
    if (conn.isOpen()) {
        // 获取到重用连接,socket还未被关闭,重新刷新socket配置
        final HttpHost host;
        if (route.getProxyHost() != null) {
            host = route.getProxyHost();
        } else {
            host = route.getTargetHost();
        }
        final SocketConfig socketConfig = resolveSocketConfig(host);
        // 重新刷新timeout
        conn.setSocketTimeout(socketConfig.getSoTimeout());
    }
    return conn;
}

在给定时间内在连接池中获取连接,调用此方法时将阻塞,直到连接变为可用、超时过期或连接管理器关闭为止。如果在阻塞时或开始之前调用了cancel(),将抛出一个InterruptedException

PoolingHttpClientConnectionManager#leaseConnection
protected HttpClientConnection leaseConnection(
        final Future<CPoolEntry> future,
        final long timeout,
        final TimeUnit timeUnit) throws InterruptedException, ExecutionException, ConnectionPoolTimeoutException {
    final CPoolEntry entry;
    try {
        // future里面含有路由信息,从future中获取CPoolEntry对象
        // 见下面
        entry = future.get(timeout, timeUnit);
        if (entry == null || future.isCancelled()) {
            throw new ExecutionException(new CancellationException("Operation cancelled"));
        }
        return CPoolProxy.newProxy(entry); // 返回的是CPoolEntry的代理对象
    } catch (final TimeoutException ex) {
        throw new ConnectionPoolTimeoutException("Timeout waiting for connection from pool");
    }
}

leaseConnection做的事情就是执行future句柄定义好的获取连接操作,成功获取到CPoolEntry后会返回一个代理对象CPoolProxy,该代理的核心功能就是校验连接的可用性,一旦发现连接不可用了,就会抛出ConnectionShutdownException异常;

AbstractConnPool$future#get
private final AtomicBoolean cancelled = new AtomicBoolean(false);
// 代表完成获取连接
private final AtomicBoolean done = new AtomicBoolean(false);
// 储存的是CPoolEntry对象
private final AtomicReference<E> entryRef = new AtomicReference<E>(null); 

public E get(final long timeout, final TimeUnit timeUnit) throws InterruptedException, ExecutionException, TimeoutException {
    for (;;) {
        // 这里开始阻塞
        synchronized (this) { // this对象是指future对象
            try {
                // 第一次获取entry为null
                final E entry = entryRef.get();
                if (entry != null) {
                    return entry;
                }
                if (done.get()) {
                    // 如果已经成功获取到了连接
                    throw new ExecutionException(operationAborted());
                }
                // 租用entry,这里获取到的entry一定是非空
                final E leasedEntry = getPoolEntryBlocking(route, state, timeout, timeUnit, this);
                if (validateAfterInactivity > 0)  {
                    // 如果开启校验连接可用性
                    if (leasedEntry.getUpdated() + validateAfterInactivity <= System.currentTimeMillis()) {
                        // 检查连接是否可用
                        if (!validate(leasedEntry)) {  
                            // 连接不可用,释放连接,并重新获取连接
                            leasedEntry.close();
                            release(leasedEntry, false);
                            continue;
                        }
                    }
                }
                // 防止重复租用连接
                if (done.compareAndSet(false, true)) {
                    // 更新状态
                    entryRef.set(leasedEntry);
                    done.set(true);
                    // 成功租用连接的钩子
                    onLease(leasedEntry);
                    if (callback != null) {
                        callback.completed(leasedEntry);
                    }
                    return leasedEntry;
                } else {
                    // cas失败,该future已被其他线程用来租用连接
                    release(leasedEntry, true);
                    throw new ExecutionException(operationAborted());
                }
            } catch (final IOException ex) {
                // getPoolEntryBlocking会抛IOException,通常是socket的IO异常
                if (done.compareAndSet(false, true)) {
                    if (callback != null) {
                        callback.failed(ex);
                    }
                }
                throw new ExecutionException(ex);
            }
        }
    }
}

自旋的获取CPoolEntry对象,无论是从空闲的连接池或是新创建一个新的连接,总之从这个方法获取到的CPoolEntry一定是可用的连接对象;

AbstractConnPool#getPoolEntryBlocking
private E getPoolEntryBlocking(
        final T route, final Object state,
        final long timeout, final TimeUnit timeUnit,
        final Future<E> future) throws IOException, InterruptedException, ExecutionException, TimeoutException {

    Date deadline = null;
    if (timeout > 0) {
        deadline = new Date (System.currentTimeMillis() + timeUnit.toMillis(timeout));
    }
    this.lock.lock();
    try {
        // 根据路由返回特定的连接池
        final RouteSpecificPool<T, C, E> pool = getPool(route);
        E entry;
        for (;;) {
            if (future.isCancelled()) {
                throw new ExecutionException(operationAborted());
            }
            for (;;) {
                // 根据state去available连接池中获取连接
                entry = pool.getFree(state); 
                if (entry == null) {
                    // 连接池中没有空闲的连接
                    break;
                }
                // 获取到连接
                if (entry.isExpired(System.currentTimeMillis())) {
                    // 连接已经过期,将连接关闭
                    entry.close();
                }
                if (entry.isClosed()) {
                    // 如果连接已被关闭,将其从available移除
                    this.available.remove(entry);
                    pool.free(entry, false);
                } else {
                    break;
                }
            }
            if (entry != null) {
                // 如果从连接池中获取到连接,将连接从available中拿出来放置到租用池中
                this.available.remove(entry);
                this.leased.add(entry);
                onReuse(entry); // 重用连接的钩子
                return entry;
            }

            // 连接池中没有可用连接,将创建新的连接
            final int maxPerRoute = getMax(route);
            // 在分配新连接之前缩小连接池大小
            final int excess = Math.max(0, pool.getAllocatedCount() + 1 - maxPerRoute);
            if (excess > 0) {
                for (int i = 0; i < excess; i++) {
                    final E lastUsed = pool.getLastUsed();
                    if (lastUsed == null) {
                        break;
                    }
                    lastUsed.close();
                    this.available.remove(lastUsed);
                    pool.remove(lastUsed);
                }
            }

            if (pool.getAllocatedCount() < maxPerRoute) {
                // 每个路由的最大容量是有限的
                final int totalUsed = this.leased.size();
                final int freeCapacity = Math.max(this.maxTotal - totalUsed, 0);
                if (freeCapacity > 0) {
                    final int totalAvailable = this.available.size();
                    if (totalAvailable > freeCapacity - 1) {
                        // 空闲连接池容量满了,将最近一个空闲连接移出空闲池
                        if (!this.available.isEmpty()) {
                            final E lastUsed = this.available.removeLast();
                            lastUsed.close();
                            final RouteSpecificPool<T, C, E> otherpool = getPool(lastUsed.getRoute());
                            otherpool.remove(lastUsed);
                        }
                    }
                    // 创建出来的对象是LoggingManagedHttpClientConnection
                    final C conn = this.connFactory.create(route);
                    // 同一个连接会在RouteSpecificPool中和AbstractConnPool保存两份
                    entry = pool.add(conn);
                    this.leased.add(entry);
                    return entry;
                }
            }
            // 创建连接失败,因为该路由的连接已经达到最大上限
            boolean success = false;
            try {
                // 将该创建连接的任务缓存起来
                pool.queue(future);
                this.pending.add(future);
                // 等待直到有连接被释放
                if (deadline != null) { 
                    success = this.condition.awaitUntil(deadline);
                } else {
                    this.condition.await();
                    success = true;
                }
                if (future.isCancelled()) {
                    throw new ExecutionException(operationAborted());
                }
            } finally {
                // 一般情况下等待线程被连接池唤醒,现在应该有一个空闲连接可用
                // 但是也有可能等待超时了,无论如何只要继续循环,这两种情况都会被检查。
                pool.unqueue(future);
                this.pending.remove(future);
            }
            // check for spurious wakeup vs. timeout
            if (!success && (deadline != null && deadline.getTime() <= System.currentTimeMillis())) {
                break;
            }
        }
        throw new TimeoutException("Timeout waiting for connection");
    } finally {
        this.lock.unlock();
    }
}

// 在连接池内部每一个route对应一个连接池
private RouteSpecificPool<T, C, E> getPool(final T route) {
    RouteSpecificPool<T, C, E> pool = this.routeToPool.get(route);
    if (pool == null) {
        pool = new RouteSpecificPool<T, C, E>(route) {
            @Override
            protected E createEntry(final C conn) {
                return AbstractConnPool.this.createEntry(route, conn);
            }
        };
        this.routeToPool.put(route, pool);
    }
    return pool;
}

该方法的主要处理逻辑都是发生在下面两个结构中

CPool作为连接池的核心类,承担了连接池的管理工作,内部每个路由
都对应一个RouteSpecificPool,所以同一份连接会在两个地方都保存起来;

这个方法最主要做的事情就是:

  1. 根据路由在RouteSpecificPool池中查找一个空闲连接,如果有则判断连接是否过期超时,判断连接可用后直接返回;
  2. 如果在空闲连接池中找不到可用连接,则判断当前条件下是否允许创建连接,如果成功创建连接则直接返回;
  3. 如果连接池已经满了,将请求加入等待队列,然后将自己阻塞等待其他连接释放;

继续回到MainClientExec#exec

    ...
    // 创建连接成功后,将连接保存到上下文中
    context.setAttribute(HttpCoreContext.HTTP_CONNECTION, managedConn);
    // 如果开启老化连接检查
    if (config.isStaleConnectionCheckEnabled()) {
        if (managedConn.isOpen()) {  // 验证连接
            if (managedConn.isStale()) { 
                managedConn.close(); // 发现陈旧的连接,将连接断开
            }
        }
    }
    // 创建holder,相当于一个请求包装类
    final ConnectionHolder connHolder = new ConnectionHolder(this.log, this.connManager, managedConn);
    try {
        if (execAware != null) {
            // 如果前面已经设置了cancellable,这行代码就没有效果了
            execAware.setCancellable(connHolder);
        }
        HttpResponse response;
        for (int execCount = 1;; execCount++) {
            // 开始执行具体请求
            if (!managedConn.isOpen()) {
                // 如果是新的连接,则需要建立请求的路由
                try {
                    // 建立请求路由
                    establishRoute(proxyAuthState, managedConn, route, request, context);
                } catch (final TunnelRefusedException ex) {
                    response = ex.getResponse();
                    break;
                }
            }
            final int timeout = config.getSocketTimeout();
            if (timeout >= 0) {
                managedConn.setSocketTimeout(timeout);
            }

            ...

成功拿到可用连接后,就是开始建立请求路由,会根据当前请求目标选择不同的路径,但是最终都会调用下面这个方法;

PoolingHttpClientConnectionManager#connect
public void connect(         
        final HttpClientConnection managedConn,
        final HttpRoute route,
        final int connectTimeout,
        final HttpContext context) throws IOException {
    final ManagedHttpClientConnection conn;
    synchronized (managedConn) {
        final CPoolEntry entry = CPoolProxy.getPoolEntry(managedConn);
        conn = entry.getConnection();
    }
    final HttpHost host;
    if (route.getProxyHost() != null) { // 有代理,建立代理路由
        host = route.getProxyHost();
    } else {
        host = route.getTargetHost(); // 没有代理,直接路由
    }
    // 实际建立socket并连接的操作
    this.connectionOperator.connect(
            conn, host, route.getLocalSocketAddress(), connectTimeout, resolveSocketConfig(host), context);
}

如果是直接路由,则将底层连接套接字连接到目标地址,如果是通过代理(或多个代理)的路由,则连接到第一个代理跳转。

结合第一张图片看,将LoggingManagedHttpClientConnection对象与实际负责通信的socket绑定;

继续回到MainClientExec#exec

    ... 
    context.setAttribute(HttpCoreContext.HTTP_REQUEST, request);
    response = requestExecutor.execute(request, managedConn, context);
    ...

实际发送请求消息并拿到响应的方法;

HttpRequestExecutor#execute
public HttpResponse execute(
        final HttpRequest request,
        final HttpClientConnection conn,
        final HttpContext context) throws IOException, HttpException {
    try {
        // 发送请求,有些情况下response会随着请求返回
        HttpResponse response = doSendRequest(request, conn, context);
        if (response == null) {
            // 接受请求
            response = doReceiveResponse(request, conn, context);
        }
        return response;
    } catch (final IOException ex) {
        closeConnection(conn);
        throw ex;
    } catch (final HttpException ex) {
        closeConnection(conn);
        throw ex;
    } catch (final RuntimeException ex) {
        closeConnection(conn);
        throw ex;
    }
}

该方法主要就是处理发送请求和接受请求;

HttpRequestExecutor#doSendRequest
protected HttpResponse doSendRequest(
        final HttpRequest request,
        final HttpClientConnection conn,
        final HttpContext context) throws IOException, HttpException {
    HttpResponse response = null;
    context.setAttribute(HttpCoreContext.HTTP_CONNECTION, conn);
    context.setAttribute(HttpCoreContext.HTTP_REQ_SENT, Boolean.FALSE);
    // 发送header
    conn.sendRequestHeader(request); 
    if (request instanceof HttpEntityEnclosingRequest) {
        // 检查expect-continue握手。我们必须刷新头并等待100-continue响应来处理它。
        // 如果我们得到不同的响应,我们一定不能发送实体。
        boolean sendentity = true;
        final ProtocolVersion ver =
                request.getRequestLine().getProtocolVersion();
        if (((HttpEntityEnclosingRequest) request).expectContinue() &&
                !ver.lessEquals(HttpVersion.HTTP_1_0)) {
            // 需要处理100-continue逻辑
            conn.flush();
            // 在指定时间内等到对端回复,不需要永远等待100-continue响应,如果发生超时则发送实体请求
            if (conn.isResponseAvailable(this.waitForContinue)) {
                // 意味着在指定时间内获取到了100-continue应答
                response = conn.receiveResponseHeader();
                if (canResponseHaveBody(request, response)) {
                    // 意味着response存在响应包,解析response
                    conn.receiveResponseEntity(response);
                }
                final int status = response.getStatusLine().getStatusCode();
                if (status < 200) {
                    if (status != HttpStatus.SC_CONTINUE) {
                        throw new ProtocolException(
                                "Unexpected response: " + response.getStatusLine());
                    }
                    // 非正常的状态码,重置response
                    response = null;
                } else {
                    sendentity = false;
                } 
            }
        }
        if (sendentity) {
            // 发送请求实体
            conn.sendRequestEntity((HttpEntityEnclosingRequest) request);
        }
    }
    conn.flush();
    context.setAttribute(HttpCoreContext.HTTP_REQ_SENT, Boolean.TRUE);
    return response;
}

通过给定的连接发送请求,此方法还处理expect-continue握手;

HTTP/1.1 协议里的Expect: 100-continue 意思是,在客户端发送 Request Message 之前,HTTP/1.1 协议允许客户端先判定服务器是否愿意接受客户端发来的消息主体(基于 Request Headers)。
目的:它可以让客户端在发送请求数据之前去判断服务器是否愿意接收该数据,如果服务器愿意接收,客户端才会真正发送数据,如果客户端直接发送请求数据,但是服务器又将该请求拒绝的话,这种行为将带来很大的资源开销。
发送了100-continue的客户端不应该永远等待服务器端做出回应,超时一段时间之后,客户端应该直接将实体发送出去。

比如在POST方法时HttpPost就会去处理expect-continue的逻辑,如果在这里有判断到返回了响应,则处理响应实体;

HttpRequestExecutor#doReceiveResponse
protected HttpResponse doReceiveResponse(
        final HttpRequest request,
        final HttpClientConnection conn,
        final HttpContext context) throws HttpException, IOException {
     HttpResponse response = null;
     int statusCode = 0;

     while (response == null || statusCode < HttpStatus.SC_OK) {
         response = conn.receiveResponseHeader();
         statusCode = response.getStatusLine().getStatusCode();
         if (statusCode < HttpStatus.SC_CONTINUE) {
             throw new ProtocolException("Invalid response: " + response.getStatusLine());
         }
         // 解析response entity
         if (canResponseHaveBody(request, response)) {
             conn.receiveResponseEntity(response);
         }
     }

    return response;
}

doSendRequest中如果是Get请求,会将HTTP报文的每一行逐一写入socket缓冲区中,当缓冲区满了或是数据全部写入后会发送出去;
如果是Post请求,除了发送请求头、请求行、header等信息,还会发送请求体;

doReceiveResponse方法用来接受响应,但是不负责解析具体的entity,只是将对应的socket句柄封装在响应实体HttpResponse里面等待读取;

继续回到MainClientExec#exec,到这里已经拿到请求响应了;

            ... 
            // 判断连接是否是可重用状态
            if (reuseStrategy.keepAlive(response, context)) {
                final long duration = keepAliveStrategy.getKeepAliveDuration(response, context);
                 // 返回的响应头中含有Keep-Alive
                connHolder.setValidFor(duration, TimeUnit.MILLISECONDS);
                connHolder.markReusable();
            } else {
                // 连接不可重用
                connHolder.markNonReusable();
            }

            ...
            // 校验请求
            ...

        final HttpEntity entity = response.getEntity();
        if (entity == null || !entity.isStreaming()) {
            // 连接不需要并且(假定)处于可重用状态
            connHolder.releaseConnection();
            return new HttpResponseProxy(response, null);
        }
        // HttpResponse的代理类,可以用来释放与原始响应关联的客户端连接
        return new HttpResponseProxy(response, connHolder);
    } catch (final Exception ex) {
       ...
    }
}

至此整个请求流程就大概完成了,下面看看连接什么时候放回连接池;

EntityUtils.toString(response.getEntity(), "UTF-8");

最后会走到下面这个方法

private static String toString(
        final HttpEntity entity,
        final ContentType contentType) throws IOException {
    final InputStream inStream = entity.getContent();
    if (inStream == null) {
        return null;
    }
    try {
        int capacity = (int)entity.getContentLength();
        Charset charset = null;
        ...
        final Reader reader = new InputStreamReader(inStream, charset);
        final CharArrayBuffer buffer = new CharArrayBuffer(capacity);
        final char[] tmp = new char[1024];
        int l;
        while((l = reader.read(tmp)) != -1) {
            buffer.append(tmp, 0, l);
        }
        return buffer.toString();
    } finally {
        inStream.close();
    }
}

public InputStream getContent() throws IOException {
    return new EofSensorInputStream(this.wrappedEntity.getContent(), this);
}

关键在于EofSensorInputStream这个对象,该对象的核心功能就是检测EOF标识符并触发close()操作,主要用于在使用响应体或不再需要连接时自动释放底层托管的连接。

public int read() throws IOException {
    int readLen = -1;
    if (isReadAllowed()) {
        try {
            readLen = wrappedStream.read();
            checkEOF(readLen); // 检查eof
        } catch (final IOException ex) {
            checkAbort();
            throw ex;
        }
    }
    return readLen;
}
// checkEOF会走到这里
public boolean eofDetected(final InputStream wrapped) throws IOException {
    try {
        if (wrapped != null) {
            wrapped.close();
        }
        releaseConnection(); // 释放连接
    } catch (final IOException ex) {
        abortConnection(); // 中断连接
        throw ex;
    } catch (final RuntimeException ex) {
        abortConnection();
        throw ex;
    } finally {
        cleanup(); // 关闭连接
    }
    return false;
}

当从流中读取到EOF标志符后会主动释放连接;

ConnectionHolder#releaseConnection
private void releaseConnection(final boolean reusable) {
    if (this.released.compareAndSet(false, true)) {
        synchronized (this.managedConn) {
            if (reusable) {
                // 把连接放回连接池
                this.manager.releaseConnection(this.managedConn, 
                        this.state, this.validDuration, this.timeUnit);
            } else {
                try {
                    this.managedConn.close();
                } catch (final IOException ex) {
                } finally {
                    this.manager.releaseConnection(
                        this.managedConn, null, 0, TimeUnit.MILLISECONDS);
                }
            }
        }
    }
}

这里的managedConnCPoolProxy对象,managerPoolingHttpClientConnectionManager对象,如果请求完毕后只是释放,那么会把连接放回到连接池;

AbstractConnPool#release
public void release(final E entry, final boolean reusable) {     
    this.lock.lock();
    try {
        if (this.leased.remove(entry)) {
            // 根据route取连接池
            final RouteSpecificPool<T, C, E> pool = getPool(entry.getRoute());
            // 将RouteSpecificPool的连接从租用池中移除,放入空闲池
            pool.free(entry, reusable);
            if (reusable && !this.isShutDown) {
                this.available.addFirst(entry);
            } else {
                entry.close();
            }
            onRelease(entry); // hook
            Future<E> future = pool.nextPending();
            // 如果有的话从等待队列中拿到等待请求的future
            if (future != null) {
                this.pending.remove(future);
            } else {
                future = this.pending.poll();
            }
            if (future != null) {
                // 唤醒其他被阻塞的请求线程
                this.condition.signalAll();
            }
        }
    } finally {
        this.lock.unlock();
    }
}

将释放的连接从租用池中移回到空闲连接池中,然后从等待队列取出第一个请求,如果有的话会唤醒被阻塞线程,注意这里取的操作,是优先取同一个路由下的请求,如果同一个路由下没有才会取其他路由的请求;

shutdown和close的区别
  • close
    此方法会优雅地关闭连接,在关闭底层socket之前会刷新内部缓冲区,close不允许从其他线程调用来强制关闭连接。
public void close() throws IOException {
    final Socket socket = this.socketHolder.getAndSet(null);
    if (socket != null) {
        try {
            // 先会刷新缓存区
            this.inBuffer.clear();
            this.outbuffer.flush();
            try {
                try {
                    socket.shutdownOutput();
                } catch (final IOException ignore) {
                }
                try {
                    socket.shutdownInput();
                } catch (final IOException ignore) {
                }
            } catch (final UnsupportedOperationException ignore) {
                // if one isn't supported, the other one isn't either
            }
        } finally {
            socket.close();
        }
    }
}
  • shutdown
    会立即强制关闭这个连接,该方法可以允许从不同的线程来调用它终止连接。在关闭底层scoket之前,不会尝试刷新任何的内部缓冲区;
public void shutdown() throws IOException {
    final Socket socket = this.socketHolder.getAndSet(null);
    if (socket != null) {
        try {
            // 设置关闭socket的操作
            socket.setSoLinger(true, 0);
        } catch (final IOException ex) {
        } finally {
            socket.close();
        }
    }
}

TCP协议中RST标志位用来表示重置连接、复位连接,关闭异常的连接,
发送RST包关闭连接时,不必等缓冲区的包都发出去(正常要说关闭要经历4次挥手),直接就丢弃缓存区的数据并发送RST包;而接收端收到RST包后,也不必发送ACK包来确认。

setSoLinger的第一个参数为false时,TCP关闭连接时会保存默认的操作,缓冲区的数据会正常刷新;

当第一个参数为true时,会依赖第二个参数的值,当第二个参数为0时,调用close的时候,TCP连接会立即断开;缓冲区中未被发送的数据将被丢弃,并向对方发送一个RST包,TCP连接将不会进入TIME_WAIT状态(上面提到的对端不会复回ACK);

当第二个参数值不为0时,会在设置的时间段内继续尝试发送缓冲区的数据,如果超时则丢弃缓冲区的数据,并且调用close会阻塞直到设置的时间结束后才返回;

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335