Java Http多次請求復用同一連接示例詳解
概述
注:
- 本文乃是最簡單的實現(xiàn),真實場景要復雜麻煩的多
- 旨在闡述清晰多次HTTP請求復用一個連接的底層邏輯
早在HTTP/1.0時代,每次HTTP請求都要創(chuàng)建一個連接,而創(chuàng)建連接的過程需要消耗資源和時間,代價相對昂貴,為了減少資源消耗,縮短響應時間,就需要重用連接。在后來的HTTP/1.1中,引入了連接復用的機制,Http Header中加入Connection: keep-alive來告訴對方這個請求響應完成后先不忙關閉,這也是本篇文章的由來。
復用的基本條件
理論基礎
OSI是Open System Interconnection的縮寫,意為開放式系統(tǒng)互聯(lián)。國際標準化組織(ISO)制定了OSI模型,該模型定義了不同計算機互聯(lián)的標準,是設計和描述計算機網(wǎng)絡通信的基本框架。也就是如下七層模型:

當然也有大家熟知的五層模型,也就是把會話層、表示層、應用層合稱為應用層。耳熟能詳?shù)腡CP、UDP屬于數(shù)量稀少的傳輸層協(xié)議。在這之上的應用層協(xié)議百花齊放諸如:HTTP、SMTR、FTP......,然后很多中間件也自定義了通訊協(xié)議,比如Dubbo、Mysql。
讀到這里大家可能就已經(jīng)清醒的意識到,即使同屬應用層的協(xié)議,是否支持長連接也不盡相同。筆者想要傳達的一個認知:之所以能支持長連接,那是因為TCP經(jīng)歷三次握手建立連接之后,如果不出現(xiàn)其他意外是可以保證連接狀態(tài)的。也就是說應用層協(xié)議是否屬于長連接僅僅取決于成功建立TCP,發(fā)送一個請求之后,對該連接的處理策略:
- 如早期的HTTP每次發(fā)送請求,Server端回復完畢之后直接關閉則是短連接
- 如Mysql處理完一條SQL請求,然后繼續(xù)執(zhí)行下一個則是長連接
這其實就是我們的理論基礎,HTTP有希望支持長連接的前提是TCP本身就是長連接。
現(xiàn)實基礎
HTTP協(xié)議并非魔法,不是說新增一條規(guī)范,也不是簡簡單單的Header中加入Connection: keep-alive就能立馬支持長連接了。想要達到這個目的需要Client、Server端共同努力。
客戶端譬如Chrome瀏覽器,服務端譬如阿里OSS,像這樣兩端都支持了新的規(guī)范,HTTP才能快樂的成為長連接陣營中的一員。
獲取HTTP資源常見方式
因為JDK提供了相關工具、且平臺相關的第三方包也足夠優(yōu)秀,所以Java獲取HTTP資源并非難事。
@Slf4j
public class SinaPicDownload {
/* 微博上某個畫師的作品 */
static final String HTTP_URL = "https://wx3.sinaimg.cn/mw2000/006jQ3i8ly1h5k50zujydj35k0334kjo.jpg";
/* 下載之后放在顏如玉電腦的io文件下 */
static final String LOCAL = "/Users/admin/io/靈魂蓮華-皎月.jpeg";
public static void main(String[] args) {
try (
InputStream in = new URL(HTTP_URL).openStream();
FileOutputStream out = new FileOutputStream(LOCAL)
) {
byte[] buffer = new byte[1024 << 2];
int read;
while ((read = in.read(buffer)) > -1) {
out.write(buffer, 0, read);
}
out.flush();
}
catch (Throwable e) {
log.error("獲取HTTP資源失敗:", e);
}
}
}

配合Java 7之后提供的try-with-resources語法糖,你甚至僅僅只需要不到二十行的代碼就可以輕而易舉的達到目的,但是缺點也顯而易見,通過這種方法每次只能獲取一個資源,用完之后只能完畢。我當時就在想,Java怎么實現(xiàn)一次連接多次請求呢?
Transfer-Encoding
筆者在上文提到的理論基礎上推測到肯定可以使用Java提供的Socket建立TCP連接,關鍵問題是怎么跟Server端描述HTTP請求呢?

類比到現(xiàn)實生活中,兩者能順暢交流必然要求雙方都可以聽懂對方的語言。那HTTP有沒有一種Client、Server都能解析的規(guī)范呢,HTTP Transfer-Encoding正是在這種背景下應運而生。通俗的來講Transfer-Encoding就是一種雙方都約定好的格式,我按照這個格式Encoding,你按照這個格式Decoding,ta大概長這個樣子:

可想而知剛剛獲取那張圖片資源的是時候,我們肯定是這么跟新浪微博服務端說的:

