淺析TCP和nodejs中TCP的簡單應用

emmmmmmm...
tcp
咱們應該都知道,tcp是一種網絡協議。
javascript

提及網絡,在大學學計算機網絡的時候,記得老師講過網絡一共分7層,這7層從上到下依次是物理層、數據鏈路層、網絡層、傳輸層、會話層、表示層和應用層。 咱們來看張圖:java

OSI是Open System Interconnection的縮寫,意爲開放式系統互聯。國際標準化組織(ISO)制定了OSI模型,該模型定義了不一樣計算機互聯的標準,是設計和描述計算機網絡通訊的基本框架。OSI模型把網絡通訊的工做分爲7層,分別是物理層、數據鏈路層、網絡層、傳輸層、會話層、表示層和應用層

上面是基本網絡模型的組成。咱們再來看一下tcp/ip的參考模型node

TCP/IP是傳輸控制協議/網絡互聯協議的簡稱。早期的TCP/IP模型是一個四層結構,從下往上依次是網絡接口層、互聯網層、傳輸層和應用層。後來在使用過程當中,借鑑OSI七層參考模型,將網絡接口層劃分爲了物理層和數據鏈路層,造成五層結構。緩存

咱們看到了tcp/ip網絡模型把osi中的應用層、表示層、會話層合併成了應用層。
Tcp協議其實是在傳輸層。傳輸層是面向鏈接的、可靠的的進程到進程通訊的協議。TCP提供全雙工服務,即數據可在同一時間雙向傳播。TCP將若干個字節構成一個分組,此分組稱爲報文段(Segment)。提供了一種端到端的鏈接。 傳輸層的協議主要是TCP ,TCP(Transimision Control Protocal)是一種可靠的、面向鏈接的協議,傳輸效率低。服務器


