關(guān)于Socket埠忘,看我這幾篇就夠了(二)之HTTP

在上一篇中,我們初步的講述了socket的定義馒索,以及socket中的TCP的簡單用法莹妒。

這篇我們主要講的是HTTP相關(guān)的東西。

什么是HTTP

HTTP -> Hyper Text Transfer Protocol(超文本傳輸協(xié)議)绰上,它是基于TCP/IP協(xié)議的一種無狀態(tài)連接

特性

無狀態(tài)

無狀態(tài)是指旨怠,在標(biāo)準(zhǔn)情況下,客戶端的發(fā)出每一次請求蜈块,都是獨立的鉴腻,服務(wù)器并不能直接通過標(biāo)準(zhǔn)http協(xié)議本身獲得用戶對話的上下文。

這里百揭,可能很多人會有疑問爽哎,我們平時使用的http不是這樣的啊,服務(wù)器能識別我們請求的身份啊器一,要不免登錄怎么做啊?

所以額外解釋下课锌,我們說的這些狀態(tài),如cookie/session是由服務(wù)器與客戶端雙方約定好盹舞,每次請求的時候产镐,客戶端填寫,服務(wù)器獲取到后查詢自身記錄(數(shù)據(jù)庫踢步、內(nèi)存)癣亚,為客戶端確定身份,并返回對應(yīng)的值获印。

從另一方面也可說述雾,這個特性和http協(xié)議本身無關(guān),因為服務(wù)器不是從這個協(xié)議本身獲取對應(yīng)的狀態(tài)兼丰。

無狀態(tài)也可這樣理解: 從同一客戶端連續(xù)發(fā)出兩次http請求到服務(wù)器玻孟,服務(wù)器無法從http協(xié)議本身上獲取兩次請求之間的關(guān)系

無連接

無連接指的是,服務(wù)器在響應(yīng)客戶端的請求后鳍征,就主動斷開連接黍翎,不繼續(xù)維持連接

結(jié)構(gòu)

http 是超文本傳輸協(xié)議,顧名思義艳丛,傳輸?shù)氖且欢ǜ袷降奈谋鞠坏В裕覀兘酉聛碇v述一下這個協(xié)議的格式

在http中氮双,一個很重要的分割符就是 CRLF(Carriage-Return Line-Feed) 也就是 \r 回車符 + \n 換行符碰酝,它是用來作為識別的字符

請求 Request

請求格式

上圖為請求格式

請求行

GET / HTTP/1.1\r\n

首行也叫請求行,是用來告訴服務(wù)器戴差,客戶端調(diào)用的請求類型送爸,請求資源路徑請求協(xié)議類型

請求類型也就是我們常說的(面試官總問的)GET暖释,POST等等發(fā)送的位置袭厂,它位于請求的最開始

請求資源路徑是提供給服務(wù)器內(nèi)部的尋址路徑,用來告訴服務(wù)器客戶端希望訪問什么資源饭入,在瀏覽器中訪問 http://www.reibang.com/p/6cfbc63f3a2b (用簡書做一波示范了)嵌器,則我們請求的就是 /p/6cfbc63f3a2b

請求協(xié)議類型目前使用最多的是HTTP/1.1說不定在不遠(yuǎn)的未來,將會被HTTP/2.0所取代

注:

  1. 所使用鏈接為https鏈接谐丢,但是其內(nèi)容與http一樣爽航,因此使用該鏈接做為例子,ssl 將會在接下來的幾篇文章中講述

  2. 請求行的不同內(nèi)容需要用 " "空格符 來做分割

  3. 請求行的結(jié)尾需要添加CRLF分割符

請求頭Request Headers

請求行之后乾忱,一直到請求體(body)讥珍,之間的部分,被我們成為請求頭窄瘟。

請求頭的長度并不固定衷佃,我們可以放置無限多的內(nèi)容到請求頭中。

但是請求頭的格式是固定的蹄葱,我們可以把它看做是鍵值對氏义。

格式:

key: value\r\n

我們通常所說的cookie便是請求頭中的一項

一些常用的http頭的定義與作用: https://blog.csdn.net/philos3/article/details/76946029

注:

當(dāng)所有請求頭都已經(jīng)結(jié)束(即我們要發(fā)送body)的時候锄列,我們需要額外增加一個空行(CRLF) 告訴服務(wù)器請求頭已經(jīng)結(jié)束

請求體Request Body

