在Node.js中,提供了一個net模塊,專用于實現(xiàn)TCP服務(wù)器與TCP客戶端之間的通信。
1、創(chuàng)建TCP服務(wù)器
const server = net.createServer([options], [connectionListener]);
createServer方法返回被創(chuàng)建的TCP服務(wù)器,其中使用兩個可選參數(shù):
options:其值為一個對象,可以在該對象中使用一個布爾值類型的allowHalfOpen屬性,當該屬性值被指定為false時,當TCP服務(wù)器接收到客戶端發(fā)送的一個FIN包時將會回發(fā)一個FIN包,當該屬性值被設(shè)定為true時,當TCP服務(wù)器接收到客戶端發(fā)送的一個FIN包時不回發(fā)FIN包,這使得TCP服務(wù)器可以繼續(xù)向客戶端發(fā)送數(shù)據(jù),但是不會繼續(xù)接收客戶端發(fā)送的數(shù)據(jù)。開發(fā)者必須調(diào)用end方法來關(guān)閉socket連接。該屬性值默認為false。
connectionListener:用于指定當客戶端與服務(wù)器端建立連接時所要調(diào)用的回調(diào)函數(shù),該回調(diào)函數(shù)使用一個參數(shù),參數(shù)值為該TCP服務(wù)器監(jiān)聽的socket端口對象。
function(socket) {
}
我們也可以不在createServer方法中使用connectionListener參數(shù),而是通過對connection事件進行監(jiān)聽,并且指定該事件的回調(diào)函數(shù)的方法來指定當客戶端與服務(wù)器建立連接時需要執(zhí)行的處理方法如下所示:
server.on('connection', function(socket) {
})
創(chuàng)建TCP服務(wù)器之后,可以使用listen方法通知服務(wù)器開始監(jiān)聽客戶端連接。該方法具有如下三種指定方法。
方法一:指定方法如下所示(代碼中server代表一個TCP服務(wù)器)
server.listen(port, [host], [backlog], [callback])
port:必須,用于指定需要監(jiān)聽的端口號,參數(shù)值為0時TCP服務(wù)器分配一個隨機端口號。
host:可選,用于指定需要監(jiān)聽的IP地址或主機名,如果省略該參數(shù),服務(wù)器將監(jiān)聽來自于任何IPv4地址的客戶端連接。
backlog:可選,參數(shù)值為一個整數(shù)值,用于指定位于等待隊列中的客戶端連接的最大數(shù)量,一旦超越這個長度,TCP服務(wù)器將開始拒絕來自于新的客戶端的連接請求,改參數(shù)的默認值為511。
callback:回調(diào)函數(shù),可選。該回調(diào)函數(shù)不使用任何參數(shù)。
方法二:指定方法如下所示(代碼中server代表一個使用Unix端口的服務(wù)器)
server.listen(path, [callback])
path:指定需要監(jiān)聽的路徑。
callback:回調(diào)函數(shù),可選。該回調(diào)函數(shù)不使用任何參數(shù)。
這種形式的listen方法用于通知一個使用Unix端口的服務(wù)器開始監(jiān)聽來自于指定路徑的客戶端連接。當對使用Unix端口的服務(wù)器指定了需要監(jiān)聽的路徑后,服務(wù)器將立即開始監(jiān)聽來自于該路徑的客戶端連接,這時觸發(fā)該服務(wù)器的listening事件,可使用listen方法的callback參數(shù)來指定listening事件觸發(fā)時調(diào)用的回調(diào)函數(shù),該回調(diào)函數(shù)不使用任何參數(shù)。
方法三:指定方法如下所示(代碼中server代表一個TCP服務(wù)器)
server.listen(handle, [callback])
handle:指定需要監(jiān)聽的socket句柄。
callback:回調(diào)函數(shù),可選。該回調(diào)函數(shù)不使用任何參數(shù)。
這種形式的listen方法用于通知一個TCP服務(wù)器開始監(jiān)聽來自于指定socket句柄(該句柄可以為一個TCP服務(wù)器對象,可以為一個socket端口對象,也可以為一個文件描述符,在Windows操作系統(tǒng)中不支持對文件描述符的監(jiān)聽)的客戶端連接。當對TCP服務(wù)器指定了需要監(jiān)聽的socket句柄后,服務(wù)器端將立即開始監(jiān)聽來自于該socket句柄的客戶端連接,這時觸發(fā)該服務(wù)器的listening事件,可使用listen方法的callback參數(shù)來指定listening事件觸發(fā)時調(diào)用的回調(diào)函數(shù),該回調(diào)函數(shù)不使用任何參數(shù)。
如果不在上述三種形式的listen方法中使用callback參數(shù),我們也可以通過監(jiān)聽TCP服務(wù)器對象的listening事件,并指定該事件觸發(fā)時調(diào)用的回調(diào)函數(shù)方法來指定TCP服務(wù)器開始監(jiān)聽時所需執(zhí)行的處理,方法如下所示:
server.on('listening', function(){
// 回調(diào)函數(shù)代碼略
})
在對TCP服務(wù)器指定需要監(jiān)聽的地址及端口時,如果該地址及端口已被占用,將產(chǎn)生一個錯誤代碼為“EADDRINUSE”的錯誤(表示用于監(jiān)聽的地址及端口已被占用),同時將觸發(fā)TCP服務(wù)器的error事件。
server.on('error', function(e) {
if(e.code == 'EADDRINUSE') {
// 回調(diào)函數(shù)代碼略
}
})
2、TCP服務(wù)器擁有的屬性和方法
1)、使用TCP服務(wù)器的address方法,來查看服務(wù)器所監(jiān)聽的地址信息
let address = server.address()
該方法返回一個對象具有如下屬性:
- port:屬性值為TCP服務(wù)器監(jiān)聽的socket端口號,如8080。
- address:屬性值為TCP服務(wù)器監(jiān)聽的地址,如127.0.0.1。
- family:屬性值為一個標識了TCP服務(wù)器所監(jiān)聽的地址是IPv4地址還是IPv6地址的字符串,如“IPv4”。
2)、使用TCP服務(wù)器的getConnections方法,來查看當前與TCP服務(wù)器建立連接的客戶端連接數(shù)量。
server.getConnections(callback)
該方法是一個異步方法,可以指定一個callback作為參數(shù),該回調(diào)函數(shù)使用兩個參數(shù):
err:參數(shù)值為獲取客戶端連接數(shù)時所觸發(fā)的錯誤對象。
count:參數(shù)值為獲取到的客戶端連接數(shù)。
3)、TCP服務(wù)器的maxConnections屬性,用于指定TCP服務(wù)器可以接收的客戶端連接的最大數(shù)量。
server.maxConnections = 2; //設(shè)置TCP服務(wù)器最大連接數(shù)為2
4)、使用TCP服務(wù)器的close方法,顯式指定服務(wù)器拒絕所有新的客戶端連接。
server.close(callback)
在使用close方法時,并不會斷開所有現(xiàn)存的客戶端連接。當這些客戶端連接被關(guān)閉時,TCP服務(wù)器將被自動關(guān)閉,同時觸發(fā)TCP服務(wù)器的close事件。
server.on('close', function() {
}
3、socket端口對象
在Node.js中,使用net.Socket代表一個socket端口對象。該對象具有以下屬性和方法:
1)、使用socket端口對象的address方法,獲取該socket端口對象相關(guān)的地址信息
let address = server.address()
該方法返回一個對象具有如下屬性:
- port:屬性值為TCP服務(wù)器監(jiān)聽的socket端口號,如8080。
- address:屬性值為TCP服務(wù)器監(jiān)聽的地址,如127.0.0.1。
- family:屬性值為一個標識了TCP服務(wù)器所監(jiān)聽的地址是IPv4地址還是IPv6地址的字符串,如“IPv4”。
socket端口對象可用來讀取客戶端發(fā)送的流數(shù)據(jù),每次接收到客戶端發(fā)送的流數(shù)據(jù)時觸發(fā)data事件。
socket.on('data', function(data){
})
在該回調(diào)函數(shù)中,使用一個參數(shù),參數(shù)值為一個Buffer對象(在未使用socket端口對象的setEncoding方法指定編碼方式時)或一個方法字符串對象(在使用socket端口對象的setEncoding方法指定編碼方式后)。我們可以使用如下所示方法設(shè)置讀取到數(shù)據(jù)的編碼格式:
方法一:使用socket端口對象的setEncoding方法。
socket.setEncoding('utf8');
socket.on('data', function(data) {
console.log(data)
}
方法二:使用Buffer對象的toString方法。
socket.on('data', function(data) {
console.log(data.toString())
}
2)、使用socket端口對象具有一個bytesRead屬性,屬性值為該socket端口對象接收到的客戶端發(fā)送數(shù)據(jù)的字節(jié)數(shù)。
當客戶端連接被關(guān)閉時觸發(fā)socket端口對象的end事件。可以通過對該事件進行監(jiān)聽并且指定事件處理函數(shù)來指定當客戶端連接被關(guān)閉時所需執(zhí)行的處理,該回調(diào)函數(shù)不使用任何參數(shù)。
const net = require('net');
let server = net.createServer();
server.on('connection', function(socket) {
socket.setEncoding('utf8');
socket.on('data', function(data) {
console.log(data);
console.log('已接收的字節(jié)數(shù)據(jù)', socket.bytesRead);
});
socket.on('end', function() {
console.log('客戶端連接被關(guān)閉');
});
})
server.listen(8080, 'locahost');
3)、可以使用socket對象的pipe方法,將客戶端發(fā)送的流數(shù)據(jù)書寫到文件等其他目標對象中。
socket.pipe(destination, [options])
該方法使用以下兩個參數(shù):
destination:參數(shù)值必須為一個可用于寫入流數(shù)據(jù)的對象。
options:可選,參數(shù)值為一個對象,可以在該對象中使用一個布爾類型的end屬性,如果該屬性值為true,則當數(shù)據(jù)被全部讀取完畢時立即結(jié)束寫操作;如果該屬性值為false,則并不結(jié)束寫操作,目標對象中可以被繼續(xù)寫入新的數(shù)據(jù),該屬性的默認屬性值為true。
const fs = require('fs');
const net = require('net');
let file = fs.createWriteStream('./message.txt');
let server = net.createServer();
server.on('connection', function(socket) {
socket.pipe(file)
})
server.listen(8080, 'localhost')
4)、當使用了pipe方法指定要寫入的目標對象后,可以使用socket端口對象的unpipe方法取消目標對象的寫入操作。
socket.unpipe([destination])
該方法使用以下參數(shù):
destination:可選,參數(shù)值為pipe方法中指定的被寫入的目標對象。如果不使用該參數(shù),則取消所有對在pipe方法中指定的目標對象的寫入操作。
5)、使用socket端口對象的pause方法,可以暫停data事件的觸發(fā),這時服務(wù)器端將把每一個客戶端發(fā)送的數(shù)據(jù)暫存在一個單獨的緩存區(qū)中。
socket.pause();
6)、在使用了pause方法暫停data事件的觸發(fā)之后,可以使用socket端口對象的resume方法恢復(fù)data事件的觸發(fā),這時將讀取被緩存的該客戶端數(shù)據(jù)。
socket.resume()
7)、在Node.js中,未對客戶端連接指定默認超時時間,可以使用socket端口對象的setTimeout方法指定與該端口相連接的超時時間。
socket.setTimeou(timeout, [callback])
該方法使用以下參數(shù):
timeout:參數(shù)值為一個整數(shù)數(shù)值,用于指定客戶端連接的超時時間,單位為毫秒。
callback:可選,當客戶端連接超時時使用的回調(diào)函數(shù),該回調(diào)函數(shù)不使用任何參數(shù)。當客戶端連接超時時,觸發(fā)socket端口對象的timeout事件。我們也可以對timeout事件進行監(jiān)聽:
const fs = require('fs');
const net = require('net');
let file = fs.createWriteStream('./message.txt');
let server = net.createServer();
server.on('connection', function(socket) {
socket.setTimeout(10*1000);
socket.pause();
socket.on('timeout', function() {
socket.resume();
socket.pipe(file);
});
})
server.listen(8080, 'localhost')
在使用了一次setTimeout方法指定客戶端連接的超時時間后,可以通過再使用一次setTimeout方法并將timeout參數(shù)值指定為0的方法來取消對客戶端連接超時時間的指定。
4、創(chuàng)建TCP客戶端
在Node.js中,創(chuàng)建TCP客戶端只需要創(chuàng)建一個用于連接TCP服務(wù)器的socket端口對象即可。
const net = new net.socket([options])
在net.socket對象的構(gòu)造函數(shù)中,可以使用一個可選的options對象,其中可以使用如下屬性:
fd:用于指定一個現(xiàn)存的socket的文件描述符,TCP客戶端將使用這個現(xiàn)存的socket端口與服務(wù)器相連接。
type:用于指定客戶端所使用的協(xié)議,可指定值為“tcp4”、“tcp6”或者“unix”。
allowHalfOpen:該屬性值被指定為false時,當TCP服務(wù)器接收到客戶端發(fā)送的一個FIN包時將會回發(fā)一個FIN包,當該屬性值被設(shè)定為true時,當TCP服務(wù)器接收到客戶端發(fā)送的一個FIN包時不回發(fā)FIN包,這使得TCP服務(wù)器可以繼續(xù)向客戶端發(fā)送數(shù)據(jù),但是不會繼續(xù)接收客戶端發(fā)送的數(shù)據(jù)。開發(fā)者必須調(diào)用end方法來關(guān)閉socket連接。該屬性值默認為false。
創(chuàng)建了socket端口對象之后,即可使用socket端口對象的如下所示的兩種connect方法連接TCP服務(wù)器。
方法一:指定方法如下所示(代碼中socket代表一個socket端口對象)
socket.connect(port, [host], [connectListener])
port:必須,用于指定需要連接的TCP服務(wù)器端口。
host:可選,用于指定需要連接的TCP服務(wù)器地址,該地址可以是一個IP地址,也可以為一個主機名,如果不指定host參數(shù),將默認使用本地主機名localhost。
connectListener:可選,該參數(shù)用于指定一個當客戶端與TCP服務(wù)器成功連接時調(diào)用的回調(diào)函數(shù),該回調(diào)函數(shù)不使用任何參數(shù)。
當客戶端與TCP服務(wù)器建立連接后,觸發(fā)socket端口對象的connect事件,我們也可以通過對connect事件的監(jiān)聽來執(zhí)行相關(guān)處理:
socket.on('connect', function(){
})
方法二:指定方法如下所示(代碼中socket代表一個socket端口對象)
socket.connect(patch, [connectListener])
這種形式的connect方法用于與一個使用unix端口的服務(wù)器進行連接,方法中使用以下參數(shù):
path:必須,用于指定服務(wù)器所使用的unix端口的路徑。
connectListener:可選,該參數(shù)用于指定一個當客戶端與TCP服務(wù)器成功連接時調(diào)用的回調(diào)函數(shù),該回調(diào)函數(shù)不使用任何參數(shù)。
當TCP客戶端與TCP服務(wù)器建立連接后,TCP客戶端用于建立連接的socket端口對象與TCP服務(wù)器端用于監(jiān)聽客戶端連接的socket端口對象都具有如下所示的屬性:
- remoteAddress:連接的另一端所使用的遠程地址,例如74.125.127.X。
- remotePort:連接的另一端所使用的端口號,例如80。
- localAddress:本地用于建立連接的地址,例如192.168.1.1。
- localPort:本地用于建立連接的端口號,例如80。
socket端口對象也可被用來寫入向客戶端或服務(wù)器端發(fā)送的流數(shù)據(jù),當流數(shù)據(jù)被寫入后將立即被發(fā)送到客戶端或服務(wù)器端。當需要寫入流數(shù)據(jù)時,可以使用write方法。
socket.write(data, [encoding], [callback])
該方法使用以下參數(shù):
data:參數(shù)值可以為一個Buffer對象或一個字符串,用于指定需要寫入的數(shù)據(jù)。
encoding:可選,用于指定當data參數(shù)為字符串時以什么編碼方式寫入。
callback:可選,用來指定當數(shù)據(jù)被寫入完畢時所要調(diào)用的回調(diào)函數(shù),該回調(diào)函數(shù)不使用任何參數(shù)。
在一個快速的網(wǎng)絡(luò)中,當數(shù)據(jù)量較少的時候,Node.js總是將數(shù)據(jù)直接發(fā)送到操作系統(tǒng)專用于發(fā)送數(shù)據(jù)的TCP緩存區(qū)中,然后從該TCP緩存區(qū)中取出數(shù)據(jù)發(fā)送給對方。在一個慢速的網(wǎng)絡(luò)中或需要發(fā)送大量數(shù)據(jù)時,TCP客戶端或服務(wù)器端所發(fā)送的數(shù)據(jù)并不一定會立即被對方接收,在這種情況下,Node.js會將這些數(shù)據(jù)緩存在緩存隊列中,在對方可以接收數(shù)據(jù)的情況下將緩存隊列中的數(shù)據(jù)通過TCP緩存區(qū)發(fā)送給對方。socket端口對象的write方法返回一個布爾類型的返回值,當數(shù)據(jù)直接被發(fā)送的TCP緩存區(qū)中時,改返回值為true,當數(shù)據(jù)直接被發(fā)送到緩存隊列時,該返回值為false。當返回值為false且TCP緩存區(qū)中數(shù)據(jù)已全部被發(fā)送出去時,觸發(fā)drain事件。
可以使用socket端口對象的bufferSize屬性值來查看用于緩存隊列中當前緩存的字符數(shù)。
在TCP服務(wù)器與TCP客戶端建立連接或進行通信的過程中發(fā)生錯誤時,將觸發(fā)TCP服務(wù)器使用的與該客戶端相連接的socket端口對象或TCP客戶端使用的與服務(wù)器端相連接的socket端口對象的error事件。
socket.on('error', function(err){
})
在捕捉到錯誤之后,我們應(yīng)該使用觸發(fā)觸發(fā)錯誤的socket端口對象的destroy方法銷毀該socket端口對象,以確保該socket端口對象不會再被利用。
socket.destroy()
當TCP服務(wù)器與TCP客戶端建立連接后,TCP服務(wù)器可以使用與該客戶端相連接的socket端口對象的end方法關(guān)閉該客戶端的連接,TCP客戶端也可以使用與服務(wù)器端相連接的socket端口對象的end方法關(guān)閉與服務(wù)器端的連接。
socket.end([data], [encoding])
data:可選,其參數(shù)值可以為一個Buffer對象或一個字符串,用于指定在關(guān)閉連接前需要向另一端(客戶端或服務(wù)器端)追加發(fā)送的數(shù)據(jù)。
encoding:可選,用于指定當data參數(shù)為字符串時以什么編碼方式進行發(fā)送。
另外可以通過對end事件的監(jiān)聽,來執(zhí)行斷開連接后要執(zhí)行的操作。
socket.on('end', function() {
})
在默認情況下,運行TCP服務(wù)器的應(yīng)用程序不會自動退出,即使客戶端連接已被全部關(guān)閉。我們可以使用TCP服務(wù)器的unref方法指定當客戶端連接被全部關(guān)閉時退出應(yīng)用程序。
server.unref()
在使用了TCP服務(wù)器的unref方法之后,可以繼續(xù)使用TCP服務(wù)器的ref方法繼續(xù)阻止應(yīng)用程序的退出。
server.ref()
當socket端口徹底關(guān)閉時,觸發(fā)socket端口對象的close事件,可以通過對close事件的監(jiān)聽并且指定事件函數(shù)的方法來指定當socket端口關(guān)閉時需要執(zhí)行的處理。
socket.on('close', function(had_error) {
})
had_error:參數(shù)值為布爾類型,當參數(shù)值為true時,表示該socket端口的關(guān)閉是由于一個錯誤而引起的,當參數(shù)值為false時,表示該socket端口被正常關(guān)閉。
在TCP客戶端與服務(wù)器端建立連接后,當一方主機突然出現(xiàn)斷電、重啟、系統(tǒng)崩潰等意外情況時,將來不及向另一方發(fā)送用于關(guān)閉連接的FIN包,這樣另一方將永遠處于連接狀態(tài)。
在Node.js中,可以通過使用用于建立客戶端連接或服務(wù)器端連接的socket對象的setKeepAlive方法來解決這一問題。
socket.setKeepAlive([enable], [initialDelay])
enable:可選,參數(shù)值為布爾類型,當參數(shù)值為true時,啟用Keep-alive機制。啟用Keep-alive機制后,使用了setKeepAlive方法的這一段會不斷的向?qū)Ψ桨l(fā)送一個探測包,如果發(fā)送探測包后對方?jīng)]有發(fā)回響應(yīng)的話,則認為對方已關(guān)閉連接,執(zhí)行對方關(guān)閉連接時應(yīng)該執(zhí)行的處理,如果該參數(shù)為false,則不啟用Keep-alive機制,該屬性值默認為false,即不采用Keep-alive機制。
initialDelay:可選,指定每個多久發(fā)送一次探測包,單位為毫秒,如果將改參數(shù)設(shè)置為0,則保持系統(tǒng)中的默認設(shè)置的Keep-alive探測包的發(fā)送間隔時間,或者保持程序中當前已設(shè)置的Keep-alive探測包的發(fā)送間隔時間,該參數(shù)默認為0。
5、 net模塊中的類方法
-
isIP方法
isIP方法用于判斷一個字符串值是否為一個IP地址。
net.isIP(input)
input:該參數(shù)為一個字符串,如果字符串值不為IP地址,方法返回0,如果字符串值為IPv4地址,方法返回4;如果字符串值為IPv6地址,方法返回6。
-
isIPv4方法
isIPv4方法用于判斷一個字符串值是否為一個isIPv4地址。
net.isIPv4(input)
input:該參數(shù)為一個字符串,如果字符串值為一個有效的isIPv4地址,則方法返回true,否則返回false。
-
isIPv6方法
isIPv6方法用于判斷一個字符串值是否為一個isIPv6地址。
net.isIPv6(input)
input:該參數(shù)為一個字符串,如果字符串值為一個有效的isIPv6地址,則方法返回true,否則返回false。