zoukankan      html  css  js  c++  java
  • websocket入门和实现聊天室

    1.先提一个思考?

    在传统http思维当中,有(浏览器1,服务器,浏览器2三个角色),如何实现浏览器1发送消息,然后,浏览器2接收看到浏览器1发送的消息,反之一样。

    http能不能实现这种聊天的效果?

    答案当然是能的,但是比较麻烦。

    因为 http是基于 请求 -------- 响应 这种模型的

    服务器没有办法“主动”给浏览器发送消息的

    所以只能在浏览器2当中,加入ajax轮询,不断的请求服务器,以获取到最新的消息,来展示到客户端

    2.websocket介绍

    websocket是一种网络协议,允许客户端和服务端全双工的进行网络通讯,服务器可以给客户端发消息,客户端也可以给服务器发消息。

    相当于浏览器和客户端之间建立的是一种长链接,不会断开,在这个管道内能够互相的,不断的进行信息交互 (而传统http是:浏览器请求数据,服务端响应数据后,链接断开)

    3.websocket的使用

    在HTML5中,浏览器已经实现了websocket的API,直接使用即可。WebSocket-MDN

    3.1 创建websocket连接

    // 参数1: url:连接的websocket属性
    // 参数2: protocol,可选的,指定连接的协议
    // var socket = new WebSocket('ws://echo.websocket.org') =>官方提供的地址
    var Socket = new WebSocket(url, [protocol] );
    

    3.2 websocket的一些事件

    事件 事件处理程序 描述
    open Socket.onopen 连接建立触发
    message Socket.onmessage 客户端接收服务端数据时触发
    error Socket.onerror 通信发生错误时触发
    close Socket.onclose 连接关闭时触发

    3.3 websocket方法

    方法 描述
    Socket.send() 使用连接发送数据
    Socket.close() 关闭连接

    4.使用node.js开发websocket服务

    先简单了解一下,有关于nodejs-websocket有哪些api,和怎么使用

    前端页:

      <style>
          div {
             200px;
            height: 200px;
            border: 1px solid #000;
          }
        </style>
    
      <!-- 用于收集输入内容 -->
        <input type="text" placeholder="请输入需要发送的内容" />
        <!-- 用于发送websocket请求 -->
        <button>websocket测试</button>
        <!-- 用于显示websock服务器的响应 -->
        <div class="show"></div>
    
        <script>
          var input = document.querySelector('input')
          var button = document.querySelector('button')
          var div = document.querySelector('div')
          // 1. 创建websocket对象, 这个地址是官方提供的地址
          // var socket = new WebSocket('ws://echo.websocket.org')
          var socket = new WebSocket('ws://localhost:3000')
    
          // 2. 给websocket注册事件
          socket.addEventListener('open', function() {
            // 与服务端建立连接的时候触发
            div.innerText = '恭喜你,与服务端建立连接了'
          })
          // 如何给服务器发送消息
          button.addEventListener('click', function() {
            socket.send(input.value)
            input.value = ''
          })
    
          // 如果接收服务器的数据
          socket.addEventListener('message', function(e) {
            console.log('接收到服务器的数据了', e)
            // 将接收到服务器的数据展示出来
            div.innerText = e.data
          })
    
          socket.addEventListener('close', () => {
            div.innerHTML = '与服务器断开连接'
          })
        </script>
    

    服务端(app.js): 使用前需下载nodejs-websocket包

    //1.导入nodejs-websocket包
    const ws = require('nodejs-websocket')
    const PORT = 3000
    
    //2.创建一个服务
    
    //2.1 如何处理用户的请求 
    //每次只要有用户连接,函数就会被执行,会给当前连接的用户创建 一个connect对象
    var server = ws.createServer(connect =>{
        console.log('有用户连接上来了')
        //每当接收到用户传递过来的数据,这个text事件就会被触发
        connect.on('text',data=>{
            console.log('接收到了用户的数据:'+data)
            //返回客户发送的消息
            connect.send('用户发送的消息是:'+ data)
        })
    
    
        //只要websocket连接断开了,close事件就会触发
        connect.on('close',()=>{
            console.log('连接断开了')
        })
    
        //在处理用户断开连接后,除了处理colse事件,还必需处理error事件,不然会报错
        connect.on('error',()=>{
            console.log('用户连接异常')
        })
    })
    server.listen(PORT,()=>{
        console.log('服务启动成功,监听了端口:'+PORT)
    })
    

    node app.js 即可启动服务

    5.websocket开发简易聊天室

    前端页:

       <!-- 用于收集输入内容 -->
        <input type="text" placeholder="请输入需要发送的内容" />
        <!-- 用于发送websocket请求 -->
        <button>websocket测试</button>
        <!-- 用于显示websock服务器的响应 -->
        <div class="show"></div>
    
        <script>
          var input = document.querySelector('input')
          var button = document.querySelector('button')
          var div = document.querySelector('div')
          // 1. 创建websocket对象, 这个地址是官方提供的地址
          // var socket = new WebSocket('ws://echo.websocket.org')
          var socket = new WebSocket('ws://localhost:3000')
    
          // 2. 给websocket注册事件
          socket.addEventListener('open', function() {
            // 与服务端建立连接的时候触发
            div.innerText = '恭喜你,与服务端建立连接了'
          })
          // 如何给服务器发送消息
          button.addEventListener('click', function() {
            socket.send(input.value)
            input.value = ''
          })
    
          // 如果接收服务器的数据
          socket.addEventListener('message', function(e) {
            //将服务端返回的json字符串,转成对象
            var data = JSON.parse(e.data)
            //创建一个div(此时只是创建,并没有插入到页面当中)
            var dv = document.createElement('div')
            //在床的div中插入数据
            dv.innerHTML = data.msg + '----' + data.date
            //不同的消息类型改变消息的样式
            if (data.type === 0) {
              dv.style.color = 'green'
            }
            if (data.type === 1) {
              dv.style.color = 'red'
            }
            if (data.type === 2) {
              dv.style.color = 'gray'
            }
            //最后将带有数据的盒子插入到页面当中
            div.appendChild(dv)
          })
    
          socket.addEventListener('close', () => {
            div.innerHTML = '与服务器断开连接'
          })
        </script>
    

    服务端(app.js):

    const ws = require('nodejs-websocket')
    
    //端口
    const PORT = 3000
    //返回客户端是哪种信息的标记
    const TYPE_MSG = 0  
    const TYPE_ENTER = 1
    const TYPE_LEAVE = 2
    
    //用户的数量,用户进入+1,用户离开-1
    let userCount = 0
    const server = ws.createServer(connect => {
      console.log('有新用户连接了')
      // 每次有新用户连接,需要给所有用户发送一条新增用户的消息
      userCount++
      //给每个connect对象添加一个name名称
      connect.userName = 'user' + userCount
      // 给所有的用户进行广播,客户端可根据不同的type值渲染不同样式的消息
      broadcast({
        type: TYPE_ENTER,
        msg: connect.userName + '进入了聊天室',
        date: new Date().toLocaleTimeString()
      })
    
      connect.on('text', msg => {
        // 如果接收到用户的数据, 需要发送给所有的用户
        broadcast({
          type: TYPE_MSG,
          msg: msg,
          date: new Date().toLocaleTimeString()
        })
      })
      connect.on('close', () => {
        console.log('用户断开连接')
        userCount--
        // 给所有的用户发送一条用户离开的消息
        broadcast({
          type: TYPE_LEAVE,
          msg: `${connect.userName}离开了聊天室`,
          date: new Date().toLocaleTimeString()
        })
      })
      connect.on('error', () => {
        console.log('连接失败')
      })
    })
    
    //广播消息的方法  connections:存有全部用户,相当于connect数组集合
    function broadcast(msg) {
      server.connections.forEach(conn => {
        //原生的websocekt的sendText()不允许返回对象数据,只能返回字符串
        conn.sendText(JSON.stringify(msg))
      })
    }
    
    server.listen(PORT, () => {
      console.log('服务器启动成功了', PORT)
    })
    

    6.原生websocket的缺点

    一次给所有用户广播消息的功能,都需要通过一个存有所有用户的数组属性来自己封装,支持的事件少,返回给客户端的数据也只能是字符串格式,提供的api少

    但是:这其实也不能说是websocket的缺点,因为它本身就是提供基础能力而出现的,并不是为了解决我们业务代码的便利而出现,所以,通常在websocket的特性的时候,我们常常会用框架来更简单,高效的实现我们所需要的功能 -> socket.io

    7.socket.io的基本用法

    前端页面(当前页面是创建在与app.js同级目录下):

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <meta http-equiv="X-UA-Compatible" content="ie=edge" />
        <title>Document</title>
      </head>
      <body>
        哈哈
        <script src="/socket.io/socket.io.js"></script>
        <script>
          // 连接socket服务
          // 参数:服务器地址
          var socket = io('http://localhost:3000')
    
          // 接受服务器返回的数据
          // socket.on('send', data => {
          //   console.log(data)
          // })
          socket.emit('hehe', { name: 'zs', age: 18 })
    
          socket.on('send', function(data) {
            console.log(data)
          })
        </script>
      </body>
    </html>
    
    

    app.js:

    const http  = require('http');
    const fs = require('fs');
    const app = http.createServer();
    
    app.listen(3000,()=>{
        console.log('服务器启动成功')
    });
    
    //将创建的node服务传入到socket.io方法中
    const io = require('socket.io')(app);
    
    function handler (req, res) {
      fs.readFile(__dirname + '/index.html',
      (err, data) => {
        if (err) {
          res.writeHead(500);
          return res.end('Error loading index.html');
        }
    
        res.writeHead(200);
        res.end(data);
      });
    }
    //监听用户连接的事件
    io.on('connection', (socket) => {
      console.log('有用户进入了')
      //socket.emit 方法表示给到浏览器发送数据
      //参数1:事件的名字(自定义)
      socket.emit('send', { hello: 'world' });
      socket.on('hehe', (data) => {
        console.log(data);
      });
    });
    

    总结:socket.io在前后端通用,socket表示用户连接,socket.emit 表示触发某个事件,socket.on表示监听某个事件,在使用socket.io的时候,不管是前端还是后端,都是这样接收和发送事件来进行数据的传输

    8.实现一个多功能的聊天室

    github项目代码地址:

    1.使用了socket.io express  => app.js (参照socket.io官方demo)
    2.在index.html 中引入socket.io 等包
    3.使用express处理静态资源,并且重定向访问 / 根目录时 => 静态资源文件夹(public)下的index.html
    
    node app.js即可启动项目
    

    9.聊天室的前后端业务代码总结

    代码地址:https://github.com/Y-Yin/js-project-demo

    9.1 简单登录

    ===前端===
    1.选择头像=>
    $('#login_avatar li').on('click', function() {
      $(this)
        .addClass('now')
        .siblings()
        .removeClass('now')
    })
    //点击添加now类,让被点击的头像出现选择框
    
    
    
    2.点击登录按钮,发送头像和用户名给服务器=>
    $('#loginBtn').on('click', function(){
      // 获取用户名
      var username = $('#username').val().trim()
      if (!username) {
        alert('请输入用户名')
        return
      }
      // 获取选择的头像
      var avatar = $('#login_avatar li.now img').attr('src')
      // 需要告诉socket io服务,登录,并且传入信息
      socket.emit('login', {
        username: username,
        avatar: avatar
      })
    })
    
    ===后端===
    对用户重复登陆的处理  =>
        // 记录所有已经登录过的用户
    	const users = []
    	//寻找users数组中有没有重复用户名
        let user = users.find(item => item.username === data.username)
        if (user) {
          // 表示用户存在, 登录失败. 服务器需要给当前用户响应,告诉登录失败
          socket.emit('loginError', { msg: '登录失败' })
          // console.log('登录失败')
        } else {
          // 表示用户不存在, 登录成功
          users.push(data)
          // 告诉用户,登录成功
          socket.emit('loginSuccess', data)
          // console.log('登录成功')
    

    9.2显示个人用户信息

    ===前端===
    //监听登录成功后,服务器有把个人信息返回了
    socket.on('loginSuccess', data => {
      // 需要显示聊天窗口
      // 隐藏登录窗口
      $('.login_box').fadeOut()
      $('.container').fadeIn()
      // 设置个人信息
      console.log(data)
      $('.avatar_url').attr('src', data.avatar)
      $('.user-list .username').text(data.username)
    
      username = data.username
      avatar = data.avatar
    })
    

    9.3显示加入群聊的消息

    ===后端===
    //使用socket.io的内置api给每个用户发送消息,把头像和名称发送给每一个人
    io.emit('addUser', data)
    //data传给前端的不仅有消息数据,还有消息类型type:0,1,2... 前端根据数据的类型,渲染出不同的消息样式
    //=> 比如先通过消息类型创建好元素,并且加入样式,再将样式插入到页面当中
    

    9.4用户列表和聊天人数

    //后端判断当登录成功之后,继续emit一个事件,将所有用户数据都扔给前端
    

    9.5离开聊天室

    //使用内置,用户离开触发的事件api
    
    //=>
    1.把当前y用户的信息从users中删除调用
    2.告诉所有人,有人离开了聊天室
    3.告诉所有人,userlist发生更新了
    
    //=>
    当登录成功的时候,就将当前用户的信息存储起来
    socket.username = data.username
    socket.avatar = data.avatar
    当用户离开的时候,判断离开的用户是全局用户中的哪一个
    let idx = users.findIndex(item => item.username === socket.username)
    根据下标删除这个用户
    users.splice(idx,1)
    告诉所有人,有人离开的聊天室
    

    9.6消息总是在最底部开始显示

    //使用到了一个方法 :element.scrollIntoView() -> 原生dom方法,不是jq方法
    
    //找到盒子当中的最后一个dom对象,跳转到那个高度
    //children(':last')找最后一个子元素
    $('.box-bd').children(':last').get(0).scrollIntoView(false)
    
    

    9.7发送图片

    前端:
    <a> <label> <input ...></label></a>标签包住一个隐藏的input type="file" 标签,这样,我们在点击a标签的时候也就是点击了被隐藏的input选择文件标签
    
    $('input_file').on('change',function(){
        //拿到上传的文件
        var file = this.files[0]
        //需要把文件发送到服务器,借助于h5新增的fileReader
        var fr = new FileReader()
        //读取这个文件
        fr.readAsDataURL(file)
        //读取成功
        fr.onload = function(){
            //fr.result为图片读取成功后的结果
            console.log(fr.result)
            socket.emit('send',{
                username:name,
                avater:avater,
                img:fr.result
            })
        }
    })
    
    后端
    接收到用户上传的图片数据后,直接广播给所有用户,通过事件将数据发出
    
    前端:
    所有用户接收到图片消息,将图片数据append进聊天盒子
    $('...').append('  <img src="${data.img}"> ')
    

    bug :发送图片消息,聊天框总是不在底部开始显示消息

    原因:因为图片还没加载完成,就调用了scrollIntoView方法

    解决:监听最后一张图片的加载,在调用scrollIntoView

    9.8 jquery-emoji 表情包的使用

    具体查看 jquery-emoji 插件文档
    
  • 相关阅读:
    web 框架本质 及python三大框架对比
    jquery 进阶 bootstrap
    BOM DOM jQuery
    JS基础
    Linux(8)- nginx+uWSGI+virtualenv+supervisor 发布web服务器
    Linux(7)- Nginx.conf主配置文件、Nginx虚拟主机/访问日志/限制访问IP/错误页面优化、Nginx反向代理、Nginx负载均衡
    Linux(6)- redis发布订阅/持久化/主从复制/redis-sentinel/redis-cluster、nginx入门
    Linux(5)- MariaDB、mysql主从复制、初识redis
    Linux(4)- centos7安装python3、Linux下安装、配置virtualenv、确保开发环境的一致性、虚拟环境之virtualenvwrapper、vim
    Linux(3)- 用户管理、文件与目录权限、常用命令、Linux软件包管理
  • 原文地址:https://www.cnblogs.com/JCDXH/p/13034401.html
Copyright © 2011-2022 走看看