vue+websocket实现即时通讯

最近被q要求做一个即时通讯的页面,当时一个脑袋两个大,但是研究了两天后发现其实也不是很难,所以在这里跟大家分享一下

一、介绍

首先实现即时通讯最重要的就是websocket,websocket其实就是一种网络通信协议

一般来说我们在开发过程中,都是客户端发起一个请求给服务端,然后接收服务端的响应,在客户端没有发起请求的情况下,服务端无法给客户端发送信息。这样就注定如果服务端有连续变化的时候我们只能通过轮询(也就是定时发送询问)这种极其耗费资源的方式,所以websocket就应运而生,它的最大特点就是,服务器可以主动向客户端推送信息     
websocket的协议标识符是ws(加密是wss)

                                                   (图源自网络)

二、客户端(此处使用的vue)

先是关于websocket的关键代码,我们判断是否支持websocket,然后对其进行一个监听,这个是初始化

由于我想进行1对1 的对话所以我在最开始的时候发送了一次信息用来把user存储起来,方便到时候分发

initWebsocket() {
      let that = this;
      if (window.WebSocket) {
        var ws = new WebSocket("ws://localhost:8001");
        that.ws = ws;
//监听连接成功
        ws.onopen = function(e) {
          console.log("连接服务器成功");
          let msg = {
            type: 1,
            uid: that.uid
          };
          ws.send(JSON.stringify(msg));//向服务器发送信息,这个地方我是先把每一个访问的user都先存储起来方便实现1对1对话
        };
//监听连接失败
        ws.onclose = function(e) {
          console.log("服务器关闭");
        };
//监听报错
        ws.onerror = function() {
          console.log("连接出错");
        };
        // 接收服务器的消息
        ws.onmessage = function(e) {
          let message = JSON.parse(e.data);
          console.log(message);
          that.chatList.push(message);
        };
      }
    },

下面是完整的vue界面代码

<template>
  <div class="ContactWrap">
    <div class="Contact">
      <div class="ContactSide">
        <div class="ContactSide-tip">联系人</div>
        <div v-for="(item,index) in userList" :key="index">
          <div class="ContactItem" @click="chooseUser(item)" :class="{ChooseItem : item.uid == chooseId}">
            <img class="UserAvator" :src="item.avaUrl" alt="头像" />
            <div class="UserContent">
              <div class="UserMsg">
                <span class="UserName">{
   
   {item.username}}</span>
                <span class="MsgTime">06-22</span>
              </div>
              <div class="UserSnippet">{
   
   {item.content}}</div>
            </div>
          </div>
        </div>
      </div>
      <div class="ContactBox">
        <div class="ContactBox-header">再见</div>
        <div class="MessageBox" ref="MessageBox">
          <div
            v-for="(item,index) in chatList"
            :key="index"
            class="Message"
            :style="item.id == uid?'flex-direction:row-reverse':''"
          >
            <!-- <div class="UserHead"> -->
            <img :src="item.avaUrl" class="UserAvator" />
            <!-- </div> -->
            <div class="UserMsg" :class="item.id == uid?'RightMessage':'LeftMessage'">
              <span :style="item.id == uid?' float: right;':''">{
   
   {item.content}}</span>
            </div>
          </div>
        </div>
        <!-- 输入框 -->
        <div class="InputBox">
          <textarea v-model="msg" class="InputTextarea" rows="3" @keyup.enter="send"></textarea>
          <div class="InputBox-footer">
            <div class="FooterDesc">按Enter键发送</div>
            <button class="sendButton"  @click="send">发送</button>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
import util from "@/utils/index.js";

