资讯专栏INFORMATION COLUMN

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

Lavender / 1654人阅读

摘要:开始聊天发送聊天信息时消息,这样后端就知道是谁要发给谁,根据用户名去找到具体的线程去多带带推送消息,实现单聊。前端待完善左侧聊天列表没有实现,每搜索一个在线用户,应该动态显示在左侧,点击该用户,动态显示右侧聊天窗口进行消息发送。

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

效果:

登陆

首先一个新的用户,需要先登陆,输入自己的昵称,然后点击登陆。后端服务会把你的用户名和当前的线程进行邦定,这样就可以通过你的用户名找到你的线程。登陆成功,后端返回定义好的消息 success,前端判断记录CHAT.me,这样给别人发消息时就可以携带自己的信息。

查找用户

在输入框输入用户名,就可以返回对应的用户的线程,这样你就可以把消息发送给你要聊天的对象。如果不存在,后端回返回消息给前端,该用户不存在。如果存在,就记录此用户名到CHAT.to中,这样你发送消息的时候就可以发送给对应用户了。

开始聊天

发送聊天信息时me:to:消息,这样后端就知道是谁要发给谁,根据用户名去找到具体的线程去多带带推送消息,实现单聊。

前端待完善

左侧聊天列表没有实现,每搜索一个在线用户,应该动态显示在左侧,点击该用户,动态显示右侧聊天窗口进行消息发送。现在是你和所有人的单聊消息都会显示在右侧,没有完成拆分,因为这是一个页面,处理起来比较麻烦,我一个后端就不花时间搞了,感兴趣的可以自己去实现。

前端代码

因为注视比较详细,就直接复制整个代码到这里,大家自己看。



  
    
    单人聊天
    
    
  
  

    

mike单人聊天室,等你来聊



后端改造

加入一个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 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 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() + " 加入
");
        }
        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() + " 离开
");
        }
        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啥都想搞

求关注啊。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/101348.html

相关文章

  • netty搭建web天室(1)

    摘要:提供异步的事件驱动的网络应用程序框架和工具,用以快速开发高性能高可靠性的网络服务器和客户端程序。总结我们完成了服务端的简单搭建,模拟了聊天会话场景。 之前一直在搞前端的东西,都快忘了自己是个java开发。其实还有好多java方面的东西没搞过,突然了解到netty,觉得有必要学一学。 介绍 Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框...

    izhuhaodev 评论0 收藏0
  • netty搭建web天室(2)群聊

    摘要:上节课完成了的后端搭建,搞定了简单的请求响应,今天来结合前端来完成群聊功能。其实后端群聊很简单,就是把一个用户的输入消息,返回给所有在线客户端,前端去负责筛选显示。 上节课完成了netty的后端搭建,搞定了简单的http请求响应,今天来结合前端websocket来完成群聊功能。话不多说先上图:showImg(https://segmentfault.com/img/bVbnCa8?w=...

    microelec 评论0 收藏0
  • swoole 超简单 构建天室.资辞 群聊,组聊,单聊.

    摘要:今天来做一个简单的聊天室支持换房间支持私信的写的代码有点渣里面有很多不是很好的地方毕竟我只是一个野生程序猿环境地址样子差不多是这个样子的我不想把代码发到我的服务器上因为这个项目太小了很垃圾而且怕被攻击这里有录的一个演示视频没有广告的你们可以 今天来做一个简单的聊天室,支持换房间,支持私信的. 写的代码有点渣,里面有很多不是很好的地方.毕竟我只是一个野生程序猿. 环境: php7.0...

    Towers 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<