После недели молчания я разработал чат

Vue.js

предисловие

Я не публиковал статьи на прошлой неделе, поэтому я здесь, чтобы извиниться перед всеми. Сегодня давайте разработаем чат с нуля. Хорошо, давайте начнем сейчас.

Узнать о веб-сокетах

Для разработки чатов нам нужно использовать WebSocket, сетевой протокол связи, так зачем его использовать?

Сначала процитируем абзац из статьи Жуань Ифэн:

Любой, кто новичок в WebSockets, задаст один и тот же вопрос: у нас уже есть протокол HTTP, зачем нам еще один протокол? Какую пользу это может принести?

Ответ прост, потому что протокол HTTP имеет изъян: связь может инициировать только клиент.

Например, если мы хотим узнать сегодняшнюю погоду, клиент может только отправить запрос на сервер, а сервер возвращает результат запроса. Протокол HTTP не может позволить серверу активно передавать информацию клиенту.

Характеристики этого одностороннего запроса обречены быть очень неприятными для клиента, чтобы узнать, есть ли у сервера непрерывные изменения состояния. Мы можем использовать только «опрос»: время от времени отправляется запрос, чтобы узнать, есть ли на сервере новая информация. Самый типичный сценарий — чат.

Опрос неэффективен и тратит ресурсы впустую (потому что вам нужно постоянно подключаться, иначе HTTP-соединение всегда открыто). Поэтому инженеры задумались, а есть ли способ лучше. Так был изобретен WebSocket.

Давайте позаимствуем официальное введение на веб-сайте MDN, чтобы резюмировать:

WebSockets — это передовая технология. Он может открыть интерактивный сеанс связи между браузером пользователя и сервером. Используя этот API, вы можете отправлять сообщения на сервер и получать ответы, управляемые событиями, без необходимости запрашивать ответ у сервера.

Протокол WebSocket родился в 2008 году и стал международным стандартом в 2011 году. Все браузеры уже поддерживают его.

Возможности веб-сокета

  1. Сервер может активно передавать информацию клиенту, а клиент также может активно отправлять информацию серверу.Это настоящий двусторонний равноправный диалог и относится к разновидности серверной технологии push.
  2. На основе протокола TCP реализация на стороне сервера относительно проста.
  3. Он имеет хорошую совместимость с протоколом HTTP. Порты по умолчанию также 80 и 443, а протокол HTTP используется на этапе рукопожатия, поэтому его нелегко экранировать во время рукопожатия, и он может проходить через различные прокси-серверы HTTP.
  4. Формат данных относительно легкий, производительность невелика, а связь эффективна.
  5. Текст может быть отправлен, или двоичные данные могут быть отправлены.
  6. Ограничений по одному и тому же источнику нет, и клиенты могут взаимодействовать с любым сервером.
  7. Идентификатор протоколаws(если зашифровано,wss),которыйwsвести перепискуhttp,wssвести перепискуhttps. URL-адрес сервера — это URL-адрес. которыйws://www.xx.comилиwss://www.xx.com

Websocket Client часто использует API

WebSocketОбъект предоставляет API для создания и управления соединениями WebSocket, а также для отправки и получения данных через это соединение.

использовать WebSocket()конструктор для создания WebSocket.

Атрибуты

  1. WebSocket.onopen

    Используется для указания функции обратного вызова после успешного подключения.

  2. WebSocket.onmessage

    Используется для указания функции обратного вызова при получении информации с сервера.

  3. WebSocket.onclose

    Используется для указания функции обратного вызова после закрытия соединения.

  4. WebSocket.onerror

    Используется для указания функции обратного вызова после сбоя соединения.

метод

  1. WebSocket.close()

Закройте текущую ссылку.

  1. WebSocket.send(data)

Клиент отправляет данные на сервер, ставя их в очередь для передачи.

Пример клиента

// Create WebSocket connection.
const socket = new WebSocket('ws://localhost:8080'); // 这里的地址是服务器的websocket服务地址

// Connection opened
socket.onopen = function(evt) { 
  console.log("Connection open ..."); 
  ws.send("Hello WebSockets!");
};

// Listen for messages
socket.onmessage = function(evt) {
  console.log( "Received Message: " + evt.data);
  socket.close();
};

// Connection closed
socket.onclose = function(evt) {
  console.log("Connection closed.");
};     

Веб-сокет-сервер

Здесь на сервере мы используемNode.js, Вот несколько часто используемых библиотек.

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

Для конкретного использования вы можете просмотреть подробную документацию в Интернете, и я не буду представлять их здесь по отдельности. Но в этой статье. Я буду использовать его для всехwsа такжеnodejs-websocketЭти два модуля используются для разработки проекта соответственно.

Представлены как клиент, так и сервер! Давайте действовать сейчас!

Разработайте локальный (или локальный сетевой) чат (первый тип)

мы будем основываться наVue.js@3.0Чаты развиваются, причина в том, чтобы охватить новые технологии. Как построить vue scaffolding, я не буду здесь представлять, думаю, все это сделают. Перейдем непосредственно к коду.

клиент

<template>
  <div class="home">
    <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">发送</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 name = new Date().getTime().toString();
    const bg = randomRgb();
    const chatArr = reactive([]);
    
    // WebSocket初始化
    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) {
      const obj = JSON.parse(msg.data);
      chatArr.push(obj);
      await nextTick(); // 异步更新DOM
      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
    };
  },
};
</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: модуль 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");
});
// 群发消息
function broadcast(data) {
  server.connections.forEach((conn) => {
    conn.sendText(data);
  });
}

Список проектов

Количество людей в сети равно нулю.Это не баг, потому что на локалке в то время это не делалось, а только на нем был поставлен этот раздел. Однако я поместил эту функцию на стороне облачного сервера. Что ж, давайте посмотрим.

Разработать облачный чат (второй тип)

клиент

<template>
  <div class="home">
    <div class="count">
      <p>在线人数:{{ 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">发送</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. Далее посмотрим, как это работает.

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
};

// 创建request请求监听器
const processRequest = (req, res) => {
  res.writeHead(200);
  res.end("Websocket linked successfully");
};

const app = httpServ
  .createServer(
    {
      // 向server传递key和cert参数
      key: fs.readFileSync(cfg.ssl_key),
      cert: fs.readFileSync(cfg.ssl_cert),
    },
    processRequest
  )
  .listen(cfg.port);

// 实例化WebSocket服务器
const wss = new WebSocketServer({
  server: app,
});
// 群发
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-адреса, который должен быть перенаправлен на доменное имя. Поэтому я использую nginx для переадресации и настраиваю следующие параметры.

    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;
    }

Затем перезапустите облачный сервер и посмотрите на результат.

Список проектов

Итак, вот облачный чат, который может отображать количество людей онлайн в режиме реального времени, чтобы вы могли знать, сколько людей общается с вами здесь.

Эпилог

Спасибо за прочтение, надеюсь, я не зря потратил ваше время. Прочитав статью, поспешите создать собственный чат.

Пригласите друзей издалека.

  • Добро пожаловать, чтобы обратить внимание на мой общедоступный номер前端历劫之路
  • ключевые слова ответа电子书, вы можете получить 12 популярных электронных книг.
  • ключевые слова ответа红宝书第4版Чтобы получить последний «JavaScript Advanced Program Design» (четвертый издание) электронная книга.
  • Я создал техническую группу обмена и совместную группу. Существует множество лидеров крупных производителей крупных производителей. После выполнения официального счета щелкните меню ниже, чтобы узнать больше, и добавьте меня на WeChat. Я с нетерпением жду вашего присоединения Отказ