Node.js第十五篇:Socket.IO

第一章:认识Socket.IO

1.1-WebSocket

传统的客户端和服务器通信协议是HTTP:客户端发起请求,服务端进行响应,服务端从不主动勾搭客户端

这种模式有个明显软肋,就是同步状态。而实际应用中有大量需要客户端和服务器实时同步状态的场景,比如聊天室、股票行情、在线共享文档等都需要客户端实时拿到服务器的最新状态。

针对这种实时同步的需求,一种简单的方式是轮询,比如每隔5s发一次http请求去拿服务器最新的状态数据。但这种方式会存在数据延迟,浪费带宽等副作用。

更完美的方式是使用WebSocket,浏览器原生支持,W3C标准协议,客户端和服务器建立持久性连接可以互发消息。

1.2-Socket.IO是什么

socket.io 是一个类库,内部封装了WebSocket,可以在浏览器与服务器之间建立实时通信。

如果某些旧版本的浏览器不支持WebSocket,socket.io会使用轮询代替。另外它还具有可发送二进制消息、多路复用、创建房间等特性,因此相比直接使用原生WebSocket,socket.io是更好的选择。

开发一个实时应用主要分两部分:服务端和客户端,socket.io分别提供了相应的模块供我们方便地调用。

1.3-什么是Socket

套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口的组合。

作用:完成两个应用程序之间的数据传输

1.4-常用的API

官方文档:https://socket.io/docs/#

服务端

io对象

  • on方法用来监听事件
    • io.on('connection',socket=>{ })
      • connection对象监听客户端连接
      • socket表示当前进入的客户端socket对象
  • emit('事件名称',数据) 服务端向所有客户端发送消息
    • 事件名称,客户端或服务端约定好向对方发送消息对方要触发的事件
    • 数据,传递的信息
  • io.to(roomid).emit('事件名称', 数据) 向指定的属于同一组的客户端发送消息
    • roomid 分组名称表示

socket对象

  • on方法用来监听事件
    • socket.on('disconnect',()=>{})
      • 若有一个客户端端口连接,则会触发该事件
    • socket.on('自定义其他事件名称',(data)=>{})
      • 自定其他事件名,客户端或服务端约定好对方发送消息时,用哪个事件触发
      • data,接收对方发送过来的数据
  • id属性,socket对象的标识
  • emit('事件名称',数据) 向对应的客户端发送消息
    • 事件名称,客户端或服务端约定好向对方发送消息对方要触发的事件
    • 数据,传递的信息
  • join(roomid) 加入某个分组
    • roomid,字符串,可以自定义
  • leave(roomid) 从某个分组中脱离
  • socket.broadcast.to(roomid).emit('事件名称', 数据) 向所有客户端(同属一组,除了自己)发送数据

客户端

io对象

  • io(url) 连接指定的服务端,并返回一个sokcet对象
    • url,服务端连接地址,如:http://localhost

socket对象

  • socket.on('connect', function () {}) 客户端和服务端建立连接成功后要触发的事件
  • socket.on('disconnect', function () {}) 客户端和服务端断开连接要触发的事件(比如服务器崩溃)
  • socket.on('自定义其他事件名称',(data)=>{})
    • 自定其他事件名,客户端或服务端约定好对方发送消息时,用哪个事件触发
    • data,接收对方发送过来的数据
  • socket.emit('事件名称',数据) 客户端向服务端发送消息

第二章:Socket.IO快速入门

在Node.js中使用Socket.IO

2.1-需求

  • 服务端和客户端建立连接
  • 客户端和服务端建立连接,并向服务端发送一条消息
  • 服务端向所有客户端,发送一条消息
  • 有一个客户端离线,服务端可以接受到通知

2.2-服务端程序

导入第三方模块npm install socket.io

const http = require("http");
const url = require("url");
const path = require("path");
const fs = require("fs");
const mime = require("mime");
// 创建服务对象
const app = http.createServer();
// 监听请求,处理静态资源
app.on("request", (req, res) => { 
  // 获取请求的路径
  let { pathname } = url.parse(req.url, true);
  // 拼接服务器上文件的物理路径
  let realPath = path.join(__dirname, "public", pathname);
  // 获取请求的资源类型
  let type = mime.getType(realPath);
  // 读取服务器本地文件
  fs.readFile(realPath, (err, data) => { 
    if (err) {
      res.writeHead(404,{"Content-type":type+";charset=utf-8"});
      res.end("访问资源不存在");
      return;
    }
    res.writeHead(200);
    res.end(data);
  });
  

});
// 【重点!!-导入socket.io并和http服务对象关联】
const io = require('socket.io')(app)
// 【重点!!-检测客户端连接进入】
io.on('connection', socket => {
  console.log('一个客户端进入')
  // 注册to-server事件,接收客户端向服务端发送的数据
  socket.on('to-server', (data) => {
    console.log('客户端说: ' + data)
    // 向对应的客户端发送数据
    socket.emit('to-client', '我是服务端数据')
    // 向所有在线的客户端发送数据
    // io.emit('to-client','我是服务端数据')
  })
  // 检测一个客户端断开连接
  socket.on('disconnect', () => {
    console.log('一个客户端离开')
  })
})
// 开启端口4000
app.listen(4000);