咱們再來看一下tcp協議的格式

  •   源端口號和目標端口號,計算機經過端口號識別訪問哪一個服務,好比http服務或ftp服務,發送方端口號是進行隨機端口,目標端口號決定了接收方哪一個程序來接收。
  •   32位序列號 TCP用序列號對數據包進行標記,以便在到達目的地後從新重裝,假設當前的序列號爲s,發送數據長度爲 l,則下次發送數據時的序列號爲 s + l。在創建鏈接時一般由計算機生成一個隨機數做爲序列號的初始值確認應答號 它等於下一次應該接收到的數據的序列號。假設發送端的序列號爲 s,發送數據的長度爲 l,那麼接收端返回的確認應答號也是 s + l。發送端接收到這個確認應答後,能夠認爲這個位置之前全部的數據都已被正常接收。
  •   首部長度:TCP 首部的長度,單位爲 4 字節。若是沒有可選字段,那麼這裏的值就是 5。表示 TCP 首部的長度爲 20 字節。控制位 TCP的鏈接、傳輸和斷開都受這六個控制位的指揮
  •         PSH(push急迫位) 緩存區將滿,馬上傳輸速度
  •         RST(reset重置位) 鏈接斷了從新鏈接
  •         URG(urgent緊急位) 緊急信號
  •         ACK(acknowledgement 確認)爲1表示確認號
  •         SYN(synchronous創建聯機) 同步序號位

  •         TCP創建鏈接時要將這個值設爲1
  •         FIN發送端完成位,提出斷開鏈接的一方把FIN置爲1表示要斷開鏈接
  •     窗口值說明本地可接收數據段的數目,這個值的大小是可變的。當網絡通暢時將這個窗口值變大加快傳輸速度,當網絡不穩定時減小這個值能夠保證網絡數據的可靠傳輸。它是來在TCP傳輸中進行流量控制的
  •   窗口大小:用於表示從應答號開始可以接受多少個 8 位字節。若是窗口大小爲 0,能夠發送窗口探測。
  •   效驗和: 用來作差錯控制,TCP校驗和的計算包括TCP首部、數據和其它填充字節。在發送TCP數據段時,由發送端計算校驗和,當到達目的地時又進行一次檢驗和計算。若是兩次校驗 和一致說明數據是正確的,不然 將認爲數據被破壞,接收端將丟棄該數據
  •   緊急指針:盡在 URG(urgent緊急) 控制位爲 1 時有效。表示緊急數據的末尾在 TCP

  •   數據部分中的位置。一般在暫時中斷通訊時使用(好比輸入 Ctrl + C)。
  • 三次握手

    想必你們都知道這麼個東西。。那麼到底什麼是三次握手呢。。
    舉個🌰
    假如說我去咖啡店買咖啡。這時我要問店員了:請問有某某咖啡嗎?而後店員告訴我:有的。你帶錢了麼。而後我就說:我帶錢了,給來一杯把。。而後咱們該給錢給錢該給咖啡給咖啡網絡

    咱們來看這個例子。假如說我就是客戶端,店員是服務器。
    而後我去問服務器,你能夠創建鏈接嘛(一次握手)?服務器說我能夠創建連接,你能夠創建鏈接嗎(二次握手)?這時我再回復他說,我能夠創建連接(三次握手)...併發

    emmmmm...而後咱們兩個智障就愉快的創建了連接。。作一些偷偷摸摸傳東西的事情。。框架

    爲了更形象的來解讀一下三次握手,咱們來看張圖:socket

    咱們能夠很清楚的看到。
      第一次握手: 創建鏈接。客戶端發送鏈接請求,發送SYN報文,將seq設置爲0。而後,客戶端進入SYN_SEND狀態,等待服務器的確認。
      第二次握手: 服務器收到客戶端的SYN報文段。須要對這個SYN報文段進行確認,發送ACK報文,將ack設置爲1。同時,本身還要發送SYN請求信息,將seq爲0。服務器端將上述全部信息一併發送給客戶端,此時服務器進入SYN_RECV狀態。
      第三次握手: 客戶端收到服務器的ACK和SYN報文後,進行確認,而後將ack設置爲1,seq設置爲1,向服務器發送ACK報文段,這個報文段發送完畢之後,客戶端和服務器端都進入ESTABLISHED狀態,完成TCP三次握手。
    tcp

    四次揮手

    咱們再來舉個🌰

    假如我到飯店去吃飯,我吃完了要買單。這時我會喊服務員買單->服務員說您要買單嗎->服務員說一共xxxx元->我給錢以後就能夠走人了

    比喻可能不太恰當。。但差很少這個意思,咱們直接看圖

      第一次揮手:客戶端向服務器發送一個FIN ACK報文段。此時,客戶端進入 FIN_WAIT_1狀態,這表示客戶端沒有數據要發送服務器了,請求關閉鏈接;

      第二次揮手:服務器收到了客戶端發送的FIN報文段,向客戶端回一個ACK報文段。服務器進入了CLOSE_WAIT狀態,客戶端收到服務器返回的ACK報文後,進入FIN_WAIT_2狀態;

      第三次揮手:服務器會觀察本身是否還有數據沒有發送給客戶端,若是有,先把數據發送給客戶端,再發送FIN報文;若是沒有,那麼服務器直接發送FIN報文給客戶端。請求關閉鏈接,同時服務器進入LAST_ACK狀態;

      第四次揮手:客戶端收到服務器發送的FIN報文段,向服務器發送ACK報文段。而後客戶端進入TIME_WAIT狀態;服務器收到客戶端的ACK報文段之後,就關閉鏈接;此時,客戶端等待2MSL(大約4分鐘?)後依然沒有收到回覆,則證實Server端已正常關閉,客戶端也能夠關閉鏈接了。


    瞭解完三次握手和四次揮手後咱們可能會有幾個問題

    1.爲何客戶端和服務器須要三次握手

    答:三次握手的主要目的是保證客戶端和服務端都是能夠正常進行的收發信息。

    2.爲何須要四次揮手?

    答:四次揮手的目的是爲了確保雙方的數據發送完畢均可以斷開。

    3.爲何握手是三次,揮手倒是4次

    答:server端收到fin報文時可能並不會當即關閉。關於這個咱們想一下,有可能客戶端發送斷開鏈接的fin報文,server還有數據須要傳輸,這時就不該該當即斷開連接。


    nodejs中的TCP(net模塊)

    在Node.js中,net模塊實現了基於TCP的數據通訊

    咱們來看一下建立一個簡單的tcp服務端的方法

    建立TCP服務器
    let net  = require('net');
    let server = net.createServer(function(socket){
        console.log('客戶端已經連接');
    })
    server.listen('8080',function(){
        console.log('server is run in 8080');
    })
    複製代碼

    這樣就使用nodejs建立了一個簡單的tcp服務器; 注意:createServer的回調中有一個參數socket,指的是TCP服務器監聽的socket端口對象。

    設置最大連接數以及監聽客戶端的連接數量

    server.getConnections((err,count)=>{//得到當前的連接個數
        console.log('已經連接'+count+'個用戶')
    });
    server.maxConnections = 2;//限制當前的最大鏈接個數爲2
    複製代碼

    socket對象

    net.Socket表明一個socket端口對象,他是一個雙工流(可讀可寫)

    address

    獲取客戶端地址

    let net = require('net');
    let server = net.createServer(function(socket){
       console.log(socket.address())
    });
    server.listen(8080);
    複製代碼

    結果:

    pipe

    let ws = fs.createWriteStream(path.join(__dirname,'./1.txt'));
    
    let server = net.createServer(function(socket){
      socket.pipe(ws,{end:false}); // 第二個參數讓文件不自動關閉
      setTimeout(function(){
        ws.end(); //關閉可寫流
        socket.unpipe(ws); //取消管道
      },15000);
    });
    複製代碼

    能夠看到咱們建立了一個可寫流,咱們能夠把socket(雙工流)中的東西pipe到可寫流中。

    server和client

    建立server

    let net = require('net');
    
    let server = net.createServer(function(socket){
      socket.setEncoding('utf8');
      socket.on('data',function(data){
        console.log(data); //獲取從客戶端發來的數據
      })
      socket.end('服務端關閉');
    });
    server.on('connection',function(){
      console.log('客戶端連接');
    })
    server.listen(8080);
    複製代碼

    建立client

    let net = require('net');
    //createConnection第一個參數是你要鏈接到服務器的端口號
    let socket = net.createConnection(8080,function(){
      socket.write('hello'); //向服務端發送
      socket.on('data',function(data){
        console.log(data); //接受服務端數據
      });
    });
    複製代碼
    相關文章
    相關標籤/搜索