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
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
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
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地址