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)究驴。
- MSS(maximun segment size): 最大分段尺寸
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)上博客