export default {
  name: "contact",
  components: {},
  data() {
    return {
      uid: 0,
      ws: null,
      msg: "", //聊天信息
      chatList: [
        {
          id: 14,
          content: "你说啥",
          avaUrl:
            "https://pic4.zhimg.com/v2-a12b2d609fa2d5d16c10ea069419f3c3_xs.jpg"
        },
        {
          id: 1,
          content: "我说你可真好看",
          avaUrl:
            "https://pic4.zhimg.com/v2-a12b2d609fa2d5d16c10ea069419f3c3_xs.jpg"
        },
        {
          id: 14,
          content: "谢谢夸奖,也就一般般啦",
          avaUrl:
            "https://pic4.zhimg.com/v2-a12b2d609fa2d5d16c10ea069419f3c3_xs.jpg"
        }
      ], //聊天记录
      userList: [
        {
          uid:1,
          username: "再见",
          content: "做好自己",
          time: "6-22",
          avaUrl:
            "https://pic4.zhimg.com/v2-a12b2d609fa2d5d16c10ea069419f3c3_xs.jpg"
        },
        {
             uid:2,
          username: "再见",
          content: "做好自己",
          time: "6-22",
          avaUrl:
            "https://pic4.zhimg.com/v2-a12b2d609fa2d5d16c10ea069419f3c3_xs.jpg"
        },
        {
             uid:3,
          username: "再见",
          content: "做好自己",
          time: "6-22",
          avaUrl:
            "https://pic4.zhimg.com/v2-a12b2d609fa2d5d16c10ea069419f3c3_xs.jpg"
        },
        {
             uid:4,
          username: "再见",
          content: "做好自己",
          time: "6-22",
          avaUrl:
            "https://pic4.zhimg.com/v2-a12b2d609fa2d5d16c10ea069419f3c3_xs.jpg"
        }
      ],
      chooseId:0
    };
  },
  mounted() {
    this.initWebsocket();
    this.uid = util.getUser().id;//可以自己写个假数据
  },
  methods: {
    //创建websocket链接
    initWebsocket() {
      let that = this;
      if (window.WebSocket) {
        var ws = new WebSocket("ws://localhost:8001");
        that.ws = ws;
        ws.onopen = function(e) {
          console.log("连接服务器成功");
          let msg = {
            type: 1,
            uid: that.uid
          };
          ws.send(JSON.stringify(msg));
        };
        ws.onclose = function(e) {
          console.log("服务器关闭");
        };
        ws.onerror = function() {
          console.log("连接出错");
        };
        // 接收服务器的消息
        ws.onmessage = function(e) {
          let message = JSON.parse(e.data);
          console.log(message);
          that.chatList.push(message);
        };
      }
    },
    //发送信息
    send() {
      let toId;
      if (this.uid == 14) {
        toId = 1;
      } else {
        toId = 14;
      }
      let msg = {
        id: this.uid,
        bridge: [this.uid, toId],
        content: this.msg,
        avaUrl:
          "https://pic4.zhimg.com/v2-a12b2d609fa2d5d16c10ea069419f3c3_xs.jpg"
      };
      this.ws.send(JSON.stringify(msg)); //这里是把聊天内容发给服务端
      this.msg = "";
      setTimeout(() => {
        this.scrollBottm();
      }, 200);
    },
    //滚动条滚动到底部
    scrollBottm() {
      let el = this.$refs["MessageBox"];
      el.scrollTop = el.scrollHeight;
    },
    //选择联系人
    chooseUser(user){
        this.chooseId = user.uid;
    }
  }
};
</script>
<style lang="scss" scoped>
.Contact {
  height: 614px;
  width: 1002px;
  background-color: #fff;
  border: 1px solid #ebebeb;
  box-shadow: 0 0 4px 0 rgba(26, 26, 26, 0.1);
  border-radius: 3px;
  margin: 8px auto 0;
  display: flex;
  //联系人
  .ContactSide {
    width: 286px;
    height: 100%;
    border-right: 1px solid #ebebeb;
    .ContactSide-tip {
      height: 40px;
      line-height: 40px;
      font-weight: 600;
      padding: 0 30px;
      border-bottom: 1px solid #ebebeb;
    }
  }
  .ContactItem {
    padding: 12px 20px 12px 29px;
    cursor: pointer;
    display: flex;
    border-bottom: 1px solid #f7f8fa;
    .UserContent {
      flex: 1;
      .UserMsg {
        display: flex;
        align-items: center;
        justify-content: space-between;
        .UserName {
          font-size: 15px;
          columns: #444444;
          font-weight: 600;
        }
        .MsgTime {
          font-size: 12px;
          color: #999999;
          float: right;
        }
      }
    }
  }
  .ChooseItem{
    background: #f5f4f4;
 }
  .UserAvator {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    margin-right: 10px;
  }
  .ContactBox {
    width: 710px;
    &-header {
      font-size: 15px;
      margin: 0 14px;
      height: 20px;
      padding-bottom: 9px;
      padding-top: 21px;
      border-bottom: 1px solid #ebebeb;
      font-weight: 600;
      text-align: center;
    }
    //聊天框
    .MessageBox {
      height: 362px;
      overflow: scroll;
      .Message {
        display: flex;
        margin: 20px;
        .UserMsg {
          max-width: 388px;
          border-radius: 8px;
          padding: 6px 12px;
          font-size: 14px;
          position: relative;
          margin: 0 8px;
          text-align: left;
          white-space: pre-wrap;
          word-break: break-all;
        }
        .LeftMessage {
          background-color: #f6f6f6;
          color: #444;
          &::after {
            content: "";
            position: absolute;
            width: 8px;
            height: 8px;
            left: -4px;
            top: 14px;
            background: #f6f6f6;
            -webkit-transform: rotate(45deg);
            transform: rotate(45deg);
          }
        }
        .RightMessage {
          background-color: #0084ff;
          color: #fff;
          &::after {
            content: "";
            position: absolute;
            width: 8px;
            height: 8px;
            right: -4px;
            top: 14px;
            background: #0084ff;
            -webkit-transform: rotate(45deg);
            transform: rotate(45deg);
          }
        }
      }
    }
    //输入框
    .InputBox {
      padding: 0 10px;
      border-top: 1px solid #ebebeb;
      display: flex;
      flex-direction: column;
      justify-content: space-between;
      height: 170px;
      background: #fff;
      z-index: 10;
      .InputTextarea {
        margin-top: 20px;
        width: 100%;
        border: none;
        font-size: 14px;
        flex: 1;
      }
      &-footer {
        display: flex;
        align-items: center;
        justify-content: flex-end;
        height: 50px;
        .FooterDesc {
          font-size: 14px;
          color: #bfbfbf;
          padding-right: 10px;
        }
        .sendButton {
          color: #fff;
          background-color: #0084ff;
          border-radius: 6px;
          width: 72px;
          height: 32px;
          font-size: 13px;
          line-height: 32px;
          text-align: center;
        }
      }
    }
  }
}

