WebSocket介绍
WebSocket是一种在Web浏览器和服务器之间建立持久性连接的协议,它允许双向通信,而不需要像HTTP协议那样每次请求都需要重新建立连接。
WebSocket是全双工通信协议,可以在客户端和服务器之间实现实时数据传输。
WebSocket通常在以下场景下使用:
- 实时数据传输:例如股票市场、体育比赛、即时消息等需要实时更新数据的应用场景。
- 在线游戏:需要实时传输游戏状态、位置、动作等数据。
- 视频会议:需要实时传输音视频数据,WebSocket可以用于传输控制信息。
- 实时协作:例如团队协作、在线编辑等场景,需要实时传输文本、图像、音频等数据。
- 实时通知:例如推送通知、即时聊天等场景,需要实时传输消息。
WebSocket适用于需要实时双向通信的应用场景,它可以提供更好的用户体验和更高的交互性。
WebSocket状态码
状态码 |
含义 |
101 |
HTTP协议切换为WebSocket协议。连接成功。 |
1000 |
正常断开连接。 |
1001 |
服务器断开连接。 |
1002 |
websocket协议错误。 |
1003 |
客户端接受了不支持数据格式(只允许接受文本消息。是客户端限制不接受二进制数据,而不是websocket协议不支持二进制数据)。 |
1006 |
异常关闭。 |
1007 |
客户端接受了无效数据格式(文本消息编码不是utf-8)。 |
1009 |
传输数据量过大。 |
1010 |
客户端终止连接。 |
1011 |
服务器终止连接。 |
1012 |
服务端正在重新启动。 |
1013 |
服务端临时终止。 |
1014 |
通过网关或代理请求服务器,服务器无法及时响应。 |
1015 |
TLS握手失败。 |
WebSocket Web API
WebSocket Web API: https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket
WebSocket 对象提供了用于创建和管理 WebSocket 连接,以及可以通过该连接发送和接收数据的 API。
构造函数
WebSocket(url[, protocols])
: 使用 WebSocket() 构造函数来构造一个 WebSocket 对象。
属性
https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket/binaryType
WebSocket.binaryType
: 使用二进制的数据类型连接。
WebSocket.bufferedAmount
: 未发送至服务器的字节数。
WebSocket.extensions
: 服务器选择的扩展。
WebSocket.onclose
: 用于指定连接关闭后的回调函数。
WebSocket.onerror
: 用于指定连接失败后的回调函数。
WebSocket.onmessage
: 用于指定当从服务器接受到信息时的回调函数。
WebSocket.onopen
: 用于指定连接成功后的回调函数。
WebSocket.protocol
: 服务器选择的下属协议。
WebSocket.readyState
: 当前的链接状态。
WebSocket.url
: WebSocket 的绝对路径。
方法
https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket/close
WebSocket.close([code[, reason]])
: 关闭当前链接。
WebSocket.send(data)
: 对要传输的数据进行排队。
事件
https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket/close_event
使用 addEventListener() 或将一个事件监听器赋值给本接口的 oneventname 属性,来监听下面的事件。
close
: 当一个 WebSocket 连接被关闭时触发。 也可以通过 onclose 属性来设置。
error
: 当一个 WebSocket 连接因错误而关闭时触发,例如无法发送数据时。 也可以通过 onerror 属性来设置。
message
: 当通过 WebSocket 收到数据时触发。 也可以通过 onmessage 属性来设置。
open
: 当一个 WebSocket 连接成功时触发。 也可以通过 onopen 属性来设置。
JS使用WebSocket示例
以下是使用WebSocket的示例代码:
在前端JavaScript代码中:
1 2 3 4 5 6 7 8 9 10 11 12 13
| const socket = new WebSocket('ws://localhost:8080');
socket.addEventListener('open', function (event) { socket.send('Hello Server!'); });
socket.addEventListener('message', function (event) { console.log('Message from server ', event.data); });
socket.addEventListener('close', function (event) { console.log('WebSocket connection closed'); });
|
在后端Node.js代码中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const WebSocket = require('ws'); const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', function connection(ws) { console.log('WebSocket connection established');
ws.on('message', function incoming(message) { console.log('received: %s', message); ws.send('Server received: ' + message); });
ws.on('close', function () { console.log('WebSocket connection closed'); }); });
|
在这个示例中,前端JavaScript代码创建了一个WebSocket对象,并连接到服务器的端口8080。当连接建立时,它会向服务器发送一条消息,当从服务器收到消息时,它会打印消息内容。当WebSocket连接关闭时,它会打印一条消息。
后端Node.js代码使用WebSocket模块创建了一个WebSocket服务器,并在端口8080上监听连接请求。当客户端连接到服务器时,它会打印一条消息。当从客户端收到消息时,它会打印消息内容并将消息发送回客户端。当WebSocket连接关闭时,它会打印一条消息。
Vue3中封装WebSocket
封装websocket
/utils/websocket.ts
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
| import { ElMessage, ElNotification as message } from 'element-plus'; import { getEnvName } from '~/utils'; import { backend_url } from '~/constvars'; const defaultUrl = 'wss://api-backend-stg.com';
export class WlWebsocket { private websocket: any = null;
private wsBaseUrl = defaultUrl; private socket_open = false; private hearbeat_timer: any = null; private hearbeat_interval = 8 * 1000; private is_reconnect = false; private reconnect_count = 3; private reconnect_current = 1; private reconnect_timer: any = null; private reconnect_interval = 5 * 1000;
private url = ''; private query = '';
constructor(url: string, query: string = '') { this.url = url; this.query = query; let env: any = getEnvName(); if (!!env) { this.wsBaseUrl = 'wss://' + backend_url[env]; } console.log('getEnvName() env', env, this.wsBaseUrl); }
init = (receiveMessage: Function | null, onSocketClose: Function | null) => { if (!('WebSocket' in window)) { message.warning('浏览器不支持WebSocket'); return null; } const wsUrl = this.wsBaseUrl + this.url + this.query;
console.log('==wsUrl==', wsUrl);
this.websocket = new WebSocket(wsUrl);
this.websocket.onmessage = (data: any) => { if (!!receiveMessage) { const res = JSON.parse(data?.data); if (res.err_code != 0) { ElMessage.error(res?.message); } receiveMessage(JSON.parse(data?.data)); } };
this.websocket.onclose = (e: any) => { console.log('WebSocket连接关闭', e); if (!!onSocketClose) { onSocketClose(); } this.socket_open = false; if (!!this.is_reconnect) { console.log( !!this.is_reconnect, 'WebSocket连接关闭 重新连接------', this.reconnect_current ); this.reconnect_timer = setTimeout(() => { if (this.reconnect_current > this.reconnect_count) { clearTimeout(this.reconnect_timer); this.is_reconnect = false; return; } this.reconnect_current++; this.reconnect(); }, this.reconnect_interval); } };
this.websocket.onopen = () => { console.log('WebSocket连接成功'); this.socket_open = true; };
this.websocket.onerror = (e: any) => { console.log('WebSocket连接出错', e); ElMessage.error(e.code + ' 连接出错'); }; };
heartbeat = () => { console.log('== this.heartbeat 开启心跳'); this.hearbeat_timer && clearInterval(this.hearbeat_timer);
this.hearbeat_timer = setInterval(() => { console.log('WebSocket setInterval(()) sen data'); }, this.hearbeat_interval); };
send = (data: string, callback = null) => { console.log( 'WebSocket=====send=====', this.websocket.readyState, this.websocket.OPEN, this.websocket );
if (this.websocket.readyState === this.websocket.OPEN) { console.log('send if', this.websocket); this.websocket.send(JSON.stringify(data)); callback && callback(); } else { console.log('send else'); clearInterval(this.hearbeat_timer); ElMessage.warning('无法发送消息,socket链接已断开'); } };
close = () => { console.log('WebSocket 关闭关闭关闭==='); this.is_reconnect = false; this.websocket.close(); this.websocket = null; };
reconnect = () => { if (this.websocket && !this.is_reconnect) { console.log('重新连接 this.close();'); this.close(); } }; }
|
封装websocket的接口API
/apis/test.ts
1 2 3 4 5 6 7
| import { toQueryString } from '~/utils'; import { WlWebsocket } from '~/utils/websocket';
export const apiGetTestList = (query: any) => { return new WlWebsocket(`/api/test/${query}/list`); };
|
页面中调用接口API
/views/test/index.vue
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
| <template> <div> <div> <el-button class="search-button" @click="filter()" :disabled="loading"> 筛选 </el-button>
<el-row v-if="tableList.length > 0">
<el-table :data="tableList" v-loading="loading"> <el-table-column prop="test_id" label="test" width="240"> </el-table-column> </el-table>
<div> <el-pagination v-model:currentPage="searchForm.page" v-model:page-size="searchForm.size" :page-sizes="[5, 10, 20, 50]" layout="total, sizes, prev, pager, next, jumper" :total="totalNumber" @size-change="handleSizeChange" @current-change="handleCurrentChange" /> </div>
</el-row> </div> </div> </template>
<script setup lang="ts"> import { ref, reactive, onBeforeUnmount, onBeforeMount } from 'vue'; import { Icon } from '@iconify/vue/dist/iconify'; import { iconMap } from '~/auth'; import { apiGetTestList } from '~/apis/order-image';
const loading = ref(false);
const tableList = ref([] as any); const totalNumber = ref(0);
const searchForm = reactive({ page: 1, size: 10, });
const webSocket = ref();
const refreshListData = (res: any) => { const date = new Date(); console.log( '重新渲染table数据 res 时间', date.getMinutes() + ':' + date.getSeconds(), res ); loading.value = false;
if (res.data !== null) { tableList.value = res.data; totalNumber.value = res.total; } else { tableList.value = []; } };
const sendSocketMessage = () => { const msg = { page: searchForm.page, size: searchForm.size, update: true, }; webSocket.value.send(msg); };
const closeSocket = () => { if (!!webSocket.value && webSocket.value.websocket != null) { webSocket.value.close(); } };
const publicSearch = () => { webSocket.value = apiGetTestList({ query: '1111' }); webSocket.value.init( (res: any) => { refreshListData(res); }, () => { loading.value = false; } ); };
const handleSizeChange = (val: any) => { loading.value = true; searchForm.size = val; searchForm.page = 1; sendSocketMessage(); };
const handleCurrentChange = (val: any) => { loading.value = true; searchForm.page = val; sendSocketMessage(); };
const filter = async () => { console.log(' filterEvent '); loading.value = true; searchForm.size = 10; searchForm.page = 1; publicSearch(); };
onBeforeUnmount(() => { closeSocket(); }); </script>
<style lang="scss" scoped></style>
|