如果說header我們沒有那么多的使用機(jī)會的話,那么body則是幾乎每個開發(fā)人員都必須接觸的了惯悠。

通常邻邮,當(dāng)我們進(jìn)行 POST 請求的時候,我們上傳的參數(shù)就在這里了克婶。

服務(wù)器是如何獲得我們上傳的完整Body呢?換句話說筒严,就是服務(wù)器怎么知道我們的body已經(jīng)傳輸完畢了呢?

我們想一下,如果我們在需要實現(xiàn)這個協(xié)議的時候情萤,我們會怎么做?

  • 可以約定特殊字節(jié)作為終止字符鸭蛙,當(dāng)讀取到指定字符時,即認(rèn)為讀取完畢

  • 發(fā)送方肯定知道要發(fā)送的數(shù)據(jù)的大小筋岛,直接告訴接收方娶视,接收方只需要在收到指定大小的數(shù)據(jù)的時候就可以停止接收了

  • 發(fā)送方也不知道數(shù)據(jù)的大小(或者他需要花很大成本才能知道數(shù)據(jù)的大小),就先告訴接收方睁宰,我現(xiàn)在也不知道有多少歇万,等發(fā)送的時候看,真正發(fā)送的時候告訴接收方勋陪,"我這次要發(fā)送多少"贪磺,最后告訴接收方,"我發(fā)完了"诅愚,接收方以此停止接收寒锚。‘

也許你會有別的想法违孝,那恭喜你刹前,你可以自己實現(xiàn)類似的接收方法了。

目前雌桑,服務(wù)器是依靠上述三種方法接收的:

  • 約定特殊字節(jié):

客戶端在發(fā)送完數(shù)據(jù)后喇喉,就調(diào)用關(guān)閉socket連接,服務(wù)器在收到關(guān)閉請求后開始解析數(shù)據(jù)校坑,并返回結(jié)果拣技,最后關(guān)閉連接

  • 確定數(shù)據(jù)大小:

客戶端在請求頭中給定字段 Content-Length,服務(wù)器解析到對應(yīng)數(shù)據(jù)后接受body耍目,當(dāng)body數(shù)據(jù)達(dá)到指定長度后膏斤,服務(wù)器開始解析數(shù)據(jù),并返回結(jié)果

  • 不確定數(shù)據(jù)大小(Http/1.1 可用)

客戶端在請求頭中給定頭 Transfer-Encoding: chunked邪驮,隨后開始準(zhǔn)備發(fā)送數(shù)據(jù)

發(fā)送的每段數(shù)據(jù)都有特定的格式莫辨,

格式為:

  1. 長度行:

每段數(shù)據(jù)的開頭的文本為該段真實發(fā)送的數(shù)據(jù)的16進(jìn)制長度CRLF分割符

  1. 數(shù)據(jù)行:

真實發(fā)送的數(shù)據(jù)CRLF分割符

例:

12\r\n // 長度行 16進(jìn)制下的12就是10進(jìn)制下的 18
It is a chunk data\r\n // 數(shù)據(jù)行 CRLF 為分割符

結(jié)尾段:

用以告訴服務(wù)器數(shù)據(jù)發(fā)送完成,開始解析或存儲數(shù)據(jù)。

結(jié)尾段格式固定

0\r\n
\r\n 

目前沮榜,客戶端使用這種方法的不多盘榨。

到這里,如何告訴服務(wù)器應(yīng)該接收多少數(shù)據(jù)的部分已經(jīng)完成了

接下來就到了蟆融,告訴服務(wù)器较曼,數(shù)據(jù)究竟是什么了

同樣也是頭部定義:Content-Type

Content-Type介紹:
https://blog.csdn.net/qq_23994787/article/details/79044908

到這里,Request的基本格式已經(jīng)講完

響應(yīng) Response

響應(yīng)格式

相應(yīng)結(jié)構(gòu)

其實Response 和 Request 從協(xié)議上分析振愿,他們是一樣的,但是他們是對Http協(xié)議中文本協(xié)議的不同的實現(xiàn)弛饭。

響應(yīng)行

HTTP/1.1 200 OK\r\n

首行也叫響應(yīng)行冕末,是用來告訴客戶端當(dāng)前請求的處理狀況的,由請求協(xié)議類型侣颂,服務(wù)器狀態(tài)碼档桃,對應(yīng)狀態(tài)描述構(gòu)成

請求協(xié)議類型 是用來告訴客戶端,服務(wù)器采用的協(xié)議是什么冗澈,以便于客戶端接下來的處理福压。

