目录
1、网络请求
网络是用物理链路将各个孤立的工作站或主机相连在一起,组成数据链路,从而达到资源共享和通信的目的。通信是人与人之间通过某种媒体进行的信息交流与传递。网络通信是通过网络将各个孤立的设备进行连接,通过信息交换实现人与人,人与计算机,计算机与计算机之间的通信。按通信执行类型可以分为同步通信(请求)和异步通信(请求),按建立通信的方式可以分为短连接通信和长连接通信。
2、同步与异步的区别与使用
同步请求:客户端构建请求 --> 提交请求 --> 等待服务器处理 --> 服务器返回信息 --> 客户端处理返回信息。
举一个不太恰当的例子——你要买一个三分甜、少冰、加椰果和燕麦的奶茶【构建请求内容】,你走到柜台跟服务员说了要这么一杯奶茶【提交请求】,然后服务员就转身去做这杯奶茶了【服务器处理】,这个时候你只能站在那里等着,直到服务员把这杯奶茶给你【同步等待】,你拿完就走回单位上班了【处理信息与后续操作】;
异步请求:构建请求并设置回调函数处理服务器返回信息 --> 提交请求 --> 服务器处理(客户端继续其他工作)–> 服务器返回信息 --> 执行回调函数处理信息。
再举一个不太恰当的例子——你要买一个三分甜、少冰、加椰果和燕麦的奶茶【构建请求内容】,同时雇了一个小弟,对他说待会把这杯奶茶送到我的工作单位【设置回调函数】,然后你跟服务员说要这么一杯奶茶,然后服务员转身去做这杯奶茶,同时你也直接走回单位了【异步执行】,等服务员做完这杯奶茶后,小弟就直接拿着这杯奶茶到单位给你【执行回调函数内容】;
对于同步请求,比较适合前后有依赖的场景,如果后一个请求需要前一个请求的返回结果,这就需要同步请求了。当然,缺点也很明显——需要等待服务器返回的结果,期间只能干等。
对于异步请求,比较适合单独的请求场景,发送请求后就继续干其他活,剩余的交给回调函数小弟做,也不耽误手上的活。缺点就是难以难以协调前后有依赖关系的请求,构造的回调函数相对复杂。不过,这种场景相对较少,所以一般还是建议使用异步请求。
3、请求长连接与短连接的区别与使用
HTTP的长连接和短连接本质上是TCP长连接和短连接。长连接是指客户端与服务器端一旦建立连接以后,可以进行多次数据传输而不需重新建立连接 ,而短连接则每次数据传输都需要客户端和服务器端建立一次连接 。
长连接的优势在于省去了每次数据传输连接建立的时间开销,能够大幅度提高数据传输的速度,对于P2P应用十分适合。而对于诸如Web网站之类的B2C应用,并发请求量大,每一个用户又不需频繁的操作的场景下,维护大量的长连接对服务器无疑是一个巨大的考验。此时,短连接可能更加适用,但是短连接每次数据传输都需要建立连接,我们知道HTTP协议的传输层协议是TCP协议,TCP连接的建立和释放分别需要进行3次握手和4次握手,频繁的建立连接即增加了时间开销,同时频繁的创建和销毁Socket同样是对服务器端资源的浪费。所以对于需要频繁发送HTTP请求的应用,需要在客户端使用HTTP长连接 。
参考:TCP的三次握手与四次挥手理解及面试题
对于3次握手和4次握手,我接着上面买奶茶的场景进行深化描述。
第1次握手:客户端发送syn包(syn=x)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号;【顾客站在柜台前对服务员说:你好。此时顾客在等着服务员回答。】
第2次握手:服务器收到syn包,确认客户的SYN(ack=x+1),同时自己也发送一个SYN包(syn=y),即SYN+ACK包,此时服务器进入SYN_RECV状态;【服务员听到之后,礼貌地回答:您好。此时服务员知道有人找她。】
第3次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态。【顾客听到服务员回复后,对服务员说:我要买奶茶。此时服务员知道眼前这个人要买奶茶,建立起连接。】
数据传输:客户端与服务器进行数据交流。【服务员等着顾客说要什么样的奶茶,顾客等着服务员把奶茶、袋子、吸管、勺子拿给他】
第1次挥手:客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u,此时,客户端进入FIN-WAIT-1(终止等待1)状态。【顾客检查奶茶、袋子、吸管、勺子都有了之后,对服务员说:你好,东西齐了。】
第2次挥手:服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。【服务员听到之后,对顾客说:好的,请稍等一下,给你小票。】
最后的数据传输:客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。【服务员笑着把最后小票递给顾客。此时服务员与顾客交接小票】
第3次挥手:服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。【服务员对顾客说:请问还有其他需要吗?】
第4次挥手:客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。服务器只要收到了客户端发出的确认,立即进入CLOSED状态。【顾客对服务员说:没有了,谢谢。然后顾客转身走了,服务员也停下手上工作,等着下一位顾客到来。】
对于短连接,每次请求都按照上面的流程走一遍,这对于客户端和服务端都要有一定的对话开销,这种效率较低。 而对于长连接,在进行3次握手之后就一直保持着数据传输,节省了多次握手的开销。下面对于频繁请求,优化一下买奶茶的场景。
奶茶点开业后,生意火爆。午饭后,旁边有一家公司人人都想要喝这家的奶茶,但是如果每个人都去排队,然后服务员对每个人都这么来一遍,可能排到上班有些人都喝不上奶茶。所以,他们想了个办法——找一个奶茶代购员(长连接)。
3次握手:【奶茶代购员(顾客)与服务员进行3次握手后,建立起连接,进入数据传输阶段】
奶茶请求1:【代购员对服务员说:要××奶茶一杯。完成后拿给员工1。此时,代购员没有说东西拿完了,服务员进入等待】
奶茶请求2:【然后,代购员接着说::还要××奶茶一杯。完成后拿给员工2。此时,代购员还是不走,服务员进入等待】
奶茶请求n:【如此反复,这为奶茶代购员就一直跟服务员对接,免去了一遍遍礼貌的问候,直接说要什么样的奶茶】
虽然这样方便了这家公司的员工,但给奶茶店带来了压力。人家服务员小姐姐就一直被代购员占着,也服务不了其他顾客。当然,后面人家店里也出了规定要求服务员只能服务顾客一定时间(2小时),如果这个时间结束时顾客没有点奶茶了,就不做这位顾客的生意,以保证能照顾到其他顾客。
补充:HTTP连接是无状态的(协议对于事务处理没有记忆能力,服务器不知道客户端是什么状态。也就是说,打开一个服务器上的网页和上一次打开这个服务器上的网页之间没有任何联系),这样很容易给我们造成HTTP连接是短连接的错觉,实际上HTTP1.1默认是持久连接(长连接),HTTP1.0默认是短连接,也可以通过在请求头中设置Connection:keep-alive使得连接为长连接。
4、单个长连接与连接池的使用
连接池管理的对象是长连接,连接池技术作为创建和管理连接的缓冲池技术,目前已广泛用于诸如数据库连接等长连接的维护和管理中,能够有效减少系统的响应时间,节省服务器资源开销。其优势主要有两个:其一是减少创建连接的资源开销,其二是资源的访问控制 。对于存在并发访问或大量频繁访问的应用,需要在客户端使用连接池。
由第三节我们知道长连接能够减少建立连接的时间,而连接池则是能够减少创建连接的时间。
建立连接:是指一个客户端对象与服务端进行通信;
创建连接:是指客户端生成一个与服务端通信的对象。
还是用买奶茶来作为案例讲解吧。
有了第三节买奶茶的故事后,奶茶店生意更加火爆了。周围15个公司300号员工都喜欢喝这家奶茶。虽然奶茶店也多招了4位服务员(目前5位服务员),但奶茶店还是麻烦——如果每个公司都找一个奶茶代购员的话,服务员完全不够对接;员工也遇到了麻烦——找一名奶茶代购员是很花钱的;奶茶代购员也遇到了麻烦——有时一家公司就买2、3杯奶茶,买完自己就没事干了。因此,三方进行讨论后得到一个优化方案——把几位奶茶代购员拉进微信群(长连接池),员工想要什么奶茶直接在群里发布,空闲代购员看到之后跟服务员点奶茶,完成后拿给员工。这样既缓解了店家的压力,又方便员工找奶茶代购员,也让奶茶代购员能一直有活干,一箭三雕。
5、什么是HttpClient
HttpClient 是Apache Jakarta Common 下的子项目,可以用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。通常应用在Java项目中,同类型网络请求库有OkHttp、HttpURLConnection等。
6、HttpClient的使用方法
- 使用帮助类HttpClients创建HttpClient对象;
- 基于要发送的HTTP请求类型创建HttpGet或者HttpPost实例;
- 使用addHeader方法添加请求头部,诸如User-Agent, Accept-Encoding等参数;
- 可调用HttpGet、HttpPost共同的setParams(HetpParams params)方法来添加请求参数(对于HttpPost对象而言,也可调用setEntity(HttpEntity entity)方法来设置请求参数);
- 通过执行此HttpGet或者HttpPost请求获取Response实例;
- 从此Response实例中获取状态码,错误信息,以及响应页面等等;
- 释放连接。无论执行方法是否成功,都必须释放连接。
6.1、同步请求方法,不含连接池。
优点:简单易懂;缺点:通信开销大,不会自动关闭连接,对服务器的负荷大,无法进行异步通信。【不建议使用】
main(){
int statusCode = 0;//通信状态码
// 构造请求方法
String postUrl = "";
String postContent = "";
PostMethod postMethod = new PostMethod(postUrl);
byte[] b = postContent.getBytes("UTF-8");
InputStream is = new ByteArrayInputStream(b,0,b.length);
RequestEntity re = new InputStreamRequestEntity(is,b.length,"text/xml; charset=UTF-8");
postMethod.setRequestEntity(re);
// 构造请求客户端
HttpClient httpClient = new HttpClient();
// 执行post方法
statusCode = httpClient.executeMethod(postMethod);
}
6.2、同步请求方法,含5个连接的连接池。
优点:简单易懂;缺点:连接池量小,没有定义各种通信配置,无法进行异步通信。适合访问频率不高或单次通信的应用场景。
mian(){
int statusCode = 0;//通信状态码
// 构造请求方法
String postUrl = "";
String postContent = "";
HttpPost httpPost = new HttpPost(postUrl );
StringEntity entity = new StringEntity(postContent );
entity.setContentType("UTF-8");
httpPost.setEntity(entity);
// 构造请求客户端
CloseableHttpClient httpClient = HttpClients.createDefault();
// 执行post方法
CloseableHttpResponse response = httpClient.execute(httpPost);
statusCode = response.getStatusLine().getStatusCode();
httpPost.releaseConnection();//释放连接资源
}
6.3、同步请求方法,自定义连接池与通信配置。
优点:配置灵活;缺点:无法进行异步通信。适合访问频率高、且有前后通信依赖的应用场景。
参考:HttpClient与CloseableHttpClient
private CloseableHttpClient getCloseableHttpClient() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException{
//配置SSL证书
TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;
SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom()
.loadTrustMaterial(null, acceptingTrustStrategy)
.build();
SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext
,null, null, NoopHostnameVerifier.INSTANCE);
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", csf)
.build();
//创建连接池
PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager (registry);
connManager.setMaxTotal(100);
connManager.setDefaultMaxPerRoute(100);
RequestConfig requestConfig = RequestConfig.custom()
.setSocketTimeout(60000)
.setConnectTimeout(60000)
.setConnectionRequestTimeout(10000)
.build();
return HttpClients.custom()
.setConnectionManager(connManager)
.setDefaultRequestConfig(requestConfig)
.build();
}
//配置Post方法
private HttpPost getHttpPost(String address, String soap) throws IOException{
HttpPost httpPost = new HttpPost(address);
StringEntity entity = new StringEntity(soap);
entity.setContentType("UTF-8");
httpPost.setEntity(entity);
return httpPost;
}
mian(){
int statusCode = 0;//通信状态码
// 构造请求方法
String postUrl = "";
String postContent = "";
HttpPost httpPost = getHttpPost(postUrl, postContent );
// 同步执行方案,含连接池
CloseableHttpClient httpClient = getCloseableHttpClient();
CloseableHttpResponse response = httpClient.execute(httpPost);
statusCode = response.getStatusLine().getStatusCode();
}
6.4、异步请求方法,自定义连接池与通信配置。
优点:配置灵活,能够异步通信,解放客户端;缺点:构建回调函数存在一定难度。适合访问频率高、且有前后独立通信的应用场景。
参考:异步httpclient(httpasyncclient)的使用与总结
//获取异步的客户对象,含连接池
private CloseableHttpAsyncClient getCloseableHttpAsyncClient(){
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(50000)
.setSocketTimeout(600000)
.setConnectionRequestTimeout(1000)
.build();
//配置io线程
IOReactorConfig ioReactorConfig = IOReactorConfig.custom().
setIoThreadCount(Runtime.getRuntime().availableProcessors())
.setSoKeepAlive(true)
.build();
//设置连接池大小
ConnectingIOReactor ioReactor=null;
try {
ioReactor = new DefaultConnectingIOReactor(ioReactorConfig);
} catch (IOReactorException e) {
e.printStackTrace();
}
PoolingNHttpClientConnectionManager connManager = new PoolingNHttpClientConnectionManager(ioReactor);
connManager.setMaxTotal(100);
connManager.setDefaultMaxPerRoute(100);
return HttpAsyncClients.custom().
setConnectionManager(connManager)
.setDefaultRequestConfig(requestConfig)
.build();
}
//配置Post方法
private HttpPost getHttpPost(String address, String soap) throws IOException{
HttpPost httpPost = new HttpPost(address);
StringEntity entity = new StringEntity(soap);
entity.setContentType("UTF-8");
httpPost.setEntity(entity);
return httpPost;
}
mian(){
int statusCode = 0;//通信状态码
// 构造请求方法
String postUrl = "";
String postContent = "";
HttpPost httpPost = getHttpPost(postUrl, postContent );
// 异步执行方案,含连接池
//异步执行方案,含连接池
CloseableHttpAsyncClient httpClient = getCloseableHttpAsyncClient();
httpClient.start();
Future<HttpResponse> responseFuture = httpClient.execute(httpPost, new FutureCallback<HttpResponse>() {
// 构造回调函数
@Override
public void completed(HttpResponse httpResponse) {
System.out.println(httpResponse.getEntity().toString());
}
@Override
public void failed(Exception e) {
System.out.println(e.toString());
}
@Override
public void cancelled() {
}
});
// 轮询任务是否已经完成,或可以执行做其他的事情
while (!responseFuture.isDone()) {
try {
Thread.sleep(2000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
try {
statusCode = responseFuture.get().getStatusLine().getStatusCode();
}catch (Exception e ){
e.printStackTrace();
}
}