netty搭建web聊天室(3)单聊

上节课讲了群聊,此次来说说单聊,单聊要比群聊复杂点,然则代码也不是许多,主假如前端显现比较贫苦点。

结果:
《netty搭建web聊天室(3)单聊》

上岸

起首一个新的用户,须要先上岸,输入本身的昵称,然后点击上岸。后端效劳会把你的用户名和当前的线程举行邦定,如许就能够经由过程你的用户名找到你的线程。上岸胜利,后端返回定义好的音讯 success,前端推断纪录CHAT.me,如许给他人发音讯时就能够照顾本身的信息。
《netty搭建web聊天室(3)单聊》

查找用户

在输入框输入用户名,就能够返回对应的用户的线程,如许你就能够把音讯发送给你要谈天的对象。假如不存在,后端回返回音讯给前端,该用户不存在。假如存在,就纪录此用户名到CHAT.to中,如许你发送音讯的时刻就能够发送给对应用户了。
《netty搭建web聊天室(3)单聊》

最先谈天

发送谈天信息时me:to:音讯,如许后端就晓得是谁要发给谁,依据用户名去找到细致的线程去零丁推送音讯,完成单聊。

前端待完美

左边谈天列表没有完成,每搜刮一个在线用户,应当动态显现在左边,点击该用户,动态显现右边谈天窗口举行音讯发送。现在是你和一切人的单聊音讯都邑显现在右边,没有完成拆分,由于这是一个页面,处置惩罚起来比较贫苦,我一个后端就不花时间搞了,感兴趣的能够本身去完成。

前端代码

由于谛视比较细致,就直接复制全部代码到这里,人人本身看。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>单人谈天</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/zui/1.8.1/css/zui.min.css">
    <link rel="stylesheet" href="zui-theme.css">
  </head>
  <body>

    <div class="container">
      <div class="row">     <h1>mike单人谈天室,等你来聊</h1></div>
      <div class="row">
        <div class="input-control has-icon-left has-icon-right" style="width:50%;">
         <input id="userName" type="text" class="form-control" placeholder="谈天昵称">
         <label for="inputEmailExample1" class="input-control-icon-left"><i class="icon icon-user"></i></label>
         <label for="inputEmailExample1" class="input-control-icon-right"><a onclick="login()">上岸</a></label>

       </div>


      </div>
      <br>
      <div class="row">
        <div class="input-control search-box search-box-circle has-icon-left has-icon-right" id="searchUser">
            <input id="inputSearch" type="search" class="form-control search-input" placeholder="输入在线挚友昵称谈天...enter最先查找">
             <label for="inputSearchExample1" class="input-control-icon-left search-icon"><i class="icon icon-search"></i></label>
             <a href="#" class="input-control-icon-right search-clear-btn"><i class="icon icon-remove"></i></a>
        </div>
      </div>
      <hr>
      <div class="row">
         <div class="col-lg-3">

           <p class="with-padding bg-success">谈天列表</p>
           <div class="list-group">