服务端启动成功后,客户端可以通过http://127.0.0.1:4000/socket.io/socket.io.js在客户端操作socket

2.3-客户端程序

index.html

  <input type="text" id="message"><button id="btn">发送</button>
  <!-- 导入客户端socket.io.js -->
  <script src="http://127.0.0.1:4000/socket.io/socket.io.js"></script>
  <script>
    // 创建socket对象,设置要连接的服务器url
    var socket = io('http://127.0.0.1:4000');
    // 注册connect事件,监听和服务是否建立了连接
    socket.on('connect', function () {
      console.log('客户端和服务建立了连接')
    })
    // 注册disconnect事件,监听和服务是否断开连接
    socket.on('disconnect', function () {
      console.log('客户端和服务端断开连接了');
    })
    // 注册to-client事件,监听服务端向客户端传送的数据
    socket.on('to-client', function (data) { 
      console.log('服务端说:' + data); 
    })
    // 点击按钮向服务端发送数据
    btn.onclick = function() {
      var val = message.value
      socket.emit('to-server',val)
    }
  </script>

2.4-测试

  1. 打开多个客户端:http://127.0.0.1:4000/index.html
  2. 在控制台查看服务端发送的消息
  3. 在服务端控制台中查看客户端发送的消息

第三章:Express中使用Socket.IO

3.1-基本使用

服务端程序

const express = require('express')
const path = require('path')
const app = express()
const statiPath = path.join(__dirname, './public')
app.use(express.static(statiPath))

// 【重点-通过http模块Server方法获取关联Express的服务对象】
const server = require('http').Server(app);
// 【重点-导入socket.io模块获取io对象并关联http服务对象】
const io = require('socket.io')(server);

// 【重点-检测客户端和服务端是否连接成功】
io.on('connection', socket => {
  console.log('有一个客户端连接成功')
  // 监听当前客户端向服务端发送的数据
  socket.on('message', data => {
    console.log('客户端说:' + data)
    // 服务端向所有客户端发送消息
    io.emit('message',data)
  })
})

// 【重点-使用server监听80端口】
server.listen(80)

客户端程序

index.html

  <input type="text" id="msg"><button id="btn">发送</button>
  <script src="http://localhost/socket.io/socket.io.js"></script>
  <script>
    // 客户端创建socket对象,并配置连接服务器url
    var socket = io('http://localhost')
    // 监听服务端发送过来的数据
    socket.on('message',function(data){
      console.log(data)
    })
    // 点击按钮向服务端发送数据
    btn.onclick = function(){
      socket.emit('message',msg.value)
    }
  </script>

3.2-发送文字和图片

服务端程序

const express = require('express')
const path = require('path')
const app = express()
const statiPath = path.join(__dirname, './public')
app.use(express.static(statiPath))

// 【重点-通过http模块Server方法获取关联Express的服务对象】
const server = require('http').Server(app);
// 【重点-导入socket.io模块获取io对象并关联http服务对象】
const io = require('socket.io')(server);

// 【重点-检测客户端和服务端是否连接成功】
io.on('connection', socket => {
  console.log('有一个客户端连接成功')
  // 监听当前客户端向服务端发送的数据-文本消息
  socket.on('message', data => {
    console.log('客户端说:' + data)
    // 服务端向所有客户端发送消息
    io.emit('message',data)
  })
  // 监听当前客户端向服务端发送的数据-文件消息
  socket.on('image', data => {
    // 向其他客户端发送文件
    io.emit('image', data);
  })
})

// 【重点-使用server监听80端口】
server.listen(80)