服務(wù)器狀態(tài)碼 是一個很重要的返回值缰雇,它是用來通知服務(wù)器對本次客戶端請求的處理結(jié)果。

狀態(tài)碼非常多嘹屯,但是對于我們開發(fā)一般用到的是如下幾個狀態(tài)碼

狀態(tài)碼 對應(yīng)狀態(tài)描述 含義 客戶對應(yīng)操作
200 OK 標(biāo)志著請求被服務(wù)器成功處理
400 Bad Request 標(biāo)志著客戶端請求出現(xiàn)了問題,服務(wù)器無法識別从撼,客戶端修改后服務(wù)器才能進(jìn)行處理 修改request參數(shù)
401 Unauthorized 當(dāng)前請求需要校驗權(quán)限州弟,客戶端需要在下次請求頭部提交對應(yīng)權(quán)限信息 修改Header頭并提交對應(yīng)信息
403 Forbidden 當(dāng)前請求被服務(wù)器拒絕執(zhí)行(防火墻阻止或其他原因) 等待一段時間后再次發(fā)起,無其他解決辦法
404 Not Found 服務(wù)無法找到對應(yīng)資源(最為常見的錯誤碼) 修改Request中的資源請求路徑
405 Method Not Allowed 客戶端當(dāng)前請求方法不被允許 修改請求方法
408 Request Timeout 客戶端請求超時(服務(wù)器沒有在允許的時間內(nèi)解析出全部的Request) 重新發(fā)起請求
500 Internal Server Error 服務(wù)器自身錯誤(可能是未對操作過程中的異常進(jìn)行處理) 聯(lián)系后臺開發(fā)人員解決(誰要是說這是客戶端問題就去找他理論)

完整錯誤碼請參照網(wǎng)址:
https://baike.baidu.com/item/HTTP%E7%8A%B6%E6%80%81%E7%A0%81/5053660?fr=aladdin

響應(yīng)頭Response Headers響應(yīng)體Response Body

這些內(nèi)容與Request中對應(yīng)部分并無區(qū)別低零,顧不贅述了


我們已經(jīng)從特性與結(jié)構(gòu)兩部分講述了Http相關(guān)的屬性婆翔,到這里這篇文章的主要內(nèi)容基本上算是結(jié)束了,接下來我要講講一些其他的http相關(guān)的知識

跨域

作為移動端開發(fā)人員掏婶,我們對這個的了解不是很多啃奴,也幾乎用不到,但是我這里還是需要說明雄妥。因為現(xiàn)在已經(jīng)到了前端的時代最蕾,萬一我們以后需要踏足前端,了解跨域老厌,至少能為我們解決不少事情揖膜。

這篇文章不會詳細(xì)講解如何解決跨域,只會講解跨域形成的原因

什么是 跨域

在講跨域的時候梅桩,需要先講什么是

什么是域

在上一課講解socket的過程中壹粟,我們已經(jīng)發(fā)現(xiàn)了,想建立一個TCP/IP的連接需要知道至少兩個事情

  1. 對方的地址(host)
  2. 對方的門牌號(port)

我們只有依靠這兩個才能建立TCP/IP 的連接,其中host標(biāo)明我們該怎么找到對方趁仙,port表示洪添,我們應(yīng)該連接具體的那個端口。

服務(wù)器應(yīng)用是一直在監(jiān)聽著這個端口的雀费,這樣才能保證在有連接進(jìn)入的時候干奢,服務(wù)器直接響應(yīng)對應(yīng)的信息

向上聊聊吧,我們通常講的服務(wù)器指的是服務(wù)器應(yīng)用盏袄,比如常說Tomcat忿峻,Apache 等等,他們啟動的時候一般會綁定好一個指定的端口(通常不會同時綁定兩個端口)辕羽。所以呢逛尚,作為客戶端,就可以用host+port來確定一個指定的服務(wù)器應(yīng)用

由此刁愿,的概念就此生成绰寞,就是host + port

舉個例子: http://127.0.0.1:8056/

這個網(wǎng)址所屬的域就是127.0.0.1+8056 也可以寫成127.0.0.1:8056

這時候有人就會問了,那localhost:8056127.0.0.1:8056是同一域么铣口,他們實際是等價的啊滤钱。

他們不屬于同一域,規(guī)定的很死脑题,因為他們的host的表示不同件缸,所以不是。

跨域

