什么是 WebSocket

WebSocket 诞生背景

早期网站为了实现实时推送,采用轮询。轮询是指浏览器客户端每隔一段时间向服务器发送HTTP请求,服务器返回最新数据给客户端。轮询方式分为轮询和长轮询:
image.png

Polling轮询:

  1. function polling(url){
  2. var xhr= new XMLHttpRequest();
  3. xhr.open('GET', url);
  4. xhr.onload = function(){...}
  5. xhr.send();
  6. }
  7. setInterval(
  8. function(){
  9. polling('./poll')
  10. }
  11. ,1000)

Long-Polling轮询:

  1. function longPolling(url){
  2. var xhr= new XMLHttpRequest();
  3. xhr.open('GET', url);
  4. xhr.onload = function(){
  5. update('/longPoll')
  6. }
  7. xhr.send();
  8. }
  9. update('/longPoll')

传统模式带来的缺点,浏览器需要不断向服务器发送请求,然而HTTP请求与响应可能会包含较长的头部信息,但是真正的有效数据很少,造成带宽资源浪费。

比较新的轮询技术是 Comet)。这种技术虽然可以实现双向通信,但仍然需要反复发出请求。而且在 Comet 中普遍采用的 HTTP 长连接也会消耗服务器资源。

HTML5定义WebSocket协议,能很好节省服务器资源和带宽,实现实时通信。WebSocket使用ws或wss的统一资源标志符(URI),wss表示使用TLS的WebSocket。如下:

  1. ws://echo.websocket.org
  2. wss://echo.websocket.org

WebSocket 与 HTTP 和 HTTPS 使用相同的 TCP 端口,可以绕过大多数防火墙的限制。默认情况下,WebSocket 协议使用 80 端口;若运行在 TLS 之上时,默认使用 443 端口。

WebSocket API

手写WebSocket 服务器

WebSocket实现聊天应用

使用ws中间件建立websoket服务

服务端使用ws搭建建立服务器wsServer.js

  1. const WebSocket = require('ws')
  2. var WebSocketServer = WebSocket.Server,
  3. wss = new WebSocketServer({ port: 8899 });//服务端口8899
  4. wss.on('connection', function (ws) {
  5. console.log('服务端:客户端已连接', ws);
  6. ws.on('message', function (message) {
  7. //打印客户端监听的消息
  8. wss.clients.forEach(link => {
  9. if(link != ws && link.readyState === WebSocket.OPEN){
  10. console.log("不是自己发送的消息");
  11. link.send(message)
  12. }
  13. })
  14. });
  15. });

使用vue创建前端聊天对话框

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>webSocket</title>
  8. <style>
  9. #app{
  10. width: 400px;
  11. }
  12. #ul{
  13. width: 100%;
  14. padding:0;
  15. display: flex;
  16. flex-direction: column;
  17. justify-content: center;
  18. align-items: flex-start;
  19. }
  20. #ul li{
  21. width: 75%;
  22. list-style: none;
  23. border-radius: 10px;
  24. border: 1px solid #ccc;
  25. background-color: #f9f9f9;
  26. padding: 5px 12px;
  27. margin: 6px;
  28. }
  29. #ul li p{
  30. margin: 0;
  31. }
  32. .self{
  33. align-self: flex-end;
  34. color:coral;
  35. text-align: right;
  36. }
  37. .other{
  38. color:darkslategray;
  39. }
  40. #input{
  41. border-radius: 10px;
  42. width: 200px;
  43. font-size: 20px;
  44. height: 32px;
  45. line-height: 32px;
  46. }
  47. #btn{
  48. width: 100px;
  49. height: 32px;
  50. border: none;
  51. font-size: 20px;
  52. }
  53. </style>
  54. </head>
  55. <body>
  56. <div id="app">
  57. <div v-if="isLink">
  58. <ul id="ul">
  59. <li :class="[li.from=='self'? 'self' : 'other']" v-for="li in list">
  60. <p>
  61. {{li.value}}
  62. </p>
  63. </li>
  64. </ul>
  65. <input id="input" type="text" v-model="inputText" v-on:keyup.enter="send"></textarea>
  66. <input id="btn" type="button" value="send" @click.enter="send">
  67. </div>
  68. <div v-else>
  69. 网络没有连接
  70. </div>
  71. </div>
  72. <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
  73. <script>
  74. let ws = new WebSocket('ws://localhost:8899')
  75. var app = new Vue({
  76. el: '#app',
  77. data: {
  78. inputText: '',
  79. list:[],
  80. isLink:false
  81. },
  82. methods: {
  83. send(){
  84. if(!this.inputText) return;
  85. // 使用send给服务端发送数据
  86. ws.send(this.inputText);
  87. this.list.push({
  88. value:this.inputText,
  89. from: 'self'
  90. })
  91. this.inputText = '';
  92. }
  93. }
  94. })
  95. ws.onopen = ()=>{
  96. app.isLink = true;
  97. console.log("onopen");
  98. // ws.emit("msg", 12,21)
  99. }
  100. ws.onmessage = (data)=>{
  101. // 从服务端传回的数据
  102. console.log("onmessage receive", data.data);
  103. app.list.push({
  104. from:"other",
  105. value: data.data
  106. })
  107. }
  108. ws.onclose = ()=>{
  109. app.isLink = false;
  110. console.log("onclose");
  111. }
  112. // 自己封装emit
  113. ws.emit = (name, ...args)=>{
  114. ws.send(JSON.stringify({name, data: [...args]}))
  115. }
  116. </script>
  117. </body>
  118. </html>

