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è)客戶(hù)端創(chuàng)建了一個(gè)新連接,傳遞給net.createServer回調(diào)函數(shù)將會(huì)執(zhí)行。回調(diào)接收一個(gè)面向事件的連接對(duì)象。這個(gè)服務(wù)對(duì)象是net.Server的一個(gè)實(shí)例,僅僅是對(duì)net.Socket類(lèi)的一個(gè)封裝,而net.Socket類(lèi)又是使用雙工流來(lái)實(shí)現(xiàn)的,所以服務(wù)端在發(fā)送信息給客戶(hù)端的時(shí)候可以使用client.pipe()管道來(lái)發(fā)送。
TCP客戶(hù)端和服務(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)建客戶(hù)端并去連接服務(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ù)端和客戶(hù)端處于同一線程中的時(shí)候,兩邊互發(fā)消息,服務(wù)端接收到的流存在異常,內(nèi)容將變成客戶(hù)端發(fā)送的消息+服務(wù)端之前發(fā)送的消息。暫時(shí)只知道在write后再通過(guò)管道傳遞會(huì)發(fā)送這種情況,不知道為什么?并且也不是很清楚為什么在write了之后還需要通過(guò)管道傳遞。
-
連接服務(wù)端:
- 連接TCP服務(wù)端要?jiǎng)?chuàng)建一個(gè)客戶(hù)端對(duì)象,這個(gè)對(duì)象是是一個(gè)UNIX Socket,即:net.Socket實(shí)例。它是了一個(gè)雙向的流接口,創(chuàng)建對(duì)象服務(wù)器端的connection事件會(huì)被觸發(fā)。創(chuàng)建一個(gè)TCP客戶(hù)端可以使用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ù)在用戶(hù)內(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():銷(xiāo)毀已沒(méi)有I/O活動(dòng)的TCP連接
- Socket客戶(hù)端是一個(gè)可讀寫(xiě)的流,這意味著你可以對(duì)它進(jìn)行暫停和恢復(fù)。
- socket.pause():暫停讀取數(shù)據(jù),暫停后'data'事件不會(huì)再觸發(fā)
- socket.resume():恢復(fù)pause()方法暫停的流
-
Socket客戶(hù)端一些設(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)。用戶(hù)必須手動(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é)議類(lèi)型以及端口號(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類(lèi)中的屬性:
- socket.bufferSize:當(dāng)前準(zhǔn)備寫(xiě)入緩沖區(qū)的字符數(shù),用戶(hù)可根據(jù)此屬性對(duì)數(shù)據(jù)流進(jìn)行控制。遇到很大或增長(zhǎng)很快的 bufferSize 時(shí),用戶(hù)可用嘗試用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客戶(hù)端),例如:'198.168.0.10'
- socket. localPort:本地端口號(hào),例如:80或22
- socket.bytesRead:客戶(hù)端收到的字節(jié)數(shù)
- socket.bytesWritten:客戶(hù)端發(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)始就遵循聚類(lè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就像是高速公路上的車(chē)道寬度。
- 許多網(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ì)列已滿(mǎn),那么新到的分組將被丟棄,并沿協(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ù)注釋?zhuān)?
- 如果包長(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ā)送;
- 上述條件都未滿(mǎn)足,但發(fā)生了超時(shí)(一般為200ms),則立即發(fā)送。
UDP服務(wù)端和客戶(hù)端
var assert = require('assert');
var dgram = require('dgram');
var fs = require('fs');
var defaultSize = 16;
var port = 41234;
// 創(chuàng)建客戶(hù)端
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)始等待用戶(hù)的輸入
rl.prompt();
// 當(dāng)用戶(hù)輸入完一行按回車(chē)后觸發(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è)客戶(hù)端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)自用戶(hù)的數(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)上博客