想必各位同学在学习编程的时候就听说过聊天室项目,最近项目中也使用到了webSocket,但是对webSocket还不是很了解。所以自己在空余的时间实现了一个简单的聊天室功能来加深对webSocket的理解。
webSocket基础内容
WebSocket是一种在单个TCP连接上进行全双工通信的协议。 (没接触的时候一直以为webSocket和udp协议一样,原来websocket是tcp协议的)
WebSocket建立连接的方式通常有两种:
- 一种是基于WebSocket协议的连接方式。在这种方式下,客户端和服务器通过WebSocket协议进行协商,确定协议版本、握手等过程,最终建立连接。这个过程通常是由客户端发起的,客户端通过WebSocket API中的
WebSocket.connect()
方法来连接服务器。一旦连接建立,客户端和服务器就可以通过WebSocket的发送和接收机制进行数据传输。 - 另一种是基于HTTP协议的连接方式。在这种方式下,客户端通过HTTP请求与WebSocket服务端协商升级协议。具体来说,客户端会发送一个包含特殊头部信息的HTTP请求给服务器,服务器会根据这个请求进行协议升级。协议升级完成后,后续的数据交换则遵照WebSocket的协议进行。这种方式的连接建立是基于HTTP的握手通道的,因此与WebSocket协议方式的连接建立有所不同。
简单的说就是建立webSocket连接可以使用ws协议和http协议,但是通过http协议的方式需要包含特殊的请求头,请求头的信息包含:
- "Upgrade": 这个头部字段的值设置为"websocket",表示客户端希望将连接升级为WebSocket协议。
- "Connection": 这个头部字段的值设置为"Upgrade",表示希望保持长连接,而不是完成一次性的请求-响应后就关闭连接。
- "Sec-WebSocket-Version": 这个头部字段指定了WebSocket协议的版本,例如"13"表示WebSocket协议的版本13。
- "Sec-WebSocket-Key": 这个头部字段的值是一个随机生成的字符串,用于在连接升级过程中进行安全性验证。服务器会根据这个值生成一个响应头部,来确认连接升级的合法性。
结合代码理解
js
// 创建WebSocket对象
var ws = new WebSocket('ws://example.com/ws');
// 连接建立后的处理函数
ws.onopen = function(event) {
console.log('WebSocket connected'); // 在这里可以发送消息给服务器
};
// 接收到服务器消息的处理函数
ws.onmessage = function(event) {
console.log('Received message:', event.data); // 在这里可以处理接收到的消息
};
// 连接关闭后的处理函数
ws.onclose = function(event) {
console.log('WebSocket disconnected:', event.code, event.reason);
};
// 连接错误的处理函数
ws.onerror = function(error) {
console.error('WebSocket error:', error);
};
// 发送消息给服务器的处理函数
function sendMessage() {
var message = document.getElementById('message').value;
ws.send(message);
}
以上是基于WebSocket协议的连接方式
js
// 生成一个随机的Sec-WebSocket-Key
const randomKey = Math.random().toString(36).substring(7);
// 将Sec-WebSocket-Key进行Base64编码
const encodedKey = btoa(randomKey);
// 构造请求头信息
const headers = {
'Sec-WebSocket-Version': '13',
'Sec-WebSocket-Key': encodedKey,
'Sec-WebSocket-Protocol': 'chat'
};
// 发起POST请求,建立Websocket连接
fetch('wss://example.com/ws', {
method: 'POST',
headers: headers,
body: '' // 请求正文为空
})
.then(response => response.text()) // 获取响应文本
.then(data => {
if (data === 'Connection established') {
console.log('Websocket connection established');
// 在连接建立后,使用WebSocket的发送和接收机制进行数据传输
// ...
} else {
console.log('Websocket connection failed:', data);
}
})
.catch(error => console.error('Error:', error));
首先生成了一个随机的Sec-WebSocket-Key,然后将其进行Base64编码。接下来,构造了一个包含请求头信息的对象,其中指定了Websocket协议的版本、Sec-WebSocket-Key和子协议。然后,使用Fetch API发起了一个POST请求,将请求发送到服务器,其中请求头信息和请求正文为空。
在获取到响应后,通过response.text()方法获取响应文本。如果响应文本为"Connection established",表示Websocket连接已经建立。在这个示例中,我们在控制台输出了一条消息。在实际应用中,你可以根据具体的需求,在连接建立后使用WebSocket的发送和接收机制进行数据传输。 (原来除了使用html5 的 websocket Api之外,使用普通的请求方式也是可以连接websocket的,当然需要加上这些特殊的请求头)
聊天室简单版代码
客户端代码
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>聊天室</title>
<style>
* {
box-sizing: border-box;
overflow: hidden;
}
html, body {
padding: 0;
margin: 0;
}
.config {
position: absolute;
z-index: 100;
width: 100%;
height: 100%;
background: #40E0D0; /* fallback for old browsers */
background: -webkit-linear-gradient(to right, #FF0080, #FF8C00, #40E0D0); /* Chrome 10-25, Safari 5.1-6 */
background: linear-gradient(to right, #FF0080, #FF8C00, #40E0D0); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
display: flex;
align-items: center;
justify-content: center;
}
.config--box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #f5f5f5;
border: 1px solid #ccc;
border-radius: 5px;
padding: 20px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.config--title {
font-size: 24px;
font-weight: bold;
margin-bottom: 20px;
}
.config--ipt {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20px;
}
.config--ipt input {
border: none;
border-radius: 5px;
padding: 10px;
font-size: 16px;
outline: none;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.config--btn button {
border: none;
border-radius: 5px;
padding: 10px 20px;
font-size: 16px;
font-weight: bold;
color: #fff;
background-color: #007bff;
cursor: pointer;
outline: none;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.config--btn button:hover {
background-color: #0069d9;
}
.index {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #f5f5f5;
padding: 20px;
}
#index #messageBox {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
height: calc(100vh - 200px);
overflow-y: auto;
}
#index #messageBox div {
width: 100%;
border-radius: 5px;
padding: 10px;
margin-bottom: 10px;
}
#index #messageBox div span {
font-size: 12px;
}
#index #messageBox div p {
margin: 0;
}
#messageBox {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
height: 400px;
overflow-y: auto;
}
.center {
text-align: center;
font-size: 12px;
color: #666;
}
.left {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
}
.left .bubble {
margin-bottom: 10px;
border-radius: 20px;
padding: 10px 15px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
background-color: #fff;
color: #333;
}
.left span {
margin-left: 10px;
}
.right {
display: flex;
flex-direction: column;
align-items: flex-end;
}
.right .bubble {
margin-bottom: 10px;
border-radius: 20px;
padding: 10px 15px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
background-color: #def1fc;
color: #333;
}
.right span {
margin-right: 10px;
}
.operation {
position: relative;
}
#index textarea {
border: none;
border-radius: 5px;
padding: 10px;
font-size: 16px;
outline: none;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
margin-right: 10px;
flex-grow: 1;
width: 100%;
}
#index button {
border: none;
border-radius: 5px;
padding: 10px 20px;
font-size: 16px;
font-weight: bold;
color: #fff;
background-color: #007bff;
cursor: pointer;
outline: none;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
position: absolute;
right: 20px;
top: 20px;
}
#index button:hover {
background-color: #0069d9;
}
</style>
</head>
<body>
<div id="config" class="config">
<div class="config--box">
<div class="config--btn">
<button id="connectBtn">进入聊天室</button>
</div>
</div>
</div>
<div id="index" class="index">
<div id="messageBox">
</div>
<div class="operation">
<textarea id="chat" cols="30" rows="10" placeholder="发送消息"></textarea>
<button id="sendBtn">发送</button>
</div>
</div>
<script>
let ws = null;
const list = [];
let identifier = '';
const connectBtn = document.getElementById('connectBtn');
const config = document.getElementById('config');
const sendBtn = document.getElementById('sendBtn');
const outPut = (obj) => {
list.push({
key: list.length,
...obj,
})
let str = ''
list.forEach(item => {
switch (item.type) {
case 'me':
str += `<div class="right">
<span>我</span>
<p class="bubble">${item.data}</p>
</div>`
break;
case 'people':
str += `<div class="left">
<span>
${item.ip}
</span>
<p class="bubble">${item.data}</p>
</div>`
break
case 'system':
str += `<div class="center">${item.data}</div>`
break;
default:
break;
}
});
document.getElementById('messageBox').innerHTML = str;
}
function send(obj) {
const sendMsg = JSON.stringify(obj)
if(ws) {
ws.send(sendMsg)
}
}
connectBtn.addEventListener('click', () => {
if(!ws) {
ws = new WebSocket('ws://你的后端ip地址:3000/ws');
ws.onopen = function (params) {
outPut({
type: 'system',
data: '我加入聊天',
})
};
ws.onmessage = function (e) {
const message = JSON.parse(e.data)
if(message.type === 'init') {
identifier = message.identifier
} else {
outPut({
...message,
type: message.identifier === identifier ? 'me' : 'people'
})
}
};
ws.onclose = function(evt) {
outPut({
type: 'system',
data: '聊天室关闭'
})
};
ws.onerror = function (evt) {
outPut({
type: 'system',
data: '连接失败'
});
};
} else {
outPut({
type: 'system',
data: '加入聊天'
})
}
config.style.display = 'none';
})
sendBtn.addEventListener('click', () => {
const msg = document.getElementById('chat').value;
if(!msg && msg === '') return
send({
identifier,
data: msg,
});
document.getElementById('chat').value = '';
});
document.addEventListener("keydown", function(event) {
if (event.key === "Enter") {
const msg = document.getElementById('chat').value;
if(!msg && msg === '') return
send({
identifier,
data: msg,
});
document.getElementById('chat').value = '';
}
});
</script>
</body>
</html>
服务端代码
js
const WebSocket = require('ws');
const http = require('http');
const express = require('express');
const app = express();
// 创建一个HTTP服务器
const server = http.createServer(app);
// 存储每个客户端的标识
const clients = new Map();
// 创建WebSocket服务器并将其附加到HTTP服务器
const wss = new WebSocket.Server({ noServer: true });
// 生成唯一的客户端标识符
function generateClientId() {
return Math.random().toString(36).substring(7);
}
server.on('upgrade', (request, socket, head) => {
switch (request.url) {
case '/ws':
wss.handleUpgrade(request, socket, head, (ws) => {
Socket = socket
wss.emit('connection', ws, request);
});
break;
default:
break;
}
});
// 监听WebSocket连接事件
wss.on('connection', (socket, request) => {
// 获取客户端的唯一标识符
const clientId = generateClientId();
// 将客户端标识符存储在映射中
clients.set(clientId, socket);
socket.send(JSON.stringify({identifier: clientId, type: 'init'}));
// 获取客户端的IP地址
const clientIP = request.connection.remoteAddress;
// 监听客户端发送的消息
socket.on('message', async(message) => {
// 解析消息中的标识符和数据
const { identifier, data, type } = JSON.parse(message);
// 如果标识符不匹配,将消息广播给其他客户端
clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify({
ip: clientIP,
identifier,
data,
}));
}
});
});
wss.on('error', (message) => {
console.log('报错了');
});
wss.on('close', (message) => {
console.log('连接关闭:')
// 移除客户端的标识符
clients.delete(clientId);
})
wss.on('open', (message) => {
console.log('open', message)
})
});
// 启动HTTP服务器
server.listen(3000, () => {
console.log('HTTP服务器已启动,监听端口3000');
});
客户端使用vscode插件golive插件就可以用啦,服务端代码复制到index.js文件中然后node index一下就启动了,切换一下客户端接口地址你就可以在局域网中和其他同学聊起来了。
刚开始使用websocket时遇到的问题
- 最开始使用websocket的时候使用new webSocket()时候里面必须要使用 ws:// 或者 wss:// 不能使用 http:// 欢迎补充O(∩_∩)O哈哈~
总结
先这样,再这样,然后这样,接着这样,铛铛铛铛!就弄出来啦,你学废了吗😜