socket拋磚引玉

爲何要講socket呢?

其實也沒什麼特別的緣由,主要是本身想了解一下網絡通訊這塊的東西,而後一直以爲socket高深難懂,因此決定挑戰一下本身,作一個簡單的學習瞭解。web

之前由於項目緣由接觸過socket,可是當時是使用的socket.io的這個框架,對於socket的底層是如何實現的,以及框架是如何封裝的並無進行深刻的研究。編程

因此此次就拋磚引玉簡單的講一下我對socket的理解,很是歡迎各位大佬多多指教。數組

什麼是socket?

字面上socket又成爲「套接字」安全

實際上:網絡上 的 兩個程序 經過一個雙向的通訊鏈接 實現數據的交換,這個鏈接的一端稱爲一個socket服務器

客戶端和服務端的socket創建一個鏈接,經過兩端創建的這個通訊管道進行網絡的請求和響應。socket能夠理解成通訊管道(隧道)的兩個端口,一個入口,一個出口網絡

Socket通訊.jpg

網絡通訊的三要素

  • IP地址(網絡上主機設備的惟一標識框架

    • 用來尋找對應服務器的主機
  • 端口號(定位程序)socket

    • 用於標示進程的邏輯地址,不一樣進程的有不一樣的端口號
    • 至關於當前服務器(當前電腦)上對應的 web應用程序
    • 有效端口:0 ~ 65535,其中0 ~ 1024由系統使用或者稱做保留端口,開發中建議使用1024以上的端口
  • 傳輸協議(使用什麼樣的方式進行交互,通訊規則)函數

    • 常見協議:TCP、UDP

傳輸協議

TCP(傳輸控制協議)

  • 須要創建鏈接,造成傳輸數據的通道
  • 在鏈接中進行大數據傳輸(數據的大小不受限制
  • 經過3次握手完成鏈接,是可靠協議,安全送達
  • 必須創建鏈接,效率會稍低

UDP(用戶數據報協議)

  • 數據 及 源和目的 封裝成數據包中,不須要創建鏈接
  • 每一個數據報的大小限制在64K以內
  • 由於無需鏈接,所以是不可靠協議
  • 不須要創建鏈接,速度快

套接字

套接字的類型有不少種,好比 DARPA Internet 地址(Internet 套接字)、本地節點的路徑名(Unix套接字)、CCITT X.25地址(X.25 套接字)等。涉及到網絡請求方面的主要指的都是Internet套接字學習

根據數據的傳輸方式,能夠將Internet套接字分紅兩種類型

流格式套接字(SOCK_STREAM)也叫「面向鏈接的套接字」

  • SOCK_STREAM 是一種可靠的、雙向的通訊數據流,數據能夠準確無誤地到達另外一臺計算機,若是損壞或丟失,能夠從新發送。

  • 流格式套接字有本身的糾錯機制

  • SOCK_STREAM 有如下幾個特徵:

    • 數據在傳輸過程當中不會消失;
    • 數據是按照順序傳輸的;
    • 數據的發送和接收不是同步的(有的也稱「不存在數據邊界」)
  • 能夠將 SOCK_STREAM 比喻成一條傳送帶,只要傳送帶自己沒有問題(也就是不斷網),就能夠保證數據不會丟失;同時,比較晚傳送的數據不會先到達,比較早傳送的數據不會晚到達,保證了數據的傳遞是按照順序傳遞的。

SOCK_STREAM傳送帶.png

  • 流式套接字使用的是傳輸控制協議(TCP),進而保證了數據能夠 準確無誤的順序到達,而且接收者不須要和發送者保持相同的節奏接收數據。

    • 流格式套接字的內部有一個緩衝區(也就是字符數組),經過 socket 傳輸的數據將保存到這個緩衝區。接收端在收到數據後並不必定當即讀取,只要數據不超過緩衝區的容量,接收端有可能在緩衝區被填滿之後一次性地讀取,也可能分紅好幾回讀取。
    • 也就是說,無論數據分幾回傳送過來,接收端只須要根據本身的要求讀取,不用非得在數據到達時當即讀取。傳送端有本身的節奏,接收端也有本身的節奏,它們是不一致的。
  • 應用場景:HTTP協議就是基於面向鏈接的套接字(TCP服務),數據信息必需要準確無誤的進行傳輸

數據報格式套接字(SOCK_DGRAM)也叫「無鏈接的套接字」

  • 計算機只管傳輸數據,不做數據校驗,若是數據在傳輸中損壞,或者沒有到達另外一臺計算機,是沒有辦法補救的。也就是說,數據錯了就錯了,沒法重傳。

  • SOCK_DGRAM 有如下幾個特徵:

    • 強調快速傳輸而非傳輸順序;
    • 傳輸的數據可能丟失也可能損毀;
    • 限制每次傳輸的數據大小;
    • 數據的發送和接收是同步的(有的教程也稱「存在數據邊界」)。
  • 能夠將SOCK_DGRAM理解爲快遞行業,用貨車發往同一地點的兩件包裹無需保證順序,只要以最快的速度送到客戶手中便可。這種方式存在損壞或者丟失的風險,並且對快遞包裹的大小也是有必定限制的。因此當傳遞大量包裹的時候就須要分批發送。

SOCK_DGRAM快遞小哥.png

  • 用兩輛車分別發送的兩件包裹,接收者也須要分兩次接收,因此數據的發送和接收必須是同步的。(也能夠理解爲:接收次數和發送次數是相同的)

  • 總之,數據報套接字是一種不可靠的、不按順序傳遞的、以追求速度爲目的的套接字,使用的是UDP協議。

  • 應用場景:QQ視頻聊天 & QQ語音 都是使用SOCK_DGRAM來進行數據傳輸的。

面向鏈接和無鏈接的套接字的理解

網絡數據傳輸.png

面向鏈接的套接字

  • 對於面向鏈接的套接字則是在通訊以前先肯定好一條路徑,沒有特殊狀況,之後固定走這條路徑進行數據的傳遞,固然若是這條路徑出問題或者被破壞的話則會在發送數據以前從新創建新的路徑。
  • 爲了確保數據的準確性以及發送順序,須要在收到數據包的確認消息以後才進行下一次數據的發送,不然進行數據的重發。
  • 很是可靠,確保萬無一失

無鏈接的套接字

  • 對於無鏈接的套接字,每一個數據包能夠選擇不一樣的路徑,固然也能夠選擇相同的路徑,不管中途走那條通道或者經歷了什麼,最終到達便可,不管前後以及最終到達是否成功
  • 盡最大努力交付 原則

Socket通訊流程

socket起源於Unix,而Unix/Linux基本哲學之一就是「一切皆文件」,均可以用「打開open -> 讀寫write/read -> 關閉close」模式來操做。也能夠理解爲socket就是該模式的一個實現,socket便是一種特殊的文件

流程圖

socket通訊-TCP.jpg

socket通訊-UDP.png

接口簡介

  • socket(): 建立socket套接字

  • bind(): 將套接字和IP、端口綁定,一般由服務端調用

  • listen(): TCP專用,開啓監聽狀態,等待用戶發起請求,套接字一直處於「睡眠」中,直到客戶端發起請求才會被「喚醒」

  • accept(): TCP專用,服務器等待客戶端鏈接請求,通常是阻塞態(暫停運行),直到客戶端發起請求

  • connect(): TCP專用,客戶端向服務器主動發起鏈接請求,直到服務器傳回數據後,connect() 才運行結束

  • send(): TCP專用,發送數據

  • recv(): TCP專用,接收數據

  • sendto(): UDP專用,發送數據到指定的IP地址和端口,數據信息包含目標地址

  • recvfrom(): UDP專用,接收數據,返回數據遠端的IP地址和端口,接收數據請求的時候將目標地址信息保存

  • shutdown(): TCP專用,優雅的斷開流鏈接,斷開輸入流 斷開輸出流 同時斷開I/O流,會等緩衝區的數據發送完畢以後再斷開。用來關閉鏈接,而不是套接字,因此須要在調用以後再調一次close(),才能將套接字從內存中清除

  • closesocket(): 關閉socket套接字,不只會關閉服務端的socket,還會通知客戶端鏈接已斷開,客戶端也會清理 socket 相關資源,該操做會丟失輸出緩衝區中的數據

不一樣協議下socket通訊的注意點

基於TCP的socket通訊

  • TCP的服務端與客戶端必須一對一的創建鏈接才能夠進行數據的通訊

  • 在TCP中,套接字是一對一的關係,若是要向10個客戶端提供服務的話,那麼除了負責監聽的套接字以外,還須要建立10個套接字

  • 建立好TCP套接字以後,傳輸數據時無需添加地址信息,由於TCP的套接字與對方套接字相鏈接,知道數據的目標地址信息

基於UDP的socket通訊

  • UDP中的服務器端和客戶端沒有鏈接,無需在鏈接狀態下交換數據

  • UDP 套接字不會保持鏈接狀態,每次傳輸數據都要添加目標地址信息,這至關於在郵寄包裹前填寫收件人地址

  • 在UDP中,無論是服務端仍是客戶端都只須要1個套接字便可

  • UDP不存在嚴格的服務端和客戶端的區分,只是由於其提供服務而稱爲服務端

問題探討

http 的長短鏈接 & 長短輪詢

長鏈接 & 短鏈接

首先須要強調一點:HTTP協議是基於請求/響應模式的,所以只要服務端給了響應,本次HTTP鏈接就結束了。 也能夠理解爲是本次HTTP請求就結束了,根本沒有長鏈接 短鏈接這一說

對於網絡上說的HTTP分爲長鏈接和短鏈接,實際上是指TCP鏈接。TCP鏈接是一個雙向的通道,它是能夠保持一段時間不關閉的,所以TCP鏈接纔是真正有長鏈接和短鏈接之分的

對於HTTP鏈接的說法 實際上是HTTP請求和HTTP響應更爲準確,因此 長鏈接是指的TCP鏈接,而不是HTTP鏈接

怎樣算是把HTTP變成長鏈接?

首先要明白 長鏈接意味着鏈接會被重複使用,是指TCP的鏈接通道被重複使用,並非HTTP請求的複用,是多個HTTP請求能夠複用同一個TCP鏈接,這樣節省了不少TCP鏈接創建和斷開的握手消耗。

HTTP1.0協議不支持長鏈接,從HTTP1.1協議之後默認是長鏈接,咱們能夠看到平時的Web應用的HTTP頭部Connection也確實是keep-alive,可是須要服務端和客戶端都設置纔算是長鏈接

長鏈接也並非永久鏈接的,若是一段時間內(具體的時間長短,是能夠在header當中進行設置的,也就是所謂的超時時間),這個鏈接沒有HTTP請求發出的話,那麼這個長鏈接就會被斷掉。

長鏈接優點: 假設打開一個網頁,裏面確定包含不少CSS、JS等一系列資源,若是是短鏈接的話(每次都要從新創建TCP鏈接),因此一個網頁的打開須要耗費幾個甚至幾十個的TCP鏈接。若是是長鏈接的話,那麼這麼屢次HTTP請求(這些請求包括請求網頁內容,CSS文件,JS文件,圖片等等),其實使用的都是同一個TCP鏈接,很顯然這樣能夠節省不少的消耗。

長輪詢 & 短輪詢

假設一個場景:淘寶界面顯示庫存的剩餘個數,客戶端寫一個死循環,不停的去請求服務端的數據

短輪詢

  • 服務端馬上返回請求的結果

長輪詢

  • 當服務端收到客戶端的請求的時候,並非馬上去返回結果,而是檢查一下請求的數據有沒變化,檢測到有變化則當即返回,不然一直等到超時爲止。

對於客戶端來講,長輪詢和短輪詢是同樣的,就是不停的去請求;對於服務端來講,短輪詢的狀況下服務端每次請求無論有沒有變化都會當即返回結果信息,而長輪詢的狀況下是有變化才返回結果信息,沒變化的話則不會返回信息,直到超時爲止

長短輪詢和長短鏈接的區別

  • 1.決定方式

    • 一個TCP鏈接是否爲長鏈接,是經過設置HTTP的Connection Header來決定的,並且是須要兩邊都設置纔有效
    • 一種輪詢方式是否爲長輪詢,是根據服務端的處理方式來決定的,與客戶端沒有關係
  • 2.實現方式

    • 鏈接的長短是經過協議來規定來實現的
    • 輪詢的長短,是服務器經過編程的方式手動掛起請求來實現的

Socket 的短鏈接 長鏈接

Socket的長短鏈接的差異:整個客戶端和服務端的請求是經過一個socket仍是多個socket進行的

  • 長鏈接是整個通訊過程當中,服務端和客戶端只用一個socket對象,長期保持socket的鏈接
  • 短鏈接是每次請求的時候都新建一個socket,處理完一個請求就直接關閉socket

關於鏈接的關閉問題

Socket鏈接中 斷開流 其實就是斷開鏈接了,由於Socket原本就是依靠流進行通訊的

在關閉鏈接的時候 必定要顯示關閉socket,而不是經過調用shutDown方法關閉某個流,這樣容易形成內存泄漏

順序是先關流再關socket

如何實現長鏈接?

首先要知道:要實現長鏈接,通常須要發送結束標記符號來告訴客戶端 - 服務端的某段消息已經發送完畢,不然客戶端會一直阻塞在read方法

因此 客戶端須要本身主動退出讀取的動做,爲了防止客戶端一直阻塞在read()方法處

咱們能夠在服務端每發送完一段消息而且刷新前就進行一個寫入結束符號的標誌,當客戶端解析到結束符號時,就可直接退出read的循環讀取操做,避免一直阻塞。

Socket如何保持長鏈接?

方法1:應用層本身實現心跳包

節點(防火牆)會自動把必定時間以內沒有數據交互的鏈接給斷掉,因此要保持長鏈接,須要保活,須要發送心跳包,防止長時間沒有數據交互,致使鏈接被斷掉

客戶端和服務端制定一個通訊協議,每隔必定時間(通常15秒左右),由一方發起,向對方發送協議包;對方收到這個包後,按指定好的通訊協議回一個。若沒收到回覆,則判斷網絡出現問題,服務器可及時的斷開鏈接,客戶端也能夠及時重連。

總的來講:心跳包主要也就是用於長鏈接的保活和斷線處理。通常的應用下,斷定時間在30-40秒比較不錯。若是實在要求高,那就在6-9秒

方法2:TCP的KeepAlive保活機制

經過TCP協議層發送KeepAlive包。這個方法只需設置好你使用的TCP的KeepAlive項就好,其餘的操做系統會幫你完成。操做系統會按時發送KeepAlive包,一發現網絡異常,立刻斷開。

開啓KeepAlive功能須要消耗額外的寬帶和流量,因此TCP協議層默認並不開啓KeepAlive功能,另外一方面,KeepAlive設置不合理時可能會由於短暫的網絡波動而斷開如今的TCP鏈接。而且,默認的KeepAlive超時須要7,200,000 MilliSeconds, 即2小時,探測次數爲5次。對於不少服務端應用程序來講,2小時的空閒時間太長。所以,咱們須要手工開啓KeepAlive功能並設置合理的KeepAlive參數。

TCP的 SO_KEEPALIVE。系統默認是設置的2小時的心跳頻率。可是它檢查不到機器斷電、網線拔出、防火牆這些斷線。並且邏輯層處理斷線可能也不是那麼好處理。通常,若是隻是用於保活仍是能夠的。

TCP粘包問題

客戶端同一時間發送幾條數據,而服務端接收的是這幾條數據合在一塊兒的一條數據,通過TCP的傳輸,三條數據被合併成一條了,這就是數據的粘包問題。 也稱數據的無邊界性,read()/recv()函數不知道數據包的開始或結束標誌(實際上也沒有任何開始或結束標誌),只把它們當作連續的數據流來處理。

假設咱們但願客戶端每次發送一位學生的學號,讓服務器端返回該學生的姓名、住址、成績等信息,這時候可能就會出現問題,服務器端不能區分學生的學號。例如第一次發送 1,第二次發送 3,服務器可能當成 13 來處理,返回的信息顯然是錯誤的。

解決辦法

封包的時候給每一個包的數據加一個標記,來標明數據的長度和類型(類型顯然是須要的,咱們須要知道它是文本、圖片、仍是錄音等等,來用正確的方式處理這個數據)。

拆包的時候,先獲取到咱們給每一個包的標記,而後根據標記的數據長度,去獲取數據。最後再根據標記的類型去處理數據。(文字輸出、圖片展現、錄音播放等等)

相關文章
相關標籤/搜索