1. TCP
在Node.js中,提供了net模組用來實現TCP伺服器和客戶端的通訊。
1.1 TCP伺服器
net.createServer([options][, connectionListener])
複製程式碼
- options.allowHalfOpen 是否允許單方面連線,預設值為false
- connectionListener引數用於指定當客戶端與伺服器建立連線時所要呼叫的回撥函式,回撥中有一個引數socket,指的是TCP伺服器監聽的socket埠物件
也可以通過監聽connection事件的方式來指定監聽函式
server.on('connection',function(socket){});
複製程式碼
1.1.1 啟動TCP伺服器
可以使用listen方法通知伺服器開始監聽客戶端的連線
server.listen(port,[host],[backlog],[callback])
複製程式碼
- port 必須指定的埠號
- host 指定需要監聽的IP地址或主機名,如果省略的話伺服器將監聽來自於任何客戶端的連線
- backlog指定位於等待佇列中的客戶端連線的最大數量,預設值為511
server.on('listening',function(){});
複製程式碼
1.1.2 使用TCP伺服器
let net = require('net');
let server = net.createServer(function(socket){
console.log('客戶端已連線');
});
server.listen(8080,'localhost',function(){
console.log('伺服器開始監聽');
});
複製程式碼
1.1.3 address
server.address()
複製程式碼
- port 埠號
- address TCP伺服器監聽的地址
- family 協議的版本
1.1.4 getConnections
檢視當前與TCP伺服器建立連線的客戶端的連線數量以及設定最大連線數量
server.getConnections(callback);
server.maxConnections = 2;
複製程式碼
1.1.5 close
使用close方法可以顯式拒絕所有的客戶端的連線請求,當所有已連線的客戶端關閉後伺服器會自動關閉,並觸發伺服器的close事件。
server.close();
server.on('close',callback);
複製程式碼
1.2 socket
1.2.1 address
net.Socket代表一個socket埠物件,它是一個可讀可寫流。
let net = require('net');
let util = require('util');
let server = net.createServer(function(socket){
server.getConnections((err,count)=>{
server.maxConnections = 1;
console.log('最大連線數量%d,當前連線數量%d',server.maxConnections,count);
});
let address = socket.address();
console.log('客戶端地址 %s',util.inspect(address));
});
複製程式碼
1.2.2 讀取資料
let server = net.createServer(function (socket) {
socket.setEncoding('utf8');
socket.on('data', function (data) {
console.log('本次收到的內容為%s,累計收到的位元組數是%d', data, socket.bytesRead);
});
});
複製程式碼
1.2.3 監聽關閉事件
let server = net.createServer(function (socket) {
socket.on('end', function () {
console.log('客戶端已經關閉');
});
});
複製程式碼
1.2.4 pipe
pipe方法可以將客戶端傳送的資料寫到檔案或其它目標中。
socket.pipe(destinatin,[options]);
複製程式碼
- options.end 設定為false時當客戶端結束寫操作或關閉後並不會關閉目標物件,還可以繼續寫入資料
let net = require('net');
let path = require('path');
let ws = require('fs').createWriteStream(path.resolve(__dirname, 'msg.txt'));
let server = net.createServer(function (socket) {
socket.on('data', function (data) {
console.log(data);
});
socket.pipe(ws, { end: false });
socket.on('end', function () {
ws.end('over', function () {
socket.unpipe(ws);
});
});
});
複製程式碼
1.2.5 unpipe
const net = require('net');
const path = require('path');
let file = require('fs').createWriteStream(path.join(__dirname, 'msg.txt'));
let server = net.createServer(function (socket) {
socket.pipe(file, {
end: false
});
setTimeout(function () {
file.end('bye bye');
socket.unpipe(file);
}, 5000);
// socket.on('end', function () {
// file.end('bye bye');
// });
});
server.listen(8080);
複製程式碼
1.2.5 pause&resume
pause
可以暫停data
事件觸發,伺服器會把客戶端傳送的資料暫存在快取區裡
const net = require('net');
const net = require('net');
const path = require('path');
let file = require('fs').createWriteStream(path.join(__dirname, 'msg.txt'));
let server = net.createServer(function (socket) {
socket.pause();
setTimeout(function () {
socket.resume();
socket.pipe(file);
}, 10 * 1000);
});
server.listen(8080);
複製程式碼
1.2.6 setTimeout
let net = require('net');
let path = require('path');
let ws = require('fs').createWriteStream(path.resolve(__dirname, 'msg.txt'));
let server = net.createServer(function (socket) {
socket.setTimeout(5 * 1000);
socket.pause();
socket.on('timeout', function () {
socket.pipe(ws);
});
//socket.setTimeout(0);取消超時時間的設定
});
server.listen(8080);
複製程式碼
1.2 TCP客戶端
1.2.1 建立TCP客戶端
let socket = new net.Socket([options])
複製程式碼
- fd socket檔案描述符
- type 客戶端所有協議
- allowHalfOpen 是否允許半連線,伺服器收到FIN包時不回發FIN包,可以使伺服器可以繼續向客戶端發資料
socket.connect(port, host, callback);
socket.on('connect', callback);
複製程式碼
1.2.2 向伺服器端寫入資料、end 、error、destroy,close
- write表示向伺服器寫入資料
- end 用於結束連線
- error 連線發生錯誤
- destroy 銷燬流
- close 表示連線關閉成功,hasError=true代表有可能有錯誤
socket.write(data,[encoding],[callback]); 複製程式碼
let net = require('net');
let server = net.createServer(function (socket) {
console.log("客戶端已經連線");
socket.setEncoding('utf8');
socket.on('data', function (data) {
console.log("已接收客戶端傳送的資料:%s", data);
socket.write('伺服器:' + data);
})
socket.on('error', function (err) {
console.log('與客戶端通訊過程中發生了錯誤,錯誤編碼為%s', err.code);
socket.destroy();
});
socket.on('end', function (err) {
console.log('客戶端已經關閉連線');
socket.destroy();
});
socket.on('close', function (hasError) {
console.log(hasError ? '異常關閉' : '正常關閉');
});
});
server.listen(808, function () {
let client = new net.Socket();
client.setEncoding('utf8');
client.connect(808, '127.0.0.1', function () {
console.log('客戶端已連線');
client.write('hello');
setTimeout(function () {
client.end('byebye');
}, 5000);
});
client.on('data', function (data) {
console.log('已經接收到客戶端發過來的資料:%s', data);
});
client.on('error', function (err) {
console.log('與伺服器通訊過程中發生了錯誤,錯誤編碼為%s', err.code);
client.destroy();
});
});
複製程式碼
1.2.3 close
停止server接受建立新的connections並保持已經存在的connections
server.getConnections((err, count) => {
if (count == 2) server.close();
});
複製程式碼
1.2.4 unref&ref
unref方法指定發客戶端連線被全部關閉時退出應用程式 如果將allowHalfOpen方法,必須使用與客戶端連線的socket埠物件的end 方法主動關閉伺服器端連線
let net = require('net');
let server = net.createServer({ allowHalfOpen: true }, function (socket) {
console.log("客戶端已經連線");
socket.setEncoding('utf8');
socket.on('data', function (data) {
console.log("已接收客戶端傳送的資料:%s", data);
socket.write('伺服器確認資料:' + data);
})
socket.on('error', function (err) {
console.log('與客戶端通訊過程中發生了錯誤,錯誤編碼為%s', err.code);
socket.destroy();
});
socket.on('end', function (err) {
console.log('客戶端已經關閉連線');
socket.end();
server.unref();
});
socket.on('close', function (hasError) {
if (hasError) {
console.log('由於錯誤導致socket關閉');
server.unref();
} else {
console.log('埠正常關閉');
}
});
server.getConnections((err, count) => {
if (count == 2) server.close();
});
});
server.listen(808, function () { });
server.on('close', function () {
console.log('伺服器關閉');
});
複製程式碼
1.2.5 bufferSize
write的返回值和bufferSize屬性值
let server = net.createServer({ allowHalfOpen: true }, function (socket) {
console.log("客戶端已經連線");
socket.setEncoding('utf8');
let rs = fs.createReadStream(path.resolve(__dirname, 'a.txt'), { highWaterMark: 2 });
rs.on('data', function (data) {
let flag = socket.write(data);
console.log("flag:", flag);
console.log('快取位元組:' + socket.bufferSize);
console.log('已傳送位元組:' + socket.bytesWritten);
})
socket.on('data', function (data) {
console.log('data', data);
});
socket.on('drain', function (err) {
"快取區已全部傳送"
});
});
複製程式碼
1.2.6 keepAlive
當伺服器和客戶端建立連線後,當一方主機突然斷電、重啟、系統崩潰等意外情況時,將來不及向另一方傳送FIN包,這樣另一方將永遠處於連線狀態。 可以使用setKeepAlive方法來解決這一個問題
socket.setKeepAlive([enaable],[initialDelay]);
複製程式碼
- enable 是否啟用嗅探,為true時會不但向對方傳送探測包,沒有響應則認為對方已經關閉連線,自己則關閉連線
- initialDelay 多久傳送一次探測包,單位是毫秒
1.2.7 聊天室1.0
/**
* 1.建立一個伺服器
* 2. 客戶端可以連線伺服器
* 3.客戶端可以發言,然後廣播給大家
* 4.客戶端連線和退出後都要通知大家。
* 5.顯示當前的線上人數
*/
let net = require('net');
let util = require('util');
let clients = {};
let server = net.createServer(function (socket) {
server.getConnections(function (err, count) {
socket.write(`weclome,there is ${count} users now,please input your username\r\n`);
});
let nickname;
socket.setEncoding('utf8');
socket.on('data', function (data) {
data = data.replace(/\r\n/, '');
if (data == 'byebye') {
socket.end();
} else {
if (nickname) {
broadcast(nickname, `${nickname}:${data}`);
} else {
nickname = data;
clients[nickname] = socket;
broadcast(nickname, `welcome ${nickname} joined us!`);
}
}
});
socket.on('close', function () {
socket.destroy();
});
}).listen(8088);
function broadcast(nickname, msg) {
for (let key in clients) {
if (key != nickname) {
clients[key].write(msg + '\r\n');
clients[nickname].destroy();
delete clients[nickname];
}
}
}
複製程式碼
1.2.8 聊天室2.0
var key = scoket.remoteAddress+':'+socket.remotePort;
users[key] = {name:'匿名',socket};
socket.on('data',function(){
parse(data);
});
function parse(msg){
swtich(msg.type){
case 'secret':
secret(msg.user,msg.text);
break;
}
case 'boardcast':
boardcast(message.text);
break;
case 'cname':
cname(messsage.text);
break;
case 'list':
list();
break;
default:
socket.write('不能識別命令');
break;
}
function secret(user,text){
}
function boardcast(text){
}
function cname(text){
}
function list(){
}
複製程式碼
b:text 廣播
c:nickname:text 私聊
n:nickname 改名
l 列出線上使用者列表
on('data',function(data){
if(data == 'quit){
}else if(data == 'help'){
}else(){
write(data);
}
});
function convert(){
}
複製程式碼
1.3 類方法
- isIP 判斷字串是否是IP
- isIPv4 判斷字串是否是IPv4地址
- isIPv6 判斷字串是否是IPv6地址
2. UDP
2.1 建立socket
let socket = dgram.createSocket(type,[callback]);
socket.on('messsage',function(msg,rinfo){});
複製程式碼
- type 必須輸入,制定時udp4還是udp6
- callback 從該介面接收到資料時呼叫的回撥函式
- msg 接收到的資料
- rinfo 資訊物件
- address 傳送著的地址
- family ipv4還是ipv6
- port 傳送者的socket埠號
- size 傳送者所傳送的資料位元組數
socket.bind(port,[address],[callback]);
socket.on('listening',callabck;
複製程式碼
- port 繫結的埠號
- address 監聽的地址
- callback 監聽成功後的回撥函式
2.2 向外傳送資料
如果傳送資料前還沒有繫結過地址和埠號,作業系統將為其分配一個隨機埠並可以接收任何地址的資料
socket.send(buf,offset,length,port,address,[callback]);
複製程式碼
- buffer 代表快取區
- offset 從快取區第幾個位元組開始發
- length 要傳送的位元組數
- port 對方的埠號
- address 接收資料的socket地址
callback 制定當資料傳送完畢時所需要的回撥函式
- err 錯誤物件
- byets 實際傳送的位元組數
2.3 address
獲取此socket相關的地址資訊
let address = socket.address(); 複製程式碼
- port
- address
- family
2.5 UDP伺服器
var dgram = require('dgram');
var socket = dgram.createSocket('udp4');
socket.on('message',function(msg,rinfo){
console.log(msg.toString());
console.log(rinfo);
socket.send(msg,0,msg.length,rinfo.port,rinfo.address);
});
socket.bind(41234,'localhost');
複製程式碼
2.6 UDP客戶端
var dgram = require('dgram');
var socket = dgram.createSocket('udp4');
socket.on('message',function(msg,rinfo){
console.log(msg.toString());
console.log(rinfo);
});
socket.setTTL(128);
socket.send(new Buffer('zz'),0,6,41234,'localhost',function(err,bytes){
console.log('傳送了個%d位元組',bytes);
});
socket.on('error',function(err){
console.error(err);
});
複製程式碼
2.7 廣播
建立一個UDP伺服器並通過該伺服器進行資料的廣播
2.7.1 伺服器
let dgram = require('dgram');
let server = dgram.createSocket('udp4);
server.on('message',function(msg){
let buf = new Bufffer('已經接收客戶端傳送的資料'+msg);
server.setBroadcast(true);
server.send(buf,0,buf.length,41235,"192.168.1.255");
});
server.bind(41234,'192.168.1.100');
複製程式碼
2.7.2 客戶端
let dgram = require('dgram');
let client = dgram.createSocket('udp4);
client.bind(41235,'192.168.1.102);
let buf = new Buffer('hello');
client.send(buf,0,buf.length,41234,'192.168.1.100');
client.on('message',function(msg,rinfo){
console.log('received : ',msg);
});
複製程式碼
2.8 組播
- 所謂的組播,就是將網路中同一業務型別進行邏輯上的分組,從某個socket埠上傳送的資料只能被該組中的其他主機所接收,不被組外的任何主機接收。
- 實現組播時,並不直接把資料傳送給目標地址,而是將資料傳送到組播主機,作業系統將把該資料組播給組內的其他所有成員。
- 在網路中,使用D類地址作為組播地址。範圍是指 224.0.0.0 ~ 239.255.255.255,分為三類
- 區域性組播地址: 224.0.0.0 ~ 224.0.0.255 為路由協議和其他用途保留
- 預留組播地址: 224.0.1.0 ~ 238.255.255.255 可用於全球範圍或網路協議
- 管理許可權組播地址 : 239.0.0.0 ~ 239.255.255.255 組織內部使用,不可用於Internet
把該socket埠物件新增到組播組中。
socket.addMembership(multicastAddress,[multicastInterface]);
複製程式碼
- multicastAddress 必須指定,需要加入的組播組地址
- multicastInterface 可選引數,需要加入的組播組地址
socket.dropMembership(multicastAddress,[multicastInterface]);
socket.setMulticastTTL(ttl);
socket.setMulticastLoopback(flag);
複製程式碼
2.8.1 伺服器
let dgram = require('dgram');
let server = dgram.createSocket('udp4');
server.on('listening',function(){
server.MulticastTTL(128);
server.setMulticastLoopback(true);
server.addMembership('230.185.192.108');
});
setInterval(broadcast,1000);
function broadcast(){
let buffer = Buffer.from(new Date().toLocaleString());
server.send(buffer,0,buffer.length,8080,"230.185.192.108");
}
複製程式碼
2.8.2 客戶端
let dgram = require('dgram');
let client = dgram.createSocket('udp4');
client.on('listening',function(){
client.addMembership('230.185.192.108');
});
client.on('message',function(message,remote){
console.log(message.toString());
});
client.bind(8080,'192.168.1.103');複製程式碼