NODEJS硬實(shí)戰(zhàn)筆記 (TCP與UDP)

NodeJS TCP與UDP

一個(gè)最簡(jiǎn)單的TCP服務(wù)端

var net = require('net');
var clients = 0;
    
var server = net.createServer(function (client) {
    
  clients++;
  var clientId = clients;
  console.log('Client connected:', clientId);
    
  client.on('end', function () {
    console.log('Client disconnected:', clientId);
  });
    
  client.write('Welcome client: ' + clientId + 'rn');
  client.pipe(client);
});
    
server.listen(8000, function () {
  console.log('Server started on port 8000');
});

當(dāng)一個(gè)客戶端創(chuàng)建了一個(gè)新連接噪馏,傳遞給net.createServer回調(diào)函數(shù)將會(huì)執(zhí)行÷潭回調(diào)接收一個(gè)面向事件的連接對(duì)象欠肾。這個(gè)服務(wù)對(duì)象是net.Server的一個(gè)實(shí)例,僅僅是對(duì)net.Socket類的一個(gè)封裝拟赊,而net.Socket類又是使用雙工流來(lái)實(shí)現(xiàn)的刺桃,所以服務(wù)端在發(fā)送信息給客戶端的時(shí)候可以使用client.pipe()管道來(lái)發(fā)送。

TCP客戶端和服務(wù)端

// 主要是驗(yàn)證每次的連接都對(duì)應(yīng)不同的clientId
var assert = require('assert');
var net = require('net');
    
var clients = 0;
var expectedAssertions = 2;
    
// 創(chuàng)建一個(gè)服務(wù)端
var server = net.createServer(function (client) {
  clients++;
  var clientId = clients;
  console.log('Client connected:', clientId);
    
  client.on('end', function () {
    console.log('Client disconnected:', clientId);
  });
    
  client.write('Welcome client:' + clientId + '\r\n');
  client.pipe(client);
});
    
// 主線程監(jiān)聽(tīng)8000端口
server.listen(8000, function () {
  console.log('Server started on port 8000');
    
  runTest(1, function () {
    runTest(2, function () {
      console.log('Tests finished');
      assert.equal(0, expectedAssertions);
      server.close()
    })
  });
    
  // 創(chuàng)建客戶端并去連接服務(wù)端
  function runTest(expectedId, done) {
    var client = net.connect(8000);
    
    client.on('data', function (data) {
      var expected = 'Welcome client:' + expectedId + '\r\n';
      assert.equal(data.toString(), expected);
      expectedAssertions--;
      client.end();
    });
    
    client.on('end', done)
  }
});