客户端程序

  <div class="talk">
    <!-- 聊天记录 -->
    <div class="box">
      <ul id="ul"></ul>
    </div>
    <!-- 发送文本消息 -->
    <p>
      <textarea id="msg" placeholder="请输入内容"></textarea>
      <!-- 按钮 -->
      <button id="btn">发送</button>
      <button id="btn2">图片</button>
      <input type="file" style="display:none" id="fileDom">
    </p>

  </div>

  <script src="http://localhost/socket.io/socket.io.js"></script>
  <script>
    // 客户端创建socket对象,并配置连接服务器url
    var socket = io('http://localhost')
    // 监听服务端发送过来的数据-文本
    socket.on('message', function (data) {
      var li = document.createElement('li');
      li.innerText = data;
      ul.appendChild(li)
    })
    // 监听服务端发送过来的数据-文本
    socket.on('image', function (data) {
      var li = document.createElement('li');
      li.innerHTML = '<img src="'+data+'">';
      ul.appendChild(li)
    })
    // 点击按钮向服务端发送数据-文字
    btn.onclick = function () {
      socket.emit('message', msg.value)
    }
    // 点击按钮向服务端发送数据-图片
    btn2.onclick = function () {
     fileDom.click()
    }
    // 上传事件触发
    fileDom.onchange = function() {
     // 获取读取的文件
     var file = fileDom.files[0];
     // 创建fileReader对象
     var reader = new FileReader();
     // 读取文件内容
     reader.readAsDataURL(file)
     // 读取完毕后,发送到服务端
     reader.onload = function(ev){
       // console.log(reader.result)
       // 发送文件数据
       socket.emit('image',reader.result)
     }
    }
  </script>

第四章:Koa中使用Socket.IO

导入第三方模块 npm install koa-socket-2

参考文档:https://www.npmjs.com/package/koa-socket-2

服务端程序

const koa = require('koa')
const router = require('koa-router')()
const path = require('path')
const static = require('koa-static')
// 【导入koa-socket-2模块】
const IO = require('koa-socket-2');

// 创建koa服务对象
const app = new koa()
// 【创建IO对象】
const io = new IO();
// 【和koa服务对象关联】
io.attach(app)

// 定义roomid,实现分组
let roomid = 'group';

// 【监听客户端连接服务】
app._io.on('connection', socket => {
  console.log('有新的客户端进入')
  // 新的客户端socket加入组中
  socket.join(roomid);
  // 【监听客户端发送的数据】
  socket.on('message', data => {
    console.log('客户端发送的数据是:' + data)
  })
  // 服务端向当前客户端发送数据
  socket.emit('message', '对您广播:您好,同志')
  // 服务端向所有客户端发送数据
  app._io.emit('message', '全员广播:同志们,咱们大家好')
  // 服务端向所有客户端(同属一组,除了自己)发送数据
  socket.broadcast.to('group').emit('message', '小组中的朋友们,大家好')
  // 服务端向所有客户端(同属一组,包括了自己)发送数据
  app._io.to('group').emit('message', '小组中的朋友们,大家好2')
  // 检测一个客户端离线
  socket.on('disconnect', () => {
    // socket.id 获取socket的唯一标识
    console.log('id为' + socket.id + '客户端离线了');
  })
});

// 配置路由和静态资源
app.use(router.routes())
app.use(router.allowedMethods()); 
app.use(static(path.join(__dirname,'./public')))

app.listen(80)

客户端程序

  <input type="text" id="text">
  <button id="btn">向服务端发送数据</button>
  <script src="http://localhost/socket.io/socket.io.js"></script>
  <script>
    // 连接服务端,并创建客户端socket对象
    var socket = io('http://localhost');

    // 点击按钮向服务端发送数据
    btn.onclick = function() {
      var val = text.value;
      socket.emit('message',val);
    }

    // 监听服务端发送的数据
    socket.on('message',function(data){
      console.log('服务端说:' + data);
    })
  </script>

第五章:案例-Open聊天室

5.1-需求

登录界面

  1. 用户进入登录页面
  2. 用户输入昵称,点击进入聊天室
  3. 后端检测,该用户是否存在(是否已经存在有该昵称的socket)
  4. 存在,则提示用户更换用户名
  5. 不存在,则允许用户进入聊天室,并在后端系统中保存该用户的socket

聊天界面

  1. 功能1:展示出所有在线用户
  2. 功能2:群聊
  3. 功能3:私聊
  4. 功能4:上线消息提醒
  5. 功能5:发送图片
  6. 功能6:新消息提醒

5.2-页面交互流程图

通过流程图直观了解业务

5.3-代码下载

代码没有做详细优化,后续会更新

https://gitee.com/lpl666/openliaotianshi.git

原文地址:https://www.cnblogs.com/lpl666/p/12986612.html