<a href="#" class="list-group-item">
<h4 class="list-group-item-heading"><i class="icon-user icon-2x"></i>&nbsp;&nbsp;may</h4>
</a>
<a href="#" class="list-group-item active">
<h4 class="list-group-item-heading"><i class="icon-user icon-2x"></i>&nbsp;&nbsp;steve</h4>
</a>

            </div>
         </div>
         <div class="col-lg-1"></div>
         <div class="col-lg-8">
           <div class="comments">
         <section class="comments-list" id="chatlist">

         </section>
         <footer>
           <div class="reply-form" id="commentReplyForm1">

             <a href="###" class="avatar"><i class="icon-user icon-2x"></i></a>
             <form class="form">
               <div class="form-group">
                 <textarea id="inputMsg" class="form-control new-comment-text" rows="2" value="" placeholder="最先谈天...  输入enter 发送音讯"></textarea>
               </div>
             </form>
           </div>
         </footer>
       </div>
         </div>

      </div>
    </div>

    <!-- ZUI Javascript 依靠 jQuery -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/zui/1.8.1/lib/jquery/jquery.js"></script>
    <!-- ZUI 规范版压缩后的 JavaScript 文件 -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/zui/1.8.1/js/zui.min.js"></script>
    <script type="text/javascript">
         window.CHAT = {
            isLogin: false,
            to: "",
            me: "",
            WS:{},
            init: function () {
              if (window.WebSocket) {
                this.WS = new WebSocket("ws://A156B7L58CCNY4B:8090/ws");
                this.WS.onmessage = function(event) {
                 var data = event.data;
                 console.log("收到数据:" + data);
                 //返回搜刮音讯
                 if(data.indexOf("search") != -1){
                     new $.zui.Messager('提醒音讯:'+data, {
                        type: 'info' // 定义色彩主题
                     }).show();
                     if(data.indexOf("已找到")){  //能够举行会话
                       CHAT.to = data.split(":")[1];
                     }
                 }

                 //返回上岸音讯
                 if(data == "success"){
                     CHAT.isLogin = true;
                     new $.zui.Messager('提醒音讯:上岸胜利', {
                        type: 'success' // 定义色彩主题
                     }).show();
                     //衔接胜利不再修正昵称
                      $("#userName").attr("disabled","disabled");
                      CHAT.me = $("#userName").val();
                 }

                 //返回谈天信息
                 if (data.split(":").length==3 && CHAT.me == data.split(":")[1]) {
                     CHAT.to = data.split(":")[0];  //设置对话
                     appendOtherchat(data);
                 }
               },

               this.WS.onclose = function(event) {
                  console.log("衔接封闭");
                  CHAT.isLogin = false;
                  $("#userName").removeAttr("disabled");
                  new $.zui.Messager('提醒音讯:谈天中缀', {
                     type: 'danger' // 定义色彩主题
                  }).show();
               },

               this.WS.onopen = function(evt) {
                  console.log("Connection open ...");
               },

              this.WS.onerror = function(event) {
                  console.log("衔接失利....");
                  CHAT.isLogin = false;
                  $("#userName").removeAttr("disabled");
                  new $.zui.Messager('提醒音讯:谈天中缀', {
                     type: 'danger' // 定义色彩主题
                  }).show();
               }
              } else {
                alert("您的浏览器不支持谈天,请替换浏览器");
              }

            },
            chat:function (msg) {
              this.WS.send(msg);
            }
        }

        CHAT.init();

        function login() {
          var userName = $("#userName").val();
          if (userName != null && userName !='') {
            //初始化谈天
            CHAT.chat("init:"+userName);
          } else {
            alert("请输入用户名登录");
          }

        }


        function Trim(str) {
          return str.replace(/(^\s*)|(\s*$)/g, "");
        }

        function appendMy (msg) {  //拼接本身的谈天内容
          document.getElementById('chatlist').innerHTML+="<div class='comment'><a class='avatar pull-right'><i class='icon-user icon-2x'></i></a><div class='content pull-right'><div><strong>我</strong></div><div class='text'>"+msg+"</div></div></div>";

        }

        function appendOtherchat(msg) {  //拼接他人的谈天信息到谈天室
          var  msgs = msg.split(":");
          document.getElementById('chatlist').innerHTML+="<div class='comment'><a class='avatar'><i class='icon-user icon-2x'></i></a><div class='content'><div><strong>"+msgs[0]+"</strong></div><div class='text'>"+msgs[2]+"</div></div></div>";
        }


        //搜刮在线职员发送音讯
        document.getElementById("inputSearch").addEventListener('keyup', function(event) {
           if (event.keyCode == "13") {
            //回车实行查询
             CHAT.chat("search:"+$('#inputSearch').val());
           }

        });

        //发送谈天音讯
        document.getElementById('inputMsg').addEventListener('keyup', function(event) {
        if (event.keyCode == "13") {
         //回车实行查询
        var  inputMsg = $('#inputMsg').val();
        if (inputMsg == null || Trim(inputMsg) == "" ) {
             alert("请输入谈天音讯");
        } else {
            var  userName = $('#userName').val();
            if (userName == null || userName == '') {
              alert("请输入谈天昵称");
            } else {
              //发送音讯 定义音讯花样   me:to:[音讯]
              CHAT.chat(userName+":"+CHAT.to+":"+inputMsg);
              appendMy(inputMsg);
              //发送完清空输入
               document.getElementById('inputMsg').focus();
               document.getElementById('inputMsg').value="";
            }
        }
     }
 });
    </script>
  </body>
</html>

后端革新

  • 到场一个UserMap,邦定user和Channel
package netty;

import java.util.HashMap;
import java.util.Map;

import io.netty.channel.Channel;

/**
 * The class UserMap
 */
public class UserMap {
  private HashMap<String, Channel> users = new HashMap();
  private static UserMap instance;
  
  public static UserMap getInstance () {
      if (instance == null) {
          instance = new UserMap();
      }
      return instance;
  }
  
  private UserMap () {
      
  }
  public void addUser(String userId, Channel ch) {
      this.users.put(userId, ch);
  }
  
  public Channel getUser (String userId) {
      return this.users.get(userId);
  }
  
  public void deleteUser (Channel ch) {
      for (Map.Entry<String, Channel> map: users.entrySet()) {
        if (map.getValue() == ch) {
            users.remove(map.getKey());
            break;
        }
    }
  }
}
  • ChatHandler革新
package netty;

import java.time.LocalDateTime;

import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.util.concurrent.GlobalEventExecutor;

/**
 * 
 */
public class ChatHandler extends SimpleChannelInboundHandler{
    
