`
suichangkele
  • 浏览: 199937 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论
阅读更多

上一个文章说了http连接的缓存池,以及借出的过程,借出使用的是HttoRoute,先根据route定位到RouteSpecificPool,然后从RouteSpecificPool中借出一个httpConnection,借出后都是用一个Entry包装的,entry里面含有httpRouter+httpConnection。这一篇说一下借出之前的过程以及借出后的使用操作。

缓存的对象是CPool,他的使用是在HttpClientConnectionManager里面,这个类从他的名字就能看出来是管理HttpConnection的,他的实现类一般都是用的PoolingHttpClientConnectionManager,即使用缓存的httpConnection管理器,这个类的里面就会用到了CPool类,在创建一个PoolingHttpClientConnectionManager的时候可以发现:

public PoolingHttpClientConnectionManager(
	final HttpClientConnectionOperator httpClientConnectionOperator,
	final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory,
	final long timeToLive, final TimeUnit tunit) {
	super();
	this.configData = new ConfigData();//这个是一些配置参数,都是用于socket连接以及http通信的参数,后面会详细写
	this.pool = new CPool(new InternalConnectionFactory(this.configData, connFactory), 2, 20, timeToLive, tunit);//如果没有传递一个CPool,则要生产一个默认的,默认的里面会会设置每个Route最多可以有两个connection,一个CPool最多可以开10个connection,第一个参数是用于创建一个httpConnection的,暂时先忽略第一个参数
	this.pool.setValidateAfterInactivity(5000);//设置校验的最大时间,上一篇文章中说了如果一个PoolEnty的updateTime + 校验时间<当前时间,就要检查一下是否可以,如果没有设置校验时间默认是5秒。
	this.connectionOperator = Args.notNull(httpClientConnectionOperator, "HttpClientConnectionOperator");//ConnectionOperato是管理具体的http连接的,比如使用schema解析端口,dns确定ip,然后将本地一个socket和远端的socket进行连接
	this.isShutDown = new AtomicBoolean(false);
}

/**
 * Visible for test.
 */
PoolingHttpClientConnectionManager(
		final CPool pool,//也可以指定一个CPool,这样就不会那些配置很低的连接了
		final Lookup<ConnectionSocketFactory> socketFactoryRegistry,//这个类更简单,是用于确定socket类型的,如果是http的就是创建一个socket,如果是https的就会创建一个加密的socket.
		final SchemePortResolver schemePortResolver, 
		final DnsResolver dnsResolver) {
	super();
	this.configData = new ConfigData();
	this.pool = pool;
	this.connectionOperator = new DefaultHttpClientConnectionOperator(socketFactoryRegistry, schemePortResolver, dnsResolver);//功能同上
	this.isShutDown = new AtomicBoolean(false);
}

 看完了构造方法可以发现他里面创建了一个CPool,并更加熟悉了CPool的配置,CPool里面含有一个connectionFactory,用于创建http连接,不过这个过于底层,直接和http的请求的细节相关联,所以我没有看,而且不影响应用层的理解;在创建了CPool之后还可以使用方法修改pool的配置,比如;

@Override
public void setMaxTotal(final int max) {//修改总的连接的爽
	this.pool.setMaxTotal(max);
}
 @Override
public void setDefaultMaxPerRoute(final int max) {//修改默认的route对应的httpConnection的数量,如果某个route没有配置的话
	this.pool.setDefaultMaxPerRoute(max);
}
 @Override
public void setMaxPerRoute(final HttpRoute route, final int max) {//指定某个route最大的connection的数量
	this.pool.setMaxPerRoute(route, max);
}

 既然这个类管理了一个CPool,那么借出connection的方法就在这个类里面了,方法是:requestConnection

public ConnectionRequest requestConnection(final HttpRoute route,final Object state) {//根据httpRoute借出一个,返回的并不是一个PoolEnty或者是connection,而是用一个ConnectionReuqet封装了,
	Args.notNull(route, "HTTP route");
	final Future<CPoolEntry> future = this.pool.lease(route, state, null);//从pool中借出一个,此方法可能会借出一个,也可能等待,等待后可能最终会借出一个,也可能是超时。
	return new ConnectionRequest() {
		@Override
		public boolean cancel() {
			return future.cancel(true);
		}
		@Override
		public HttpClientConnection get(final long timeout,final TimeUnit tunit) throws InterruptedException, ExecutionException, ConnectionPoolTimeoutException {//再次调用get方法才会获得最终的connection,里面会调用leaseConnection方法
			return leaseConnection(future, timeout, tunit);
		}
	};
}

protected HttpClientConnection leaseConnection(final Future<CPoolEntry> future,	final long timeout,final TimeUnit tunit) throws InterruptedException, ExecutionException, ConnectionPoolTimeoutException {
	final CPoolEntry entry;
	try {
		entry = future.get(timeout, tunit);
		if (entry == null || future.isCancelled()) {
			throw new InterruptedException();
		}
		Asserts.check(entry.getConnection() != null, "Pool entry with no connection");
		return CPoolProxy.newProxy(entry);//这个方法将借出的CpoolEntry进行了封装,封装为一个HttpClientConnection,
	} catch (final TimeoutException ex) {
		throw new ConnectionPoolTimeoutException("Timeout waiting for connection from pool");
	}
}

对于连接的获取是在 ClientExecChain里面,httpClient自带了很多的exec,也就是http请求的发起对象,我就看了一个最简单的:MinimalClientExec,这个是最简单的http请求的执行器,仅仅执行最基础的操作,不会进行失败重连,如果有错误也不会处理而是抛出来,对于这个类和httpConnection、httpConnectionFactory一样,不要过多的去深究,因为内部的网络通信和http的具体协议都在这里面了,对于我们的应用层是没有必要知道这些的。仅仅看看他的exec方法,这个方法就是执行http请求的方法

@Override
public CloseableHttpResponse execute(final HttpRoute route,final HttpRequestWrapper request,final HttpClientContext context,final HttpExecutionAware execAware) throws IOException, HttpException {
	rewriteRequestURI(request, route);

	final ConnectionRequest connRequest = connManager.requestConnection(route, null);//从连接池里面获得connection
	if (execAware != null) {//不用看,没懂什么意思,但是不妨碍应用层面的了解
		if (execAware.isAborted()) {
			connRequest.cancel();
			throw new RequestAbortedException("Request aborted");
		} else {
			execAware.setCancellable(connRequest);
		}
	}

	final RequestConfig config = context.getRequestConfig();//获得这次请求的配置,这里面含有三个时间,下面有

	final HttpClientConnection managedConn;
	try {
		final int timeout = config.getConnectionRequestTimeout();//从连接池中获得connection的超时时间
		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) {
		Throwable cause = ex.getCause();
		if (cause == null) {
			cause = ex;
		}
		throw new RequestAbortedException("Request execution failed", cause);
	}

	final ConnectionHolder releaseTrigger = new ConnectionHolder(log, connManager, managedConn);//用一个对象封装connection,这个对象增加了是否可用以及可用时间的功能,以方便重用这个connection,所以起名叫做releaseTrigger
	try {
		if (execAware != null) {//
			if (execAware.isAborted()) {
				releaseTrigger.close();
				throw new RequestAbortedException("Request aborted");
			} else {
				execAware.setCancellable(releaseTrigger);
			}
		}

		if (!managedConn.isOpen()) {//刚创建出来的connection是没有open的,没有关联socket,此时要建立tcp连接
			final int timeout = config.getConnectTimeout();//建立连接的超时时间
			this.connManager.connect(managedConn,route,timeout > 0 ? timeout : 0,context);//这个地方就用到了连接的方法,下面有介绍
			this.connManager.routeComplete(managedConn, route, context);//标志建立连接完成了
		}
		final int timeout = config.getSocketTimeout();//socket的超时时间
		if (timeout >= 0) {
			managedConn.setSocketTimeout(timeout);
		}

//下面这一堆是用于发送http请求用的,不涉及应用层面,所以不用看
		HttpHost target = null;
		final HttpRequest original = request.getOriginal();
		if (original instanceof HttpUriRequest) {
			final URI uri = ((HttpUriRequest) original).getURI();
			if (uri.isAbsolute()) {
				target = new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme());
			}
		}
		if (target == null) {
			target = route.getTargetHost();
		}

		context.setAttribute(HttpCoreContext.HTTP_TARGET_HOST, target);
		context.setAttribute(HttpCoreContext.HTTP_REQUEST, request);
		context.setAttribute(HttpCoreContext.HTTP_CONNECTION, managedConn);
		context.setAttribute(HttpClientContext.HTTP_ROUTE, route);

		httpProcessor.process(request, context);
		final HttpResponse response = requestExecutor.execute(request, managedConn, context);
		httpProcessor.process(response, context);

		// The connection is in or can be brought to a re-usable state.
		if (reuseStrategy.keepAlive(response, context)) {//这里就看到了1.0和1.1的区别,1.1是keepalive的,而1.0则不是
			// Set the idle duration of this connection
			final long duration = keepAliveStrategy.getKeepAliveDuration(response, context);//判断剩余的时间是多少,从他的具体实现来看是查看了一下一个叫做timeout的heade来确认的,所以我猜测是由服务端确定的这个数字。
			releaseTrigger.setValidFor(duration, TimeUnit.MILLISECONDS);//标记这个connection在多少时间内不需要校验。
			releaseTrigger.markReusable();//设置为可重用
		} else {//如果是1.0的,则标记这个连接是不可复用的
			releaseTrigger.markNonReusable();
		}

		// check for entity, release connection if possible
		final HttpEntity entity = response.getEntity();
		if (entity == null || !entity.isStreaming()) {//这里的isSteaming我一直没有理解,难不成有的http不是基于stream的?我做过的试验都不会进入这个方法。如果有人知道这里的原因,请联系我,我的qq是:1308567317
			// connection not needed and (assumed to be) in re-usable state
			releaseTrigger.releaseConnection();
			return new HttpResponseProxy(response, null);
		} else {//我做的试验都是进入这个,返回的这个代理对象增加了一些功能,比如在关闭inpustream的时候可以自动释放连接。下一篇会看这个对象。
			return new HttpResponseProxy(response, releaseTrigger);
		}
	} catch (final ConnectionShutdownException ex) {
		final InterruptedIOException ioex = new InterruptedIOException(
				"Connection has been shut down");
		ioex.initCause(ex);
		throw ioex;
	} catch (final HttpException ex) {
		releaseTrigger.abortConnection();
		throw ex;
	} catch (final IOException ex) {
		releaseTrigger.abortConnection();
		throw ex;
	} catch (final RuntimeException ex) {
		releaseTrigger.abortConnection();
		throw ex;
	}
}

 上面说了如何使用connection的,大致的逻辑是先借一个,然后判断其有没有连接socket(如果是新建的就没有),如果没有就要调用connManager.connect(),即将当前的connection绑定到一个socket上。这个方法还是很关键的,看下PoolingHttpClientConnectionManager.connect方法

 @Override
public void connect(final HttpClientConnection managedConn,final HttpRoute route,final int connectTimeout,final HttpContext context) throws IOException {//给一个connection添加底层的socket连接,第一个参数是要添加socket的connection,第二个是route,第三个则是指定的tcp连接的连接超时时间。
	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();
	}
	final InetSocketAddress localAddress = route.getLocalSocketAddress();
	SocketConfig socketConfig = this.configData.getSocketConfig(host);//获得socket的配置,比如缓冲大小、是否复用、linger时间
	if (socketConfig == null) {
		socketConfig = this.configData.getDefaultSocketConfig();
	}
	if (socketConfig == null) {
		socketConfig = SocketConfig.DEFAULT;
	}
	this.connectionOperator.connect(conn, host, localAddress, connectTimeout, socketConfig, context);
}

 上面的连接方法是委托给了 DefaultHttpClientConnectionOperator connectionOperator属性,这个DefaultHttpClientConnectionOperator 类就是将创建的httpConnection和某个socket关联起来,因为http最终还是要使用tcp连接,使用socket,那一块的功能代码都在这个类里面了。

看一下这个类的重要方法:

public class DefaultHttpClientConnectionOperator implements HttpClientConnectionOperator {

	static final String SOCKET_FACTORY_REGISTRY = "http.socket-factory-registry";
	private final Log log = LogFactory.getLog(getClass());
	private final Lookup<ConnectionSocketFactory> socketFactoryRegistry;
	private final SchemePortResolver schemePortResolver;
	private final DnsResolver dnsResolver;

	public DefaultHttpClientConnectionOperator(final Lookup<ConnectionSocketFactory> socketFactoryRegistry,	final SchemePortResolver schemePortResolver,final DnsResolver dnsResolver) {
		super();
		Args.notNull(socketFactoryRegistry, "Socket factory registry");
		this.socketFactoryRegistry = socketFactoryRegistry;//用于获得socketFactory,根据schema获得,如果是http的就返回一个能创建socket的registry,如果是https的就返回一个能创建加密socket的registry
		this.schemePortResolver = schemePortResolver != null ? schemePortResolver :DefaultSchemePortResolver.INSTANCE;//在连接的时候要指定端口,这个用于根据schema确定端口
		this.dnsResolver = dnsResolver != null ? dnsResolver :	SystemDefaultDnsResolver.INSTANCE;//用于根据dns确定ip
	}

	@SuppressWarnings("unchecked")
	private Lookup<ConnectionSocketFactory> getSocketFactoryRegistry(final HttpContext context) {
		Lookup<ConnectionSocketFactory> reg = (Lookup<ConnectionSocketFactory>) context.getAttribute(SOCKET_FACTORY_REGISTRY);
		if (reg == null) {
			reg = this.socketFactoryRegistry;
		}
		return reg;
	}

        //用于将创建的httpConnection和socket关联起来,因为http最终还是要靠tcp连接的,所以还是要使用socket,刚刚创建的httpConnection的isOpen返回的额是false,此时就要进行connect,进入这个方法,
	public void connect(final ManagedHttpClientConnection conn,//创建的连接,注意这个连接只是制定了连接了具体参数和怎么连接,并没有底层的socket,使用socket需要经过这个方法
			final HttpHost host,//连接的远端服务器
			final InetSocketAddress localAddress,
			final int connectTimeout,//连接的超时时间,即tcp的超时时间
			final SocketConfig socketConfig,//建立socket连接的配置参数,比如socketTimeOUT,是否重用,soLinger,keepAlive,接受缓存和发送缓存大小
			final HttpContext context) throws IOException {//httpContext在整个httpConnection的使用过程中使用
		final Lookup<ConnectionSocketFactory> registry = getSocketFactoryRegistry(context);
		final ConnectionSocketFactory sf = registry.lookup(host.getSchemeName());//根据schema确定socketFactory的类型,分为http的和https的,获得一个socketFactory,用于产生一个socket
		if (sf == null) {
			throw new UnsupportedSchemeException(host.getSchemeName() +" protocol is not supported");
		}
		final InetAddress[] addresses = host.getAddress() != null ?new InetAddress[] { host.getAddress() } : this.dnsResolver.resolve(host.getHostName());
		final int port = this.schemePortResolver.resolve(host);//如果host中没有指定port则根据schema确定port,如果是http的就是80,https的是433
		for (int i = 0; i < addresses.length; i++) {
			final InetAddress address = addresses[i];
			final boolean last = i == addresses.length - 1;

			Socket sock = sf.createSocket(context);//产生一个socket   下面都是配置socket的参数
			sock.setSoTimeout(socketConfig.getSoTimeout());//包的最大间隔时间
			sock.setReuseAddress(socketConfig.isSoReuseAddress());//是否使用time_wait阶段的端口,
			sock.setTcpNoDelay(socketConfig.isTcpNoDelay());
			sock.setKeepAlive(socketConfig.isSoKeepAlive());
			final int linger = socketConfig.getSoLinger();
			if (linger >= 0) {
				sock.setSoLinger(true, linger);
			}
			conn.bind(sock);//这个httpConnection使用这个socket

			final InetSocketAddress remoteAddress = new InetSocketAddress(address, port);
			if (this.log.isDebugEnabled()) {
				this.log.debug("Connecting to " + remoteAddress);
			}
			try {
				sock = sf.connectSocket(connectTimeout, sock, host, remoteAddress, localAddress, context);//建立连接,不过我没懂这里为啥要写两遍conn.bind(sock)
				conn.bind(sock);
				return;
			} catch (final SocketTimeoutException ex) {
				if (last) {
					throw new ConnectTimeoutException(ex, host, addresses);
				}
			} catch (final ConnectException ex) {
				if (last) {
					final String msg = ex.getMessage();
					if ("Connection timed out".equals(msg)) {
						throw new ConnectTimeoutException(ex, host, addresses);
					} else {
						throw new HttpHostConnectException(ex, host, addresses);
					}
				}
			} catch (final NoRouteToHostException ex) {
				if (last) {
					throw ex;
				}
			}
		}
	}
}

上面这个类的最终的作用就是创建一个socket然后和服务端进行连接,并将一个socket关联到一个httpConnection上,刚创建的httpConnection的isOpen方法是false,此时就要进行connect。这个部分的代码太深了,在BHttpConnectionBase中,这个类是所有httpConnection的基类,我没仔细看,我认为这个类已经和http协议涉及的太密切了,不再是应用层的东西了,所以就没有看。

 

 这篇博客就是讲了httpConnection是如何使用的,主要就是从pool中借出一个来,然后添加底层的socket tcp连接,然后使用其发送请求,至于详细的发送请求的那一块直接略过了,因为那些是http自己的小细节,已经不属于我们的应用层了,所以就没看。

至此http的存储以及获取还有使用已经都看了,下一篇看一下httpConnection的归还以及关闭。

 

分享到:
评论

相关推荐

    httpclient-tutorial

    HttpClient是Apache HTTP Components项目中用于Java的一个重要的客户端HTTP连接库,广泛用于网络编程,特别是网络爬虫的开发。以下是对文件内容的详细解读和知识点的梳理: 1. HTTP客户端的基础知识 - HTTP请求:...

    HttpClient实现文件下载

    3. **客户端程序**:使用 HttpClient 发起 HTTP 请求来获取文件。 #### 三、关键概念解释 - **HttpClient**:Apache HttpClient 是 Apache HttpComponents 项目中的一个子项目,提供了一组高级APIs用于发送HTTP...

    httpclient4.5.5官方手册

    Apache HttpClient是一个广泛使用在Java应用程序中,用于执行HTTP协议相关的操作的开源库。本手册重点介绍了HttpClient 4.5.5版本的使用方法和相关知识点,为读者提供了一个详尽的学习和参考材料。 首先,在基础...

    VB源程序代码加解读

    VB还可以进行网络编程,例如使用Winsock控件实现TCP/IP通信,或者使用HTTPClient类进行Web请求。这使得VB能够开发网络应用,如FTP客户端、邮件发送程序等。 十、API调用 VB可以通过 Declare 语句调用操作系统级别...

    10-图解HTTP1

    以下是对该书主要内容的详细解读: 1. **HTTP请求**: - **请求行**:HTTP请求的第一部分,包括方法(如GET或POST)、URL和HTTP版本号,用于告诉服务器如何处理请求。 - **请求报头**:包含了客户端信息、请求...

    Android网络编程 王家林

    - 使用HttpClient:介绍Apache HttpClient库,及其在Android中的应用,包括GET和POST请求、处理响应数据等。 3. **异步网络请求** - AsyncTask:解析Android提供的异步任务类,用于在后台线程执行网络请求,避免...

    jeecms lib2.zip

    10. **httpcore-4.0.1.jar**:这是Apache HttpCore库,提供HTTP协议的基本实现,包括连接管理、传输编码等,是HttpClient的基础组件。 这些库的集成使用,体现了Jeecms在构建过程中对性能优化、数据库交互、安全...

    java 发短信例子

    在Java编程语言中,发送短信是一项常见的功能,...以上就是使用Java发送短信涉及的关键知识点,包括API的使用、JSP集成、jar包管理以及文档解读等。实践中,你可能需要结合实际项目需求和特定的短信服务商进行调整。

    疯狂android讲义源码11-19章

    - HTTP通信:使用HttpURLConnection或HttpClient进行网络请求,处理响应数据。 - JSON解析:学习Gson、Jackson等库,将JSON数据转换为Java对象。 - AsyncTask:异步任务处理,提供在UI线程中更新进度的方法。 - ...

    esp8266太空人网络天气时间源码(白色款).zip

    开发者可能使用了ESP8266WiFi库来建立和维护Wi-Fi连接,使用HTTP客户端库如ESP8266HTTPClient来从网络获取天气API的数据。这些API通常提供JSON格式的天气信息,包括温度、湿度、风速等。代码会解析这些数据并将其...

    C#100个实例程序

    - WebClient和HttpClient类:进行HTTP请求和响应处理。 12. **数据库操作** - ADO.NET:连接数据库、执行SQL语句、填充DataSet和DataView。 - LINQ to SQL:使用C#查询数据库,简化数据操作。 13. **设计模式**...

    Android25图灵聊天项目------HttpURLConnection请求get文本数据

    2. **设置请求属性**:你可以设置连接超时、读取超时,以及请求方法为GET。此外,还可以设置Accept头来指定服务器返回的数据类型。 ```java connection.setConnectTimeout(5000); connection.setReadTimeout...

    Android模拟测验库.docx编程资料

    - **知识点概述**:使用`HttpClient`发送GET请求。 - **详细解释**: - `HttpGet`类:用于构建HTTP GET请求。 - `Get`、`URLConnection`、`HttpPost`:这些不是用于构建GET请求的合适类或接口。 ### ...

    java解析hj212.rar

    3. **处理网络通信**:根据传输层协议,使用Java的Socket编程或者HttpClient库进行网络通信。接收服务器发送的HJ212数据,或者发送请求获取数据。 4. **异常处理**:在解析和通信过程中,可能会遇到各种错误,如...

    android程序员简历12.doc

    - **网络通信**:他能有效地使用HTTP、Socket通信,以及XML和JSON解析,实现网络数据交换。 - **设计模式**:张利洁了解并能应用常见的设计模式,如工厂模式和单例模式,这有助于提高代码的灵活性和可复用性。 2....

    SpringJarSpringJarSpringJar

    4. `commons-httpclient.jar`:Apache Commons HttpClient,提供了HTTP客户端API,用于进行HTTP通信,Spring使用它来处理HTTP请求。 5. `commons-lang.jar`:Apache Commons Lang,包含了一些Java语言功能的扩展,...

    100 Recipes for Programming Jav - Jamie Munro

    - **解释**: 介绍了如何使用Apache HttpClient来处理与服务器之间通信过程中遇到的无效SSL证书问题。这通常涉及到信任管理器的配置和SSL上下文的设置。 ##### 食谱20: Java中日期时间的转换 - **知识点**: 日期时间...

    达内it培训 java培训电子书 内部资料 系列22 JAVA企业面试题精选 pdf

    2. HttpURLConnection与HttpClient的选择与使用。 3. JSON、XML数据的解析与序列化库的使用,如Jackson、Gson、Fastjson。 六、JVM内存管理 1. 堆内存与栈内存的区别,新生代与老年代的划分。 2. 分代垃圾收集器:...

    W5500IO模块用户使用手册.pdf

    本文主要针对W5500IO模块的用户使用手册进行解读,并提炼出其中的重要知识点。 ### W5500芯片简介 W5500芯片是一个带有全硬线化TCP/IP协议栈的以太网控制器芯片。它支持8个独立的socket通信,支持TCP, UDP, IPv4, ...

    Java知识图谱-架构师课程系列

    7. **图7:互联网应用技术-中间件.jpg**:中间件可能是重点,比如Tomcat、Jetty等应用服务器,Nginx反向代理服务器,以及Log4j、Apache HttpClient等工具的使用。 8. **图8:互联网应用技术-解决方案篇.jpg**:这...

Global site tag (gtag.js) - Google Analytics