我的全部文章整理在此篇,將陸續更新收錄:知無涯,行者之路莫言終(個人編程之路)html
無論什麼語言,什麼系統,都離不開網絡,這個系列就來深刻一下網絡
首先是挾天子以令諸侯
的Http,它把握了整個網絡的命脈。全部人必須對它言聽計從。git
1.dns的尋址(域名解析)
2.tcp/ip的三次握手,創建鏈接
3.客戶端請求和服務端響應的詳細分析
4.騰訊雲免費ssl證書獲取,以及基於springboot2設置https支持
複製代碼
注:本文的服務端代碼已放在Github,若是沒有基礎,能夠參見個人SpringBoot入門級系列:
若是不想接觸後端,本文也能夠看,不過理解方面多少會有些欠缺,
畢竟http是客戶端
和服務端
兩我的的事,撇看一者來看,是不現實的。github
chrome中輸入網址敲回車以後,瀏覽器會根據域名找到對應的服務器地址
這裏以個人網站:http://www.toly1994.com/
爲例web
在此以前先簡單說一下urlspring
基本URL包含
模式(或稱協議)、服務器名稱(或IP地址)、路徑和文件名,
協議://用戶名:密碼@子域名.域名.頂級域名:端口號/目錄/文件名.文件後綴?參數=值#標誌。
複製代碼
也就是根據域名找到對應的ip地址:首先看瀏覽器自身有沒有緩存:
chrome://net-internals/#dns
chrome
若是1沒有的話,查看本機上的DNS緩存,
ipconfig /displaydns
編程
若是2沒有的話,查看host文件是否有對應的網址ip,
C:\Windows\System32\drivers\etc
json
前三步沒有查到,這說明本地無該網站的DNS緩存,由寬帶運營商的服務器進行查詢
若是無緩存,會一級一級的去找,知道找到toly1994.com對應的服務器(即個人服務器),
最後將查到的服務器ip地址返回給剛纔敲網址的瀏覽器後端
TCP/IP
鏈接:爲了簡單些,使用
http://www.toly1994.com:8080/swords/21
客戶端在訪問時,第一步就是查詢域名所對的ip地址(即服務器住哪)api
網上的圖有點醜,這裏特地畫了一幅,對於TCP/IP會有專文總結,
這裏先認識兩個控制位:SYN和ACK
SYN:同步序列編號(Synchronize Sequence Numbers),在創建鏈接時使用
|-- 當SYN=1,ACK=0時,表示這是一個請求創建鏈接的報文段;
|-- 當SYN=1,ACK=1時,表示對方贊成創建鏈接。
|-- SYN=1,說明這是一個請求創建鏈接或贊成創建鏈接的報文。只有在前兩次握手中SYN才置爲1。
ACK:確認序號標誌(Acknowledgment):前面的確認號字段是否有效。
|-- 只有當ACK=1時,前面的確認號字段纔有效。爲0表示報文中不含確認信息,忽略確認號字段。
|-- TCP規定,鏈接創建後,ACK必須爲1。
複製代碼
問一下服務器在不在
客戶端發送SYN=1,seq=J(J爲隨機數字)的報文給服務器
服務端看到SYN=1,知道客戶端要請求鏈接
服務器說在
服務端發送SYN=1,ACK=1,seq=K(K爲隨機數字),ack=J+1的報文給客戶端
客戶端看到SYN=1,ack=J+1,便知道服務端給本身回話了
客戶端說我也還在
客戶端發送ACK=1,ack=K+1的報文給服務器
服務端看到ack=K+1,知道客戶端收到了剛纔的話
這樣就創建了一個穩固的
TCP/IP
鏈接
上面說到服務端和客戶端創建了鏈接,接下來就是
請求
與響應
了
在此以前先看一下chrome試中和網絡相關的工具
請求是由客戶端發出的,也就是
chrome
瀏覽器程序,關於Upgrade-Insecure-Requests詳見
客戶端將本身的狀況和請求的東西用請求頭髮送給服務器,服務器根據請求頭找到資源
|-- 請求方式 資源路徑 http版本
GET /swords/21 HTTP/1.1
|-- 請求的服務器域名+端口號
Host: 192.168.10.104:8080
|-- 鏈接參數
Connection: keep-alive
|-- 緩存控制參數
Cache-Control: max-age=0
|-- 升級-不安全-的請求:
Upgrade-Insecure-Requests: 1
|-- 用戶代理:告訴服務器本身的現狀
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36
|-- 該次請求能夠接收的文件類型
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
|-- 聲明客戶端支持的編碼類型
Accept-Encoding: gzip, deflate
|-- 聲明瀏覽器支持的語言
Accept-Language: zh-CN,zh;q=0.9
複製代碼
chrome的調試工具展示的已經處理過了,並不是原樣,這裏先看一下,等會再看原生的,
服務器發送響應給客戶端,客戶端根據響應進行下一步動做
這裏chrome調試不夠用了,使用PostMan進行請求,使用
Fiddler
進行抓包,
基本使用很簡單,裝上就好了。如下的測試認真看,這是之後常常用到的
網上不少要不就是講的太抽象,要麼就是太片面,這裏好好把一些凌亂的點理一下
默認是全部網絡請求都會顯示在左側,你能夠這樣過濾:
GET http://192.168.10.104:8080/swords/21 HTTP/1.1
cache-control: no-cache
Postman-Token: e72160d9-48db-4933-8c10-e0157c0f86db
User-Agent: PostmanRuntime/7.4.0
Accept: */*
Host: 192.168.10.104:8080
accept-encoding: gzip, deflate
Connection: keep-alive
複製代碼
注意響應頭和響應體(數據)之間有一個空行
Content-Length
這個字段很重要,它表示響應體(數據)的字節大小(以下圖:)
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Date: Fri, 01 Feb 2019 04:56:35 GMT
Content-Length: 406
{"id":21,"name":"Excalibur","info":"Excalibur是傳說中不列顛國王亞瑟王從湖之仙女那獲得的聖劍。此劍在是精靈在阿瓦隆(Avalon)所打造,劍鍔由黃金所鑄、劍柄上鑲有寶石,並因其鋒刃削鐵如泥","imgurl":"http://localhost:8080/imgs/timg.jpg","create_time":"2018-07-17T08:29:36.000+0000","modify_time":"2018-07-17T08:29:36.000+0000","origin":"亞瑟王"}
複製代碼
上面是將參數做爲url的最後一級,是一種Restful的書寫規範
這裏,將請求的參數加在url後,是url自身書寫規範,和上面基本沒什麼區別:
http://192.168.10.104:8080/swords/id?id=21
GET http://192.168.10.104:8080/swords/id?id=21 HTTP/1.1
cache-control: no-cache
Postman-Token: b7d97a6f-4c97-4651-8ccf-16541e24747c
User-Agent: PostmanRuntime/7.4.0
Accept: */*
Host: 192.168.10.104:8080
accept-encoding: gzip, deflate
Connection: keep-alive
複製代碼
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Date: Fri, 01 Feb 2019 05:35:24 GMT
Content-Length: 406
{"id":21,"name":"Excalibur","info":"Excalibur是傳說中不列顛國王亞瑟王從湖之仙女那獲得的聖劍。此劍在是精靈在阿瓦隆(Avalon)所打造,劍鍔由黃金所鑄、劍柄上鑲有寶石,並因其鋒刃削鐵如泥","imgurl":"http://localhost:8080/imgs/timg.jpg","create_time":"2018-07-17T08:29:36.000+0000","modify_time":"2018-07-17T08:29:36.000+0000","origin":"亞瑟王"}
複製代碼
與GET:請求中加入請求參數(params)惟一的區別就是請求方法不一樣
使用POST+請求參數,參數依然在url中,但不明文顯示,注意與下面POST提交表單的區別
POST表單時請求含有請求體,而POST+請求參數並無請求體,參數依然經過url傳遞
POST http://192.168.10.104:8080/api/sword?name=擎天劍&imgurl=http://192.168.10.104:8080/imgs/oQttHzCOUqeOatEH.jpg&info=天地一劍,開世擎天&origin=天晴仞 HTTP/1.1
cache-control: no-cache
Postman-Token: c60f5455-8263-4928-a2b3-278af9d198fd
User-Agent: PostmanRuntime/7.6.0
Accept: */*
Host: 192.168.10.104:8080
cookie: JSESSIONID=8B28A94404EF579B0E246ECD7FD04056
accept-encoding: gzip, deflate
content-length: 0
Connection: keep-alive
複製代碼
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Date: Mon, 03 Feb 2019 15:00:35 GMT
Content-Length: 227
{"code":200,"msg":"操做成功","data":{"id":70,"name":"擎天劍","info":"天地一劍,開世擎天","imgurl":"http://192.168.10.104:8080/imgs/oQttHzCOUqeOatEH.jpg","create_time":null,"modify_time":null,"origin":"天晴仞"}}
複製代碼
咱們都填過表單,如登錄界面,表單採用post方式提交
這時候請求體(Body)就有用了,能夠將一些額外的數據傳遞給服務器
這樣的好處就是不用將數據暴露在url裏了,注意一下表格數據發送的格式:
POST http://192.168.10.104:8080/api/sword HTTP/1.1
cache-control: no-cache
Postman-Token: cf6cb7e3-e66d-4339-9685-54f46af7db12
User-Agent: PostmanRuntime/7.6.0
Accept: */*
Host: 192.168.10.104:8080
cookie: JSESSIONID=8B28A94404EF579B0E246ECD7FD04056
accept-encoding: gzip, deflate
content-type: multipart/form-data; boundary=--------------------------789466732494020503103134
content-length: 567
Connection: keep-alive
----------------------------789466732494020503103134
Content-Disposition: form-data; name="name"
擎天劍
----------------------------789466732494020503103134
Content-Disposition: form-data; name="imgurl"
http://192.168.10.104:8080/imgs/oQttHzCOUqeOatEH.jpg
----------------------------789466732494020503103134
Content-Disposition: form-data; name="info"
天地一劍,開世擎天
----------------------------789466732494020503103134
Content-Disposition: form-data; name="origin"
天晴仞
----------------------------789466732494020503103134--
複製代碼
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Date: Mon, 04 Feb 2019 05:00:35 GMT
Content-Length: 227
{"code":200,"msg":"操做成功","data":{"id":70,"name":"擎天劍","info":"天地一劍,開世擎天","imgurl":"http://192.168.10.104:8080/imgs/oQttHzCOUqeOatEH.jpg","create_time":null,"modify_time":null,"origin":"天晴仞"}}
複製代碼
注意一下這裏文件上傳時請求的格式,能夠和上面的表單對比一下
POST http://192.168.10.104:8080/upload HTTP/1.1
cache-control: no-cache
Postman-Token: c12cd0dc-fbcd-4726-9c97-ebacc6498d26
User-Agent: PostmanRuntime/7.4.0
Accept: */*
Host: 192.168.10.104:8080
accept-encoding: gzip, deflate
content-type: multipart/form-data; boundary=--------------------------131785098353427999614106
content-length: 345
Connection: keep-alive
----------------------------131785098353427999614106
Content-Disposition: form-data; name="file"; filename="應龍.txt"
Content-Type: text/plain
《應龍》--張風捷特烈
一遊小池兩歲月,
洗卻凡世幾閒塵。
時逢雷霆風會雨,
應乘扶搖化入雲。
----------------------------131785098353427999614106--
複製代碼
HTTP/1.1 200
Content-Type: text/plain;charset=UTF-8
Content-Length: 24
Date: Fri, 01 Feb 2019 06:53:03 GMT
應龍.txt上傳成功!
複製代碼
也就是在客戶端請求是攜帶請求的額外原生數據(以下),服務端能夠拿到這些數據
可見請求體的數據也是和請求頭隔着一行
POST http://192.168.10.104:8080/postString HTTP/1.1
cache-control: no-cache
Postman-Token: e532e186-4a4c-4a9f-82bb-8045b1cd9403
Content-Type: text/plain
User-Agent: PostmanRuntime/7.4.0
Accept: */*
Host: 192.168.10.104:8080
accept-encoding: gzip, deflate
content-length: 53
Connection: keep-alive
海的彼岸有我不曾見證的風采--創世神無
複製代碼
服務器裏我讓數據原樣返回,固然你也能夠處理一下再返回
HTTP/1.1 200
Content-Type: text/plain;charset=UTF-8
Content-Length: 53
Date: Fri, 01 Feb 2019 06:19:52 GMT
海的彼岸有我不曾見證的風采--創世神無
複製代碼
注意一下,傳遞二進制文件和表單傳遞文件、原生數據的區別
|--POST-二進制文件 格式上同傳遞 原生數據,因爲是二進制流,能夠傳遞任意的數據
|--POST-二進制文件和表單上傳文件都能上傳文件,但請求體是徹底不一樣的
POST http://192.168.10.104:8080/PostFile HTTP/1.1
cache-control: no-cache
Postman-Token: 607a26e6-9c84-4b6b-97e9-3f539cf9a150
Content-Type: text/plain
User-Agent: PostmanRuntime/7.4.0
Accept: */*
Host: 192.168.10.104:8080
accept-encoding: gzip, deflate
content-length: 137
Connection: keep-alive
《應龍》--張風捷特烈
一遊小池兩歲月,
洗卻凡世幾閒塵。
時逢雷霆風會雨,
應乘扶搖化入雲。
複製代碼
HTTP/1.1 200
Content-Type: text/plain;charset=UTF-8
Content-Length: 7
Date: Fri, 01 Feb 2019 07:28:35 GMT
SUCCESS
複製代碼
關於PUT,DELETE等於請求與POST基本一致,就很少說了
下面是一個簡單的表單,界面未優化,來看一下多個字段是如何請求的
這樣也許你會對錶單有更深的認識,也會對多文件上傳有思路
POST http://192.168.10.104:8080/submit_form HTTP/1.1
Host: 192.168.10.104:8080
Connection: keep-alive
Content-Length: 626
Cache-Control: max-age=0
Origin: http://192.168.10.104:8080
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7Mqt2T4cA2gNVkCa
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Referer: http://192.168.10.104:8080/add_form
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
------WebKitFormBoundary7Mqt2T4cA2gNVkCa
Content-Disposition: form-data; name="name"
弒神劍
------WebKitFormBoundary7Mqt2T4cA2gNVkCa
Content-Disposition: form-data; name="info"
一劍弒神
------WebKitFormBoundary7Mqt2T4cA2gNVkCa
Content-Disposition: form-data; name="origin"
噬神者
------WebKitFormBoundary7Mqt2T4cA2gNVkCa
Content-Disposition: form-data; name="file"; filename="應龍.txt"
Content-Type: text/plain
《應龍》--張風捷特烈
一遊小池兩歲月,
洗卻凡世幾閒塵。
時逢雷霆風會雨,
應乘扶搖化入雲。
------WebKitFormBoundary7Mqt2T4cA2gNVkCa--
複製代碼
上面說了一大堆請求和響應的格式,如今說一下他們的用處
這麼想吧:瀏覽器將請求頭髮給服務器,手機能夠做爲客戶端,職能上和瀏覽器並沒有區別
服務器並不會區分是瀏覽器仍是手機,它只認請求頭,而後作出反應
客戶端使用socket鏈接服務端,經過socket的輸出流將請求頭寫給服務器
服務器看到請求頭就會作出相應的反應,這裏的請求頭就是四-6
的請求頭
/**
* 經過ip和端口鏈接服務端核心代碼
*
* @param ip ip地址
* @param port 端口
*/
private void connServer(String ip, int port) {
String header = "POST http://192.168.10.105:8080/postString HTTP/1.1\n" +
"cache-control: no-cache\n" +
"Postman-Token: e532e186-4a4c-4a9f-82bb-8045b1cd9403\n" +
"Content-Type: text/plain\n" +
"User-Agent: PostmanRuntime/7.4.0\n" +
"Accept: */*\n" +
"Host: 192.168.10.105:8080\n" +
"accept-encoding: gzip, deflate\n" +
"content-length: 53\n" +
"Connection: keep-alive\n" +
"\n" +
"海的彼岸有我不曾見證的風采--創世神無";
try {
//1.建立客戶端Socket對象(ip,端口)
mSocket = new Socket(ip, port);
//2.經過socket對象獲取字節輸出流,幷包裝成PrintWriter----用於發送給服務端數據
mPwOut = new PrintWriter(mSocket.getOutputStream(), true);
//3.經過socket對象獲取字節輸出流
mPwOut.write(header);
} catch (IOException e) {
e.printStackTrace();
}finally {
mPwOut.close();
}
}
|-- Android中使用
new Thread(()->{
connServer("192.168.10.105", 8080);
}).start();
複製代碼
經過HttpServletRequest獲取輸入流,進而獲得請求體
你能夠根據輸入流來自定義一些操做,如保存,轉換等
---->[SwordController#postString]----------------------------------
@PostMapping("/postString")
public String postString(HttpServletRequest request) {
ServletInputStream is = null;
try {
is = request.getInputStream();
StringBuilder sb = new StringBuilder();
byte[] buf = new byte[1024];
int len = 0;
while ((len = is.read(buf)) != -1) {
sb.append(new String(buf, 0, len));
}
System.out.println(sb.toString());
return sb.toString();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
複製代碼
請求頭是
四-7
的,這裏上傳一張圖片到服務器
/**
* 經過ip和端口鏈接服務端核心代碼
*
* @param ip ip地址
* @param port 端口
*/
private void connServerPostFile(String ip, int port) {
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.pic_22);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
byte[] datas = baos.toByteArray();
String header = "POST http://192.168.10.105:8080/PostFile HTTP/1.1\n" +
"cache-control: no-cache\n" +
"Postman-Token: 607a26e6-9c84-4b6b-97e9-3f539cf9a150\n" +
"Content-Type: text/plain\n" +
"User-Agent: PostmanRuntime/7.4.0\n" +
"Accept: */*\n" +
"Host: 192.168.10.105:8080\n" +
"accept-encoding: gzip, deflate\n" +
"content-length: " + datas.length + "\n" +
"Connection: keep-alive\n" +
"\n";
try {
//1.建立客戶端Socket對象(ip,端口)
mSocket = new Socket(ip, port);
mOs = mSocket.getOutputStream();
mOs.write(header.getBytes());
mOs.write(datas);
//3.經過socket對象獲取字節輸出流
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
mOs.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
複製代碼
也就是說服務器只認請求,只有頭對了,它就提供服務。
就像員工吃食堂,管你張三李四,有工做牌就能食堂就提供打飯服務。
網絡框架HttpURLConnection
,okHttp
底層都少不了對這些的封裝
okHttp
對錶單的拼接okHttp也是按照Http的請求格式規範來對錶單進行拼合的
若是在用okHttp時,能意識到你的請求是什麼樣子的,會不會視野更開闊呢?
public final class MultipartBody extends RequestBody {
public static final MediaType MIXED = MediaType.get("multipart/mixed");
public static final MediaType ALTERNATIVE = MediaType.get("multipart/alternative");
public static final MediaType DIGEST = MediaType.get("multipart/digest");
public static final MediaType PARALLEL = MediaType.get("multipart/parallel");
public static final MediaType FORM = MediaType.get("multipart/form-data");
--->private static final byte[] COLONSPACE = {':', ' '};
--->private static final byte[] CRLF = {'\r', '\n'};
--->private static final byte[] DASHDASH = {'-', '-'};
--->private final ByteString boundary;//分割線
private final MediaType originalType;
private final MediaType contentType;
private final List<Part> parts;
private long contentLength = -1L;
MultipartBody(ByteString boundary, MediaType type, List<Part> parts) {
this.boundary = boundary;
this.originalType = type;
this.contentType = MediaType.get(type + "; boundary=" + boundary.utf8());
this.parts = Util.immutableList(parts);
}
...
public static final class Part {
public static Part create(RequestBody body) {
return create(null, body);
}
public static Part create(@Nullable Headers headers, RequestBody body) {
if (body == null) {
throw new NullPointerException("body == null");
}
if (headers != null && headers.get("Content-Type") != null) {
throw new IllegalArgumentException("Unexpected header: Content-Type");
}
if (headers != null && headers.get("Content-Length") != null) {
throw new IllegalArgumentException("Unexpected header: Content-Length");
}
return new Part(headers, body);
}
//下面是拼合表單的方法
public static Part createFormData(String name, String value) {
return createFormData(name, null, RequestBody.create(null, value));
}
public static Part createFormData(String name, @Nullable String filename, RequestBody body) {
if (name == null) {
throw new NullPointerException("name == null");
}
---> StringBuilder disposition = new StringBuilder("form-data; name=");
appendQuotedString(disposition, name);
if (filename != null) {
disposition.append("; filename=");
appendQuotedString(disposition, filename);
}
Headers headers = new Headers.Builder()
---> .addUnsafeNonAscii("Content-Disposition", disposition.toString())
.build();
return create(headers, body);
}
...
}
public static final class Builder {
private final ByteString boundary;
private MediaType type = MIXED;
private final List<Part> parts = new ArrayList<>();
public Builder() {
this(UUID.randomUUID().toString());
}
public Builder(String boundary) {
this.boundary = ByteString.encodeUtf8(boundary);
}
/**
* 設置 MIME type
*/
public Builder setType(MediaType type) {
if (type == null) {
throw new NullPointerException("type == null");
}
if (!type.type().equals("multipart")) {
---> throw new IllegalArgumentException("multipart != " + type);
}
this.type = type;
return this;
}
...
}
}
複製代碼
配置好後打包上線
OK 這就能夠經過https訪問了,簡單支持一下,其餘的就不深究了
這篇也挺長了,關於緩存、服務器的響應碼就放到下一篇吧。
項目源碼 | 日期 | 附錄 |
---|---|---|
V0.1--無 | 2018-3-12 | 無 |
V0.2--無 | 2018-3-19 | 增長四次揮手 |
發佈名:
網絡篇:協天子令諸侯[-Http-]
捷文連接:juejin.im/post/5c87a7…
筆名 | 微信 | |
---|---|---|
張風捷特烈 | 1981462002 | zdl1994328 |
個人github:github.com/toly1994328
個人簡書:www.jianshu.com/u/e4e52c116…
個人簡書:www.jianshu.com/u/e4e52c116…
我的網站:www.toly1994.com
1----本文由張風捷特烈原創,轉載請註明
2----歡迎廣大編程愛好者共同交流
3----我的能力有限,若有不正之處歡迎你們批評指證,一定虛心改正
4----看到這裏,我在此感謝你的喜歡與支持