文章目錄
Https協議通信過程
https通信一般使用非對稱加密算法進行密鑰傳遞,使用對稱加密算法進行後續業務數據加密傳輸,一次完整的Https通信過程如下:
- TCP三次握手(建立通信雙方可靠連接)
- 通信協議協商(確認客戶端與服務端加密算法)
- 驗證CA證書(客戶端驗證服務器CA證書合法性)
- 傳遞會話密鑰(客戶端與服務器協商會話密鑰)
- 加密通信(使用協商加密算法和會話密鑰進行加密通信)
TCP三次握手(建立可靠通信連接)
TCP是面向連接的協議,客戶端與服務器進行通信之前,需要建立可靠的連接。因此TCP協議通過三次握手來建立客戶端與服務器的可靠連接。
先了解一下涉及的關鍵詞,其中比較重要的字段有:
-
Seq:序列號(Sequence number) 佔32位,用來對TCP發起方發送的報文進行標記。
-
ack:確認序列號(Acknowlegment number) 佔32位,ack=Seq+1(即確認序列號等於發送發的Seq+1)
注意:只有ACK標誌位爲1時,該字段纔有效。
-
Flags:標誌位(Flags)共6個,即URG、ACK、PSH、RST、SYN、FIN等。具體含義如下:
-
SYN:該標誌位,表示請求建立一個新連接。
-
ACK:該標誌位,表示確認序列號有效。
-
FIN:該標誌位,表示釋放一個連接。
-
RST:該標誌位,表示重置連接
-
URG:該標誌位,表示緊急指針(urgent pointer)有效
-
PSH:該標誌位,表示接收方應該儘快將這個報文交給應用層
-
TCP三次握手示意圖
- 【第一次握手】客戶端向服務器發送一個TCP數據包,等待服務器確認,並自己進入
SYN_SENT
狀態,數據包包含:- 標誌位SYN:表示請求服務器連接新連接。
- 序列號Seq:客戶端發送TCP數據包序列號。
- 【第二次握手】服務器收到客戶端的TCP數據包之後,結束
LISTEN
監聽階段,返回給客戶端一個TCP數據包,自己進入SYN_REVD狀態,數據包包含:- 標誌位SYN:告訴客戶端允許建立連接
- 標誌位ACK:確認序列號有效(即告訴客戶端服務器能收到你的數據)
- 序列號Seq:服務端生成的序列號
- 確認序列號ack(客戶端的Seq+1):服務端返回給客戶端的確認序列號(客戶端的序列號+1)
- 【第三次握手】客戶端收到服務的數據包,對ack確認序列號進行驗證(自身的Seq+1於其做比對),如果驗證通過,則會回覆給服務器一個數據包,並進入
ESTABLELISTED
建立階段,數據包包含:- 標誌位ACK:確認序列號有效(告訴服務器,我知道你收到我發的數據了)。
- 序列號Seq(服務返回的ack):將服務器返回的確認序列號作爲自己的Seq。
- 確認序列號ack(服務器的Seq+1):將服務器的Seq序列號+1作爲確認序列號
隨後,服務器收到來自客戶端的TCP報文之後,驗證ack之後,明確了從服務器到客戶端的數據傳輸是正常的。結束SYN-SENT階段,進入ESTABLISHED階段。至此TCP三次握手結束,客戶端與服務器已經建立了可靠的連接,後續可進行數據通信。
注意:握手中涉及的Seq和ack值都是在初始值基礎上進行計算的,一旦中途出現某一方發送的TCP報文丟失,變無法繼續進行握手,以此確保了"三次握手"的順利完成,保證了TCP報文傳輸的連貫性。
通信協議協商(確認通信雙方加密算法)
Https在經過TCP三次握手客戶端與服務器建立了可靠通信連接之後,緊接着客戶端與服務器要進行通信協議的協商,來確定後續的通信加密方式,具體流程如下:
- 客戶端發送一段消息給服務器攜帶支持的
加密算法
(圖中Cipher Suites)和一段隨機數字
Random1。其中隨機數字Random1會在後面的對稱加密中用到
- 服務器收到後,會在客戶端支持的加密算法中選擇一個自己也支持的加密算法,作爲後面通信使用的加密算法以及隨機數字Random2,並告知客戶端。(本次實驗中服務端選擇TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 作爲加密算法)
證書驗證
如果上一步通信協議協商,服務器支持客戶端的通信協議,則服務端發送證書到客戶端進行身份驗證(CA證書認證)。在進行證書驗證之前我們先了解一下Https證書認證工作流程
以及證書鏈
-
Https證書認證工作流程
- 服務器生成一對密鑰,私鑰自己留着(用來解密客戶端使用數字證書內的公鑰加密的數據),
公鑰
及部分個人信息
提交給數字證書認證機構(CA)。 - CA進行審覈,並用
CA自己的私鑰
對服務器提供的公鑰及信息進行簽名生成數字證書
,並將此證書發給提交者。 - 在https建立連接時,客戶端從服務器獲取數字證書,
客戶端使用內置CA根證書的公鑰,對服務器證書內CA私鑰簽名的數據進行驗籤
,如果驗籤通過,說明該數字證書確實是CA頒發的,從而可以確認該證書中的公鑰確實是合法服務器端提供的。
- 服務器生成一對密鑰,私鑰自己留着(用來解密客戶端使用數字證書內的公鑰加密的數據),
-
證書鏈認證
如果服務器的證書不是CA根證書頒發,而是通過
中級證書機構
簽名頒發的證書,服務器在 SSL 握手期間不會僅向客戶端發送它的證書,而是發送一個證書鏈,包括服務器 CA 以及到達可信的根 CA 所需要的任意中間證書,客戶端或瀏覽器使用內置的CA根證書公鑰對中間證書進行驗證,如果根證書信任該中級證書,則該中級證書頒發的證書也是可信的,這就是證書鏈了。例如,主要在本次的消息中的兩個證書:
中級證書頒發機構的證書
及其頒發的服務器證書
傳遞會話密鑰
上一步客戶端對服務器證書認證之後,客戶端生成一個隨機數Random3,然後使用服務器證書內的公鑰,對Random3加密後發送給服務器(只有服務器使用對應的私鑰才能解開)。這樣https加密的密鑰傳遞流程纔算走完。(此時客戶端和服務器都具備了隨機數Random1+Random2+Random3)
加密通信
由於非對稱加密的運算成本較高,所以非對稱加密算法一般只用來進行祕鑰傳遞
,所以完成祕鑰傳遞之後,客戶端一般會使用之前與服務器協商的加密算法,將Random1+Random2+Random3作爲對稱加密算法的祕鑰進行加密通信。至此整個Https協議通信結束(包含:建立連接、通信協議協商、證書認證、密鑰傳遞、加密通信)
Https證書認證在Android中應用
在Android4.2(Jelly Bean)開始,Android平臺目前包含在每個版本中更新的100多個CA(證書授權機構)。CA具有一個證書個一個私鑰,這點與服務器相似。爲服務器頒發證書時,CA使用其私鑰對證書進行簽名。然後客戶端可以使用CA公鑰對服務器證書內簽名數據進行驗籤,以此來確認服務器證書是否是客戶端CA頒發的有效證書。
HTTPS示例
HTTPS通信所用到的證書由CA提供,需要在服務器中進行相應的設置才能生效。
HttpURLConnection
中已經支持了Https證書驗證功能且默認爲 SSLSocketFactory
,只要是通過知名CA機構簽發的證書,那麼,可以使用下面簡單代碼發起安全的請求,因爲Android平臺已經包含了該知名CA。
URL url = new URL("https://wikipedia.org");
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
InputStream in = urlConnection.getInputStream();
copyInputStreamToOutputStream(in, System.out);
驗證服務器證書常見問題
不過還有一些注意事項
javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found
出現這種情況原因有很多,其中包括:
- 未知CA(頒發服器器證書的CA未知)
- 自簽名證書(服務器證書不是CA頒發,而是自簽名生成的)
- 缺少中間證書授權機構(服務器配置缺少中間CA)
針對上面情況討論如何解決,同時保持與服務器的連接出於安全狀態
未知CA頒發服務器證書如何驗籤?
-
問題描述
在這種情況下,由於您具有系統不信任的 CA,將發生
SSLHandshakeException
。原因可能是您有一個由 Android 尚不信任的新 CA 頒發的證書,或您的應用在沒有 CA 的較舊版本上運行。CA 未知的原因通常是因爲它不是公共 CA,而是政府、公司或教育機構等組織頒發的僅供自己使用的私有 CA。 -
解決方案
首先需要辦法證書的未知CA提供公鑰證書,讓
HttpsURLConnection
來信任您指定的 CA證書(而非系統默認的CA集)具體操作:-
使用
InputStrem
獲取一個特定的CA證書 -
用該CA證書創建
keyStore
-
用
KeyStore
創建和初始化TrustManager
TrustManager
是系統用來驗證來自服務器證書的工具,可以使用一個或多個CA證書從KeyStore
創建TrustManager
,而這些創建的TrustManager
將僅信任這些CA -
通過
TrustManager
來初始化SSLContextSSLContext
它會提供一個SSLSocketFactory
,您可以用來替換來自HttpsURLConnection
的默認SSLSocketFactory
。這樣一來,連接將使用您的 CA 驗證證書。 -
替換
HttpsURLConnection
中的默認的SSLSocketFactory爲SSLContext
創建的SSLSocketFactory
/** *1.從本地路徑加載創建證書 */ InputStream caInput = new BufferedInputStream(new FileInputStream("server.crt")); //約定證書公鑰格式爲x.509來實例化證書工廠類 CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); //生成本地證書 Certificate ca; try { ca = cf.generateCertificate(caInput); } finally { caInput.close(); } /** *2.通過CA證書創建一個包含可信ca的密鑰存儲庫keyStore */ String keyStoreType = KeyStore.getDefaultType(); KeyStore keyStore = KeyStore.getInstance(keyStoreType); keyStore.load(null, null); keyStore.setCertificateEntry("ca", ca); /** *3.通過keystore初始化TrustManagers */ String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm); tmf.init(keyStore); TrustManagers mTrustManagers = tmf.getTrustManagers() /** *4.通過TrustManager來初始化SSLContext */ SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, mTrustManagers, null); /** *5.替換HttpsURLConnection中默認的SSLSocketFactory,至此連接將使用您的 CA 來驗證證書 */ URL url = new URL("https://certs.cac.washington.edu/CAtest/"); HttpsURLConnection urlConnection =(HttpsURLConnection)url.openConnection(); urlConnection.setSSLSocketFactory(sslContext.getSocketFactory()); ...
-
自簽名服務器證書如何驗籤?
-
問題描述
如果使用自簽名的服務器證書,也就是服務器充當自己的 CA。這與上面的未知CA情況相似,因此您可以使用前面介紹的方法。當然你也可以通過重寫校驗證書鏈
TrustManager
中的方法checkServerTrusted()
來使用指定CA證書對服務器證書進行驗證。 -
解決方案
首先需要服務端提供自簽名的公鑰證書,後續我們將使用此(
windows(.cer)
/linux(.crt)
)證書對服務器證書進行校驗。具體步驟如下:-
創建一個
X509TrustManager
接口實現類A,實現該接口內的checkServerTrusted()
方法 -
在
checkServerTrusted()
方法中使用服務器提供的公鑰的證書,來對服務器證書進行驗證(使用證書內的公鑰來驗籤服務器證書內通過私鑰簽名的數據)。 -
使用實現
X509TrustManager
接口的A類來初始化SSLContext
對象 -
通過SSLContext對象提供的
getSocketFactory()
方法返回的SSLSocketFactory
對象來覆蓋HttpsURLConnection
類默認的的SSLSocketFactory
對象
創建實現
X509TrustManager
接口checkServerTrusted()
方法的TrustManagerImpl類,來對服務器證書進行驗證。class TrustManagerImpl implements X509TrustManager { @Override public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { //TODO 用來驗證客戶端證書的方法,這裏沒有做雙向驗證因此忽略 } @Override public void checkServerTrusted(X509Certificate[] x509Certificates, String authType)throws CertificateException { //TODO 用於驗證服務器證書的方法 try { /** *驗證服務器證書鏈是否有效 */ if (x509Certificates == null) { System.err.println("X509Certificate array is null"); return; } if (x509Certificates.length <= 0) { System.err.println("X509Certificate is empty"); return; } if (null == authType || !authType.contains("RSA")) { System.err.println("authType is not RSA"); return; } /** *如果上述條件都通過,使用本地證書公鑰驗籤服務器證書內私鑰簽名的數據(如果驗籤通過, *說明該服務器證書是合法自簽名證書)。 */ //從本地路徑加載證書流 InputStream certInput = new BufferedInputStream(context.getAssets().open("server.crt")); //約定證書公鑰格式爲x.509來實例化證書工廠類 CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); //生成本地X509Certificate證書 X509Certificate localcertificate; try { localcertificate = (X509Certificate) certificateFactory.generateCertificate(certInput); } finally { certInput.close(); } //獲取服務器證書鏈的根證書 X509Certificate certificate = x509Certificates[0]; //驗證根證書有效期 certificate.checkValidity(); //使用本地證書公鑰來驗籤服務器證書私鑰簽名的數據 certificate.verify(localCertificate.getPublicKey()); } catch (InvalidKeyException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (NoSuchProviderException e) { e.printStackTrace(); } catch (SignatureException e) { e.printStackTrace(); } } @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } }
使用實現
X509TrustManager
接口的TrustManagerImpl類來初始化SSLContext
對象SSLContext sslContext = SSlContext.getInstance("TLS"); sslContext.init(null,new TrustManager[]{new TrustManagerImpl()},null);
通過SSLContext對象提供的
getSocketFactory()
方法返回的SSLSocketFactory
對象來覆蓋HttpsURLConnection
類默認的的SSLSocketFactory
對象URL url = new URL("https://xxx.xxxx.xxxx"); HttpsURLConnection httpsURLConnection = (HttpsURLConnection) url.openConnection(); httpsURLConnection.setSSLSocketFactory(sslContext.getSocketFactory()); ....
-
至此,我們可通過自簽名服務器證書及未知CA頒發證書實現與服務器https傳輸證書驗證。
主機名驗證常見問題
在驗證SSL連接有兩個關鍵環節,首先是上面驗證證書是否來自值的信任的來源,還有就是主機名驗證
常見錯誤如下:
java.io.IOException: Hostname 'example.com' was not verified
...
...
java.io.IOException: HTTPS hostname wrong: should be <xx.xxx.xxx.xxx>
出現上述問題一個原因是服務器配置錯誤。配置服務器所使用的證書不具有與您嘗試連接的服務器的主題或主題備用名稱字段。在握手期間,如果請求URL的主機名和服務器的標識主機名不匹配,則驗證機制可以回調此接口實現程序來確定是否應該允許此連接(如果是主機名一致是不會調用該函數,即已明確主機名有效,並且不需要您的幫助),如果回調內實現不恰當,默認接受所有域名,則有安全風險。
因此我們在實現的HostnameVerifier
子類中,需要使用verify
函數效驗服務器主機名的合法性,否則會導致惡意程序利用中間人攻擊繞過主機名效驗。
//反例
HostnameVerifier hnv=new HosernameVerifier(){
@Override
public boolean verify(String hostname,SSLSession session){
return ture;//對URL與主機名不一致的回調,不做處理,直接接受所有域名,存在安全隱患
}
}
//正列
HostnameVerifier hnv=new HosernameVerifier(){
@Override
public boolean verify(String hostname,SSLSession session){
//對請求URL與主機名不一致的回調,我們做合法性驗證,目標主機名是否爲我們指定的一致
if("youhostname".equals(hostname)){
return true;
}else{
HostnameVerifier hv=HttpsURLConnection.getDefaultHostnameVerifier();
return hv.verify(hostname,session);
}
}
}
擴展
根證書、CA解釋以及數字證書辦法過程
- CA(Certificate Authority):被稱爲證書授權中心,是數字證書發放和管理的機構。
- 根證書:是CA認證中心給自己頒發的證書,是信任鏈的起始點。安裝根證書意味着對這個CA認證中心的信任。
- 數字證書頒發過程一般爲:用戶首先產生自己的密鑰對,並將公共密鑰及部分個人身份信息傳送給CA認證中心。認證中心在覈實身份後,將執行一些必要的步驟,以確信請求確實由用戶發送而來,然後,認證中心將發給用戶一個數字證書,該數字證書內包含用戶的
個人信息
和他的公鑰信息
,同時還附有認證中心的簽名信息
。
根證書與中級(中間根)證書
-
根證書
根證書是由CA機構自己頒發,是頒發SSL證書的核心,是信任鏈的起始點
。根證書庫是下載客戶端瀏覽器時預先加載根證書的合集。因此根證書是十分重要的,因爲它可確保瀏覽器自動信任已使用私鑰簽名的SSL證書。 -
中級(中間根)證書
中間根證書,是由CA機構的根證書頒發
。證書頒發機構(CA)不會直接從根目錄頒發服務器證書(即SSL證書),因爲這種行爲是十分危險的,因爲一旦發生錯誤頒發或者需要撤銷root,則使用root簽名的每個證書都會被撤銷信任。因此,爲了規避上述風險,CA機構一般會引用中間根。CA機構使用其私鑰對中間根進行簽名,使瀏覽器信任中間根。然後CA機構使用中間根證書的私鑰來簽署用戶申請的SSL證書。這種中間根的形式可以重複多次,即使用中間根簽署另一箇中間件,然後CA機構通過中間件簽署SSL證書。
計算機中內置的是最頂級機構的根證書,不過不用擔心,根證書的公匙在子級也是適用的
(通過中級證書頒發機構(中間根)簽發的證書也可通過根證書公鑰驗籤)。
知名CA機構
TCP爲什麼要進行三次握手,一次或者兩次不行嗎?
-
首先考慮一次握手
TCP區別於UDP,TCP是面向連接的協議,因此在通信之前,需要確保客戶端與服務器已經建立起了可靠的連接。而一次握手(即客戶端向服務器發出連接請求,沒有收到服務器的應答)無法確認是否已經建立連接,因此一次握手不符合TCP面向連接的設計思想。
-
再來看看兩次握手
兩次握手即客戶端向服務器發出連接請求,服務器接收到並告訴客戶端允許連接這是兩次握手。那麼TCP爲什麼不使用兩次握手,兩次握手會出現什麼問題呢?
假如,客戶端第一次發送一個TCP請求連接包SYNA+Seq,由於網絡原因沒有到達服務器。此時客戶端會會將此次請求認爲無效,進而重新發送一個請求連接包SYNB+Seq,此時服務收到該連接請求包SYNB+Seq,爲該請求申請連接資源,並應答一個SYN+ACKB。與此同時客戶端第一次發送的連接請求包SYNA+Seq延遲後到達了服務器,此時服務器認爲是一個新的連接請求,所以服務器又爲這個連接申請資源並返回Seq+ACKA。但是客戶端會認爲這個ACKA是無效的,並不會理會。但是服務器會一直爲這個連接維持着資源,造成資源浪費。
-
三次握手,可解決連接資源浪費問題
我們再來看看三次握手時如何解決上述連接資源浪費問題的,當客戶端第二次發送的連接請求SYNB+Seq包,到達服務器,服務器應答SYN+ACKB後,客戶端緊接着回覆一個ACK,告訴服務器,我收到你的允許連接應答了,咱門可以進行連接了。如果此時客戶端第一次發送的請求連接SYNA+Seq包到達了服務器,服務器爲該連接申請資源,並應答客戶端SYN+ACKA,此時客戶端認爲該應答時無效的,不予理會。此時服務器爲該連接申請的資源一直遲遲等不到客戶端的反饋,服務器也認爲該連接時無效的,便會釋放相關連接資源。
但是這時會有一個問題,就是客戶端在完成兩次握手之後,便認爲連接已經建立,而第三次握手可能由於網絡原因在傳輸中丟失,服務器便會認爲連接時無效的,這時候,如果客戶端向服務器寫數據,服務器將以
RST
(重置連接
)包應答,這時客戶端便可感知到服務器的錯誤。因此TCP使用三次握手來建立可靠的連接,可避免服務器申請無效的連接資源。
TCP四次揮手
揮手之前主動釋放連接的客戶端結束ESTABLISHED
階段。隨後開始四次揮手
-
客戶端向服務器發送釋放連接TCP包,此時,客戶端進入
FIN-WAIT-1(終止等待1)
階段,即半關閉階段。並且停止在客戶端到服務器端方向上發送數據,但是客戶端仍然能接收從服務器端傳輸過來的數據。數據包內容包含:- 標誌位FIN:表示請求釋放連接
- 序列號Seq:客戶端發送數據的序列號(TCP規定,FIN報文段即使不攜帶數據,也要消耗一個序號)
注意:這裏不發送的是正常連接時傳輸的數據(非確認報文),而不是一切數據,所以客戶端仍然能發送ACK確認報文。
-
服務器端接收到從客戶端發出的TCP報文之後,確認了客戶端想要釋放連接,隨後服務器端結束
ESTABLISHED
階段,進入CLOSE-WAIT(半關閉狀態)
階段並返回一段TCP報文,其中:- 標誌位ACK:表示接收到客戶端發送的釋放連接的請求
- 確認序列號ack:服務端返回給客戶端的確認序列號(客戶端的序列號+1)
- 序列號Seq:服務器響應客戶端的TCP包序列號
-
客戶端收到從服務器端發出的TCP報文之後,確認了服務器收到了客戶端發出的釋放連接請求,隨後客戶端結束
FIN-WAIT-1
階段,進入FIN-WAIT-2
階段。 -
服務器端自從發出ACK確認報文之後,經過
CLOSED-WAIT
階段,做好了釋放服務器端到客戶端方向上的連接準備,再次向客戶端發出一段TCP報文,隨後服務器端結束CLOSE-WAIT
階段,進入LAST-ACK
階段。並且停止在服務器端到客戶端的方向上發送數據,但是服務器端仍然能夠接收從客戶端傳輸過來的數據其中:- 標誌位FIN:
- 標誌位ACK:表示已經準備好釋放連接了(注意:這裏的ACK並不是確認收到服務器端報文的確認報文)
- 確認序列號ack:表示是在收到客戶端報文的基礎上,將其序號Seq值加1作爲本段報文確認號Ack的值。
- 序列號Seq:
-
客戶端收到從服務器端發出的TCP報文,確認了服務器端已做好釋放連接的準備,結束
FIN-WAIT-2
階段,進入TIME-WAIT
階段,並向服務器端發送一段報文,其中:- 標誌位ACK:表示
接收到服務器準備好釋放連接的信號
- 確認序列號ack:表示是在收到了服務器端報文的基礎上,將其序號Seq值+1作爲本段報文確認號的值。
- 序列號Seq:表示是在收到了服務器端報文的基礎上,將其確認號Ack值作爲本段報文序號的值。
注意:注意此時TCP連接還沒有釋放,必須經過2MSL(最長報文段壽命)的時間後,當客戶端撤銷相應的TCB後,才進入CLOSED狀態。
- 標誌位ACK:表示
-
服務器端收到從客戶端發出的TCP報文之後結束
LAST-ACK
階段,進入CLOSED
階段。由此正式確認關閉服務器端到客戶端方向上的連接。客戶端等待完2MSL之後,結束TIME-WAIT階段,進入CLOSED階段,由此完成“四次揮手”。
PS:爲什麼TIME_WAIT狀態需要經過2MSL(最大報文段生存時間)才能返回到CLOSE狀態?
在Client發送出最後的ACK回覆,但該ACK可能丟失。Server如果沒有收到ACK,將不斷重複發送FIN片段。所以Client不能立即關閉,它必須確認Server接收到了該ACK。Client會在發送出ACK之後進入到TIME_WAIT狀態。Client會設置一個計時器,等待2MSL的時間。如果在該時間內再次收到FIN,那麼Client會重發ACK並再次等待2MSL。所謂的2MSL是兩倍的MSL(Maximum Segment Lifetime)。MSL指一個片段在網絡中最大的存活時間,2MSL就是一個發送和一個回覆所需的最大時間。如果直到2MSL,Client都沒有再次收到FIN,那麼Client推斷ACK已經被成功接收,則結束TCP連接。
參考文章: