Практический чат WebSocket: от разработки к развертыванию

Node.js Docker WebSocket

В этой статье объясняются только некоторые ключевые моменты, пожалуйста, изучите исходный код Fork для получения подробной информации.

Если на демо-странице никого нет, можно создать несколько страниц и скопировать адрес для входа.Каждая страница является самостоятельным посетителем, совместимым с ПК и мобильным доступом.

Демо

Исходный код на гитхабе

Оригинальный пост в блоге

Интерфейсная реализация

Стек передовых технологий

  • Parcel: инструмент сборки, пакетный инструмент сборки с нулевой конфигурацией
  • socket.io: кроссплатформенная коммуникационная библиотека WebSocket с согласованными интерфейсными и внутренними API, которые могут запускать пользовательские события и реагировать на них.

внешний интерфейс

Про компоновку интерфейса и говорить нечего, просто пишите по собственным предпочтениям. Вы также можете напрямую обратиться к DEMO и исходному коду этого проекта.

Введение в socket.io

socket.ioСуществует согласованный API для внешнего и внутреннего интерфейса, поэтому разница между использованием внешнего интерфейса и стороны Node невелика.

socket.ioОсновной API предназначен для запуска пользовательских событий и реагирования на них. Вы можете запускать любые пользовательские имена событий, кроме подключения, сообщения и отключения, которые нельзя использовать. Некоторые из пользовательских событий, используемых в этом проекте, перечислены ниже.

официальный сайт socket.io

# 客户端触发自定义事件:test
socket.emit('test', data)

# 服务端响应事件
socket.on('test', data => {})

Триггерные события внешнего интерфейса

  • Войти в чат
  • отправлять сообщения

Внешний интерфейс реагирует на события

  • Статус входа
  • системное уведомление
  • отправка сообщения

Анализ исходного кода

подключиться к серверу

import io from './assets/js/socket.io'
let socket = io('ws://47.91.235.153:3000')
// 连接服务器
socket.on('connect', function () {
  console.log('成功连接服务器')
})

Войти в чат

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

# 发送登录事件
function userLogin () {
  let loginName = document.getElementById('js-loginName').value
  if (loginName === '') {
    alert('你必须输入用户名')
  } else {
    // 发送登录事件
    socket.emit('login', {
      name: loginName
    })
  }
}
oLoginBtn.addEventListener('click', userLogin)

# 响应登录状态
socket.on('login', function (data) {
  if (data.status === 'ok') {
    loginStatus = true
    oLogin.style.visibility = 'hidden'
  } else {
    alert(data.text)
  }
})

системное уведомление

Системные уведомления могут отправляться только сервером, в основном возвращая уведомления о входе и выходе пользователей из комнаты и возвращении текущему онлайн-пользователю.

socket.on('sys', function (data) {
  // 在线人数
  oCount.innerHTML = data.count
  // 加入消息列表
  oMessageBox.innerHTML += `<li class="sys">
    <div class="name">系统通知</div>
    <div class="message">${data.text}</div>
  </li>`
	// 遍历显示在线用户
  let sUser = ''
  data.users.forEach(el => {
    sUser += `<li>${el}</li>`
  });
  oUserBox.innerHTML = sUser
})

отправка сообщения

function sendMessage () {
  // 获取输入框
  let oText = document.getElementById('js-text')
  // 当前输入的内容
  let sText = oText.value
  // 为空不提交
  if (sText === '') {
    return false
  }
  // 触发消息发送事件
  socket.emit('message', {
    name: nickName,
    text: sText
  })
  // 消息列表追加本人发送的消息
  oMessageBox.innerHTML += `<li class="my">
    <div class="name">${nickName}</div>
    <div class="message">${sText}</div>
  </li>`
  // 重置内容为空
  oText.value = ''
  // 消息列表滚动到最底部
  oMessageBox.scrollTop = oMessageBox.scrollHeight
}
oEnter.addEventListener('click', sendMessage)

Получать сообщения группового чата

Принимайте широковещательные сообщения, отправленные на фон, исключая сообщения, отправленные мной.

socket.on('message', function (data) {
  // 消息列表追加消息
  oMessageBox.innerHTML += `<li>
    <div class="name">${data.name}</div>
    <div class="message">${data.text}</div>
  </li>`
  // 消息列表滚动到底部
  oMessageBox.scrollTop = oMessageBox.scrollHeight
})

Разрабатывайте и упаковывайте с помощью Parcel

Parcelочень прост в использовании и не требует настройки для запуска и упаковки приложения

Посылка китайский официальный сайт

# 安装
npm install -g parcel-bundler

# 开发:http://localhost:1234/ 访问
parcel index.html

# 编译
parcel build index.html

Реализация узлового сервера

Стек узловых технологий

  • http
  • socket.io

Сторона узла socket.io

Имеет тот же API, что и внешний интерфейс, здесь не так много объяснений.

io.on('connection', function (socket) {
	// 响应当前连接用户的事件
	socket.on('test', data => {})
	// 给当前连接的用户发送事件
	socket.emit('test', data)
	// 广播给所有人
	io.emit('test', data)
	// 广播给除当前用户外所有人
	socket.broadcast.emit('test', data)
})

Анализ исходного кода

Включить веб-сокет

var app = require('http').createServer()
var io = require('socket.io')(app)
// WebSocket 连接
io.on('connection', function (socket) {
	// 所有的事件触发响应都写在这里
})
// 启用3000端口
app.listen(3000, function () {
  console.log('WebSocket 启用端口 on *: 3000')
})

Вход пользователя в чат

Потому что это всего лишь небольшой тренировочный проект, а реального пользовательского центра нет. только один массивusersдля хранения текущего онлайн-пользователя.

socket.on('login', function (data) {
	// 检查 users 中是否有重名用户
	if (users.indexOf(data.name) >= 0) {
		console.log(data.name + ' 已有重名用户,请重新输入昵称。')
		// 发送登录失败事件
		socket.emit('login', {
	   		status: 'err',
	    	text: '已有重名用户,请重新输入昵称。'
		})
	} else {
		// 添加一个用户
		users.push(data.name)
		// 设置当前用户的 nickName
		socket.nickName = data.name
		console.log(data.name + ' 进入了房间')
		console.log('当前用户', users)
		// 发送进入房间的系统通知
		io.emit('sys', {
			text: socket.nickName + ' 进入了房间',
			count: users.length,
			users: users
		})
		// 发送登录成功的通知
		socket.emit('login', {
			status: 'ok'
		})
	}
})

сообщение

После получения информации, отправленной пользователем, транслировать ее всем, кроме пользователя-отправителя.

socket.on('message', function (data) {
	socket.broadcast.emit('message', data)
})

Пользователь отключен

socket.on('disconnect', function () {
	let index = users.indexOf(socket.nickName)
	if (index >= 0) users.splice(index, 1)
	// 用户离开房间发送系统通知
	io.emit('sys', {
		text: socket.nickName + ' 离开了房间',
		count: users.length,
		users, users
	})
	console.log(socket.nickName + '离开了房间')
	console.log('当前用户', users)
})

Развертывание запущено

Несмотря на то, что проект небольшой, передняя и задняя части разделены, поэтому в качестве упражнения код проекта развертывается на разных серверах отдельно.

Развертывание сервера NodeJS

Внутренний код развертывается на гонконгском сервере Alibaba Cloud, а системаCentOS 7. использоватьDockerКонтейнер работает среда узла.

докер китайское руководство

Безопасность сервера Alibaba Cloud очень высока, но и здесь есть много ям.Обратите внимание на следующие моменты.

  • Сервер по умолчанию закрывает все внешние порты, и вам нужно добавить соответствующую групповую политику безопасности в консоли.
  • Частые локальные операции ssh и FTP могут попасть в черный список, вам необходимо установить локальный IP в белый список в облачном щите DDoS Anti-DDoS Pro IP
  • Брандмауэр CentOS 7 по умолчанию изменен с iptables на FirewallD

1. Установите его на серверftpа такжеDocker, в гугле много способов установки 2. ИспользуйтеDockerУстановитьNodeзеркало

$ Docker pull node

3. Запустите контейнер и смонтируйте локальный каталог (все данные в контейнере кэшируются, поэтому некоторые файлы, которые необходимо часто изменять и модифицировать, напрямую монтируются в локальный каталог)

$ docker run -it -p 3000:3000 \
$ --mount type=bind,source=/home/www/chat,target=/home/www/chat \
$ node:latest \
$ /bin/bash

4. Загрузите код и введите контейнер для запуска

Загрузите файл кода на сервер/home/www/chat中, а затем в контейнер.

  • index.js
  • package.json
$ npm install
$ node index.js

Если вам нужно повторно войти в контейнер из контейнера

# 容器ID可以用 docker ps -a 查看
$ docker exec -it f9dd88d7f
# 进入容器中的项目目录
$ cd /home/www/chat
# 安装依赖
$ npm install
# 运行项目
$ node index.js

Развертывание веб-интерфейса

Развертывание внешнего кода намного проще, чем внутреннего, потому что внешний код содержит только некоторые статические файлы, такие как HTML, CSS и JS, и вы можете просто найти статический сервер и поместить его туда.

Для таких проектов настоятельно рекомендуется использовать платформу Alibaba Cloud.对象存储OSS, сверхдешевая базовая версия стоит всего 9 юаней в год.

  1. Создайте новый блок хранения Bucket
  2. Связывание доменных имен в управлении доменными именами
  3. Настроить статический хостинг веб-сайтов в базовых настройках
  4. Упакуйте и загрузите скомпилированные интерфейсные файлы

Интерфейсный код упаковки

$ parcel build index.html

После успешной упаковки/distЗагрузите каталог в блок хранения OSS, только что созданный в Alibaba Cloud, измените ссылку на ресурс в каталоге index.html как корневой каталог, переместите index.html в корневой каталог, а затем получите к нему доступ через связанное доменное имя.