├── ReadMe.md
├── server
├── index.js
└── ws.js
└── index.html
/ReadMe.md:
--------------------------------------------------------------------------------
1 | #### 使用流程
2 |
3 | ```
4 | git clone https://github.com/JinJieTan/my-websocket.git
5 |
6 | cd my-websocket
7 |
8 | cd server
9 |
10 | node index.js
11 |
12 | 打开index.html访问即可
13 |
14 | ```
15 |
16 | #### 欢迎Star,加入2000人的大前端交流群
17 |
18 | 加我的微信:CALASFxiaotan(备注加群)
--------------------------------------------------------------------------------
/server/index.js:
--------------------------------------------------------------------------------
1 | const MyWebSocket = require('./ws');
2 | const ws = new MyWebSocket({ port: 8080 });
3 |
4 | ws.on('data', (data) => {
5 | console.log('receive data:' + data);
6 | ws.send('this message from server');
7 | });
8 |
9 | ws.on('close', (code, reason) => {
10 | console.log('close:', code, reason);
11 | });
12 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
38 |
39 |
40 |
41 |
42 |
43 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/server/ws.js:
--------------------------------------------------------------------------------
1 | const { EventEmitter } = require('events');
2 | const { createServer } = require('http');
3 | const crypto = require('crypto');
4 | const MAGIC_STRING = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; // 固定的字符串
5 | function hashWebSocketKey(key) {
6 | const sha1 = crypto.createHash('sha1'); // 拿到sha1算法
7 | sha1.update(key + MAGIC_STRING, 'ascii');
8 | return sha1.digest('base64');
9 | }
10 | function handleMask(maskBytes, data) {
11 | const payload = Buffer.alloc(data.length);
12 | for (let i = 0; i < data.length; i++) {
13 | // 遍历真实数据
14 | payload[i] = maskBytes[i % 4] ^ data[i]; // 掩码有4个字节依次与真实数据进行异或运算即可
15 | }
16 | return payload;
17 | }
18 | const OPCODES = {
19 | CONTINUE: 0,
20 | TEXT: 1,
21 | BINARY: 2,
22 | CLOSE: 8,
23 | PING: 9,
24 | PONG: 10,
25 | };
26 | function encodeMessage(opcode, payload) {
27 | let buf;
28 | // 0x80 二进制为 10000000 | opcode 进行或运算就相当于是将首位置为1
29 | let b1 = 0x80 | opcode; // 如果没有数据了将FIN置为1
30 | let b2; // 存放数据长度
31 | let length = payload.length;
32 | console.log(`encodeMessage: length is ${length}`);
33 | if (length < 126) {
34 | buf = Buffer.alloc(payload.length + 2 + 0); // 服务器返回的数据不需要加密,直接加2个字节即可
35 | b2 = length; // MASK为0,直接赋值为length值即可
36 | buf.writeUInt8(b1, 0); //从第0个字节开始写入8位,即将b1写入到第一个字节中
37 | buf.writeUInt8(b2, 1); //读8―15bit,将字节长度写入到第二个字节中
38 | payload.copy(buf, 2); //复制数据,从2(第三)字节开始,将数据插入到第二个字节后面
39 | }
40 | return buf;
41 | }
42 |
43 | class MyWebsocket extends EventEmitter {
44 | constructor(options) {
45 | super(options);
46 | this.options = options;
47 | this.server = createServer();
48 | options.port ? this.server.listen(options.port) : this.server.listen(8080); //默认端口8080
49 | this.server.on('upgrade', (req, socket, header) => {
50 | this.socket = socket;
51 | socket.setKeepAlive(true);
52 | console.log(req.headers['sec-websocket-key'], 'key');
53 | const resKey = hashWebSocketKey(req.headers['sec-websocket-key']); // 对浏览器生成的key进行加密
54 | // 构造响应头
55 | const resHeaders = [
56 | 'HTTP/1.1 101 Switching Protocols',
57 | 'Upgrade: websocket',
58 | 'Connection: Upgrade',
59 | 'Sec-WebSocket-Accept: ' + resKey,
60 | ]
61 | .concat('', '')
62 | .join('\r\n');
63 | console.log(resHeaders, 'resHeaders');
64 | socket.write(resHeaders); // 返回响应头部
65 | socket.on('data', (data) => {
66 | // 监听客户端发送过来的数据,该数据是一个Buffer类型的数据
67 | this.buffer = data; // 将客户端发送过来的帧数据保存到buffer变量中
68 | this.processBuffer(); // 处理Buffer数据
69 | });
70 | socket.on('close', (error) => {
71 | // 监听客户端连接断开事件
72 | if (!this.closed) {
73 | this.emit('close', 1006, 'timeout');
74 | this.closed = true;
75 | }
76 | });
77 | });
78 | }
79 |
80 | // 处理客户端发送过来的真实数据
81 | handleRealData(opcode, realDataBuffer) {
82 | switch (opcode) {
83 | case OPCODES.TEXT:
84 | this.emit('data', realDataBuffer.toString('utf8')); // 服务端WebSocket监听data事件即可拿到数据
85 | break;
86 | case OPCODES.BINARY: //二进制文件直接交付
87 | this.emit('data', realDataBuffer);
88 | break;
89 | default:
90 | this.close(1002, 'unhandle opcode:' + opcode);
91 | }
92 | }
93 |
94 | processBuffer() {
95 | let buf = this.buffer;
96 | let idx = 2; // 首先分析前两个字节
97 | // 处理第一个字节
98 | const byte1 = buf.readUInt8(0); // 读取buffer数据的前8 bit并转换为十进制整数
99 | // 获取第一个字节的最高位,看是0还是1
100 | const str1 = byte1.toString(2); // 将第一个字节转换为二进制的字符串形式
101 | const FIN = str1[0];
102 | // 获取第一个字节的后四位,让第一个字节与00001111进行与运算,即可拿到后四位
103 | let opcode = byte1 & 0x0f; //截取第一个字节的后4位,即opcode码, 等价于 (byte1 & 15)
104 | // 处理第二个字节
105 | const byte2 = buf.readUInt8(1); // 从第一个字节开始读取8位,即读取数据帧第二个字节数据
106 | const str2 = byte2.toString(2); // 将第二个字节转换为二进制的字符串形式
107 | const MASK = str2[0]; // 获取第二个字节的第一位,判断是否有掩码,客户端必须要有
108 | let length = parseInt(str2.substring(1), 2); // 获取第二个字节除第一位掩码之后的字符串并转换为整数
109 | if (length === 126) {
110 | // 说明125<数据长度<65535(16个位能描述的最大值,也就是16个1的时候)
111 | length = buf.readUInt16BE(2); // 就用第三个字节及第四个字节表示数据的长度
112 | idx += 2; // 偏移两个字节
113 | } else if (length === 127) {
114 | // 说明数据长度已经大于65535,16个位也已经不足以描述数据长度了,就用第三到第十个字节这八个字节来描述数据长度
115 | const highBits = buf.readUInt32BE(2); // 从第二个字节开始读取32位,即4个字节,表示后8个字节(64位)用于表示数据长度,其中高4字节是0
116 | if (highBits != 0) {
117 | // 前四个字节必须为0,否则数据异常,需要关闭连接
118 | this.close(1009, ''); //1009 关闭代码,说明数据太大; 协议里是支持 63 位长度,不过这里我们自己实现的话,只支持 32 位长度,防止数据过大;
119 | }
120 | length = buf.readUInt32BE(6); // 获取八个字节中的后四个字节用于表示数据长度,即从第6到第10个字节,为真实存放的数据长度
121 | idx += 8;
122 | }
123 | let realData = null; // 保存真实数据对应字符串形式
124 | if (MASK) {
125 | // 如果存在MASK掩码,表示是客户端发送过来的数据,是加密过的数据,需要进行数据解码
126 | const maskDataBuffer = buf.slice(idx, idx + 4); //获取掩码数据, 其中前四个字节为掩码数据
127 | idx += 4; //指针前移到真实数据段
128 | const realDataBuffer = buf.slice(idx, idx + length); // 获取真实数据对应的Buffer
129 | realData = handleMask(maskDataBuffer, realDataBuffer); //解码真实数据
130 | console.log(`realData is ${realData}`);
131 | }
132 | let realDataBuffer = Buffer.from(realData); // 将真实数据转换为Buffer
133 | this.buffer = buf.slice(idx + length); // 清除已处理的buffer数据
134 | if (FIN) {
135 | // 如果第一个字节的第一位为1,表示是消息的最后一个分片,即全部消息结束了(发送的数据比较少,一次发送完成)
136 | this.handleRealData(opcode, realDataBuffer); // 处理操作码
137 | }
138 | }
139 |
140 | // 根据发送数据的类型设置上对应的操作码,将数据转换为Buffer形式
141 | send(data) {
142 | let opcode;
143 | let buffer;
144 | if (Buffer.isBuffer(data)) {
145 | // 如果是二进制数据
146 | opcode = OPCODES.BINARY; // 操作码设置为二进制类型
147 | buffer = data;
148 | } else if (typeof data === 'string') {
149 | // 如果是字符串
150 | opcode = OPCODES.TEXT; // 操作码设置为文本类型
151 | buffer = Buffer.from(data, 'utf8'); // 将字符串转换为Buffer数据
152 | } else {
153 | throw new Error('cannot send object.Must be string of Buffer');
154 | }
155 | this.doSend(opcode, buffer);
156 | }
157 |
158 | // 开始发送数据
159 | doSend(opcode, buffer) {
160 | this.socket.write(encodeMessage(opcode, buffer)); //编码后直接通过socket发送
161 | }
162 | }
163 |
164 | module.exports = MyWebsocket;
165 |
--------------------------------------------------------------------------------