這里發(fā)現(xiàn)了一個(gè)問(wèn)題吸祟,當(dāng)服務(wù)端和客戶端處于同一線程中的時(shí)候虏肾,兩邊互發(fā)消息廓啊,服務(wù)端接收到的流存在異常,內(nèi)容將變成客戶端發(fā)送的消息+服務(wù)端之前發(fā)送的消息封豪。暫時(shí)只知道在write后再通過(guò)管道傳遞會(huì)發(fā)送這種情況谴轮,不知道為什么?并且也不是很清楚為什么在write了之后還需要通過(guò)管道傳遞吹埠。

  • 連接服務(wù)端:
    • 連接TCP服務(wù)端要?jiǎng)?chuàng)建一個(gè)客戶端對(duì)象第步,這個(gè)對(duì)象是是一個(gè)UNIX Socket,即:net.Socket實(shí)例缘琅。它是了一個(gè)雙向的流接口粘都,創(chuàng)建對(duì)象服務(wù)器端的connection事件會(huì)被觸發(fā)。創(chuàng)建一個(gè)TCP客戶端可以使用createConnection()方法或其別名方法connect()刷袍,也可以使用構(gòu)造函數(shù)new net.Socket()翩隧。連接成功后'connect'事件會(huì)被觸發(fā)。
  • 和服務(wù)端交換數(shù)據(jù):
    • 與TCP服務(wù)端建立連接后就可以向服務(wù)端發(fā)送數(shù)據(jù)呻纹,或接收來(lái)自服務(wù)器的數(shù)據(jù)堆生。接收到服務(wù)器端的數(shù)據(jù)數(shù)據(jù)后后觸發(fā)data事件,可以通監(jiān)聽(tīng)這個(gè)事件接收數(shù)據(jù)雷酪。向服務(wù)器發(fā)送數(shù)據(jù)可以使用write()方法淑仆。數(shù)據(jù)傳輸前可以通過(guò)setEncoding()方法設(shè)置流的編碼格式。
    • socket.write(data, [encoding], [callback]):在UNIX Socket套接字上發(fā)送數(shù)據(jù)時(shí)哥力,如果第二參數(shù)用于設(shè)置發(fā)送數(shù)據(jù)的編碼方式蔗怠,默認(rèn)為UTF8編碼。如果所有數(shù)據(jù)被成功刷新到緩沖區(qū)吩跋,則返回true寞射。如果所有或部分?jǐn)?shù)據(jù)在用戶內(nèi)存里還處于隊(duì)列中,則返回false锌钮。當(dāng)緩沖區(qū)再次被釋放時(shí)桥温,'drain'事件會(huì)被觸發(fā)。(注意這邊write的實(shí)現(xiàn)本身就是流模式的轧粟,所以更不明白上面write后還通過(guò)管道傳遞的寫(xiě)法
    • 當(dāng)數(shù)據(jù)最終被完整寫(xiě)入時(shí)策治,可選參數(shù)callback會(huì)被執(zhí)行。
    • socket.setEncoding([encoding]):設(shè)置Socket流的編碼格式
  • Socket流的暫停與關(guān)閉:
    • 關(guān)閉與終端的的連接使用end()方法兰吟,end()方法在關(guān)閉連接前也可以向終端發(fā)送數(shù)據(jù)通惫。對(duì)于發(fā)生錯(cuò)誤的連接,可以調(diào)用destroy()方法混蔼,關(guān)閉已沒(méi)有 I/O 活動(dòng)的TCP連接履腋。
    • socket.end(data, [encoding]):半關(guān)閉Socket套接字。例如:當(dāng)發(fā)送一個(gè)FIN包時(shí),可能服務(wù)器仍在發(fā)送數(shù)據(jù)遵湖。(管道是雙向的悔政,所以這邊是半關(guān)閉,具體原因參見(jiàn)后期將會(huì)寫(xiě)的TCP狀態(tài)機(jī))
      如果傳入data參, 等同于調(diào)用 socket.write(data, encoding)然后調(diào)用socket.end()延旧。
    • socket.destroy():銷毀已沒(méi)有I/O活動(dòng)的TCP連接
    • Socket客戶端是一個(gè)可讀寫(xiě)的流谋国,這意味著你可以對(duì)它進(jìn)行暫停和恢復(fù)。
      • socket.pause():暫停讀取數(shù)據(jù)迁沫,暫停后'data'事件不會(huì)再觸發(fā)
      • socket.resume():恢復(fù)pause()方法暫停的流
  • Socket客戶端一些設(shè)置和方法:
    • 除了前面介紹的設(shè)置編碼的setEncoding()方法外芦瘾,還有其它一些設(shè)置方法,如:設(shè)置超時(shí)的setTimeout()方法集畅。
    • socket.setTimeout(timeout[, callback]):套接字超過(guò)timeout毫秒閑置狀態(tài)近弟,則將套接字設(shè)為超時(shí)。默認(rèn)net.Socket不存在超時(shí)挺智。
      當(dāng)一個(gè)閑置超時(shí)被觸發(fā)時(shí)祷愉,會(huì)觸發(fā)一個(gè)'timeout'事件,但是連接將不會(huì)被斷開(kāi)赦颇。用戶必須手動(dòng)end()或destroy()斷開(kāi)這個(gè)套接字二鳄。可選參數(shù)callback會(huì)被添加成為'timeout'事件的一次性監(jiān)聽(tīng)器沐扳。
    • socket.setNoDelay([noDelay]):禁用Nagle算法泥从。默認(rèn)情況下TCP連接使用Nagle算法句占,這些連接在發(fā)送數(shù)據(jù)之前對(duì)數(shù)據(jù)進(jìn)行緩沖處理沪摄。 將noDelay設(shè)成true會(huì)在每次socket.write()被調(diào)用時(shí)立刻發(fā)送數(shù)據(jù)。noDelay默認(rèn)為true纱烘。
    • socket.setKeepAlive([enable], [initialDelay]):禁用/啟用長(zhǎng)連接功能椎眯,并在第一個(gè)在閑置套接字上的長(zhǎng)連接probe被發(fā)送之前瓶埋,可選地設(shè)定初始延時(shí)。enable默認(rèn)為false。
      設(shè)定initialDelay (毫秒)寝衫,來(lái)設(shè)定在收到的最后一個(gè)數(shù)據(jù)包和第一個(gè)長(zhǎng)連接probe之間的延時(shí)。設(shè)置為0會(huì)保留默認(rèn)(或者之前)的值躲撰。默認(rèn)為0桑逝。
    • socket.address():返回Socket套接字綁定的IP地址, 協(xié)議類型以及端口號(hào)。其返回值是一個(gè)包含三個(gè)屬性的對(duì)象, 形如{ port: 2345, family: 'IPv4', address: '127.0.0.1' }山宾。
    • socket.unref():如果當(dāng)前套接字對(duì)象是事件系統(tǒng)中唯一一個(gè)活動(dòng)的套接字至扰,調(diào)用unref方法將允許程序退出。如果套接字已被 unref资锰,則再次調(diào)用 unref 并不會(huì)產(chǎn)生影響敢课。
    • socket.ref():與unref 相反。如果當(dāng)前套接字對(duì)象是僅剩的套接字,在一個(gè)之前被 unref 了的套接字上調(diào)用 ref 將不會(huì)讓程序退出(缺省行為)直秆。如果一個(gè)套接字已經(jīng)被 ref濒募,則再次調(diào)用 ref 并不會(huì)產(chǎn)生影響。
  • Socket類中的屬性:
    • socket.bufferSize:當(dāng)前準(zhǔn)備寫(xiě)入緩沖區(qū)的字符數(shù)圾结,用戶可根據(jù)此屬性對(duì)數(shù)據(jù)流進(jìn)行控制瑰剃。遇到很大或增長(zhǎng)很快的 bufferSize 時(shí),用戶可用嘗試用pause() 和 resume()來(lái)控制字符流筝野。
    • socket.remoteAddress:遠(yuǎn)程的IP地址(TCP服務(wù)端)培他,例如:'74.125.127.100'或'2001:4860:a005::68'
    • socket.remoteFamily:遠(yuǎn)程IP協(xié)議版本,例如:'IPv4'或'IPv6'
    • socket.remotePort:遠(yuǎn)程端口號(hào)遗座,例如:80或22
    • socket.localAddress:本地IP地址(TCP客戶端)舀凛,例如:'198.168.0.10'
    • socket. localPort:本地端口號(hào),例如:80或22
    • socket.bytesRead:客戶端收到的字節(jié)數(shù)
    • socket.bytesWritten:客戶端發(fā)送的字節(jié)數(shù)

TCP基礎(chǔ)知識(shí)

數(shù)據(jù)包與MTU

  • 數(shù)據(jù)包主要分為IPv4和IPv6數(shù)據(jù)包途蒋,那這里先簡(jiǎn)要說(shuō)一下IPv4協(xié)議與IPv6協(xié)議的區(qū)別:
    • 更大的地址空間猛遍,IPv4中規(guī)定IP地址長(zhǎng)度為32,即有232-1個(gè)地址号坡;而IPv6中IP地址的長(zhǎng)度為128懊烤,即有2128-1個(gè)地址。
    • 更小的路由表宽堆。IPv6的地址分配一開(kāi)始就遵循聚類(Aggregation)的原則腌紧,這使得路由器能在路由表中用一條記錄(Entry)表示一片子網(wǎng),大大減小了路由器中路由表的長(zhǎng)度畜隶,提高了路由器轉(zhuǎn)發(fā)數(shù)據(jù)包的速度壁肋。 增強(qiáng)的組播(Multicast)支持以及對(duì)流的支持(Flow-control)。
    • IPv4的數(shù)據(jù)包大小是65535字節(jié)籽慢,包括IPv4的首部浸遗,首部中說(shuō)明大小的字段為16位。
    • IPv6的數(shù)據(jù)包大小是65575字節(jié)箱亿,因?yàn)镮Pv6的首部是40字節(jié)跛锌,但因?yàn)椴凰阍谄渲兴员菼Pv4大一個(gè)首部。
    • (具體的比較分析見(jiàn)后期將會(huì)寫(xiě)的IPv4與IPv6數(shù)據(jù)報(bào)分析)
  • MTU(Maximum Transmission Unit):最大傳輸單元
    • MTU就像是高速公路上的車道寬度届惋。
    • 許多網(wǎng)絡(luò)有一個(gè)可由硬件規(guī)定的MTU髓帽。以太網(wǎng)的MTU為1500字節(jié)。有一些鏈路的MTU的MTU可以由認(rèn)為配置脑豹。IPv4要求的最小鏈路MTU為68字節(jié)郑藏。這允許最大的IPv4首部(包括20字節(jié)的固定長(zhǎng)度部分和最多40字節(jié)的選項(xiàng)部分)拼接最小的片段(IPv4首部中片段偏移字段以8個(gè)字節(jié)為單位)IPv6要求的最小鏈路MTU為1280字節(jié)

