pro-node 之 net TCP,UDP

net 模块

1
var net = require("net");

此模块提供 TCP/UDP Socket 通信,dns 查询等等基本网络功能

Socket

  • IP 地址,32 位,4byte,每个 byte 用逗号分隔,最大也就是 FF,FF,FF,FF 即 255,255,255,255
  • port,端口,16 位,2 个 byte 构成,最大 FFFF = 65535 个, 0-1023 是 著名端口,例如 HTTP 的 80,HTTPS 的 443 端口,别冲突了

net.Socket 类型,表示 socket 实例

TCP 通讯

net.Server

net.createServer

1
net.createServer([options], [connectionListener]);

第一个参数表示创建服务器的选项,allowHalfOpen,如果为 true,当客户端断开连接的时候,server 还保持该连接,这种情况下,server 应该总是显示关闭连接
第二个参数,是一个 connection 事件的 listener

1
2
3
4
5
6
7
8
9
var net = require("net");
var server = net.createServer(
{
allowHalfOpen: false
},
function(socket) {
// handle connection
}
);

创建好的是net.Server实例

net.Server listen

创建好 server 之后,需要绑定到端口,也就是监听某一端口

1
2
3
4
5
6
7
server.listen(port, [host], [backlog], [callback]);

var net = require("net");
var server = net.createServer(function(socket) {
// handle connection
});
server.listen(8000, "127.0.0.1");

第一个参数端口,传 0 或忽略表示随机选择一个端口
后面是 host,表示只接受这个 host 的请求
第三个表示请求队列的容量,默认 511,如果队列满了之后,就不再接受新的请求
最后 callback,是 listening 事件的监听器

net.Server address

一旦绑定端口成功,就可以调用 address 方法来获取绑定的 host 和 port,同时还有 family 字段,表示 IPv4 or IPv6,如果开始传的是 0 也就是随机取端口的话,现在可以显示处理,到底使用的是哪一个端口

1
2
{ address: '::1', family: 'IPv6', port: 64269 }
// ::1是IPv6的 localhost,相当于IPv4内的127.0.0.1

net.Server close

close 事件,以及 close 方法,都是 server 实例的,不要跟 socket 的 close 事件搞混

1
2
3
4
5
6
7
8
9
var net = require("net");
var server = net.createServer();
server.on("close", function() {
console.log("And now it's closed.");
});
server.listen(function() {
console.log("The server is listening.");
server.close();
});

net.Server ref/unref

在之前 setTimeout/setInterval 那说过,server 的事件循环使程序不会退出,使用 unref()来改变这种情况,ref()反向操作

net.Server error 事件

server 遇到错误时,例如要绑定的端口正在被使用,引发 err,err.code = "EADDRINUSE"

1
2
3
4
5
server.on("error", function(error) {
if (error.code === "EADDRINUSE") {
console.error("Port is already in use");
}
});

net.Socket 处理请求

也就是前面说的 server 的 connection 事件,参数是net.Socket实例

socket
有 write/end 方法写入数据
有 data/end/close/error 事件接受数据,判断 client 状态
有 pause/resume/pipe 这些 stream 的特征

socket 联系这 server 与客户端
使用localAddress,localPort获取 server 的信息
使用remoteAddress,remotePort获取客户端的信息

当客户端突然关闭的时候,socket 会触发 error 事件,err.code = "ECONNRESET",再接着引发 socket 的 close 事件,注意在多人聊天时移除客户端,不要移除两次

1
2
3
4
arr.splice(index, 1); //移除index位置
arr.indexOf(); //不存在返回-1
arr.slice(-1); //返回最后一个元素
arr.splice(-1, 0); //会变成删除最后一个元素

客户端

net.connect 创建客户端

1
2
3
4
5
6
var client = net.connect(port, [host], [connectListener]);

var net = require("net");
var client = net.connect(8000, "localhost", function() {
console.log("Connection established");
});

第三个参数是 connect 事件的监听器,表示连接上了

重载,除了指定 port host 之外,还可以用一个 plain object 代替,字段有 :
port host allowHalf localAddress

data 事件