我們已經(jīng)知道域了叔遂,跨域也就出現(xiàn)了停团,就是一個訪問另一個

我們從http協(xié)議中可以發(fā)現(xiàn)掏熬,服務(wù)器并不任何強(qiáng)制規(guī)定域佑稠,也就是說,服務(wù)器并不在乎這個訪問是從哪個域訪問過來的旗芬,同時舌胶,作為客戶端,我們也并沒有域這么一說疮丛。

那么跨域究竟是什么呢?

這就要說跨域的來源了幔嫂,我們?nèi)粘TL問的網(wǎng)站,它實際上就是html代碼誊薄,服務(wù)器將代碼下發(fā)到了瀏覽器履恩,由瀏覽器渲染并展示給我們。

開發(fā)瀏覽器的程序員在開發(fā)的時候呢蔫,也不知道這個網(wǎng)頁究竟要做什么切心,但是他們?yōu)榱税踩腱荒芙o網(wǎng)頁和客戶端(socket)同樣的權(quán)限,因此他們限制了某些操作绽昏,在本的網(wǎng)頁的某些請求操作在對方的服務(wù)器沒有添加允許該的訪問權(quán)限的時候协屡,訪問操作將不會被執(zhí)行,這些操作會對瀏覽器的安全性有很大到的影響全谤。

所以跨域就此產(chǎn)生肤晓。

跨域從頭到尾都只是一個客戶端的操作行為,從某種角度上說认然,它與服務(wù)器毫無關(guān)系补憾,因為服務(wù)器無法得知某次請求是否來自于某一網(wǎng)頁(在客戶端不配合的情況下),也就無從禁止了

對于我們移動端卷员,了解跨域后我們至少可以說盈匾,跨域與我們無關(guān)-_-

socket實現(xiàn)簡單的http請求

事實上,一篇文章如果沒有代碼上的支撐子刮,只是純理念上的闡述,終究還是感覺缺點什么窑睁,本文將在上篇文章代碼的基礎(chǔ)上做些小的改進(jìn)挺峡。