分片

  • 當(dāng)一個(gè)IP數(shù)據(jù)報(bào)從某個(gè)接口送出時(shí)晨缴,如果它的大小超過(guò)相應(yīng)鏈路的MTU译秦,IPv4和IPv6都將執(zhí)行分片。這些片段在到達(dá)終點(diǎn)之前通常不會(huì)被重組(reassembling)。IPv4主機(jī)對(duì)其產(chǎn)生的數(shù)據(jù)報(bào)執(zhí)行分片筑悴,IPv4路由器則對(duì)其轉(zhuǎn)發(fā)的數(shù)據(jù)報(bào)進(jìn)行分片们拙。然后IPv6只有主機(jī)對(duì)其產(chǎn)生的數(shù)據(jù)報(bào)執(zhí)行分片,IPv6路由器不對(duì)其轉(zhuǎn)發(fā)的數(shù)據(jù)報(bào)執(zhí)行分片阁吝。
  • IPv4首部的“不分片”(do not fragment)位(即DF位)若被設(shè)置砚婆,那么不管是發(fā)送這些數(shù)據(jù)報(bào)的主機(jī)還是轉(zhuǎn)發(fā)他們的路由器,都不允許對(duì)它們分片突勇。當(dāng)路由器接收到一個(gè)超過(guò)其外出鏈路MTU大小且設(shè)置了DF位的IPv4數(shù)據(jù)報(bào)時(shí)装盯,它將產(chǎn)生一個(gè)ICMPv4“destination unreachable,fragmentation needed but DF bit set”(目的不可到達(dá),需分片但DF位已設(shè)置)的出錯(cuò)消息甲馋。
  • 既然IPv6路由器不執(zhí)行分片埂奈,每個(gè)IPv6數(shù)據(jù)報(bào)于是隱含一個(gè)DF位。當(dāng)IPv6路由器接收到一個(gè)超過(guò)其外出鏈路MTU大小的IPv6數(shù)據(jù)報(bào)時(shí)定躏,它將產(chǎn)生一個(gè)ICMPv6 “packet too big”的出錯(cuò)消息账磺。IPv4的DF位和隱含DF位可用于路徑MTU發(fā)現(xiàn)。