聲明:
- 真實的Request Line與圖中一致
- Header其實復雜很多,配圖做了簡化
- 該請求Body為空,圖中略過
簡略實現(xiàn)
先聲明一些常量,以備后用
@Slf4j
public class ReusableHttp {
/* 顏如玉公司的OSS服務域名 */
static final String HOST = "****.oss-cn-zhangjiakou.aliyuncs.com";
static final int PORT = 80;
/* 顏如玉在OSS上放置的幾個資源 */
static final String[] URLS = new String[]{
"/context/reusable/gtyj.text",
"/context/reusable/tlyxqch.text",
"/context/reusable/yj.text",
"/context/reusable/ls.text"
};
/* CR = '\r'; LF = '\n'*/
static final byte[] CRLF = new byte[]{Chars.CR, Chars.LF};
static final String LOCAL_PATH = "/Users/admin/io/";
}
建立TCP連接,然后獲得輸出,輸入流
public static void main(String[] args) {
try {
try (
Socket socket = new Socket(HOST, PORT);
OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream()
) {
/* 復用連接,獲取資源 */
reusable(out, in);
}
}
catch (IOException e) {
log.error("請求出現(xiàn)異常", e);
}
}
寫出Request Line
/**
* Write Request Line
*
* RequestLine encoding規(guī)范
*
* **********************************************
* * method * sp * URL * sp * version * cr * lf *
* **********************************************
*/
static void writeRequestLine(OutputStream out, String url) throws IOException {
/* 注意空格一定要按照規(guī)定來擺放 */
out.write(("GET " + url + " HTTP/1.1").getBytes());
/* 最后再寫入一個回車、換行符表示Request Line結束 */
out.write(CRLF);
}
寫出Request Header
/**
* Write Request Header
*
* HeaderLine encoding規(guī)范
*
* *******************************************
* * header field name * : * value * cr * lf *
* *******************************************
* ....
* *******************************************
* * header field name * : * value * cr * lf *
* *******************************************
* ...
* ***********
* * cr * lf *
* ***********
* ***************
* * Entity Body *
* ***************
*/
static void writeHeaderLine(OutputStream out) throws IOException {
out.write("Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9".getBytes());
out.write(CRLF);
out.write("Accept-Encoding: gzip, deflate".getBytes());
out.write(CRLF);
out.write("Accept-Language: zh-CN,zh;q=0.9".getBytes());
out.write(CRLF);
out.write("Connection: keep-alive".getBytes());
out.write(CRLF);
out.write("Host: kuaimai-sheji.oss-cn-zhangjiakou.aliyuncs.com".getBytes());
out.write(CRLF);
/* 最后再寫入一個回車、換行符表示Request Header結束 */
out.write(CRLF);
}
因為是簡單的請求,所以直接省略Request Body。發(fā)出如上報文后,Server端會解析請求,然后回復。
/**
* 1.向Server端寫出請求
* 2.接受Server端回復
* 3.寫到顏如玉本地機器的io文件夾下
*
* @param out 往Server端寫出流
* @param in Server端往Client端寫入流
*/
static void reusable(OutputStream out, InputStream in) throws IOException {
for (int i = 0, s = URLS.length; i < s; i++) {
writeRequestLine(out, URLS[i]);
writeHeaderLine(out);
out.flush();
byte[] bytes = new byte[512];
in.read(bytes);
String file = LOCAL_PATH + i + ".text";
try (
FileOutputStream fo = new FileOutputStream(file)
) {
fo.write(bytes);
fo.flush();
}
catch (Throwable e) {
log.error("文件寫入出現(xiàn)異常", e);
}
}
}

可以看到功能已經(jīng)實現(xiàn),同一連接我反復請求了四次,最終得到四個資源。
以上就是Java Http多次請求復用同一連接示例詳解的詳細內(nèi)容,更多關于Java Http多請求復用的資料請關注腳本之家其它相關文章!
相關文章
SpringBoot使用maven指定依賴包的版本(解決示例)
我們在使用A依賴的時候,這個依賴有引入了第三方B依賴,這時候我想指定B依賴的版本號,下面?zhèn)€大家分享解決示例,對SpringBoot maven依賴包相關配置方法感興趣的朋友一起看看吧2024-04-04
SpringBoot整合Apache Ignite的實現(xiàn)
本文主要介紹了SpringBoot整合Apache Ignite的實現(xiàn),文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-10-10
Mybatis查不到數(shù)據(jù)查詢返回Null問題
mybatis突然查不到數(shù)據(jù),查詢返回的都是Null,但是 select count(*) from xxx查詢數(shù)量,返回卻是正常的。好多朋友遇到這樣的問題不知所措,下面小編通過本教程簡單給大家說明下2016-08-08
淺談spring使用策略模式實現(xiàn)多種場景登錄方式
本文主要介紹了spring使用策略模式實現(xiàn)多種場景登錄方式,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-12-12

