一、介绍
记得在很早前,写一个用Silverlight实现的前端程序,需要处理一个类似心跳的问题,始终找不到好的方法,后来发现了WebSocket才解决了。后来也间断的使用过,但从来没有在产品中广泛使用过。
可以这样说,对WebSocket,属于入门较早,但经验并不丰富的一种技术。大多是作为一种辅助的实现来完成一些特定的场景下的应用。
二、WebSocket
WebSocket是一种应用层的协议,它是客户端和服务端通过建立一个全双工的持久的连接来进行通信。所以这就意味着建立Websocket连接后,双方就可以实时的进行数据通信了。解决了HTTP协议那样必须每次由客户端发起,服务端才能响应,然后通信完成后就断开的情况。其具体的连接过程如下:
- 客户端通过HTTP发起一个特殊的握手请求,其中的头包含类似"Upgrade:websocket Connection:Upgrade Sec-WebSocket-Key Sec-WebSocket-version:13"等,请求升级到WebSocket
- 服务器如果支持则响应状态码------101 Switching Protocols,表示可以进行协议转换
- 双方开始进行WebSocket协议并利用建立好的TCP连接进行双向通信
有网络开发经验的可能会问,它和Socket通信有什么不同呢?明白了这些不同,其实也就知道了WebSocket的特点。
- WebSocket是应用层协议,而Socket是一个接口,它是对传输层协议TCP/IP的通信对象
- 它需要发出一个HTTP的特殊请求来进行再转成Socket。而Socket直接发起通信
- 它和socket一样都是全双工的长连接。但WebSocket传输的是数据流,而WebSocket传输的是消息或帧
- WebSocket通过HTTP发起,浏览器原生支持,防火墙一般不拦截,但Socket则不是,需要防火墙处理
可以把Websocket看成是对Socket在某种特定场景下的封装或者抽象。它的底层仍然是依赖TCP Socket进行数据通信的。
三、和Http与Tcp区别和联系
可以这样说,这三者之间的区别的核心不同在于协议的不同,HTTP和WebSocket基础都是建立在TCP协议之上的。而HTTP和WebSocket又分属于应用层上的不同协议。WebSocket不能直接单独发起,需要由HTTP发起后才能进行连接。看下面的表格说明:
| 对比项 | TCP(传输层) | HTTP(应用层) | WebSocket(应用层) |
|---|---|---|---|
| 协议层级 | 底层基础协议 | 基于 TCP | 基于 TCP(由HTTP升级) |
| 通信方向 | 全双工 | 半双工(单向请求) | 全双工 |
| 谁先发数据 | 双方均可(需自行约定) | 客户端单向发起 | 双方均可 |
| 头部大小 | 20~60 字节 | 一般很大(几百字节) | 极小(2~14字节) |
| 连接生命周期 | 长短均可 | 短连接或长轮询 | 长连接 |
| 数据边界 | 字节流(不确定,可能粘包) | 报文(边界确定) | 帧(边界确定) |
| 防火墙 | 非标准端口可能被拦 | 标准端口(80/443)无拦截 | 标准端口(80/443)无拦截 |
四、作用和应用场景
在传统的HTTP协议中,一般是使用"请求-响应"模式或"轮询"(Polling)技术。但在实际的应用场景中会出现很多需要长时间连接通信的情况。比如一个页面中有很多个通信元素(如几百图片或JS等),浏览器需要重复建立和断开N次连接,消耗的资源是不可想象的(大量的重复的元数据被传输)。还有,如果想在页面上实现聊天互动,传统的轮询模式,无法解决实时的难和长时间不通信仍然需要定时轮询的情况(严重浪费资源)。虽然后面推出了长轮询,但只解决了一小部分的问题。同样,HTTP的长连接虽然解决了部分的效率问题,但由于其仍然属于半双工,仍然也无法完全解决上面的各种情况。
所以WebSocket作用就是提高实时性、提高通信效率以及降低资源消耗。其具体的应用场景如下:
- 实时聊天及社交场景
比如在线IM,在线客服以及群聊以及直播的弹幕等等 - 在线协作
比如在线文档编辑,一个人修改其它人能即时看到结果;还有其它如在线白板等等 - 在线游戏
游戏强调的就是实时性,否则别人都砍死角色了,对方还没看到。所以游戏的状态同步、广播、操作等等都需要实时性 - 在线监控
既然是监控,就必须是延迟越低越好。保证对监控对象的随时把控 - 数据推送
这个就比较常见了,比如在线的股票期货的行情、比赛的计分以及服务器状态的监控等等,都需要及时的将数据传送到指定的页面上
五、例程
下面给出一个简单的聊天的例子,首先是客户端:
js
//客户端
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>WebSocket 实时聊天</title>
<style>
body { font-family: Arial, sans-serif; max-width: 600px; margin: 20px auto; }
#messages { border: 1px solid #ccc; height: 300px; overflow-y: auto; padding: 10px; background: #f9f9f9; }
#input-area { display: flex; margin-top: 10px; }
#input-area input { flex: 1; padding: 8px; }
#input-area button { padding: 8px 16px; margin-left: 10px; }
.msg { margin: 4px 0; }
.system { color: #888; font-style: italic; }
</style>
</head>
<body>
<h2> WebSocket 聊天室</h2>
<div id="messages"></div>
<div id="input-area">
<input type="text" id="input" placeholder="输入消息...">
<button id="sendBtn">发送</button>
</div>
<script>
// 连接 WebSocket 服务器(注意 ws 协议)
const ws = new WebSocket('ws://localhost:8080');
const messagesDiv = document.getElementById('messages');
const input = document.getElementById('input');
const sendBtn = document.getElementById('sendBtn');
// 接收服务器消息
ws.onmessage = function(event) {
const msg = event.data;
const div = document.createElement('div');
div.className = 'msg';
// 系统消息添加样式
if (msg.includes('欢迎')) {
div.classList.add('system');
}
div.textContent = msg;
messagesDiv.appendChild(div);
// 自动滚动
messagesDiv.scrollTop = messagesDiv.scrollHeight;
};
// 发送消息
function sendMessage() {
const text = input.value.trim();
if (text === '') return;
ws.send(text);
input.value = '';
}
sendBtn.addEventListener('click', sendMessage);
input.addEventListener('keydown', function(e) {
if (e.key === 'Enter') sendMessage();
});
// 连接关闭
ws.onclose = function() {
const div = document.createElement('div');
div.className = 'msg system';
div.textContent = '连接断开';
messagesDiv.appendChild(div);
};
</script>
</body>
</html>
下面是服务端:
js
//服务端
const WebSocket = require('ws');
// 创建WebSocket服务器,监听8080端口
const wss = new WebSocket.Server({ port: 8080 });
console.log('WebSocket服务器启动,监听端口8080');
// 客户端连接
wss.on('connection', function connection(ws) {
console.log('新客户端连接成功!');
// 收到消息
ws.on('message', function incoming(message) {
// 消息转换为字符串
const msgStr = message.toString();
console.log('收到消息:', msgStr);
// 广播
wss.clients.forEach(function each(client) {
if (client.readyState === WebSocket.OPEN) {
client.send(msgStr);
}
});
});
// 客户端断开
ws.on('close', function close() {
console.log('客户端连接已断开');
});
// 发送欢迎消息
ws.send('欢迎进入聊天室!');
});
这种小程序很多,这里的小程序简单至极,只是一个示例而已。
六、总结
其实很多技术的出现不是说拍脑门出来的,它是实际的应用场景倒逼出来的。比如有了互联网才有了后来的移动互联网。技术始终是围绕着实际的需求展开的。正如诺基亚的广告:科技以人为本!