緩沖區(qū)

  • 緩沖區(qū)是TCP進(jìn)行數(shù)據(jù)交流的最主要的承載部分痊远。就像高速公路兩端的休息站垮抗。
  • 而緩沖區(qū)主要是分為發(fā)送緩沖區(qū)重組緩沖區(qū)
    • MSS(maximun segment size): 最大分段尺寸
      • TCP有一個(gè)最大分段大小,用于對(duì)端TCP通告對(duì)端每個(gè)分段中能發(fā)送的最大TCP數(shù)據(jù)量碧聪。MSS的目的是告訴對(duì)端其重組緩沖區(qū)大小的實(shí)際值冒版,從而避免分片。MSS經(jīng)常設(shè)計(jì)成MTU減去IP和TCP首部的固定長(zhǎng)度逞姿。以太網(wǎng)中使用IPv4MSS值為1460辞嗡,使用IPv6的MSS值為1440(兩者TCP首部都是20字節(jié),但是IPv6首部是40字節(jié)哼凯,IPv4首部是20字節(jié))欲间。
    • 發(fā)送緩沖區(qū):
      • 每個(gè)TCP套接字有一個(gè)發(fā)送緩沖區(qū)楚里,我們可以用SO_SNDBUF套接字選項(xiàng)來(lái)更改該緩沖區(qū)的大小断部。當(dāng)某個(gè)應(yīng)用進(jìn)程調(diào)用write時(shí),內(nèi)核從該應(yīng)用進(jìn)程的緩沖區(qū)復(fù)制所有數(shù)據(jù)到縮寫(xiě)套接字的發(fā)送緩沖區(qū)班缎。如果該套接字的發(fā)送緩沖區(qū)容不下該應(yīng)用進(jìn)程的所有數(shù)據(jù)(或是應(yīng)用進(jìn)程的緩沖區(qū)大于套接字的發(fā)送緩沖區(qū)蝴光,或是套接字的發(fā)送緩沖區(qū)中已有其他數(shù)據(jù)),該應(yīng)用進(jìn)程將被投入睡眠达址。這里假設(shè)該套接字是阻塞的蔑祟,它通常是默認(rèn)設(shè)置。內(nèi)核將不從write系統(tǒng)調(diào)用返回沉唠,直到應(yīng)用進(jìn)程緩沖區(qū)中的所有數(shù)據(jù)都復(fù)制到套接字發(fā)送緩沖區(qū)疆虚。因此,從寫(xiě)一個(gè)TCP套接字的write調(diào)用成功返回僅僅表示我們可以重新使用原來(lái)的應(yīng)用進(jìn)程緩沖區(qū),并不表明對(duì)端的TCP或應(yīng)用進(jìn)程已接受到數(shù)據(jù)径簿。
      • 這一端的TCP提取套接字發(fā)送緩沖區(qū)中的數(shù)據(jù)并把它發(fā)送給對(duì)端的TCP罢屈,其過(guò)程基于TCP數(shù)據(jù)傳送的所有規(guī)則。對(duì)端TCP必須確認(rèn)收到的數(shù)據(jù)篇亭,伴隨來(lái)自對(duì)端的ACK的不斷到達(dá)缠捌,本段TCP至此才能從套接字發(fā)送緩沖區(qū)中丟棄已確認(rèn)的數(shù)據(jù)。TCP必須為已發(fā)送的數(shù)據(jù)保留一個(gè)副本译蒂,直到它被對(duì)端確認(rèn)為止曼月。本端TCP以MSS大小或是更小的塊把數(shù)據(jù)傳遞給IP,同時(shí)給每個(gè)數(shù)據(jù)塊安上一個(gè)TCP首部以構(gòu)成TCP分節(jié)柔昼,其中MSS或是由對(duì)端告知的值哑芹,或是536(若未發(fā)送一個(gè)MSS選項(xiàng)為576-TCP首部-IP首部)。IP給每個(gè)TCP分節(jié)安上一個(gè)IP首部以構(gòu)成IP數(shù)據(jù)報(bào)捕透,并按照其目的的IP地址查找路由表項(xiàng)以確定外出接口绩衷,然后把數(shù)據(jù)報(bào)傳遞給相應(yīng)的數(shù)據(jù)鏈路。每個(gè)數(shù)據(jù)鏈路都有一個(gè)數(shù)據(jù)隊(duì)列激率,如果該隊(duì)列已滿咳燕,那么新到的分組將被丟棄,并沿協(xié)議棧向上返回一個(gè)錯(cuò)誤:從數(shù)據(jù)鏈路到IP乒躺,在從IP到TCP招盲。TCP將注意到這個(gè)錯(cuò)誤,并在以后某個(gè)時(shí)候重傳相應(yīng)的分節(jié)嘉冒。應(yīng)用程序不知道這種暫時(shí)的情況曹货。
    • 重組緩沖區(qū):
      • IPv4和IPv6都定義了最小緩沖區(qū)大小,它是IPv4或IPv6任何實(shí)現(xiàn)都必須保重支持的最小數(shù)據(jù)報(bào)大小讳推。其值對(duì)IPv4為576字節(jié)顶籽,對(duì)于IPv6為1500字節(jié)。例如银觅,對(duì)于IPv4而言礼饱,我們不能判定某個(gè)給定的目的能否接受577字節(jié)的數(shù)據(jù)報(bào),為此很多應(yīng)用避免產(chǎn)生大于這個(gè)大小的數(shù)據(jù)報(bào)究驴。

