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

栏目: Java · 发布时间: 7年前

内容简介:上节课讲了群聊,这次来说说单聊,单聊要比群聊复杂点,但是代码也不是很多,主要是前端显示比较麻烦点。效果:

上节课讲了群聊,这次来说说单聊,单聊要比群聊复杂点,但是代码也不是很多,主要是前端显示比较麻烦点。

效果:

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>  may</h4>
</a>
<a href="#" class="list-group-item active">
<h4 class="list-group-item-heading"><i class="icon-user icon-2x"></i>  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)单聊

求关注啊。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。

Fluent Python

Fluent Python

Luciano Ramalho / O'Reilly Media / 2015-8-20 / USD 39.99

Learn how to write idiomatic, effective Python code by leveraging its best features. Python's simplicity quickly lets you become productive with it, but this often means you aren’t using everything th......一起来看看 《Fluent Python》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具