精华内容
下载资源
问答
  • 2020-12-24 15:10:00

    springboot+websocket 实现IM聊天功能,支持一对一、一对多聊天

    gitbub地址: https://github.com/jason1210/chat

    若有什么疑问,欢迎留言探讨!

    更多相关内容
  • 前言:上篇我们讲了如何使用vue+websocket加nodejs搭建一个聊天室, 下面我们在该聊天室基础上增加了一对一单聊功能。支持一对一单聊,一对多群聊。 ...
    前言
    上篇我们讲了如何使用vue + websocket + nodejs搭建一个聊天室,
    下面我们在该聊天室基础上增加了一对一单聊功能。支持一对一单聊,一对多群聊。
    

    先看效果:

    在这里插入图片描述
    在这里插入图片描述
    大概思路:
    主要通过参数brige区分是群聊还是单聊:
    brige为空是群聊;
    brige包含了当前登录人的uid还有聊天对应人的uid,是单聊。

    核心代码:

    // 单聊
    if (obj.brige && obj.brige.length) {
            obj.brige.forEach(item => {
                conns[item].sendText(JSON.stringify(obj))
            })
            return;
        }
    // 群聊(目前是默认写死的一个群)
    server.connections.forEach(function (conn) { // 注意:这里是server,不是ws
            conn.sendText(JSON.stringify(obj)) // 注意:这里得转成字符串发送过去,不然会报错。
        })   
    

    流程图如下:

    在这里插入图片描述
    上代码:

    服务端:

    const ws = require('nodejs-websocket')
    const moment = require('moment')
    let users = []
    let conns = {}
    
    function broadcast(obj) {
        // 单聊
        if (obj.brige && obj.brige.length) {
            obj.brige.forEach(item => {
                conns[item].sendText(JSON.stringify(obj))
            })
            return;
        }
        server.connections.forEach(function (conn) {
            conn.sendText(JSON.stringify(obj))
        })
    }
    const server = ws.createServer(function (conn) {
        conn.on('text', function (data) {
            const obj = JSON.parse(data)
            switch (obj.type) {
                case 1:
                    {
                        // 将所有uid对应的连接都保存到一个对象里
                        conns[obj.uid] = conn;
                        
                        // 不存在uid对应的用户(不是本人),才会添加,避免重复
                        const isSelf = users.some(m => m.uid === obj.uid)
                        console.log(isSelf, data.uid, users, '所有用户')
                        if (!isSelf) {
                            users.push({
                                nickname: obj.nickname,
                                uid: obj.uid
                            })
                        }
                        broadcast({
                            type: 1,
                            nickname: obj.nickname,
                            uid: obj.uid,
                            msg: `${obj.nickname}进入了聊天室`,
                            date: moment().format('YYYY-MM-DD HH:mm:ss'),
                            users,
                            brige: obj.brige
                        })
                    }
                    break;
                case 2:
                    // 聊天时候不需要users,type为1已经处理了
                    broadcast({
                        type: 2,
                        nickname: obj.nickname,
                        uid: obj.uid,
                        msg: obj.msg,
                        users,
                        date: moment().format('YYYY-MM-DD HH:mm:ss'),
                        brige: obj.brige
                    })
                    break;
            }
        })
    
        conn.on('close', function (e) {
            console.log(e, '服务端连接关闭')
        })
    
        conn.on('error', function (e) {
            console.log(e, '服务端异常')
        })
    
    }).listen(8888)
    console.log('服务端已开启')
    

    客户端:

    视图层:(由之前的class为right改为现在web-im。多加了左侧菜单栏,其他地方跟上篇一致)

    <div class="web-im">
          <div class="left">
            <div class="user" @click="triggerGroup()">
              群一
            </div>
            <div class="user" v-for="(itm, idex) in users" :key="idex" v-show="itm.uid !== uid" @click="triggerUser(itm)">
              <span>{{itm.nickname}}</span>
              </div>
          </div>
          <div class="right">
            <div class="body im-record" id="im-record">
              <p>{{title}}</p>
              <div class="ul">
                <!-- user为靠右展示样式,如果uid一致说明是本人 -->
                <div class="li" :class="{user: item.uid == uid}" v-for="(item, index) in currentMessage" :key="index">
                  <template v-if="item.type===1">
                    <p class="join-tips">{{item.msg}}</p>
                  </template>
                  <template v-else>
                    <p class="message-date">
                      <span class="m-nickname">{{item.nickname}}</span> {{item.date}}</p>
                    <p class="message-box">{{item.msg}}</p>
                  </template>
                </div>
              </div>
            </div>
            <div class="im-footer">
              <el-input placeholder="请输入你想说的内容..." v-model="msg" class="im-footer_inp"/>
              <el-button class="im-footer_btn" type="primary" @click="send">发送</el-button>
            </div>
          </div>
        </div>
    

    逻辑层:(之前是聊天框数组是messageList,现在改成currentMessage
    (注:…表示代码跟之前一致,这里不再多写)

      computed: {
        // 筛选当前brige一致的放到一个聊天数组里,区分单聊和群聊
        currentMessage () {
          const vm = this
          let data = this.messageList.filter(item => {
            return item.brige.sort().join('') === vm.brige.sort().join('')
          })
          return data
        }
      },
      
      ...
      
      // 发送信息给客户端
        sendMessage (type, msg) {
          const data = {
            uid: this.uid,
            type,
            nickname: this.nickname,
            msg,
            users: this.users,
            brige: this.brige
          }
          this.ws.send(JSON.stringify(data))
          this.msg = ''
        },
    	
    	// 切换到单聊
        triggerUser (itm) {
          this.brige = [this.uid, itm.uid]
          this.title = `和${itm.nickname}聊天`
        },
    	
    	// 切换到群聊
        triggerGroup () {
          this.brige = []
          this.title = '群聊'
        },
    

    样式层:

    .web-im {
      display: flex;
    }
      .left {
        width: 200px;
        border: 1px solid #ccc;
        .user {
          width: 100%;
          height: 36px;
          padding-left: 10px;
          border-bottom: 1px solid #ccc;
          line-height: 36px;
          .msgtip {
            display: inline-block;
            width: 20px;
            height: 20px;
            background: #46b0ff;
            margin-left: 5px;
            text-align: center;
            color: #fff;
            line-height: 20px;
            border-radius: 50%;
          }
        }
        }
      .right {
          position: relative;
          flex: 1;
          height: 600px;
          margin: 0 auto;
          .im-title {
            height: 30px;
            padding-left: 20px;
            border-bottom: 1px solid #ccc;
            line-height: 30px;
            font-size: 16px;
          }
          .im-footer {
            position: absolute;
            bottom: 0;
            left: 0;
            display: flex;
            width: 100%;
            .im-footer_inp {
              width: 80%;
            }
            .im-footer_btn {
              width: 20%;
            }
          }
          
          .im-record {
            width: 100%;
            height: 540px;
            overflow-y: auto;
            .join-tips {
              position: relative!important;
              display: block;
              width: 100%;
              left: 0!important;
              transform: none!important;
              color: #cccccc;
              font-size: 15px;
              text-align: center;
            }
            .li {
              position: relative;
              margin-bottom: 15px;
              text-align: left;
              color: #46b0ff;
              &:after {
                content: '';
                display: block;
                clear: both;
              }
              .message-date {
                font-size: 16px;
                color: #b9b8b8;
              }
              .m-nickname {
                color: #46b0ff;
              }
              &.user {
                text-align: right;
              }
            }
            .message-box {
              line-height: 30px;
              font-size: 20px;
            }
          }
        }
    

    参考链接:Nodejs + WebSocket + Vue 一对一、一对多聊天室 – 第三章

    展开全文
  • 这是个简单使用websocket实现多人聊天室,单人聊天室的demo,里面使用的是最原始的websocket的方法,附有客户端界面可以直接跑起来发送消息看效果。 该项目对应刚刚接触websocket技术是比较有用的。 2019-10-11的...
  • WebSocket实现简单多人聊天

    千次阅读 2022-01-11 21:51:23
    //创建WebSocket Server对象,监听0.0.0.0:9502端口 $ws = new Swoole\WebSocket\Server('0.0.0.0', 9502); //监听WebSocket连接打开事件 $ws->on('Open', function ($ws, $request) { $ws->p.

    前置条件

    swoole 后台不能再windocs 下运行  ,php要开启swoole4扩展,放行服务端9502端口

    1.新建后台php文件

    <?php
    //创建WebSocket Server对象,监听0.0.0.0:9502端口
    $ws = new Swoole\WebSocket\Server('0.0.0.0', 9502);
    
    //监听WebSocket连接打开事件
    $ws->on('Open', function ($ws, $request) {
        $ws->push($request->fd, "hello, welcome\n");
    });
    
    //监听WebSocket消息事件
    
    $ws->on('Message', function ($ws, $frame) {
        foreach ($ws->connections as $fd) {
            //$frame->fd 当前客户端的唯一标识
            //$frame->data 客户端发送 的数据
            //$ws->connections 获取所有的客户端连接对象
            if ($frame->fd == $fd) {
                $class = 'bubble me';
            } else {
                $class = 'bubble you';
            }
            $result = [
                'error_code' => 0,
                'data' => [
                    'data' => $frame->data,
                    'class' => $class
                ],
                'msg' => '发送成功'
            ];
            //发送群聊中的所有人
            $ws->push($fd, json_encode($result,256));
        }
    });
    
    //监听WebSocket连接关闭事件
    $ws->on('Close', function ($ws, $fd) {
        echo "client-{$fd} is closed\n";
    });
    
    $ws->start();

    2.新建前台php文件

    
    <!DOCTYPE html>
    <html lang="en" >
    
    <head>
    	<meta charset="UTF-8">
    	<title>在线聊天室</title>
    	<meta name="viewport" content="width=device-width, initial-scale=1">
    	<link rel="stylesheet" href="/css/reset.min.css">
    	<link rel="stylesheet" href="/css/style.css">
        <script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js">
        </script>
    </head>
    
    <body>
    	<div class="wrapper">
    		<div class="container">
    
    			<div class="right">
    				<div class="top"><span><span class="name">聊天室</span></span></div>
    				<div class="chat" data-chat="person2">
                        <!--template 相当于微信小程序block标签,没有实际样式输出,用来循环-->
                        <template v-for="item in chatList">
                            <div :class="item.data.class">
                                {{ item.data.data }}
                            </div>
                        </template>
    
    
    				</div>
    
    				<div class="write">
    					<input type="text"  v-model="word" @keyup.enter="send()"   />
    					<a href="javascript:;" class="write-link send" @click="send()" ></a>
    				</div>
    			</div>
    		</div>
    	</div>
    
    	<script  src="/js/index.js"></script>
    </body>
    
    </html>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
    <script>
    
        var wsServer = 'ws://47.100.168.166:9502';
        var websocket = new WebSocket(wsServer);
        websocket.onopen = function (evt) {
            console.log("Connected to WebSocket server.");
        };
    
        websocket.onclose = function (evt) {
            console.log("Disconnected");
        };
        var vm = new Vue({
    
            el: '.wrapper',
            data: {
                word:'',
                chatList:[]
            },
            methods:{
                send(){
                    websocket.send(this.word)
                    this.word = ''
                }
            }
    
        })
        websocket.onmessage = function ({data}) {
            let json = eval('(' + data + ')')
            console.log(json)
            console.log(typeof json)
            let msgArr = vm.chatList
            msgArr.push(json)
    
    
    
        };
    
        websocket.onerror = function (evt, e) {
            console.log('Error occured: ' + evt.data);
        };
    
    </script>

    3.新建css文件reset.min.css,放入css文件夹中

    html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none}table{border-collapse:collapse;border-spacing:0}

    4.新建css文件style.css,放入css文件夹中

    *, *:before, *:after {
      box-sizing: border-box;
    }
    
    :root {
      --white: #fff;
      --black: #000;
      --bg: #f8f8f8;
      --grey: #999;
      --dark: #1a1a1a;
      --light: #e6e6e6;
      --wrapper: 1000px;
      --blue: #00b0ff;
    }
    
    body {
      background-color: var(--bg);
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-rendering: optimizeLegibility;
      font-family: 'Source Sans Pro', sans-serif;
      font-weight: 400;
      background-image: url("../img/image.jpg");
      background-size: cover;
      background-repeat: none;
    }
    
    .wrapper {
      position: relative;
      left: 50%;
      width: var(--wrapper);
      height: 800px;
      -webkit-transform: translate(-50%, 0);
              transform: translate(-50%, 0);
    }
    
    .container {
      position: relative;
      top: 50%;
      left: 50%;
      width: 80%;
      height: 75%;
      background-color: var(--white);
      -webkit-transform: translate(-50%, -50%);
              transform: translate(-50%, -50%);
    }
    .container .left {
      float: left;
      width: 37.6%;
      height: 100%;
      border: 1px solid var(--light);
      background-color: var(--white);
    }
    .container .left .top {
      position: relative;
      width: 100%;
      height: 96px;
      padding: 29px;
    }
    .container .left .top:after {
      position: absolute;
      bottom: 0;
      left: 50%;
      display: block;
      width: 80%;
      height: 1px;
      content: '';
      background-color: var(--light);
      -webkit-transform: translate(-50%, 0);
              transform: translate(-50%, 0);
    }
    .container .left input {
      float: left;
      width: 188px;
      height: 42px;
      padding: 0 15px;
      border: 1px solid var(--light);
      background-color: #eceff1;
      border-radius: 21px;
      font-family: 'Source Sans Pro', sans-serif;
      font-weight: 400;
    }
    .container .left input:focus {
      outline: none;
    }
    .container .left a.search {
      display: block;
      float: left;
      width: 42px;
      height: 42px;
      margin-left: 10px;
      border: 1px solid var(--light);
      background-color: var(--blue);
      background-image: url("../img//name-type.png");
      background-repeat: no-repeat;
      background-position: top 12px left 14px;
      border-radius: 50%;
    }
    .container .left .people {
      margin-left: -1px;
      border-right: 1px solid var(--light);
      border-left: 1px solid var(--light);
      width: calc(100% + 2px);
    }
    .container .left .people .person {
      position: relative;
      width: 100%;
      padding: 12px 10% 16px;
      cursor: pointer;
      background-color: var(--white);
    }
    .container .left .people .person:after {
      position: absolute;
      bottom: 0;
      left: 50%;
      display: block;
      width: 80%;
      height: 1px;
      content: '';
      background-color: var(--light);
      -webkit-transform: translate(-50%, 0);
              transform: translate(-50%, 0);
    }
    .container .left .people .person img {
      float: left;
      width: 40px;
      height: 40px;
      margin-right: 12px;
      border-radius: 50%;
    }
    .container .left .people .person .name {
      font-size: 14px;
      line-height: 22px;
      color: var(--dark);
      font-family: 'Source Sans Pro', sans-serif;
      font-weight: 600;
    }
    .container .left .people .person .time {
      font-size: 14px;
      position: absolute;
      top: 16px;
      right: 10%;
      padding: 0 0 5px 5px;
      color: var(--grey);
      background-color: var(--white);
    }
    .container .left .people .person .preview {
      font-size: 14px;
      display: inline-block;
      overflow: hidden !important;
      width: 70%;
      white-space: nowrap;
      text-overflow: ellipsis;
      color: var(--grey);
    }
    .container .left .people .person.active, .container .left .people .person:hover {
      margin-top: -1px;
      margin-left: -1px;
      padding-top: 13px;
      border: 0;
      background-color: var(--blue);
      width: calc(100% + 2px);
      padding-left: calc(10% + 1px);
    }
    .container .left .people .person.active span, .container .left .people .person:hover span {
      color: var(--white);
      background: transparent;
    }
    .container .left .people .person.active:after, .container .left .people .person:hover:after {
      display: none;
    }
    .container .right {
      position: relative;
      float: left;
      width: 62.4%;
      height: 100%;
    }
    .container .right .top {
      width: 100%;
      height: 47px;
      padding: 15px 29px;
      background-color: #eceff1;
    }
    .container .right .top span {
      font-size: 15px;
      color: var(--grey);
    }
    .container .right .top span .name {
      color: var(--dark);
      font-family: 'Source Sans Pro', sans-serif;
      font-weight: 600;
    }
    .container .right .chat {
      position: relative;
      display: none;
      overflow: hidden;
      padding: 5px 35px 92px;
      border-width: 1px 1px 1px 0;
      border-style: solid;
      border-color: var(--light);
      height: calc(100% - 48px);
      /*justify-content: flex-end;*/
      flex-direction: column;
    }
    .container .right .chat.active-chat {
      display: block;
      display: flex;
    }
    .container .right .chat.active-chat .bubble {
      transition-timing-function: cubic-bezier(0.4, -0.04, 1, 1);
    }
    .container .right .chat.active-chat .bubble:nth-of-type(1) {
      -webkit-animation-duration: 0.15s;
              animation-duration: 0.15s;
    }
    .container .right .chat.active-chat .bubble:nth-of-type(2) {
      -webkit-animation-duration: 0.3s;
              animation-duration: 0.3s;
    }
    .container .right .chat.active-chat .bubble:nth-of-type(3) {
      -webkit-animation-duration: 0.45s;
              animation-duration: 0.45s;
    }
    .container .right .chat.active-chat .bubble:nth-of-type(4) {
      -webkit-animation-duration: 0.6s;
              animation-duration: 0.6s;
    }
    .container .right .chat.active-chat .bubble:nth-of-type(5) {
      -webkit-animation-duration: 0.75s;
              animation-duration: 0.75s;
    }
    .container .right .chat.active-chat .bubble:nth-of-type(6) {
      -webkit-animation-duration: 0.9s;
              animation-duration: 0.9s;
    }
    .container .right .chat.active-chat .bubble:nth-of-type(7) {
      -webkit-animation-duration: 1.05s;
              animation-duration: 1.05s;
    }
    .container .right .chat.active-chat .bubble:nth-of-type(8) {
      -webkit-animation-duration: 1.2s;
              animation-duration: 1.2s;
    }
    .container .right .chat.active-chat .bubble:nth-of-type(9) {
      -webkit-animation-duration: 1.35s;
              animation-duration: 1.35s;
    }
    .container .right .chat.active-chat .bubble:nth-of-type(10) {
      -webkit-animation-duration: 1.5s;
              animation-duration: 1.5s;
    }
    .container .right .write {
      position: absolute;
      bottom: 29px;
      left: 30px;
      height: 42px;
      padding-left: 8px;
      border: 1px solid var(--light);
      background-color: #eceff1;
      width: calc(100% - 58px);
      border-radius: 5px;
    }
    .container .right .write input {
      font-size: 16px;
      float: left;
      width: 392px;
      height: 40px;
      padding: 0 10px;
      color: var(--dark);
      border: 0;
      outline: none;
      background-color: #eceff1;
      font-family: 'Source Sans Pro', sans-serif;
      font-weight: 400;
    }
    .container .right .write .write-link.attach:before {
      display: inline-block;
      float: left;
      width: 20px;
      height: 42px;
      content: '';
      background-image: url("../img/attachment.png");
      background-repeat: no-repeat;
      background-position: center;
    }
    .container .right .write .write-link.smiley:before {
      display: inline-block;
      float: left;
      width: 20px;
      height: 42px;
      content: '';
      background-image: url("../img/smiley.png");
      background-repeat: no-repeat;
      background-position: center;
    }
    .container .right .write .write-link.send:before {
      display: inline-block;
      float: left;
      width: 20px;
      height: 42px;
      margin-left: 11px;
      content: '';
      background-image: url("../img/send.png");
      background-repeat: no-repeat;
      background-position: center;
    }
    .container .right .bubble {
      font-size: 16px;
      position: relative;
      display: inline-block;
      clear: both;
      margin-bottom: 8px;
      padding: 13px 14px;
      vertical-align: top;
      border-radius: 5px;
    }
    .container .right .bubble:before {
      position: absolute;
      top: 19px;
      display: block;
      width: 8px;
      height: 6px;
      content: '\00a0';
      -webkit-transform: rotate(29deg) skew(-35deg);
              transform: rotate(29deg) skew(-35deg);
    }
    .container .right .bubble.you {
      float: left;
      color: var(--white);
      background-color: var(--blue);
      align-self: flex-start;
      -webkit-animation-name: slideFromLeft;
              animation-name: slideFromLeft;
    }
    .container .right .bubble.you:before {
      left: -3px;
      background-color: var(--blue);
    }
    .container .right .bubble.me {
      float: right;
      color: var(--dark);
      background-color: #eceff1;
      align-self: flex-end;
      -webkit-animation-name: slideFromRight;
              animation-name: slideFromRight;
    }
    .container .right .bubble.me:before {
      right: -3px;
      background-color: #eceff1;
    }
    .container .right .conversation-start {
      position: relative;
      width: 100%;
      margin-bottom: 27px;
      text-align: center;
    }
    .container .right .conversation-start span {
      font-size: 14px;
      display: inline-block;
      color: var(--grey);
    }
    .container .right .conversation-start span:before, .container .right .conversation-start span:after {
      position: absolute;
      top: 10px;
      display: inline-block;
      width: 30%;
      height: 1px;
      content: '';
      background-color: var(--light);
    }
    .container .right .conversation-start span:before {
      left: 0;
    }
    .container .right .conversation-start span:after {
      right: 0;
    }
    
    @keyframes slideFromLeft {
      0% {
        margin-left: -200px;
        opacity: 0;
      }
      100% {
        margin-left: 0;
        opacity: 1;
      }
    }
    @-webkit-keyframes slideFromLeft {
      0% {
        margin-left: -200px;
        opacity: 0;
      }
      100% {
        margin-left: 0;
        opacity: 1;
      }
    }
    @keyframes slideFromRight {
      0% {
        margin-right: -200px;
        opacity: 0;
      }
      100% {
        margin-right: 0;
        opacity: 1;
      }
    }
    @-webkit-keyframes slideFromRight {
      0% {
        margin-right: -200px;
        opacity: 0;
      }
      100% {
        margin-right: 0;
        opacity: 1;
      }
    }
    

    4.新建js文件index.js,放入js文件夹中

    document.querySelector('.chat[data-chat=person2]').classList.add('active-chat');
    document.querySelector('.person[data-chat=person2]').classList.add('active');
    
    var friends = {
      list: document.querySelector('ul.people'),
      all: document.querySelectorAll('.left .person'),
      name: '' },
    
    chat = {
      container: document.querySelector('.container .right'),
      current: null,
      person: null,
      name: document.querySelector('.container .right .top .name') };
    
    
    friends.all.forEach(function (f) {
      f.addEventListener('mousedown', function () {
        f.classList.contains('active') || setAciveChat(f);
      });
    });
    
    function setAciveChat(f) {
      friends.list.querySelector('.active').classList.remove('active');
      f.classList.add('active');
      chat.current = chat.container.querySelector('.active-chat');
      chat.person = f.getAttribute('data-chat');
      chat.current.classList.remove('active-chat');
      chat.container.querySelector('[data-chat="' + chat.person + '"]').classList.add('active-chat');
      friends.name = f.querySelector('.name').innerText;
      chat.name.innerHTML = friends.name;
    }

    展开全文
  • websocket 即时通讯案例 ,一对聊天,多人聊天 java 版本
  • 令人欣慰的是,我用google找到了关于websocket的点聊天,更好的是可以和大多数系统很好的配合起来看下效果图 因为是模拟的,这里给出的是两个JSP页面A和B,里面分别向session里放了两个名字小明和小化,注意,...
  • 单向性:必须由客户端发起的个请求建立的连接,服务器接收请求,把数据返回给客户端,典型的请求响应。 无状态:服务器与客户端通过http协议建立连接,当我们浏览器向服务器发送请求然后服务给我们应答,当...

    1.网页版即时通讯背后的原理
    http协议和服务器推送技术:
    http协议为无状态,单向性的协议。

    • 单向性:必须由客户端发起的一个请求建立的连接,服务器接收请求,把数据返回给客户端,典型的一请求一响应。
    • 无状态:服务器与客户端通过http协议建立连接,当我们浏览器向服务器发送请求然后服务给我们应答,当我们再一次向服务器发送请求时,对于服务器来讲,在它看来这个请求是一个全新的用户。(在http协议中为了解决这种无状态的问题提出了cookie和session)

    那么基于http协议的特性想简单实现一个在网页上显示服务器上的时间都是个难题。

    基于http的解决方案:

    • ajax短轮询:通过前端不停的定时的向服务器发送请求,服务器收到请求后马上返回相关应答。
      缺点:带宽上的浪费。
      结果的一个延迟。当我们的客户端向服务器发送请求的时候。服务器没有最新的一个结果,但是服务器得马上给一个应答,那么这次客户端的请求相当于无效请求,那么就得等到客户端进行下一次请求才能拿到数据。

    • ajax长轮询:当客户端向服务器发送请求时,并不会每一次都马上发生应答,只有当服务器发生数据更新才发生应答,否则会停留一段时间。
      缺点:客户端没有数据到达时,http连接会停留一段时间,这会造成服务器资源浪费。

    • 基于长连接的服务器推模型sse:当客户端向服务端发送请求时,当有数据更新时发生应答但是不断开连接,有新数据更新时继续向客户端发送数据。

    2.服务器推送之最强武器——WebSocket通信

    1. 什么是websocket
    • 也是h5中的协议,实现客户端与服务端双向,基于消息的文本或二进制数据通信。(天生支持发送图片视频)
    • 适合于对数据的实时性要求很强的场景,如通信、直播、共享桌面、特别适合于客户于服务频繁交互的情况下、如实时共享、多人协作等平台。
    • 采用新协议,后端需要单独实现。
    • 客户端并不是所有浏览器都支持。
    1. websocket通信原理
    • websocket借用了http的协议来完成一部分握手
      在这里插入图片描述在这里插入图片描述
    1. websocket通信——STOMP
      websocket是个规范,在实际的实现中有html5规范中的websocketapi和websocket的子协议STOMP

      STOMP

      • 简单(流)文本定向消息协议
      • STOMP协议的前身是TTMP协议(一个简单的基于文本的协议),专为消息中间件设计。是属于消息队列的一种协议,它的简单性恰巧可以用于定义websocket的消息体格式,stomp协议很多mq都已支持,比如rabbitmq,activemq。
    2. websocket通信实现
      结合springboot实现基于stomp的微信风格——群聊
      在这里插入图片描述1.sockjs把websocket通讯相关部分进行封装,如果当前浏览器不支持websocket那么将自动降级为ajax轮询。
      2.stompjs对stomp进行一个封装。
      3.这两个js都需要引入jquery。
      在这里插入图片描述
      1.首先第一步通过sockjs.min.js拿到一个socketjs对象和服务器建立连接,连接的地址就endpoint(指定地址)
      2.stomp首先向我们的服务器端发起一个订阅(订阅到应答中转上面)
      3.当stomp客户端向服务器端发送消息时,客户端发送的消息经过应答中转拼凑成一个响应报文再发送出去,服务器端会将消息发送到所有订阅了应答中转(mass)的客户端。

      编写一个配置文件来开启对stomp协议的支持。

      @Configuration
      /*开启使用Stomp协议来传输基于消息broker的消息
      这时控制器支持使用@MessageMapping,就像使用@RequestMapping一样*/
      @EnableWebSocketMessageBroker
      public class WebSocketConfig
             implements WebSocketMessageBrokerConfigurer {
      
         @Override
         public void registerStompEndpoints(StompEndpointRegistry registry) {
             /*注册STOMP协议的节点(endpoint),并映射指定的url,
             * 添加一个访问端点“/endpointMark”,客户端打开双通道时需要的url,
             * 允许所有的域名访问,指定使用SockJS协议。*/
             registry.addEndpoint("/endpointMark")
                     .setAllowedOrigins("*")
                     .withSockJS();
      
         }
      
         //所有订阅了mass方法的客户端都会收到mass方法发送出来的消息
         @Override
         public void configureMessageBroker(MessageBrokerRegistry registry) {
             /*配置消息代理*/
             registry.enableSimpleBroker("/mass","/queue");
      
             /*一对一访问时,给用户信息时增加发送地址的前缀*/
             registry.setUserDestinationPrefix("/queue");
         }
      
      }
      

    这里给出示例:在js代码中建立连接

    //声明一个对象,作为stomp的客户端
    var stompClient = null;
    
    //浏览器加载页面后调用connect(),打开websocket通道
    $(function(){
        //打开双通道
        connect()
    })
    
    //打开通道
    function connect(){
        //连接SockJS的endpoint名称为"endpointMark"
        var socket = new SockJS("/endpointMark");
        //使用STMOP子协议的WebSocket客户端
        stompClient = Stomp.over(socket);
        //连接WebSocket服务端,后台打印连接信息
        stompClient.connect({},function (frame) {
            console.log("Connected:"+frame);
            stompTopic();//发起订阅
            conName();//获取登录者姓名
            stompQueue();//一对一发起订阅
        });
    }
    

    获取姓名 以及发起订阅操作

    //获取姓名并拼接上去
    function conName() {
        var response = $("#name_div");
        $.ajax({
            url:"name",
            type:"post",
            dataType : "json",
            async:false,
            success:function (data) {
                var b = JSON.parse(JSON.stringify(data));
                response.append("<li class='name' id='name_1'>"+b+"</li>");
            }
        });
    }
    
    //发起订阅
    function stompTopic(){
        //通过stompClient.subscribe订阅目标(destination)发送的消息(广播接收信息)
        stompClient.subscribe('/mass',function (response) {
            var message=JSON.parse(response.body);
            //展示广播所接收的内容
            var response = $("#mass_div");
            var userName=$("#name_div").html();
            if(userName==message.fromName){
                response.append("<div class='user-group'>" +
                    "          <div class='user-msg'>" +
                    "                <span class='user-reply'>"+message.chatValue+"</span>" +
                    "                <i class='triangle-user'></i>" +
                    "          </div>" +message.fromName+
                    "     </div>");
            }else{
                response.append("     <div class='admin-group'>"+
                    message.fromName+
                    "<div class='admin-msg'>"+
                    "    <i class='triangle-admin'></i>"+
                    "    <span class='admin-reply'>"+message.chatValue+"</span>"+
                    "</div>"+
                    "</div>");
            }
        });
    }
    
    //一对一,发起订阅
    function stompQueue(){
        var userId=$("#name_1").html();
        //通过stompClient.subscribe订阅目标(destination)发送的消息(队列接收信息)
        stompClient.subscribe('/queue/' + userId + '/alone',
            function(response){
                var message=JSON.parse(response.body);
                //展示一对一的接收的内容接收
                var response = $("#alone_div");
                response.append("     <div class='admin-group'>"+
                    message.fromName+
                    "<div class='admin-msg'>"+
                    "    <i class='triangle-admin'></i>"+
                    "    <span class='admin-reply'>"+message.chatValue+"</span>"+
                    "</div>"+
                    "</div>");
            });
    }
    

    订阅完成后我们只需要将我们每次发送的消息拼接到我们的前端界面上。

    展开全文
  • 1.文件用maven的方式导入到MyEcplise 直接运行Application里面的mian函数 2.然后用cmd的命令ipconfig 查看本地id 3.localhost://8080/index2.html/UID=3(localhost 换成本地ip,UID 用来模拟当前...4.台电脑访问吧
  • 记录第次使用Websocket,但终归还是成功调试成功!!
  • 最近的项目中要实现聊天的功能,类似于斗鱼TV的聊天室功能,与服务器端人商量后决定用WebSocket来做,但是在这之前我只知道Socket但是听都没有听过WebSocket,但是查看了相关的材料以后发现实现聊天室其实是...
  • 本篇文章会带大家使用Netty实现WebSocket聊天室。 代码已上传至Gitee:https://gitee.com/panchanghe/netty-project。 ​ 1. 前端代码 因为是基于网页的聊天室,所以HTML等文件必不可少。笔者是后端开发,前端...
  • 在第二篇文章结束时,我们就已经可以一对多聊天了,就是多人群聊。这次,我们进行扩展来实现一对一、一对多功能。 WebSocket客户端UI界面更改 有了一对一,一对多,我们就需要对直接的界面做出调整了。左边显示...
  • 要实现这样一个电商系统的客服聊天系统,那该系统就必须是一个支持多客服、客服一对多用户的聊天系统。 二 思路 使用 Node.js 搭建服务器,安装 websocket 模块、node-uuid模块。通过在客服端和用户端传递 客服ID 和...
  • 使用Netty构建个基于WebSocket聊天室服务器。可以使个用户使用浏览器可以同时进行相互通信。 程序逻辑: 1、客户端发送个消息; 2、该消息将被广播到所有其他连接的客户端 服务端启动后,浏览器输入...
  • 正在做毕业设计,需要做个在线客服的功能,由于在网上关于websocket的demo太入门了,只好自己琢磨,可能资质问题,花了两天时间织写了个简单的聊天页面,只能发文字。 服务端是用java写的,服务器用的是tomcat8...
  • 功能:多人聊天一对聊天。先声明一下,websocket什么鬼协议的,自已百度。。。。直接上源码:首先是首页,登录用, 这里没有用密码,简单操作,你们要密码自己加。。。pageEncoding="UTF-8"%>htmlPUBLIC"-//...
  • 实现一对聊天功能功能介绍:实现A和B单独聊天功能,即A发消息给B只能B接收,同样B向A发消息只能A接收。本篇博客是在上一遍基础上搭建,上一篇博客地址:【WebSocket】---实现游戏公告功能。底部有源码。先看演示...
  • 环境:eclipse+jdk1.8+tomcat8 项目启动后访问链接:http://localhost:8080/Chatroom/home/list.do
  • 该项目是websocket现在聊天客服,主要实现了客服和访客的一对多聊天,下载用户可以根据自己的需求修改成群聊或一对一单聊(相对来说比较简单)。敲黑板 :源码 源码 源码!!!!
  • 点击上方“Java精选”,选择“设为星标”别问别人为什么,问自己凭什么!下方有惊喜,留言必回,有问必答!每天进步点点,是成功的开始...1.websocketwebsocket最伟大...
  • SpringBoot使用WebSocket实现简单的网页一对聊天功能。 WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。 WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向...
  • 本文实例为大家分享了微信小程序WebSocket实现聊天对话功能的具体代码,供大家参考,具体内容如下 js var app = getApp(); var socketOpen = false; var frameBuffer_Data, session, SocketTask; var url = 'ws://...
  • 苦求一份uniapp 使用websocket的简单一对聊天的demo 写了很遍了,就是无法实现聊天,大哭 358016830@qq.com mailto:358016830@qq.com
  • WebSocketd是款非常特别的WebSocket服务器,它的最大特点是后台脚本不限语言,其标准输入stdin就是WebSocket的输入,标准输出stdout就是WebSocket的输出。 WebSocketd本质上是命令行的WebSocket代理,只要在命令行...
  • 这时候好学的小伙伴就会问了,那怎么实现一对一的聊天呢? 前提 注:这里的用的redis保存用户信息,你也可以用mysql数据库,我提供的只是思路,方法不唯一,能实现就行 一台服务器 安装swoole4 php>=7.2 安装...
  • 判断是否有此用户的离线消息,如果有,则构建成个数组发送给此用户。 广播消息,告诉其他用户,我上线了。 小程序收到广播后,刷新用户列表。 后端从Redis取出所有的用户,并在目前在线列表中获取对应状态,修改...
  • 主要介绍了SpringBoot集成WebSocket【基于纯H5】进行点对点[一对一]和广播[一对多]实时推送,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • 基于websocket实现的网页版聊天室,包括私聊和多对多聊天,简单易懂,个jsp页面,个config.class以及websocket.class
  • swoole:http://www.swoole.com/PHP的异步、...7-22更新------昨天已经用面向对象和redis重做了次,现在支持切换分组,私信了.并且其他地方也做了一些优化.视频演示:http://www.bilibili.com/video...视频中的列表没有
  • webSocket一对一 、一对多通信

    千次阅读 2017-09-04 10:40:49
    最近趁着空闲时间,搭建了一套webSocket的demo,以做备用,那么我们先来简单的说一下什么是webSocket? 它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 11,483
精华内容 4,593
关键字:

websocket一对多聊天