Nagle算法

  • TCP/IP協(xié)議中镊绪,無(wú)論發(fā)送多少數(shù)據(jù),總是要在數(shù)據(jù)前面加上協(xié)議頭洒忧,同時(shí)蝴韭,對(duì)方接收到數(shù)據(jù),也需要發(fā)送ACK表示確認(rèn)熙侍。為了盡可能的利用網(wǎng)絡(luò)帶寬榄鉴,TCP總是希望盡可能的發(fā)送足夠大的數(shù)據(jù)履磨。(一個(gè)連接會(huì)設(shè)置MSS參數(shù),因此庆尘,TCP/IP希望每次都能夠以MSS尺寸的數(shù)據(jù)塊來(lái)發(fā)送數(shù)據(jù))蹬耘。Nagle算法就是為了盡可能發(fā)送大塊數(shù)據(jù),避免網(wǎng)絡(luò)中充斥著許多小數(shù)據(jù)塊减余。
  • Nagle算法的基本定義是任意時(shí)刻综苔,最多只能有一個(gè)未被確認(rèn)的小段。 所謂“小段”位岔,指的是小于MSS尺寸的數(shù)據(jù)塊如筛,所謂“未被確認(rèn)”,是指一個(gè)數(shù)據(jù)塊發(fā)送出去后抒抬,沒(méi)有收到對(duì)方發(fā)送的ACK確認(rèn)該數(shù)據(jù)已收到杨刨。
  • Nagle算法的規(guī)則(可參考tcp_output.c文件里tcp_nagle_check函數(shù)注釋):
    • 如果包長(zhǎng)度達(dá)到MSS,則允許發(fā)送擦剑;
    • 如果該包含有FIN妖胀,則允許發(fā)送;
    • 設(shè)置了TCP_NODELAY選項(xiàng)惠勒,則允許發(fā)送赚抡;
    • 未設(shè)置TCP_CORK選項(xiàng)時(shí),若所有發(fā)出去的小數(shù)據(jù)包(包長(zhǎng)度小于MSS)均被確認(rèn)纠屋,則允許發(fā)送涂臣;
    • 上述條件都未滿足,但發(fā)生了超時(shí)(一般為200ms)售担,則立即發(fā)送赁遗。

