来源:Node.js

HTTP的局限

传统HTTP是一种单向请求-响应协议,客户端发送请求后,服务器才会响应并返回相应的数据

这种情况下,客户端需要主动发送请求才能获取服务器上的资源,而且每次请求都需要重新建立连接,这种方式在实时通信和持续获取资源的场景下效率较低

而Socket提供了客户端和服务器之间即时的双工通信能力,可以实时、更快速、更低延迟地的传递数据

这种情况下,数据的传输和相应几乎是实时完成的,不需要轮询或定时发送请求

Socket.IO

大型开发时通常不会使用原生WebSocket,而是使用Socket.IO

Socket.IO是一个基于事件驱动的实时通信框架,用于构建实时应用程序,它提供了双向、低延迟的通信能力,使得服务器和客户端可以实时地发送和接收数据

Socket.IO的特点

  • 实时性:作为封装后的WebSocket,能实现双工实时通信
  • 事件驱动
    • 使用事件驱动的编程模型,可以让服务器和客户端通过触发事件来发送和接收数据
    • 可以轻松地构建实时的应用程序,例如聊天应用、实时协作工具等
  • 跨平台支持
    • 可以在多个平台上使用,包括浏览器、服务器和移动设备等
    • 提供了对多种编程语言和框架的支持,如JavaScript、Node.js、Python、Java等
  • 容错性:具有容错能力,当WebSocket连接不可用时,它可以自动降级到其他传输机制,例如HTTP长轮询
  • 扩展性
    • 支持水平扩展,可以将应用程序扩展到多个服务器,并实现事件的广播和传递
    • 这使得应用程序可以处理大规模的并发连接,实现高可用性和高性能

依赖

1
pnpm install socket.io

编码

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import http from 'node:http'
import { Server } from 'socket.io'

const server = http.createServer()

// 配置socket.io,需要接收http服务器
const io = new Server(server, {
cors: true // 允许跨域
})

// 房间
const groups = {}

io.on('connection', socket => {
console.log('a user connected')
// 有人加入房间(前端发送join事件)
socket.on('join', ({name, room}) => {

// 创建房间
socket.join(room)

// 服务器有该房间,将新用户加入房间
if(groups[room]) {
groups[room].push({name, id: socket.id})
}
// 服务器没有该房间,创建房间并将新用户加入
else {
groups[room] = [{name, id: socket.id}]
}

// 发送新用户加入的消息
socket.emit('message', {user: 'admin', text: `${name} 加入了聊天室`})
// 广播房间列表
socket.emit('groups', groups)
// 向所有人广播所有房间
socket.broadcast.emit('groups', groups)
})

// 有人发送消息(前端发送message事件)
socket.on('message', ({text, room, user}) => {
// 向该房间发送消息
socket.broadcast.to(room).emit('message', {user, text})
})

// 有人断开连接
socket.on('disconnect', () => {
Object.keys(groups).forEach(key => {
// 找到离开的用户
let leaveUser = groups[key].find(item => item.id === socket.id)
if(leaveUser) {
// 删除离开的用户
groups[key] = groups[key].filter(item => item.id !== socket.id)
// 向该房间广播用户离开的消息
socket.broadcast.to(leaveUser.room).emit('message', {user: 'admin', text: `${leaveUser.name} 退出了聊天室`})
}
})
})
})

server.listen(11451, () => {
console.log('listening on *:11451')
})

index.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
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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
padding: 0;
margin: 0;
}

html,
body,
.room {
height: 100%;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
background-color: #2b313d;

}

.room {
width: 95%;
height: 95%;
overflow: hidden;
border-radius: 20px;
}

.room_inner {
width: 100%;
height: 100%;
display: flex;
}

.left {
width: 300px;
margin-right: 10px;
background-image: linear-gradient(to top, #6a85b6 0%, #bac8e0 100%);
}

.right {
background-image: linear-gradient(to top, #accbee 0%, #e7f0fd 100%);
overflow: hidden;
flex: 1;
display: flex;
flex-direction: column;
}

.header {
background-image: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 10px;
box-sizing: border-box;
font-size: 20px;
}

.main {
flex: 1;
padding: 10px;
box-sizing: border-box;
font-size: 20px;
overflow: auto;
}

.main-chat {
color: #2b313d;
}

.footer {
min-height: 160px;
background-image: linear-gradient(to top, #e6e9f055 0%, #eef1f555 100%);
border-radius: 20px;
margin: 10px;
}

.footer .ipt {
width: 100%;
height: 100%;
color: #2b313d;
outline: none;
font-size: 20px;
padding: 10px;
box-sizing: border-box;
}

.groups {
height: 100%;
overflow: auto;
position: relative;
}

.groups::-webkit-scrollbar {
position: absolute;
width: 5px;
opacity: 0;
}

.groups::-webkit-scrollbar-thumb {
width: 10px;
background-image: linear-gradient(to top, #30cfd0 0%, #330867 100%);
}

.groups::-webkit-scrollbar-track {
opacity: 0;
}

.groups:hover::-webkit-scrollbar {
opacity: 1;
}

.groups-items {
margin: 10px;
height: 50px;
width: calc(100% - 20px);
border-radius: 20px;
background-image: linear-gradient(to right, #5f72bd 0%, #9b23ea 100%);
display: flex;
align-items: center;
justify-content: center;
color: white;
}
</style>
</head>
<div class="room">
<div class="room_inner">
<div class="left">
<div class="groups">

</div>
</div>
<div class="right">
<header class="header">聊天室</header>
<main class="main">

</main>
<footer class="footer">
<div class="ipt" contenteditable></div>
</footer>
</div>
</div>

</div>

<body>
<script type="module">
import io from "https://cdn.socket.io/4.7.4/socket.io.esm.min.js"
const main = document.querySelector('.main')
const groupsEl = document.querySelector('.groups')

// 发送消息
const sendMessage = (message) => {
const div = document.createElement('div')
div.className = 'main-chat'
div.innerHTML = `${message.user}: ${message.text}`
main.appendChild(div)
}

const name = prompt('请输入名字')
const room = prompt('请输入房间名')

const socket = io('ws://localhost:11451')

// 按下enter发送消息
document.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault()
const text = document.querySelector('.footer .ipt').innerText
if (text.trim() === '') return
// 向服务器发送用户消息
socket.emit('message', {text, room, user: name})
// 前端渲染新消息
sendMessage({user: name, text})
// 清空输入框
document.querySelector('.footer .ipt').innerText = ''
}
})

// socket.io连接成功
socket.on('connect', () => {

// 发送用户进入房间的消息
socket.emit('join', {name, room})
// 有人发送了消息
socket.on('message', (message) => {
// 前端渲染新消息
sendMessage(message)
})
// 聊天室发生了变化(后端发送groups事件)
socket.on('groups', (groups) => {
// 清空房间列表
groupsEl.replaceChildren()
// 重新渲染房间列表
Object.keys(groups).forEach((key) => {
const div = document.createElement('div')
div.className = 'groups-items'
div.innerHTML = `房间:${key} 人数:${groups[key].length}`
groupsEl.appendChild(div)
})
})
})
</script>
</body>

</html>

顺便安利一个分享180种渐变色的小网站:免费的渐变背景CSS3样式