</style>

三、服务端(node临时起的服务)

这里需要安装一个插件nodejs-websocket,使用node在8001端口起一个服务,运行的时候直接找到当前文件夹 node 文件名就可以

var ws = require("nodejs-websocket");
let test = {};
let users = [];
console.log("开始链接");
//如果群聊可以使用广播,这里没用
function boardCast(obj) {
  //群聊
  // server.connections.forEach(function (conn) {
  //   conn.sendText(JSON.stringify(obj));
  // })
}

var server = ws.createServer(function (conn) {
  conn.on("text", function (res) {
    //处理前端发来的请求
    data = JSON.parse(res);
    console.log("来人了");
    console.log(data);
    //把所有uid对应的链接conn存到一个对象里面
    if (data.type == 1) {
      //如果是新用户就走这里添加到user
      if (users.indexOf(data.uid) == -1) {
        users.push(data.uid);
        test[`${data.uid}`] = conn;
      }
    } else {
      console.log('发送')
      //没有type就代表是发送信息
      data.bridge.forEach(item => {
        test[item].sendText(JSON.stringify(data));
    });
    }
  })

  conn.on("close", function (code, reason) {
    console.log("关闭连接")
  });
  conn.on("error", function (code, reason) {
    console.log("异常关闭")
  });
}).listen(8001)
console.log("websocket建立完毕")

可以开两个页面设置好不同的id就可以,第一次写可能有很多问题,仅用来为大家提供一个思路,如果写的有错误欢迎指教

猜你喜欢

转载自blog.csdn.net/wuguidian1114/article/details/107339141