Node 网络编程
前言
利用Node可以十分方便地搭建网络服务器,在WEB领域,大多数编程语言需要专门的web服务器作为容器,比如ASP,ASP.NET需要IIS作为服务器,PHP需要搭载在Apache或者Nignx环境等,JSP需要Tomcat服务器等。当对于Node而言,只需要几行代码就可以构建一个服务器,无需额外的容器。
Node提供了net、http、https、dgram这四个模块,分别用于处理TCP、HTTP、HTTPS、UDP,适用于服务器与客户端。
一、构建 TCP 服务器
1. 七层模型与TCP协议
TCP全名传输控制协议,在OSI模型中有以下七层,被称为七层网络协议。许多应用层议都是基于TCP构建,典型的有HTTP、SMTP、IMAP等协议。
TCP是面向连接的协议,其显著特征为3次握手后才形成会话。
注意:只有在会话形成之后,服务器端和客户端之间才能互相发送数据,在创建会话的过程中,服务器端和客户端分别提供一个套接字,这两个套接字共同形成了一个链接,服务器端与客户端则通过套接字实现两者之间连接的操作。
具体流程:请移步到
https://blog.csdn.net/Errrl/article/details/103662867
2. 创建TCP服务器
在基本了解TCP工作原理后,接下来就可以开始创建TCP服务器端来接受请求:
server.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 1/* 引入net核心模块 */
2var net = require('net');
3/* 创建一个TCP服务器 */
4var server = net.createServer(function(socket){
5 socket.on('data',function(data){
6 socket.write('hello');
7 });
8 socket.on('end',function(){
9 socket.write('end');
10 });
11 socket.write('welcome to node tcp');
12});
13/* 监听端口号 */
14server.listen(8000,function(){
15 console.log('server is done');
16})
17
18
利用win10自带的telnet客户端对上述的见到服务器进行会话
通过net模块构造客户端进行会话,测试上述构建的TCP服务器:
client.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 1var net = require('net');
2
3var client = net.connect({port:8000},function(){
4 console.log('client is connect');
5 client.write('world!\r\n');
6});
7
8client.on('data',function(data){
9 console.log(data.toString());
10 client.end();
11})
12
13client.on('end',function(){
14 console.log('client is disconnect');
15})
16
17
3. TCP服务器事件
(1)服务器事件
对于通过net.createServer()创建的服务器而言,他是一个EventEmitter实例,他自定义事件有以下几种:
- listening:在调用server.listen()绑定端口,简介写法为server.listen(port,listeningListener),通过listen()方法的的第二个参数传入。
- connection:每个客户端套接字连接到服务端时触发,简介写法为通过net.createServer(),最后一个参数传入。
- close:当服务器关闭时触发,在调用server.close()后,服务器将停止接受新的套接字连接,但保持当前的连接,对待所有连接都断开后会触发该事件。
- error:当服务器发生异常时,将会触发该事件。比如监听一个使用中的端口,将会触发一个异常,如果不侦听error事件,服务器将会抛出异常。
(2)连接事件
服务器可以同时与多个客户端保持连接,对于每个连接而言是典型的可写可读Stream对象。Stream对象可以用于服务器与客户端之间的通信,既可以通过data事件从一端读取另一端发来的数据,也可以通过write()方法从一端向另一端发送数据,它具有如下自定义事件:
- data:当一端调用write()发送数据时,另一端触发data事件,事件传递的数据即是write()发送的数据。
- end:当连接中的任意一端发送FIN数据时,将会触发该事件。
- connect:改时间用于客户端,当套接字与服务器端连接成功时被触发。
- drain:当任意一端调用write()发送数据时,当前这端会触发该事件。
- error:当发生异常时触发该事件。
- close:当套接字完全关闭时触发事件。
- timout:当一定时间后连续不在活跃时,该事件会被触发,通知用户当前该连接已经被闲置了。
二、构建 UDP服务器
1. 与 TCP 协议的区别
请移步到:
https://blog.csdn.net/Errrl/article/details/103662867
2. 创建 UDP 套接字
创建UDP套接字十分简单,UDP套接字一旦创建,既可以作为客户端发送数据,也可以作为服务器端接收数据,创建一个UDP套接字:
1
2
3
4 1var dgram = require('dgram');
2var socket = dgram.createSocket ('udp4')
3
4
3. 创建 UDP 服务器端
如果要想UDP套接字接受网路消息,只要调用dgram.bind(port,[address])进行绑定即可。
server.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 1var dgram = require('dgram');
2var server = dgram.createSocket ('udp4');
3
4server.on('message',function(msg,rinfo){
5 console.log("server got:"+msg+"from"+rinfo.address+":"+rinfo.port);
6})
7
8server.on('listening',function(){
9 var address = server.address();
10 console.log("server listening"+address.address+":"+address.port);
11})
12
13server.bind(41234)
14
15
该套接字将接收所有网卡上41234端口信息上的消息。在绑定完成后,将会触发listening事件。
4. 创建 UDP 客户端
创建一个客户端与服务器端进行对话:
client.js
1
2
3
4
5
6
7
8
9 1var dgram = require('dgram');
2
3var message = new Buffer('hello node udp');
4var client = dgram .createSocket('udp4');
5client.send(message,0,message.length,41234,"localhost",function(err,bytes){
6 client.close();
7})
8
9
当套接字对象用在客户端时,可以调用send()方法发送消息到网络中。send()方法的参数如下:
1
2
3 1socket.send(buf,offset,length,port,address,[callback])
2
3
- buf:buffer
- offset:buffer偏移量
- length:buffer的长度
- port:目标端口
- address:目标地址
- callback:发送完成后的回调
5. UDP 套接字事件
UDP套接字相对于TCP套接字使用起来更加简单,它只是一个EventEmitter的实例,它具有如下自定义事件:
- message:当UDP套接字侦听网卡端口后,接收到消息触发该事件,出发携带的数据为消息buffer对象和一个远程地址信息。
- listening:当UDP套接字开始侦听时触发该事件。
- close:调用close()方法时触发该事件,并不再触发message事件。如需再次触发message事件,重新绑定即可。
- error:当异常发生时出发该事件,如果不侦听,异常将直接抛出,使进程退出。
三、构建 HTTP 服务器端、客户端
在Node中构建HTTP服务极其容易,Node官网上的经典例子就展示了如何用几行代码实现一个HTTP服务器:
1
2
3
4
5
6
7
8
9 1var http = require('http');
2/* 创建服务器 */
3http.createServer(function(req,res){
4 res.writeHead(200,{'content-type':'text/plain'});
5 res.end('hello world');
6}).listen(8000,'127.0.0.1');
7console.log('server run at http://127.0.0.1:8000/');
8
9
1. HTTP
(1)HTTP 报文
在启动上述代码后,我们对经典的示例代码进行了一次报文的获取,这里使用的工具是curl,通过 -v选项,可以显示这次网络通讯所有的报文信息。
请求报文的四部分:
- TCP三次握手。
- 请求报文。(请求头、请求体)
- 响应报文(包括响应头、响应体)。
- 会话结束信息。
从上述可以看出HTTP的特点,它是基于请求响应式的,基于TCP协议。
2. http 模块、HTTP服务器端
Node的http模块包含对HTTP处理的封装,在Node中,HTTP服务继承自TCP服务器(net模块),它能够与多个客户端保持连接,由于采用事件驱动的形式,并不为每一个连接创建额外的线程或进程,并保持很低的内存占有率,所以能实现高校并发。
(1)HTTP 请求
请求头
1
2
3
4
5
6
7 1> GET / HTTP/1.1
2> Host: 127.0.0.1:8000
3> User-Agent: curl/7.55.1
4> Accept: */*
5>
6
7
请求头第一行GET / HTTP/1.1解析之后会分解成如下属性:
- request.method:值是GET,一种请求方法,常用的请求方法还有:POST、DELETE、PUT、CONNECT等请求方法。
- request.url:值为/,这就可以解释为什么在项目中查询request.url会返回/,原因就是取决于报文。
- request.httpVersion:值为1.1,表示版本(规则)。
其余的包头就以简单、规律的key:value的格式,被解析后放置在request.headers属性上传递给业务逻辑以供调用。
(2)HTTP 响应
响应头
1
2
3
4 1< HTTP/1.1 200 OK
2< content-type: text/plain
3
4
在项目中经常要写入响应头,用于获取符合类型的数据。
除此之外,http模块会自动设置一些头信息:(用于处理缓存)
1
2
3
4
5
6 1< Date: Fri, 07 Feb 2020 14:30:32 GMT
2< Connection: keep-alive
3< Transfer-Encoding: chunked
4<
5
6
响应体
调用respone.write()或者调用respone.end()传入的内容称为响应体:
1
2
3 1hello world
2
3
调用respone.write()或者调用respone.end()的区别在于:
前者只发送,不结束响应,会造成客户端处于等待状态。
而后者就会先调用write()发送完后调用end()结束响应。
(3)HTTP 服务的事件
同TCP服务一样,HTTP服务器也抽象了一些事件,以供应用层使用,同样典型的是,服务器也是一个EventEmitter实例:
- connection 事件:在开始HTTP请求和响应之前,客户端与服务器需要建立底层的TCP连接,这个连接可能因为开启了keep-alive的原因,可以在多次请求与响应之间使用;当这个连接建立时,服务器会触发一次connection事件。
- request 事件:建立TCP连接后,HTTP模块底层将在数据流中抽出HTTP请求和HTTP响应,当请求数据发送到服务器端,在解析出HTTP请求头后,将会触发该事件;在res.end()后,TCP连接可能将用于下一次请求响应。
- close 事件:与TCP服务器行为一致,调用server.close()方法停止接受新的连接,当已有的连接都断开时,触发该事件;可以给server.close()传递一个回调函数来快速注册该事件。
- checkContinue 事件:某些客户端在发送较大的数据时,并不会之间将数据发送,而是先发送一个头部带有Expect:100-continue的请求到服务器,服务器将会触发checkContinue事件;如果没有为服务器监听这个事件,服务器将会自动响应客户端100 Continue的状态码,表示可以接受数据上传;如果不接受或者数据确实超出承载时响应客户端400 Bad Request拒绝客户端继续发送数据即可。需要注意的是:当该事件发生时不会触发request事件,两个事件互斥。当客户端收到100 Continue后重新发送请求时才会触发request事件。
与预检(Preflighted)的跨域请求类似
- connect 事件:当客户端发起CONNECT请求时触发,二发起CONNECT请求通常在HTTP代理时出现;如果不监听该事件,发起该事件的连接就会中断。
- upgrade 事件:当客户端要求升级连接的协议时,需要和服务器端协商,客户端会在请求头中带上Upgrade字段,服务器端会在接收到这样的请求时触发该事件。者在后面的WebSoket中会有详细的流程介绍。如果不监听该事件,发起该请求的连接就会中断。
- clientError 事件:连接的客户端触发error事件时,这个错误会传递到服务器端,此时触发该事件。
扩展
keep-alive
在http早期,每个http请求都要求打开一个tpc socket连接,并且使用一次之后就断开这个tcp连接。
使用keep-alive可以改善这种状态,即在一次TCP连接中可以持续发送多份数据而不会断开连接。通过使用keep-alive机制,可以减少tcp连接建立次数,也意味着可以减少TIME_WAIT状态连接,以此提高性能和提高httpd服务器的吞吐率(更少的tcp连接意味着更少的系统内核调用,socket的accept()和close()的调用)
3. HTTP 客户端
http模块提供了一个底层的API:http.request(options,connect),用于构建HTTP客户端。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 1var http = require('http');
2/* 请求报文 */
3var options = {
4 hostname:'127.0.0.1',
5 port:8000,
6 path:'/',
7 method:'GET'
8}
9/* 发送报文 */
10var req = http.request(options,function(res){
11 /* 获取状态码 */
12 console.log('status:'+res.statusCode);
13 /* 获取响应头 */
14 console.log('headers:'+res.headers);
15 res.setEncoding('utf8');
16 /* 获取响应体 */
17 res.on('data',function(datas){
18 console.log(datas);
19 })
20})
21/* 发送后断开连接,缓解服务器压力 */
22req.end();
23
24
修改:
1
2
3
4 1/* 获取响应头 */
2 console.log('headers:'+JSON.stringify(res.headers));
3
4
其中options的参数配置:
- host:服务器的域名或者ip地址,默认为localhost。
- hostname:服务器名称。
- port:端口号。默认80。
- method:HTTP请求方法,默认GET。
- path:请求路径,默认/
- headers:请求头对象。
- auth:Basic认证,这个值将会被计算成请求头中的Authorization部分。
报文体的内容由请求对象的write()和end()方式实现:通过write()方法向连接中写入数据,通过end()方法告知报文结束。他与前端中的Ajax调用非常相似,
Ajax的实质就是一个异步的网络HTTP请求。
(1)HTTP 响应
HTTP客户端的响应对象与服务器端类似,在客户端请求对象中,它的事件名叫做response。客户端请求后(也就是解析报文完成后)响应头就会触发response事件,同时传递一个响应对象以供客户端进行响应操作。对于上述代码而言,res就是response,datas就是响应对象。
(2)HTTP 代理
如服务器端的实现一般http模块提供的客户端请求对象也是基于TCP层实现的,在keep-alive机制下,一个底层会话连接可以多次用于请求。为了重复使用TCP连接,http模块包含一个默认的客户端代理对象http.globalAgent。它对每一个服务端(host+port)创建连接进行了管理,默认情况下,通过客户端请求对象对同一服务器端发起的HTTP请求最多可以创建5个连接。实际上它就是一个连接池(循环代理)
那么如何进行代理,很简单:
重构options
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 1/* 设置代理 */
2var agent = new http.Agent({
3 maxSochets: 10,
4 keepAlive: true,
5})
6var options = {
7 hostname: '127.0.0.1',
8 port: 8000,
9 path: '/',
10 method: 'GET',
11 agent: agent
12}
13/* 发送报文 */
14var req = http.request(options, function (res) {
15 /* 获取状态码 */
16 console.log('status:' + res.statusCode);
17 /* 获取响应头 */
18 console.log('headers:' + JSON.stringify(res.headers));
19 res.setEncoding('utf8');
20 /* 获取响应体 */
21 res.on('data', function (datas) {
22 console.log(datas);
23 })
24})
25
26/* 发送后断开连接,缓解服务器压力 */
27req.end();
28
29
相关链接:
http://nodejs.cn/api/http.html\#http_class_http_agent
(3)HTTP 客户端事件
- response 事件:与服务器端的request事件对应的客户端在请求发送后得到服务器的响应时会触发该事件。
- socket 事件:在底层连接池中建立的连接分配给当前请求对象时,触发该事件。
- connect 事件:当客户端向服务器端发送CONNECT请求时,如果服务器响应了200状态码,客户端将会触发该事件。
- upgrade 事件:客户端向服务器端发起Upgrade请求时,如果服务器端响应了101 Switching Protocols状态,客户端将会触发该事件。
- continue 事件:客户端向服务器端发起Expect:100-continue头信息,以试图发送叫大数据量,如果服务器响应了100 Continue状态,客户端将触发该事件。
四、构建 webSocket 服务端
1. 客户端下的 webSocket
HTML:(client.html)
1
2
3
4 1<input id="content" type="text">
2<button id="send">send</button>
3
4
以一个webSocket聊天室进行实例操作:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 1/* client.html */
2var websocket = new WebSocket("ws://localhost:8000/");
3 websocket.onopen = function() {
4 console.log("webSocket open");
5 // 发送消息放在这里
6 document.getElementById("send").onclick = function() {
7 var txt = document.getElementById("content").value;
8 if (txt) {
9 /* 发送数据 */
10 websocket.send(txt);
11 }
12 }
13 }
14 websocket.onclose = function() {
15 console.log("websocket close");
16 }
17 /* 接收响应数据 */
18 websocket.onmessage = function(e) {
19 console.log(e.data);
20 var mes = JSON.parse(e.data);
21 showMessage(mes.data, mes.type);
22 }
23
24
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
45
46
47 1/* server.js */
2var ws = require("nodejs-websocket");
3/* 端口号 */
4const PORT = 8000;
5// 每进来一个客户端就记录一下
6var clientCount = 0;
7
8var server = ws.createServer(function (conn) {
9 console.log("New connection")
10 clientCount++;
11 conn.nickname = 'user' + clientCount;
12 let mes = {};
13 mes.type = "enter";
14 mes.data = conn.nickname + ' comes in'
15 broadcast(JSON.stringify(mes));
16 /* 收到 text 文本触发 */
17 conn.on("text", function (str) {
18 console.log("Received " + str);
19 let mes = {};
20 mes.type = "message";
21 mes.data = conn.nickname + ' says: ' + str;
22 broadcast(JSON.stringify(mes));
23 })
24 /* 当任一侧关闭连接时发出 */
25 conn.on("close", function (code, reason) {
26 console.log("Connection closed");
27 let mes = {};
28 mes.type = "leave";
29 mes.data = conn.nickname + ' left'
30 broadcast(JSON.stringify(mes));
31 })
32 /* 发生错误时发出(例如尝试在仍然发送二进制数据的同时发送文本数据)。如果握手无效,也会发出响应。 */
33 conn.on("error", function (err) {
34 console.log("handle err");
35 console.log(err);
36 })
37}).listen(PORT);//监听端口号
38
39console.log("websocket server running on port: " + PORT);
40/* 响应数据 */
41function broadcast(str) {
42 server.connections.forEach(function (connection) {
43 connection.sendText(str);
44 })
45}
46
47
上述代码中,浏览器与服务器端创建webSocket协议请求,onopen在请求完成后持续执行,通过事件绑定的方法绑定一个发送按钮发送数据,同时还可以通过onmessage()方法接收服务器端相应的数据的数据。这种行为与TCP客户端很相似,相较于HTTP,它能够双向通信。
并且相比于HTTP,webSocket更接近于传输层协议,它并没有在HTTP的基础上模拟服务器端的推送,而是在TCP上定义独立的协议,但是疑惑的是webSocket的握手部分由HTTP完成,这就是人们感觉webSocket是基于HTTP实现的原因。
webSocket协议主要分为两个部分:握手和数据传输。
2. webSocket 握手
客户端建立连接时,通过HTTP发起的请求报文:
上面的报文告知客户端正在更换协议(协议升级),更新应用层协议为webSocket协议,并在当前的套接字连接上应用新的协议。剩余的字段分别表示服务器端基于Sec-WebSocket-Key生成的字符串和选中的子协议。客户端将会校验Sec-WebSocket-Key的值,如果成功,将开始接下来的数据传输。
简而言之就是websocket复用了http的握手通道,客户端通过http请求与服务端进行协商,升级协议。协议升级完后校验Sec-WebSocket-Key的值,若成功后面的数据交换则遵照websocket协议,若否反之。
流程:
1、客户端申请协议升级
1
2
3
4
5
6
7
8
9 1Request URL: ws://localhost:8888/
2Request Method: GET
3Connection: Upgrade
4Upgrade: websocket
5Sec-WebSocket-Version: 13
6Sec-WebSocket-Key: uR5YP/BMO6M24tAFcmHeXw==
7Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
8
9
-
Connection: Upgrade 表示要升级协议
-
Upgrade: websocket 表示升级到websocket协议
-
Sec-WebSocket-Version: 13 表示websocket的版本
-
Sec-WebSocket-Key 表示websocket的验证,防止恶意的连接,与服务端响应的Sec-WebSocket-Accept是配套。
2、服务端响应协议升级
1
2
3
4
5
6 1Status Code: 101 Switching Protocols
2Connection: Upgrade
3Sec-WebSocket-Accept: eS92kXpBNI6fWsCkj6WxH6QeoHs=
4Upgrade: websocket
5
6
- Status Code:101 表示状态码,协议切换。
- Sec-WebSocket-Accept 表示服务端响应的校验,与客户端的Sec-WebSocket-Key是配套的。
3、Sec-WebSocket-Accept是如何计算的
将 Sec-WebSocket-Key 的值与 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 拼接。
然后通过sha1计算,再转成base64。
1
2
3
4
5
6
7
8
9
10
11 1const crypto = require('crypto');
2
3function getSecWebSocketAccept(key) {
4 return crypto.createHash('sha1')
5 .update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
6 .digest('base64');
7}
8
9console.log(getSecWebSocketAccept('uR5YP/BMO6M24tAFcmHeXw=='));
10
11
4、协议升级完后,后续的数据传输就需要按websocket协议来走。(了解即可)
websocket客户端与服务端通信的最小单位是 帧,由1个或多个帧组成完整的消息。
客户端:将消息切割成多个帧,发送给服务端。
服务端:接收到消息帧,将帧重新组装成完整的消息。
数据帧的格式
单位是1个比特位,FIN,PSV1,PSV2,PSV3 占1个比特位,opcode占4个比特位。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 10 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
2+-+-+-+-+-------+-+-------------+-------------------------------+
3|F|R|R|R| opcode|M| Payload len | Extended payload length |
4|I|S|S|S| (4) |A| (7) | (16/64) |
5|N|V|V|V| |S| | (if payload len==126/127) |
6| |1|2|3| |K| | |
7+-+-+-+-+-------+-+-------------+-------------------------------+
8| Extended payload length continued, if payload len == 127 |
9+-------------------------------+-------------------------------+
10| |Masking-key, if MASK set to 1 |
11+-------------------------------+-------------------------------+
12| Masking-key (continued) | Payload Data |
13+-------------------------------+-------------------------------+
14| Payload Data continued ... |
15+---------------------------------------------------------------+
16| Payload Data continued ... |
17+---------------------------------------------------------------+
18
19
6、掩码的算法
Masking-key掩码键是由客户端生成的32位随机数,掩码操作不会影响数据载荷的长度。
1
2
3
4
5
6
7
8 1function unmask(buffer, mask) {
2 const length = buffer.length;
3 for (var i = 0; i < length; i++) {
4 buffer[i] ^= mask[i & 3];
5 }
6}
7
8
7、实现websocket的握手,数据传输
JavaScript:(up.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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100 1const crypto = require('crypto');
2const net = require('net');
3
4//计算websocket校验
5function getSecWebSocketAccept(key) {
6 return crypto.createHash('sha1')
7 .update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
8 .digest('base64');
9}
10
11//掩码操作
12function unmask(buffer, mask) {
13 const length = buffer.length;
14 for (var i = 0; i < length; i++) {
15 buffer[i] ^= mask[i & 3];
16 }
17}
18
19//创建一个tcp服务器
20let server = net.createServer(function (socket) {
21
22 socket.once('data', function (data) {
23 data = data.toString();
24
25 //查看请求头中是否有升级websocket协议的头信息
26 if (data.match(/Upgrade: websocket/)) {
27 let rows = data.split('\r\n');
28 //去掉第一行的请求行
29 //去掉请求头的尾部两个空行
30 rows = rows.slice(1, -2);
31 let headers = {};
32 rows.forEach(function (value) {
33 let [k, v] = value.split(': ');
34 headers[k] = v;
35 });
36 //判断websocket的版本
37 if (headers['Sec-WebSocket-Version'] == 13) {
38 let secWebSocketKey = headers['Sec-WebSocket-Key'];
39 //计算websocket校验
40 let secWebSocketAccept = getSecWebSocketAccept(secWebSocketKey);
41 //服务端响应的内容
42 let res = [
43 'HTTP/1.1 101 Switching Protocols',
44 'Upgrade: websocket',
45 `Sec-WebSocket-Accept: ${secWebSocketAccept}`,
46 'Connection: Upgrade',
47 '\r\n'
48 ].join('\r\n');
49 //给客户端发送响应内容
50 socket.write(res);
51
52 //注意这里不要断开连接,继续监听'data'事件
53 socket.on('data', function (buffer) {
54 //注意buffer的最小单位是一个字节
55 //取第一个字节的第一位,判断是否是结束位
56 let fin = (buffer[0] & 0b10000000) === 0b10000000;
57 //取第一个字节的后四位,得到的一个是十进制数
58 let opcode = buffer[0] & 0b00001111;
59 //取第二个字节的第一位是否是1,判断是否掩码操作
60 let mask = buffer[1] & 0b100000000 === 0b100000000;
61 //载荷数据的长度
62 let payloadLength = buffer[1] & 0b01111111;
63 //掩码键,占4个字节
64 let maskingKey = buffer.slice(2, 6);
65 //载荷数据,就是客户端发送的实际数据
66 let payloadData = buffer.slice(6);
67
68 //对数据进行解码处理
69 unmask(payloadData, maskingKey);
70
71 //向客户端响应数据
72 let send = Buffer.alloc(2 + payloadData.length);
73 //0b10000000表示发送结束
74 send[0] = opcode | 0b10000000;
75 //载荷数据的长度
76 send[1] = payloadData.length;
77 payloadData.copy(send, 2);
78 socket.write(send);
79 });
80 }
81 }
82 });
83
84 socket.on('error', function (err) {
85 console.log(err);
86 });
87
88 socket.on('end', function () {
89 console.log('连接结束');
90 });
91
92 socket.on('close', function () {
93 console.log('连接关闭');
94 });
95});
96
97//监听8000端口
98server.listen(8000);
99
100
html:(up.html)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 1<!doctype html>
2<html lang="zh-CN">
3<head>
4 <meta charset="UTF-8">
5 <title>Document</title>
6</head>
7<body>
8<script>
9 var ws = new WebSocket('ws://localhost:8888');
10 ws.onopen = function () {
11 console.log('连接成功');
12 ws.send('你好服务端');
13 };
14 ws.onmessage = function (ev) {
15 console.log('接收数据', ev.data);
16 };
17 ws.onclose = function () {
18 console.log('连接断开');
19 };
20</script>
21</body>
22</html>
23
24
8、结束