1
2
3
4
//client.pipe(process.stdout); //输出到console 要换行啊
client.on("data", function(data) {
console.log(data.toString());
});

可以直接 client.pipe(process.stdout),但是数据不换行啊,还能不能愉快地阅读了…

error close 事件

发生错误的话,引发 error 事件,接着引发 close 事件…
像服务器要是突然挂了的话,也是 error,err.code = "ECONNRESET",连接重置 ?

TCP 实现多人聊天

中文乱码问题根本原因是 win7 的控制台是 gbk 编码,所以在 process.stdin data 事件从源头解决,其他地方不用管编码,一路 utf8

实现截图

服务器 chat-server.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
var net = require("net");
var util = require("util");

net.Socket.prototype.toString = function() {
return util.format("%s:%s", this.remoteAddress, this.remotePort);
};

var clients = [];
//不会改变length
clients.remove = function(client) {
if (~clients.indexOf(client)) {
//~-1 = 0
clients.splice(clients.indexOf(client), 1);
}
};

net
.createServer(function(client) {
var name = client.toString(); //client退出后,remoteAddr/remotePort全为undefined

console.log("%s 连接上了...", name);
client.write("服务器 : Hi " + name);
clients.push(client);

client.on("data", function(data) {
var str = util.format("%s 说 : %s", name, data);
console.log(str);
clients.forEach(function(c) {
if (c === client) return;
c.write(str);
});
});

client.on("error", function(err) {
if (err.code == "ECONNRESET") {
console.log("%s 退出了 ...", name);
clients.remove(client);
}
});
})
.listen(5000, function() {
process.title = "服务器 端口5000";
console.log("聊天服务器 端口啊 5000 ~");
});

客户端 chat-client.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
var net = require("net");
var util = require("util");
var iconv = require("iconv-lite");

var client = net.connect("5000", function() {
process.title = util.format(
"这是客户端@%s:%s",
client.localAddress,
client.localPort
);
});

//client.pipe(process.stdout); //输出到console 要换行啊
client.on("data", function(data) {
console.log(data.toString());
});

client.on("error", function(err) {
if (err.code === "ECONNRESET") {
console.log("Server都挂了,还能不能愉快地聊天了...");
process.exit();
}

throw err;
});

process.stdin.on("data", function(data) {
if (data.toString() === "quit") process.exit();

client.write(iconv.decode(data, "gbk").trimRight());
});

//process.stdout.write("我要发言 : ");
process.stdin.resume();

UDP 通讯

UDP,是 user datagram protocol,用户数据电报协议,TCP 是面向连接的,可靠的,TCP 不是,所以 TCP 不那么可靠,但是它快,应用在允许适量丢包的情况,如音视频通话啦…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var dgram = require("dgram");

//创建server
var server = dgram.createSocket("udp4", function(msg, rinfo) {
console.log("received " + rinfo.size + " bytes");
console.log("from " + rinfo.address + ":" + rinfo.port);
console.log("message is: " + msg.toString());
});
//绑定端口
server.bind(8000);

//创建客户端
var client = dgram.createSocket("udp4");
var message = new Buffer("Hello UDP");
client.send(message, 0, message.length, 8000, "127.0.0.1", callback);

因为不用连接,只要发送数据就可以了,不管对方有没有收到…
send(buffer 数据,buffer 偏移,发送长度,端口,地址,回调),用到再查吧…那么长

封装 send 函数,address,port 更符合我的逻辑

1
2
3
4
5
6
var udp = require("dgram");
var _send = udp.Socket.prototype.send;
udp.Socket.prototype.send = function(msg, address, port) {
var buf = new Buffer(msg);
_send.call(this, buf, 0, buf.length, port, address);
};

DNS

将域名翻译成 IP,访问

dns.lookup

1
dns.lookup(domin,4 or 6,callback(err,result));

dns.reverse
根据 IP 查域名

dns.resolve(domin,record_type,callback)
以及dns.resolveXXX()变种
与 lookup 的区别是,resolve 支持各种类型的记录,A 记录/cname 记录/MX 邮件记录 什么的一堆

net.isIp()/isIPv4/isIPv6
判断是否是合法的 IP 地址