UDP服務(wù)端和客戶端

var assert = require('assert');
var dgram = require('dgram');
var fs = require('fs');
var defaultSize = 16;
var port = 41234;

// 創(chuàng)建客戶端
function Client(remoteIP) {
  var socket = dgram.createSocket('udp4');
  var readline = require('readline');
  var rl = readline.createInterface(process.stdin, process.stdout);

  socket.send(new Buffer('<JOIN>'), 0, 6, port, remoteIP);

  rl.setPrompt('Message> ');
  // 開(kāi)始等待用戶的輸入
  rl.prompt();

  // 當(dāng)用戶輸入完一行按回車后觸發(fā)
  rl.on('line', function (line) {
    sendData(line)
  // readline一開(kāi)始執(zhí)行就不會(huì)結(jié)束,所以需要監(jiān)聽(tīng)close事件來(lái)關(guān)閉進(jìn)程
  }).on('close', function () {
    process.exit(0)
  });

  socket.on('message', function (msg, rinfo) {
    console.log('\n<' + rinfo.address + '>', msg.toString());
    rl.prompt();
  });

  function sendData(message) {
    socket.send(new Buffer(message), 0, message.length, port, remoteIP,
      function (err, bytes) {
        console.log('Sent:', message);
        rl.prompt();
      })
  }
}

