<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          沉寂了一周,我開發(fā)了一個聊天室

          共 12035字,需瀏覽 25分鐘

           ·

          2021-02-11 11:03

          前言

          最近一周沒有發(fā)文章了,我在這里向大家說一聲抱歉。今天,我們來從零開始開發(fā)一款聊天室。好,我們現(xiàn)在就開始。

          了解WebSocket

          開發(fā)聊天室,我們需要用到WebSocket這個網絡通信協(xié)議,那么為什么會用到它呢?

          我們首先來引用阮一峰大佬的一篇文章一段話:

          初次接觸 WebSocket 的人,都會問同樣的問題:我們已經有了 HTTP 協(xié)議,為什么還需要另一個協(xié)議?它能帶來什么好處?

          答案很簡單,因為 HTTP 協(xié)議有一個缺陷:通信只能由客戶端發(fā)起。

          舉例來說,我們想了解今天的天氣,只能是客戶端向服務器發(fā)出請求,服務器返回查詢結果。HTTP 協(xié)議做不到服務器主動向客戶端推送信息。

          這種單向請求的特點,注定了如果服務器有連續(xù)的狀態(tài)變化,客戶端要獲知就非常麻煩。我們只能使用"輪詢":每隔一段時候,就發(fā)出一個詢問,了解服務器有沒有新的信息。最典型的場景就是聊天室。

          輪詢的效率低,非常浪費資源(因為必須不停連接,或者 HTTP 連接始終打開)。因此,工程師們一直在思考,有沒有更好的方法。WebSocket 就是這樣發(fā)明的。

          我們來借用MDN網站上的官方介紹總結一下:

          WebSockets 是一種先進的技術。它可以在用戶的瀏覽器和服務器之間打開交互式通信會話。使用此API,您可以向服務器發(fā)送消息并接收事件驅動的響應,而無需通過輪詢服務器的方式以獲得響應。

          WebSocket 協(xié)議在2008年誕生,2011年成為國際標準。

          WebSocket特點

          1. 服務器可以主動向客戶端推送信息,客戶端也可以主動向服務器發(fā)送信息,是真正的雙向平等對話,屬于服務器推送技術的一種。
          2. 建立在 TCP 協(xié)議之上,服務器端的實現(xiàn)比較容易。
          3. 與 HTTP 協(xié)議有著良好的兼容性。默認端口也是80和443,并且握手階段采用 HTTP 協(xié)議,因此握手時不容易屏蔽,能通過各種 HTTP 代理服務器。
          4. 數(shù)據(jù)格式比較輕量,性能開銷小,通信高效。
          5. 可以發(fā)送文本,也可以發(fā)送二進制數(shù)據(jù)。
          6. 沒有同源限制,客戶端可以與任意服務器通信。
          7. 協(xié)議標識符是ws(如果加密,則為wss),即ws對應httpwss對應https。服務器網址就是 URL。即ws://www.xx.comwss://www.xx.com

          WebSocket客戶端常用API

          WebSocket 對象提供了用于創(chuàng)建和管理 WebSocket連接,以及可以通過該連接發(fā)送和接收數(shù)據(jù)的 API。

          使用WebSocket()構造函數(shù)來構造一個WebSocket

          屬性

          1. WebSocket.onopen

            用于指定連接成功后的回調函數(shù)。

          2. WebSocket.onmessage

            用于指定當從服務器接受到信息時的回調函數(shù)。

          3. WebSocket.onclose

            用于指定連接關閉后的回調函數(shù)。

          4. WebSocket.onerror

            用于指定連接失敗后的回調函數(shù)。

          方法

          1. WebSocket.close()

          關閉當前鏈接。

          1. WebSocket.send(data)

          客戶端發(fā)送數(shù)據(jù)到服務器,對要傳輸?shù)臄?shù)據(jù)進行排隊。

          客戶端舉例

          // Create WebSocket connection.const socket = new WebSocket('ws://localhost:8080'); // 這里的地址是服務器的websocket服務地址
          // Connection openedsocket.onopen = function(evt) { console.log("Connection open ..."); ws.send("Hello WebSockets!");};
          // Listen for messagessocket.onmessage = function(evt) { console.log( "Received Message: " + evt.data); socket.close();};
          // Connection closedsocket.onclose = function(evt) { console.log("Connection closed.");};

          常用的WebSocket服務端

          這里服務端我們使用Node.js,這里向大家介紹幾個常用的庫。

          1. ws
          2. socket.io
          3. nodejs-websocket

          具體用法,大家可以上網瀏覽詳細文檔,這里就不一一介紹啦。不過在這篇文章中。我將會給大家使用wsnodejs-websocket這兩個模塊來分別進行項目開發(fā)。

          客戶端與服務端都介紹完啦!我們就趕快行動起來吧!

          開發(fā)本地端(或局域網)聊天室(第一種)

          我們將基于[email protected]開發(fā)聊天室,原因是擁抱新技術。怎么搭建vue腳手架,這里就不介紹了,想必大家也會。我們直接就上代碼。

          客戶端

          <template>  <div class="home">    <div class="count">      <p>在線人數(shù):{{ count }}p>    div>    <div class="content">      <div class="chat-box" ref="chatBox">        <div          v-for="(item, index) in chatArr"          :key="index"          class="chat-item"        >          <div v-if="item.name === name" class="chat-msg mine">            <p class="msg mineBg">{{ item.txt }}p>            <p class="user" :style="{ background: bg }">              {{ item.name.substring(item.name.length - 5, item.name.length) }}            p>          div>          <div v-else class="chat-msg other">            <p class="user" :style="{ background: item.bg }">              {{ item.name.substring(item.name.length - 5, item.name.length) }}            p>            <p class="msg otherBg">{{ item.txt }}p>          div>        div>      div>    div>    <div class="footer">      <textarea        placeholder="說點什么..."        v-model="textValue"        autofocus        ref="texta"        @keyup.enter="send"      >textarea>      <div class="send-box">        <p class="send active" @click="send">發(fā)送p>      div>    div>  div>template>
          <script>import { onMounted, onUnmounted, ref, reactive, nextTick } from "vue";export default { name: "Home", setup() { let socket = null;????const?path?=?"ws://localhost:3000/";?//?本地服務器地址 const textValue = ref(""); const chatBox = ref(null); const texta = ref(null); const count = ref(0); const name = new Date().getTime().toString(); const bg = randomRgb(); const chatArr = reactive([]); function init() { if (typeof WebSocket === "undefined") { alert("您的瀏覽器不支持socket"); } else { socket = new WebSocket(path); socket.onopen = open; socket.onerror = error; socket.onclose = closed; socket.onmessage = getMessage; window.onbeforeunload = function(e) { e = e || window.event; if (e) { e.returnValue = "關閉提示"; socket.close(); } socket.close(); return "關閉提示"; }; } } function open() { alert("socket連接成功"); } function error() { alert("連接錯誤"); } function closed() { alert("socket關閉"); } async function getMessage(msg) { if (typeof JSON.parse(msg.data) === "number") { console.log(JSON.parse(msg.data)); count.value = msg.data; } else { const obj = JSON.parse(msg.data); chatArr.push(obj); } await nextTick(); chatBox.value.scrollTop = chatBox.value.scrollHeight; } function randomRgb() { let R = Math.floor(Math.random() * 130 + 110); let G = Math.floor(Math.random() * 130 + 110); let B = Math.floor(Math.random() * 130 + 110); return "rgb(" + R + "," + G + "," + B + ")"; } function send() { if (textValue.value.trim().length > 0) { const obj = { name: name, txt: textValue.value, bg: bg, }; socket.send(JSON.stringify(obj)); textValue.value = ""; texta.value.focus(); } } function close() { alert("socket已經關閉"); } onMounted(() => { init(); }); onUnmounted(() => { socket.onclose = close; }); return { send, textValue, chatArr, name, bg, chatBox, texta, randomRgb, count, }; },};script>

          至于樣式文件,這里我也貼出來。

          html,body{  background-color: #e8e8e8;  user-select: none;}::-webkit-scrollbar {  width: 8px;  height: 8px;  display: none;}::-webkit-scrollbar-thumb {  background-color: #D1D1D1;  border-radius: 3px;  -webkit-border-radius: 3px;  border-left: 2px solid transparent;  border-top: 2px solid transparent;}*{  margin: 0;  padding: 0;}.mine {  justify-content: flex-end;}.other {  justify-content: flex-start;}.mineBg {  background: #98e165;}.otherBg {  background: #fff;}.home {  position: fixed;  top: 0;  left: 50%;  transform: translateX(-50%);  width: 100%;  height: 100%;  min-width: 360px;  min-height: 430px;  box-shadow: 0 0 24px 0 rgb(19 70 80 / 25%);}.count{  height: 5%;  display: flex;  justify-content: center;  align-items: center;  background: #EEEAE8;  font-size: 16px;}.content {  width: 100%;  height: 80%;  background-color: #f4f4f4;  overflow: hidden;}.footer {  position: fixed;  bottom: 0;  width: 100%;  height: 15%;  background-color: #fff;}.footer textarea {  width: 100%;  height: 50%;  background: #fff;  border: 0;  box-sizing: border-box;  resize: none;  outline: none;  padding: 10px;  font-size: 16px;}.send-box {  display: flex;  height: 40%;  justify-content: flex-end;  align-items: center;}.send {  margin-right: 20px;  cursor: pointer;  border-radius: 3px;  background: #f5f5f5;  z-index: 21;  font-size: 16px;  padding: 8px 20px;}.send:hover {  filter: brightness(110%);}.active {  background: #98e165;  color: #fff;}.chat-box {  height: 100%;  padding:0 20px;  overflow-y: auto;}.chat-msg {  display: flex;  align-items: center;}.user {  font-weight: bold;  color: #fff;  position: relative;  word-wrap: break-word;  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);  width: 60px;  height: 60px;  line-height: 60px;  border-radius:8px ;  text-align: center;}.msg {  margin: 0 5px;  max-width: 74%;  white-space: normal;  word-break: break-all;  color: #333;  border-radius: 8px;  padding: 10px;  text-align: justify;  font-size: 16px;  box-shadow: 0px 0px 10px #f4f4f4;}.chat-item {  margin: 20px 0;  animation: up-down 1s both;}@keyframes up-down {  0% {    opacity: 0;    transform: translate3d(0, 20px, 0);  }
          100% { opacity: 1; transform: none; }}

          服務端

          這里使用的是Node.js。

          nodejs-websocket:websocket服務器和客戶端的nodejs模塊。

          const ws = require("nodejs-websocket");const server = ws.createServer((conn) => {  conn.on("text", (str) => {    broadcast(str);  });  conn.on("error", (err) => {    console.log(err);  });});server.listen(3000, function () {  console.log("open");});// 群發(fā)消息function broadcast(data) {  server.connections.forEach((conn) => {    conn.sendText(data);  });}

          項目一覽


          在線人數(shù)為零,這不是bug,是因為當時在本地端沒有做,只是放上了這個版塊。不過,在云服務端我已經放上了這個功能。那么,我們來看一下吧。

          開發(fā)云端聊天室(第二種)

          客戶端?

          <template>  <div class="home">    <div class="count">      <p>在線人數(shù):{{ count }}p>    div>    <div class="content">      <div class="chat-box" ref="chatBox">        <div          v-for="(item, index) in chatArr"          :key="index"          class="chat-item"        >          <div v-if="item.name === name" class="chat-msg mine">            <p class="msg mineBg">{{ item.txt }}p>            <p class="user" :style="{ background: bg }">              {{ item.name.substring(item.name.length - 5, item.name.length) }}            p>          div>          <div v-else class="chat-msg other">            <p class="user" :style="{ background: item.bg }">              {{ item.name.substring(item.name.length - 5, item.name.length) }}            p>            <p class="msg otherBg">{{ item.txt }}p>          div>        div>      div>    div>    <div class="footer">      <textarea        placeholder="說點什么..."        v-model="textValue"        autofocus        ref="texta"        @keyup.enter="send"      >textarea>      <div class="send-box">        <p class="send active" @click="send">發(fā)送p>      div>    div>  div>template>
          <script>import { onMounted, onUnmounted, ref, reactive, nextTick } from "vue";export default { name: "Home", setup() { let socket = null; const path = "wss:/xxx.com/wsline/"; // 這個網址只是測試網址,這里只是說明云服務地址 const textValue = ref(""); const chatBox = ref(null); const texta = ref(null); const count = ref(0); const name = new Date().getTime().toString(); const bg = randomRgb(); const chatArr = reactive([]); function init() { if (typeof WebSocket === "undefined") { alert("您的瀏覽器不支持socket"); } else { socket = new WebSocket(path); socket.onopen = open; socket.onerror = error; socket.onclose = closed; socket.onmessage = getMessage; window.onbeforeunload = function(e) { e = e || window.event; if (e) { e.returnValue = "關閉提示"; socket.close(); } socket.close(); return "關閉提示"; }; } } function open() { alert("socket連接成功"); } function error() { alert("連接錯誤"); } function closed() { alert("socket關閉"); } async function getMessage(msg) { if (typeof JSON.parse(msg.data) === "number") { console.log(JSON.parse(msg.data)); count.value = msg.data; } else { const obj = JSON.parse(msg.data); chatArr.push(obj); } await nextTick(); chatBox.value.scrollTop = chatBox.value.scrollHeight; } function randomRgb() { let R = Math.floor(Math.random() * 130 + 110); let G = Math.floor(Math.random() * 130 + 110); let B = Math.floor(Math.random() * 130 + 110); return "rgb(" + R + "," + G + "," + B + ")"; } function send() { if (textValue.value.trim().length > 0) { const obj = { name: name, txt: textValue.value, bg: bg, }; socket.send(JSON.stringify(obj)); textValue.value = ""; texta.value.focus(); } } function close() { alert("socket已經關閉"); } onMounted(() => { init(); }); onUnmounted(() => { socket.onclose = close; }); return { send, textValue, chatArr, name, bg, chatBox, texta, randomRgb, count, }; },};script>
          ?

          樣式文件同本地端樣式,可以查看上方的代碼。

          服務端

          這里我使用了ws模塊,并且我也搭建了https服務器,并使用了更為安全的wss協(xié)議。接下來,我們來看下是怎么操作的。

          const fs = require("fs");const httpServ = require("https");const WebSocketServer = require("ws").Server; // 引用Server類
          const cfg = { port: 3456, ssl_key: "../../https/xxx.key", // 配置https所需的文件2 ssl_cert: "../../https/xxx.crt", // 配置https所需的文件1};
          // 創(chuàng)建request請求監(jiān)聽器const processRequest = (req, res) => { res.writeHead(200); res.end("Websocket linked successfully");};
          const app = httpServ .createServer( { // 向server傳遞key和cert參數(shù) key: fs.readFileSync(cfg.ssl_key), cert: fs.readFileSync(cfg.ssl_cert), }, processRequest ) .listen(cfg.port);
          // 實例化WebSocket服務器const wss = new WebSocketServer({ server: app,});// 群發(fā)wss.broadcast = function broadcast(data) { wss.clients.forEach(function each(client) { client.send(data); });};// 如果有WebSocket請求接入,wss對象可以響應connection事件來處理wss.on("connection", (wsConnect) => { console.log("Server monitoring"); wss.broadcast(wss._server._connections); wsConnect.on("message", (message) => { wss.broadcast(message); }); wsConnect.on("close", function close() { console.log("disconnected"); wss.broadcast(wss._server._connections); });});

          我們在云服務上啟動命令。

          啟動成功!


          這里還沒有結束,因為你使用的是ip地址端口,必須轉發(fā)到域名上。所以我使用的nginx進行轉發(fā),配置如下參數(shù)。

              location /wsline/ {         proxy_pass https://xxx:3456/;         proxy_http_version 1.1;         proxy_set_header Upgrade $http_upgrade;         proxy_set_header Connection "Upgrade";         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;         proxy_set_header Host $http_host;         proxy_set_header X-Real-IP $remote_addr;         proxy_set_header X-Forwarded-Proto https;         proxy_redirect off;    }

          那么,重啟云端服務器,看下效果。

          項目一覽


          那么,到這里一款云端聊天室就這么做成了,可以實時顯示在線人數(shù),這樣你就可以知道有多少人在這里跟你聊天。

          結語

          謝謝閱讀,希望我沒有浪費你的時間??赐晡恼铝耍敲蹿s快行動起來吧,開發(fā)一款屬于自己的聊天室。

          有朋自遠方來,不亦樂乎。


          • 歡迎關注我的公眾號前端歷劫之路

          • 回復關鍵詞電子書,即可獲取12本前端熱門電子書。

          • 回復關鍵詞紅寶書第4版,即可獲取最新《JavaScript高級程序設計》(第四版)電子書。

          • 我創(chuàng)建了一個技術交流、文章分享群,群里有很多大廠的前端大佬,關注公眾號后,點擊下方菜單了解更多即可加我微信,期待你的加入。

          • 作者:Vam的金豆之路

          • 微信公眾號:前端歷劫之路

          瀏覽 40
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  北条麻妃高清无码视频 | xxxxx在线 | 人怕香蕉网 | 97久久人国产精品婷婷 | 婷婷五月六月 |