可以打开多个窗口对话框,最终效果:自己发送的消息靠右侧,颜色为橙色。接收到别人消息为黑色。
image.png

使用socket.io搭建服务端

创建socketServer.js

  1. const http = require('http');
  2. const io =require('socket.io')
  3. let httpServer = http.createServer()
  4. httpServer.listen(8899)
  5. let wsServer = io(httpServer)
  6. let socketLink = []
  7. wsServer.on('connection',socket=>{
  8. // socket.emit
  9. // socket.on
  10. socketLink.push(socket)
  11. socket.on('send', str=>{
  12. socketLink.forEach(so=>{
  13. if(so != socket){
  14. so.emit('send', str)
  15. }
  16. })
  17. })
  18. // 断开连接
  19. socket.on('disconnect', ()=>{
  20. let idx = socketLink.indexOf(socket)
  21. if(idx!=-1){
  22. socketLink.splice(idx, 1);
  23. }
  24. })
  25. })

客户端client创建连接,需要引入socket.io.js

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>socket.io</title>
  8. <style>
  9. #app{
  10. width: 400px;
  11. }
  12. #ul{
  13. width: 100%;
  14. padding:0;
  15. display: flex;
  16. flex-direction: column;
  17. justify-content: center;
  18. align-items: flex-start;
  19. }
  20. #ul li{
  21. width: 75%;
  22. list-style: none;
  23. border-radius: 10px;
  24. border: 1px solid #ccc;
  25. background-color: #f9f9f9;
  26. padding: 5px 12px;
  27. margin: 6px;
  28. }
  29. #ul li p{
  30. margin: 0;
  31. }
  32. .self{
  33. align-self: flex-end;
  34. color:coral;
  35. text-align: right;
  36. }
  37. .other{
  38. color:darkslategray;
  39. }
  40. #input{
  41. border-radius: 10px;
  42. width: 200px;
  43. font-size: 20px;
  44. height: 32px;
  45. line-height: 32px;
  46. }
  47. #btn{
  48. width: 100px;
  49. height: 32px;
  50. border: none;
  51. font-size: 20px;
  52. }
  53. </style>
  54. </head>
  55. <body>
  56. <div id="app">
  57. <div v-if="isLink">
  58. <ul id="ul">
  59. <li :class="[li.from=='self'? 'self' : 'other']" v-for="li in list">
  60. <p>
  61. {{li.value}}
  62. </p>
  63. </li>
  64. </ul>
  65. <input id="input" type="text" v-model="inputText"></textarea>
  66. <input id="btn" type="button" value="send" @click="send">
  67. </div>
  68. <div v-else>
  69. 网络没有连接
  70. </div>
  71. </div>
  72. <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
  73. <script type="text/javascript" src="http://localhost:8899/socket.io/socket.io.js"></script>
  74. <script type="text/javascript">
  75. let socket = io('ws://localhost:8899')
  76. var app = new Vue({
  77. el: '#app',
  78. data: {
  79. inputText: '',
  80. list:[],
  81. isLink:false
  82. },
  83. methods: {
  84. send(){
  85. if(!this.inputText) return;
  86. socket.emit('send', this.inputText);
  87. this.list.push({
  88. value:this.inputText,
  89. from: 'self'
  90. })
  91. this.inputText = '';
  92. }
  93. }
  94. })
  95. socket.on('send', str=>{
  96. app.list.push({
  97. value:str,
  98. from:'other'
  99. })
  100. })
  101. socket.on('connect', ()=>{
  102. console.log("已连接");
  103. app.isLink = true;
  104. })
  105. socket.on('disconnect', ()=>{
  106. console.log('断开连接');
  107. app.isLink = false;
  108. })
  109. </script>
  110. </body>
  111. </html>

最后呈现效果和ws创建的功能一样。

学习资源:

http://www.52im.net/thread-338-1-1.html

文章转载自:https://juejin.cn/post/6854573221241421838