這里就以菜鳥教程網(wǎng)的http教程作為本篇文章的測試(http://www.runoob.com/http/http-tutorial.html)(ip:47.246.3.228:80)

// MARK: - Create 建立
let socketFD = Darwin.socket(AF_INET, SOCK_STREAM, 0)

func converIPToUInt32(a: Int, b: Int, c: Int, d: Int) -> in_addr {
    return Darwin.in_addr(s_addr: __uint32_t((a << 0) | (b << 8) | (c << 16) | (d << 24)))
}
// MARK: - Connect 連接
var sock4: sockaddr_in = sockaddr_in()

sock4.sin_len = __uint8_t(MemoryLayout.size(ofValue: sock4))
// 將ip轉(zhuǎn)換成UInt32
sock4.sin_addr = converIPToUInt32(a: 47, b: 246, c: 3, d: 228)
// 因內(nèi)存字節(jié)和網(wǎng)絡(luò)通訊字節(jié)相反,顧我們需要交換大小端 我們連接的端口是80
sock4.sin_port = CFSwapInt16HostToBig(80)
// 設(shè)置sin_family 為 AF_INET表示著這個為IPv4 連接
sock4.sin_family = sa_family_t(AF_INET)
// Swift 中指針強(qiáng)轉(zhuǎn)比OC要復(fù)雜
let pointer: UnsafePointer<sockaddr> = withUnsafePointer(to: &sock4, {$0.withMemoryRebound(to: sockaddr.self, capacity: 1, {$0})})

var result = Darwin.connect(socketFD, pointer, socklen_t(MemoryLayout.size(ofValue: sock4)))
guard result != -1 else {
    fatalError("Error in connect() function code is \(errno)")
}
// 組裝文本協(xié)議 訪問 菜鳥教程Http教程
let sendMessage = "GET /http/http-tutorial.html HTTP/1.1\r\n"
    + "Host: www.runoob.com\r\n"
    + "Connection: keep-alive\r\n"
    + "USer-Agent: Socket-Client\r\n\r\n"
//轉(zhuǎn)換成二進(jìn)制
guard let data = sendMessage.data(using: .utf8) else {
    fatalError("Error occur when transfer to data")
}
// 轉(zhuǎn)換指針
let dataPointer = data.withUnsafeBytes({UnsafeRawPointer($0)})

let status = Darwin.write(socketFD, dataPointer, data.count)

guard status != -1 else {
    fatalError("Error in write() function code is \(errno)")
}
// 設(shè)置32Kb字節(jié)存儲防止溢出
let readData = Data(count: 64 * 1024)

let readPointer = readData.withUnsafeBytes({UnsafeMutableRawPointer(mutating: $0)})
// 記錄當(dāng)前讀取多少字節(jié)
var currentRead = 0

while true {
    // 讀取socket數(shù)據(jù)
    let result = Darwin.read(socketFD, readPointer + currentRead, readData.count - currentRead)

    guard result >= 0 else {
        fatalError("Error in read() function code is \(errno)")
    }
    // 這里睡眠是減少調(diào)用頻率
    sleep(2)
    if result == 0 {
        print("無新數(shù)據(jù)")
        continue
    }
    // 記錄最新讀取數(shù)據(jù)
    currentRead += result
    // 打印
    print(String(data: readData, encoding: .utf8) ?? "")

}

對應(yīng)代碼例子已經(jīng)放在github上担钮,地址:https://github.com/chouheiwa/SocketTestExample

總結(jié)

越學(xué)習(xí)越覺得自己懂得越少橱赠,我們現(xiàn)在走的每一步,都是在學(xué)習(xí)箫津。

題外話:畫圖好費勁啊狭姨,都是用PPT畫的-_-

注: 本文原創(chuàng),若希望轉(zhuǎn)載請聯(lián)系作者

參考:

菜鳥教程

百度百科

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末苏遥,一起剝皮案震驚了整個濱河市饼拍,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌田炭,老刑警劉巖师抄,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異教硫,居然都是意外死亡叨吮,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進(jìn)店門瞬矩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來茶鉴,“玉大人,你說我怎么就攤上這事景用『#” “怎么了?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長围肥。 經(jīng)常有香客問我剿干,道長,這世上最難降的妖魔是什么穆刻? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任置尔,我火速辦了婚禮,結(jié)果婚禮上氢伟,老公的妹妹穿的比我還像新娘榜轿。我一直安慰自己,他們只是感情好朵锣,可當(dāng)我...
    茶點故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布谬盐。 她就那樣靜靜地躺著,像睡著了一般诚些。 火紅的嫁衣襯著肌膚如雪飞傀。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天诬烹,我揣著相機(jī)與錄音砸烦,去河邊找鬼。 笑死绞吁,一個胖子當(dāng)著我的面吹牛幢痘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播家破,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼颜说,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了汰聋?” 一聲冷哼從身側(cè)響起门粪,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎烹困,沒想到半個月后庄拇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡韭邓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年措近,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片女淑。...
    茶點故事閱讀 39,773評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡瞭郑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鸭你,到底是詐尸還是另有隱情屈张,我是刑警寧澤擒权,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站阁谆,受9級特大地震影響碳抄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜场绿,卻給世界環(huán)境...
    茶點故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一剖效、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧焰盗,春花似錦璧尸、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至澎粟,卻和暖如春蛀序,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背活烙。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工徐裸, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人瓣颅。 一個月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓倦逐,卻偏偏與公主長得像譬正,于是被迫代替她去往敵國和親宫补。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,689評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 關(guān)于Mongodb的全面總結(jié) MongoDB的內(nèi)部構(gòu)造《MongoDB The Definitive Guide》...
    中v中閱讀 31,930評論 2 89
  • 本文整理自MIN飛翔博客 [1] 1. 概念 協(xié)議是指計算機(jī)通信網(wǎng)絡(luò)中兩臺計算機(jī)之間進(jìn)行通信所必須共同遵守的規(guī)定或...
    HoyaWhite閱讀 2,671評論 2 20
  • 深入淺出HTTP協(xié)議(WEB開發(fā)和面試必備) 1.基礎(chǔ)概念篇 a.簡介 HTTP是Hyper Text Trans...
    半世韶華憶闌珊閱讀 1,221評論 0 7
  • 1.TCP報頭格式 UDP報頭格式 TCP報頭格式 UDP報頭格式 具體的各部分解釋看 TCP報文格式詳解 - ...
    杰倫哎呦哎呦閱讀 2,454評論 0 5
  • 早上翻看了孩子的試卷曾我,發(fā)現(xiàn)各科成績都不好粉怕。早飯后,孩子玩手機(jī)抒巢,我忍不住說了他的成績贫贝。孩子顯得很不好意思。我...
    愛與感賞閱讀 268評論 3 6