// 創(chuàng)建服務(wù)端
function Server() {
  var clients = [];
  var server = dgram.createSocket('udp4');
  server.on('message', function (msg, rinfo) {
    var clientId = rinfo.address + ':' + rinfo.port;
    msg = msg.toString();

    if (!clients[clientId]) {
      clients[clientId] = rinfo;
    }

    if (msg.match(/^</)) {
      console.log('Control message:', msg);
      return;
    }

    for (var client in clients) {
      if (client !== clientId) {
        client = clients[client];
        server.send(
          new Buffer(msg), 0,
          msg.length, client.port, client.address,
          function (err, bytes) {
            if (err) console.error(err);
            console.log('Bytes sent:', bytes);
          }
        )
      }
    }
  });

  server.on('listening', function () {
    console.log('Server ready:', server.address());
  });

  server.bind(port);
}

module.exports = {
  Client: Client,
  Server: Server
};

// module.parent 返回引用該模板的模板
if (!module.parent) {
  switch (process.argv[2]) {
    case 'client':
      new Client(process.argv[3]);
      break;
    case 'server':
      new Server();
      break;
    default:
      console.log('Unknown option');
  }
}

使用dgram.createSocket創(chuàng)建一個(gè)客戶端socket與服務(wù)端相同族铆。發(fā)送一個(gè)數(shù)據(jù)報(bào)需要一個(gè)buffer來(lái)承載岩四,用偏移量來(lái)表明buffer中消息的開(kāi)始、消息的長(zhǎng)度哥攘、服務(wù)端口剖煌、遠(yuǎn)程IP和一個(gè)可選的回調(diào),當(dāng)消息發(fā)出時(shí)會(huì)被觸發(fā)献丑。

  • UDP發(fā)送緩沖區(qū)
    • 任何UDP套接字都有發(fā)送緩沖區(qū)大心┑贰(我們可以用SO_SNDBUF套接字選項(xiàng)更改它),不過(guò)它僅僅是可寫(xiě)道套接字UDP數(shù)據(jù)報(bào)大小上限创橄。如果一個(gè)應(yīng)用進(jìn)程寫(xiě)一個(gè)大于套接字發(fā)送緩沖區(qū)大小的數(shù)據(jù)報(bào),內(nèi)核將返回該進(jìn)程一個(gè)EMSGSIZE錯(cuò)誤莽红。既然UDP是不可靠的妥畏,它不必保存應(yīng)用進(jìn)程數(shù)據(jù)的一個(gè)副本邦邦,因此無(wú)需一個(gè)真正的發(fā)送緩沖區(qū)。(應(yīng)用進(jìn)程的數(shù)據(jù)在沿協(xié)議棧向下傳遞時(shí)醉蚁,通常被復(fù)制到某種格式的一個(gè)內(nèi)核緩沖區(qū)中燃辖,然而當(dāng)該數(shù)據(jù)被發(fā)送之后,這個(gè)副本被數(shù)據(jù)鏈路層丟棄了网棍。)
    • UDP簡(jiǎn)單地給來(lái)自用戶的數(shù)據(jù)報(bào)安上8字節(jié)首部以構(gòu)成UDP數(shù)據(jù)報(bào)黔龟,然后傳遞給IP。IPv4或IPv6給UDP數(shù)據(jù)報(bào)安上相應(yīng)的IP首部以構(gòu)成IP數(shù)據(jù)報(bào)滥玷,執(zhí)行路由操作確定外出接口氏身,然后或者直接把數(shù)據(jù)報(bào)加入數(shù)據(jù)鏈路層輸出隊(duì)列(如果適合于MTU),或者分片后在把每個(gè)片段加入數(shù)據(jù)集鏈路層的輸出隊(duì)列惑畴。如果某個(gè)UDP進(jìn)程發(fā)送大數(shù)據(jù)報(bào)蛋欣,那么它們相比TCP應(yīng)用數(shù)據(jù)更有可能被分片,因?yàn)門(mén)CP會(huì)把應(yīng)用數(shù)據(jù)劃分成MSS大小的塊如贷,而UDP卻沒(méi)有對(duì)等的手段陷虎。
    • 從寫(xiě)一個(gè)UDP套接字的write調(diào)用成功返回表示所寫(xiě)的數(shù)據(jù)報(bào)或其所有片段已被加入數(shù)據(jù)鏈路層的輸出隊(duì)列。如果該隊(duì)列沒(méi)有足夠的空間存放該數(shù)據(jù)報(bào)或它的某個(gè)片段杠袱,內(nèi)核通常會(huì)返回一個(gè)ENOBUFS錯(cuò)誤給它的應(yīng)用進(jìn)程尚猿。有些UDP實(shí)現(xiàn)不返回這種錯(cuò)誤,這樣甚至數(shù)據(jù)報(bào)未經(jīng)發(fā)送就被丟棄的情況進(jìn)程也不知道楣富。

