node-network
Node.js – Network
Node是一个面向网络而生的平台,它具有事件驱动、无阻塞、单线程等特性,具有良好的可伸缩性,使得它十分轻量,适合在分布式网络中扮演各种各样的角色。
Node提供了net、dgram、http、https这4个模块,分别用于处理tcp、udp、http、https,适用于服务器端和客户端。
TCP
TCP全名为传输控制协议,是面向连接的协议。其显著的特征就是在传输之间需要3次握手形成会话。tcp-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
29let net = require('net');
let server = net.createServer(function (socket) {
// new connect
// socket.setNoDelay(true); // 关闭Nagle算法
socket.on('data', function (buffer) {
console.log('socket event data', new Date().getTime(), buffer.toString());
socket.write('Hello World');
});
// close
socket.on('end', function () {
console.log('socket event end', new Date().getTime());
});
socket.on('close', function (had_error) {
console.log('socket event close', had_error);
});
socket.on('error', function (error) {
console.log('socket event error', error);
});
socket.on('drain', function () {
console.log('socket event drain');
});
socket.on('timeout', function () {
console.log('socket event timeout');
});
});
server.listen(5000, () => {
console.log('start server 5000');
});tcp-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
35
36
37
38
39let net = require('net');
let client = net.connect({port: 5000}, function () {
console.log('socket event connect');
client.write('Hello');
});
// client.on('connect', function (buffer) {
// console.log('socket event connect');
// client.write('Hello');
// });
// client.setTimeout(3000);
client.on('ready', function () {
console.log('socket event ready');
});
client.on('data', function (buffer) {
console.log('socket event data', buffer.toString());
client.end();
});
// client.setNoDelay(true);// 关闭Nagle算法
// FIN
client.on('end', function () {
console.log('socket event end');
});
// close
client.on('close', function (had_error) {
console.log('socket event close', had_error);
});
client.on('lookup', function (err, address, family, host) {
console.log('socket event lookup', address, family, host, err);
});
client.on('error', function (error) {
console.log('socket event error', error);
});
client.on('drain', function () {
console.log('socket event drain');
});
client.on('timeout', function () {
console.log('socket event timeout');
});Nagle算法
如果每次只发一个字节的内容而不优化,网络中将充满只有极少数有效数据的数据包,将十分浪费网络资源。为此,Node引入了Nagle算法。Nagle要求缓冲区的数据达到一定数量或者一定时间后才将其发送,所以小数据包将会被Nagle算法合并,以此来优化网络。这种优化虽然使网络带宽被有效使用,但数据有可能被延迟发送。
UDP
UDP又称为用户数据包协议,与TCP一样同属于网络传输层。UDP与TCP最大的区别是UDP不是面向连接的。在UDP中,一个套接字可以跟多个UDP服务通信,它虽然提供面向事务的简单不可靠传输服务,在网络差的情况下存在丢包严重的问题,但是由于它无需连接,资源消耗低,处理快速且灵活,所以常常应用在那种丢一两个数据包也不会产生重大的场景,比如音频、视频等。DNS就是基于它实现的.
udp-server.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23let dgram = require('dgram');
let server = dgram.createSocket('udp4');
// 当有新的数据包被 socket 接收时, 'message' 事件会被触发。msg 和 rinfo 会作为参数传递到该事件的处理函数中。
server.on('message', function (msg, rinfo) {
console.log('server event -> message msg, remoteInfo', msg.toString(), rinfo);
server.send('Server World', rinfo.port, rinfo.address, function () {
server.close();
});
});
// 当一个 socket 开始监听数据包信息时, 'listening' 事件将被触发。 该事件会在创建 UDP socket 之后被立即触发。
server.on('listening', function () {
console.log('server event -> listening', server.address())
});
// 当有任何错误发生时, 'error' 事件将被触发。 事件发生时,事件处理函数仅会接收到一个 Error 对象。
server.on('error', function (error) {
console.log('server event -> error', error);
server.close();
});
server.on('close', function () {
console.log('server event -> close')
});
server.bind(8000);udp-client.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16let dgram = require('dgram');
let client = dgram.createSocket('udp4');
client.on('message', function (msg, rinfo) {
console.log('client event -> message msg, remoteInfo', msg.toString(), rinfo);
});
client.on('listening', function () {
console.log('client event -> listening', client.address())
});
client.on('error', function (error) {
console.log('client event -> error', error);
client.close();
});
client.on('close', function () {
console.log('client event -> close')
});
client.send('Client Hello', 8000);HTTP
HTTP的全称是超文本传输协议,HTTP构建在TCP之上,属于应用层协议
在Node中,HTTP服务继承于TCP服务(net模块),它能够与多个客户端保持连接,由于其采用事件驱动的形式,并不为每一个连接创建额外的线程或进程,保持很低的内存占用,所以能实现高并发。HTTP服务和TCP服务模型有区别的地方在于,在开启keepalive后,一个TCP会话可以用于多次请求和响应。TCP服务以connection为单位进行服务,HTTP服务会话以request为单位进行服务。HTTP模块即是将connection到request的过程进行了封装。
在Node中构建Http服务器及其容易,寥寥几行代码就可以实现一个HTTP服务器,代码如下:1
2
3
4
5
6
7let http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type':'text/plain'});
res.end('Hello HTTP\n');
}).listen(8080 , '127.0.0.1', function () {
console.log('HTTP Server running at http://127.0.0.1/8080');
});启动上述服务器代码后,我们通过工具curl进行一次报文的获取,如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18curl -v http://127.0.0.1:8080
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
GET / HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: curl/7.64.1
Accept: */*
< HTTP/1.1 200 OK
< Content-Type: text/plain
< Date: Sat, 07 Dec 2019 10:32:24 GMT
< Connection: keep-alive
< Transfer-Encoding: chunked
<
Hello HTTP
* Connection #0 to host 127.0.0.1 left intact
* Closing connection 0从上述报文中,我们可以看到这次网络通信的报文信息分为几个部分,第一部分内容为经典的TCP的三次握手过程,如下所示:
1
2
3* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)第二部分是在完成握手之后,客户端向服务端发送请求报文,如下所示:
1
2
3
4
5GET / HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: curl/7.64.1
Accept: */*第三部分是服务端完成处理后,向客户端发送响应内容,包括响应头和响应体,如下所示:
1
2
3
4
5
6
7< HTTP/1.1 200 OK
< Content-Type: text/plain
< Date: Sat, 07 Dec 2019 10:32:24 GMT
< Connection: keep-alive
< Transfer-Encoding: chunked
<
Hello HTTP最后部分是结束本次会话的信息,如下所示:
1
2* Connection #0 to host 127.0.0.1 left intact
* Closing connection 0从上述的报文信息中,可以看出HTTP是基于请求响应式的,以一问一答的形式实现服务,虽然基于TCP会话,但是本身却无会话的特点。简而言之,HTTP服务只做两件事情:处理HTTP请求和发送HTTP响应。
WebSocket
WebSocket实现了客户端与服务端之间的长连接。推荐库 socket.io
WebSocket与传统HTTP有如下好处:- 客户端与服务器只建立一个TCP连接,可以使用更少的连接
- WebSocket服务器端可以推送数据到客户端,比HTTP请求响应模式更灵活、高效
- 有更轻量的协议头,减少数据传输量
WebSocket协议主要由握手和数据传输两部分组成。这里我们用Node模拟浏览器发起协议切换的行为,代码如下:
WebSocket.js1
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
39let http = require('http');
class WebSocket {
constructor(host, port) {
this.host = host;
this.port = port;
if (!!host && !!port)
this.connect();
}
onopen() {
console.log('WebSocket onopen');
}
onmessage(data) {
console.log('WebSocket onmessage', data.toString());
}
setSocket(socket) {
this.socket = socket;
this.socket.on('data', this.onmessage);
}
send(data){
data && this.socket.write(data);
}
connect() {
// WebSocket 握手协议部分是在 HTTP实现的
let req = http.request({
port: this.port,
hostname: this.host,
headers: {'Connection': 'Upgrade', 'Upgrade': 'WebSocket'}
});
req.on('upgrade', (response, socket, head) => {
console.log('WebSocket event->upgrade');
this.setSocket(socket);
this.onopen();
});
req.end();
}
}服务端
Server.js1
2
3
4
5
6
7
8
9
10
11
12
13
14
15let server = http.createServer( function (req, res) {
res.writeHead(200, {'Content-Type':'text/plain'});
res.end('hello world\n');
});
server.on('upgrade', function (response, socket, head) {
console.log('server event->upgrade', head);
let header = ['HTTP/1.1 101 Switching Protocols', 'Upgrade: WebSocket', 'Connection: Upgrade'];
socket.setNoDelay(true);
socket.write(header.concat('', '').join('\r\n'));
let websocket = new WebSocket();
websocket.setSocket(socket);
websocket.send('Hello Client');
});
server.listen(8000);客户端
Client.js1
2
3
4
5let wb = new WebSocket('127.0.0.1', 8000);
wb.onopen = function () {
console.log('onopen');
wb.send('Hello Server');
};