網絡類型
網絡連接類型
- 單 工通信:數據只能一方發送到另一方。(例:UDP協議,Android廣播)
- 半雙工通信:數據可以從A發送到B,也可以從B發到A,但同一時刻,只能有一個方向的數據傳輸。(例:Http協議,向服務器發起請求,服務器返回響應)
- 全雙工通信:任意時刻,都會存在A到B和B到A的雙向傳輸。(例:Socket協議,長連接通道,IM聊天,視頻通話)
- Http1.0協議默認關閉長連接屬于半雙工通信
可增加http請求頭字段"Connection:Keep-Alive",Http1.1默認開啟
可增加HTTP請求頭字段"Connection:close"
Https協議
保證http數據傳輸的可靠性。s指SSL/TLS協議
App(Http) : TCP IP
App(Https):SSL/TLS TCP IP
Https協議相比于Http協議在TCP協議前會先走SSL/TLS協議
對稱加密與非對稱加密
- https能保證傳輸可靠性,是因為對數據進行了加密,http為明文傳輸,數據易被竊取和冒充
- http優化過程中,對數據進行了加密傳輸,https因此誕生
對稱加密
對稱加密和解密采用一把鑰匙,需雙方約定,發送方通過密鑰加密數據,接收方使用同一密鑰解密獲取傳遞數據。
- 流程
發送方 A 發送用密鑰加密后的數據 ---> 接收方 B 接收加密數據并用密鑰解密
- 優缺點
流程簡單,解析數據快。
安全性差,密鑰管理有風險(因需約定同一key,key存在傳輸劫持風險,統一存儲存在被攻擊風險)。
非對稱加密
兩把密鑰:私鑰 + 公鑰,公鑰任何人可知,發送方使用公鑰加密,接收方可以用只有自己知道的私鑰解密并獲取數據。
- 流程
接收方B發送公鑰至發送方A ---> A發送數據,公鑰加密 ---> B接收數據,私鑰解密
- 優缺點
私鑰只有自己知道。
流程繁瑣,解密速度慢。
公鑰的安全保障
CA證書
- 生成規則
接收方公鑰+接受方身份信息+其他信息--->Hash算法--->消息摘要--->CA機構私鑰加密--->數字簽名
- 最終發送給接收方是數字證書
數字證書:數字簽名+公鑰+接收方的個人信息等
- 合法性驗證
比較數字證書的消息摘要(Hash算法和CA公鑰解密)是否一致
https的傳輸流程
- 一個https請求,包含2次http傳輸:以請求www.google.com流程為例
- 客戶端向服務端發起請求,要訪問google,先于google服務器建立連接
- 服務端有公鑰和私鑰,公鑰可以發給客戶端,然后給客戶端發送一個SSL證書,證書包含:CA簽名、公鑰、google的信息
- 客戶端接收到SSL證書后,對CA簽名解密,判斷證書合法性,不合法就斷開連接,合法則生成一個隨機數,作為數據對稱加密的密鑰,通過公鑰加密發送至服務端
- 服務端接收到客戶端加密數據后,通過私鑰解密,拿到對稱加密的密鑰,然后將google相關數據通過對稱加密密鑰加密,發送到客戶端
- 客戶端通過解密拿到服務端的數據,請求結束
- https非完全非對稱加密
對稱加密密鑰傳遞有風險,前期通過非對稱加密傳遞對稱加密密鑰,后續數據傳遞都是通過對稱加密,以提高數據解析效率。
DNS 解析
- 添加trace日志除了可以看到net timeout錯誤,還可以看到UnknowHostException:DNS解析失敗,未解析獲取到服務器ip地址
添加DNS解析策略
Object HttpTool {
private const val TAG = "HttpTool"
private const val URL = "https://www.xxx.com"
fun initHttp() {
val client = OkHttpClient.Builder().build()
Request.Builder().url(URL).build().also {
kotlin.runCatching {
client.newCall(it).execute()
}.onFailure {
Log.e(TAG, "initHttp: error: $it")
}
}
}
}
很明顯xxx.com域名不存在,就會報如下錯誤:
java.net.UnknownHostException: Unable to resolve host "www.xxx.com": No address associated with hostname
- 一旦遇到如上問題:域名被劫持修改,整個服務會處于宕機狀態,體驗很差。
- 優化方案:通過OkHttp提供的自定義DNS解析器優化處理
package okhttp3;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.List;
// 路由尋址
public interface Dns {
/**
* 使用{@link InetAddress#getAllByName}請求底層操作系統查找IP地址。
* 大多數自定義{@link Dns}實現都應委派到此實例。
*/
Dns SYSTEM = (hostname) -> {
if (hostname == null) {
throw new UnknownHostException("hostname == null");
} else {
try {
return Arrays.asList(InetAddress.getAllByName(hostname));
} catch (NullPointerException var3) {
UnknownHostException unknownHostException = new UnknownHostException("Broken system behaviour for dns lookup of " + hostname);
unknownHostException.initCause(var3);
throw unknownHostException;
}
}
};
/**
* 返回{@code hostname}的IP地址,按OkHttp嘗試的順序排列。
* 如果連接到某個地址失敗,OkHttp將重試與下一個地址的連接,直到要么建立了連接,要么耗盡了IP地址集,要么超出了限制。
*/
List<InetAddress> lookup(String var1) throws UnknownHostException;
}
// 自定義尋址策略
class MyDns: Dns {
private const val TAG = "MyDns"
override fun lookup(hostname: String): MutableList<InetAddress> {
val result = mutableListOf<InetAddress>()
var systemAddressList: MutableList<InetAddress>? = null
// 通過系統DNS解析
kotlin.runCatching {
systemAddressList = Dns.SYSTEM.lookup(hostname)
}.onFailure {
Log.e(TAG, "lookup: it")
}
if (systemAddressList != null && systemAddressList!!.isNotEmpty()) {
result.addAll(systemAddressList!!)
} else {
// 系統DNS解析失敗,走自定義路由
result.add(InetAddress.getByName(www.google.com))
// 一臺服務器支持多域名
// result.addAll(InetAddress.getAllByName(ip).toList())
}
}
}
使用:
val client = OkHttpClient.Builder().dns(MyDns()).build()
- 使用如上方案,www.xxx.com域名解析失敗就會替換為 www.google.com,從而避免請求失敗問題
接口數據適配策略
- 數據格式異常:服務器定義int類型返回字符串"" 或 空數組返回null...
List適配:空解析為空數組
import com.google.gson.Gson
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import java.lang.reflect.Type
import java.util.Collections
class ListTypeAdapter: JsonDeserializer<List<*>> {
override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): List<*> {
return try {
if (json?.isJsonArray == true) {
Gson().fromJson(json, typeOfT)
} else {
Collections.EMPTY_LIST
}
} catch (e: Exception) {
Collections.EMPTY_LIST
}
}
}
// String適配:是否為基礎類型(String,Number,Boolean),基礎類型正常轉
import com.google.gson.Gson
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import java.lang.reflect.Type
class StringTypeAdapter: JsonDeserializer<String> {
override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): String {
return try {
if (json?.isJsonPrimitive == true) {
Gson().fromJson(json, typeOfT)
} else {
""
}
} catch (e: Exception) {
""
}
}
}
使用:
GsonBuilder()
.registerTypeAdapter(String::class.java, StringTypeAdapter())
.registerTypeAdapter(List::class.java, ListTypeAdapter())
.create().also {
GsonConverterFactory.create(it)
}