部分內(nèi)容摘抄自網(wǎng)上博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末谊路,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子菩彬,更是在濱河造成了極大的恐慌缠劝,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件骗灶,死亡現(xiàn)場(chǎng)離奇詭異惨恭,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)耙旦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)脱羡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人免都,你說(shuō)我怎么就攤上這事锉罐。” “怎么了绕娘?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵脓规,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我险领,道長(zhǎng)侨舆,這世上最難降的妖魔是什么秒紧? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮挨下,結(jié)果婚禮上熔恢,老公的妹妹穿的比我還像新娘。我一直安慰自己臭笆,他們只是感情好叙淌,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著愁铺,像睡著了一般鹰霍。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上帜讲,一...
    開(kāi)封第一講書(shū)人閱讀 51,679評(píng)論 1 305
  • 那天衅谷,我揣著相機(jī)與錄音,去河邊找鬼似将。 笑死获黔,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的在验。 我是一名探鬼主播玷氏,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼腋舌!你這毒婦竟也來(lái)了盏触?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤块饺,失蹤者是張志新(化名)和其女友劉穎赞辩,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體授艰,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡辨嗽,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了淮腾。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片糟需。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖谷朝,靈堂內(nèi)的尸體忽然破棺而出洲押,到底是詐尸還是另有隱情,我是刑警寧澤圆凰,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布杈帐,位于F島的核電站,受9級(jí)特大地震影響送朱,放射性物質(zhì)發(fā)生泄漏娘荡。R本人自食惡果不足惜干旁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一驶沼、第九天 我趴在偏房一處隱蔽的房頂上張望炮沐。 院中可真熱鬧,春花似錦回怜、人聲如沸大年。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)翔试。三九已至,卻和暖如春复旬,著一層夾襖步出監(jiān)牢的瞬間垦缅,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工驹碍, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留壁涎,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓志秃,卻偏偏與公主長(zhǎng)得像怔球,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子浮还,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

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