    public static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    public static UserMap usermap = UserMap.getInstance();
    /**
     * 每当从效劳端收到新的客户端衔接时,客户端的 Channel 存入ChannelGroup列表中,并关照列表中的其他客户端 Channel
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {  
        Channel incoming = ctx.channel();
        for (Channel channel : channels) {
            channel.writeAndFlush("[SERVER] - " + incoming.remoteAddress() + " 到场\n");
        }
        channels.add(ctx.channel());
    }
    
    /**
     * 每当从效劳端收到客户端断开时,客户端的 Channel 移除 ChannelGroup 列表中,并关照列表中的其他客户端 Channel
     */
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {  
        Channel incoming = ctx.channel();
        for (Channel channel : channels) {
            channel.writeAndFlush("[SERVER] - " + incoming.remoteAddress() + " 脱离\n");
        }
        channels.remove(ctx.channel());
    }
    
    /**
     * 会话建立时
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception { // (5)
        Channel incoming = ctx.channel();
        System.out.println("ChatClient:"+incoming.remoteAddress()+"在线");
    }
    
    /**
     * 会话结束时
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception { // (6)
        Channel incoming = ctx.channel();
        System.out.println("ChatClient:"+incoming.remoteAddress()+"掉线");
        //消灭离线用户
        this.usermap.deleteUser(incoming);
    }
    
    /**
     * 出现非常
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (7)
        Channel incoming = ctx.channel();
        System.out.println("ChatClient:"+incoming.remoteAddress()+"非常");
        // 当出现非常就封闭衔接
        cause.printStackTrace();
        ctx.close();
    }
    
    /**
     * 读取客户端发送的音讯,并将信息转发给其他客户端的 Channel。
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object  request) throws Exception {
           if (request instanceof FullHttpRequest) { //是http要求
               FullHttpResponse response = new DefaultFullHttpResponse(
                        HttpVersion.HTTP_1_1,HttpResponseStatus.OK , Unpooled.wrappedBuffer("Hello netty"
                                .getBytes()));
                response.headers().set("Content-Type", "text/plain");
                response.headers().set("Content-Length", response.content().readableBytes());
                response.headers().set("connection", HttpHeaderValues.KEEP_ALIVE);
                ctx.channel().writeAndFlush(response);
           } else if (request instanceof TextWebSocketFrame) { // websocket要求
               //此处id为neety自动分配给每一个对话线程的id,有两种,一个长id一个短id,长id唯一,短id能够会反复
               String userId = ctx.channel().id().asLongText();
               //客户端发送过来的音讯
               String msg = ((TextWebSocketFrame)request).text();
               System.out.println("收到客户端"+userId+":"+msg);
               
               //发送音讯给一切客户端  群聊
               //channels.writeAndFlush(new TextWebSocketFrame(msg));
            
               
               
               // 邦定user和channel 
               // 定义每一个上线用户主动发送初始化信息过来,照顾本身的name,然后完成绑定  模子  init:[usrname]
               // 现实场景中应当运用user唯一id
              if (msg.indexOf("init") != -1) {
                String userNames[] = msg.split(":");
                if ("init".equals(userNames[0])) { // 纪录新的用户
                    this.usermap.addUser(userNames[1].trim(), ctx.channel());
                    ctx.channel().writeAndFlush(new TextWebSocketFrame("success"));
                }
              }
              
                
                //搜刮在线用户      音讯模子  search:[username]
                if (msg.indexOf("search") != -1) {
                    Channel ch = this.usermap.getUser(msg.split(":")[1].trim());
                    if (ch != null) { //此用户存在
                        ctx.channel().writeAndFlush(new TextWebSocketFrame("search:"+msg.split(":")[1].trim()+":已找到"));
                    } else { // 此用户不存在
                        ctx.channel().writeAndFlush(new TextWebSocketFrame("search:"+msg.split(":")[1].trim()+":未找到"));
                    }
                    
                }
               
               //发送音讯给指定的用户    音讯模子  me:to:[msg]
                if (msg.split(":").length == 3) {  //推断是单聊音讯
                    this.usermap.getUser(msg.split(":")[1].trim()).writeAndFlush(new TextWebSocketFrame(msg));
                }
                
               //ctx.channel().writeAndFlush(new TextWebSocketFrame(((TextWebSocketFrame)request).text()));
           }
    }

}

解释很细致,本身看

总结

音讯模子应当定义一个零丁的类来治理,我现在是用的String字符串来推断,提早划定了一些模子,经由过程推断来相应前端的要求,比较简单。另有就是没有运用数据库,前端不能显现谈天纪录,不能完成音讯的已读未读。现实场景中应当对音讯举行加密存储,且不能窥伺用户隐私。
前端能够运用localstorage来存储谈天纪录,本身能够扩大。
前端的显现能够有点题目,本身能够调。实在主假如进修netty后端的搭建

别忘了关注我 mike啥都想搞
《netty搭建web聊天室(3)单聊》

求关注啊。

    原文作者:Mike晓
    原文地址: https://segmentfault.com/a/1190000017999627
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