基于NETTY+Bootstrap实现的聊天平台

qq_41889073 2022-01-15 18:13:48

一.NETTY简介

     首先是NETTY的官方定义,”Netty 是一款异步的事件驱动的网络应用程序框架,支持快速地开发可维护的高性能的面向协议的服务器

    

 

        我们知道JAVA的IO模型分为四种,其中NIO,AIO支持异步操作,在NIO中引入了缓冲区Buffer,通道Channel和多路复用器Selector的概念那为什么还要引入NETTY,简单来说是由于原生NIO存在的问题,导致我们需要一个更加友好的框架来支持异步操作。

        1.NIO的类库和API繁杂使用麻烦,你需要熟练掌握Selectol,ServerSocketChannel,SocketChannel,ByteBuffer 等。

        2.需妥具备其他的额外技能做制垫,例如熟悉Java 多线程编程。这是因为NIO编程涉及到Reactor 模式,你必须对多钱程和网络编程非常如悉,才能编写出高质量的NIO程序。

        3.可靠性能力补齐, 工作量和难度都非常大。例如客户端面临断连重连、网络间断、半包读写、失败缓存的处理等问题, NI0 编程的特点是功能开发相对容易,但是可靠性能力补齐的工作量和难度都非常大。

        4.JDK NIO的BUG,比如epoll bug,这个BUG会在linux上导致cpu 100%,使得nio server/client不可用。

     而相比较之下,NETTY则具有统一的API接口,并支持各种传输类型,并且具有卓越的性能,这里进行网络处理的最主要的实体是类Channel Pipline,它是Channel注册调用相应的Handler所用到的类,Channel就是操作系统层与交道之间的数据通道,其包含一个字符缓冲区与具体的实例,在本项目中就是socket,下面是ChannelPipline调用Handler的方法:

     注册handler的方法有很多,这里不做赘述,下图是Channel与ChannelPipline之间的关系

 二.BootStrap简介

       来自 Twitter,是目前最受欢迎的前端框架。基于 HTML、CSS、JAVASCRIPT ,它简洁灵活,使得 Web 开发更加快捷。同时,Bootstrap 还是最受欢迎的 HTML、CSS 和 JS 框架,用于开发响应式布局、移动设备优先的 WEB 项目。作为一个框架,它和jQuery EasyUI、WeUI一样,助力于前端开发。简而言之,使用Bootstrap让前端开发变得简洁高效,其主要优点如下:

  1. 移动设备优先
  2. 支持各种主流浏览器
  3. 容易上手,只需要基础的css与javascript知识就可以应用到开发
  4. 为开发人员提供了一个简洁统一的开发方案
  5. 包含了强大的内置组件,易于定制。

三.后端代码,

   1.不同包之间的逻辑关系

 

  2.包中具体类

 

  3.核心信息类WsMessage

      具体属性字段如下

 /** 消息id */
    private String id;
    /** 消息发送类型 */
    private Integer code;
    /** 发送人用户id */
    private String sendUserId;
    /** 发送人用户名 */
    private String username;
    /** 接收人用户id,多个逗号分隔 */
    private String receiverUserId;
    /** 发送时间 */
    private Date sendTime;
    /** 消息类型 */
    private Integer type;
    /** 消息内容 */
    private String msg;
    /** 消息扩展内容 */
    private Map<String, Object> ext;

  4.核心处理类WebSocketSimpleChanelBoundHandler

     服务端与客户端建立连接

public void channelActive(ChannelHandlerContext ctx) throws Exception {
        //创建新的 WebSocket 连接,保存当前 channel
        logger.info("————客户端与服务端连接开启————");
//        // 设置高水位
//        ctx.channel().config().setWriteBufferHighWaterMark();
//        // 设置低水位
//        ctx.channel().config().setWriteBufferLowWaterMark();
    }

   服务端与客户端断开连接

ublic void channelInactive(ChannelHandlerContext ctx) throws Exception {
    logger.info("————客户端与服务端连接断开————");
    websocketInfoService.clearSession(ctx.channel());
}

  处理客户端请求websocket的核心方法

protected void channelRead0(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {
    if (o instanceof FullHttpRequest) {
        //处理客户端向服务端发起 http 请求的业务
        handHttpRequest(channelHandlerContext, (FullHttpRequest) o);
    } else if (o instanceof WebSocketFrame) {
        //处理客户端与服务端之间的 websocket 业务
        handWebsocketFrame(channelHandlerContext, (WebSocketFrame) o);
    }
}

 服务端处理客户端http请求的方法

 websocket连接过程如下:

  1. 首先客户端向服务端发送http请求,经过三次握手后,建立起tcp连接
  2. 然后,服务端收到客户端的http请求后,同样用http协议回馈数据
  3. 最后,客户端收到连接成功的信息后,借助tcp进行通信
    rivate void handHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request) {
        // 如果请求失败或者该请求不是客户端向服务端发起的 http 请求,则响应错误信息
        if (!request.decoderResult().isSuccess()
                || !("websocket".equals(request.headers().get("Upgrade")))) {
            // code :400
            sendHttpResponse(ctx, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
            return;
        }
        //新建一个握手
        handshaker = factory.newHandshaker(request);
        if (handshaker == null) {
            //如果为空,返回响应:不受支持的 websocket 版本
            WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
        } else {
            //否则,执行握手
            Map<String, String> params = RequestParamUtil.urlSplit(request.uri());
            String userId = params.get("userId");
            Channel channel = ctx.channel();
            NettyAttrUtil.setUserId(channel, userId);
            NettyAttrUtil.refreshLastHeartBeatTime(channel);
           handshaker.handshake(ctx.channel(), request);
           SessionHolder.channelGroup.add(ctx.channel());
           SessionHolder.channelMap.put(userId, ctx.channel());
           logger.info("握手成功,客户端请求uri:{}", request.uri());
           
           // 推送用户上线消息,更新客户端在线用户列表
           Set<String> userList = SessionHolder.channelMap.keySet();
           WsMessage msg = new WsMessage();
           Map<String, Object> ext = new HashMap<String, Object>();
           ext.put("userList", userList);
           msg.setExt(ext);
           msg.setCode(MessageCodeConstant.SYSTEM_MESSAGE_CODE);
           msg.setType(MessageTypeConstant.UPDATE_USERLIST_SYSTEM_MESSGAE);
           SessionHolder.channelGroup.writeAndFlush(new TextWebSocketFrame(JSONObject.toJSONString(msg)));
           
        }
    }

    服务端向客户端发送响应信息 

    private void handHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request) {
        // 如果请求失败或者该请求不是客户端向服务端发起的 http 请求,则响应错误信息
        if (!request.decoderResult().isSuccess()
                || !("websocket".equals(request.headers().get("Upgrade")))) {
            // code :400
            sendHttpResponse(ctx, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
            return;
        }
        //新建一个握手
        handshaker = factory.newHandshaker(request);
        if (handshaker == null) {
            //如果为空,返回响应:不受支持的 websocket 版本
            WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
        } else {
            //否则,执行握手
            Map<String, String> params = RequestParamUtil.urlSplit(request.uri());
            String userId = params.get("userId");
            Channel channel = ctx.channel();
            NettyAttrUtil.setUserId(channel, userId);
            NettyAttrUtil.refreshLastHeartBeatTime(channel);
           handshaker.handshake(ctx.channel(), request);
           SessionHolder.channelGroup.add(ctx.channel());
           SessionHolder.channelMap.put(userId, ctx.channel());
           logger.info("握手成功,客户端请求uri:{}", request.uri());
           
           // 推送用户上线消息,更新客户端在线用户列表
           Set<String> userList = SessionHolder.channelMap.keySet();
           WsMessage msg = new WsMessage();
           Map<String, Object> ext = new HashMap<String, Object>();
           ext.put("userList", userList);
           msg.setExt(ext);
           msg.setCode(MessageCodeConstant.SYSTEM_MESSAGE_CODE);
           msg.setType(MessageTypeConstant.UPDATE_USERLIST_SYSTEM_MESSGAE);
           SessionHolder.channelGroup.writeAndFlush(new TextWebSocketFrame(JSONObject.toJSONString(msg)));
           
        }
    }

    四.前端核心代码 

         1.index.html

       包含一个重复的信息栏显示在线用户与群聊列表,以及聊天框和对应的发送按钮,并将发送按钮与chat.js里的sendmessage函数进行绑定,实现发送功能

<body class="background">
<div class="mianFrame">
    <div class="panel scroll" id="panel">
        <div class="header">
            <img class="avatar img-rounded" id="myAvatar" src="../img/dog.png"
                 style="height: 50px;width: 50px">
            <div id="nick" style="color: white;font-size: large"></div>
        </div>
        <h3 id="userCount" style="color: white;font-size: large;margin-left: 20px"></h3>
        <div id="userList" style="border-top:1px solid #cccccc"></div>
    </div>
    <div id="repeatBox">
        <div class="box" id="box">
            <div class="textareaHead" id="textareaHead">群聊室</div>
            <div class="textarea scroll" id="responseContent"></div>
            <form onSubmit="return false;">
                <label>
                    <textarea id="sendTextarea" class="box_ft" name="message"></textarea>
                </label>
                <div class="send">
                    <button class="sendButton" onClick="sendMessage(this.form.message.value)">发送</button>
                </div>
            </form>
        </div>
    </div>
</div>
</body>

        2.chat.js核心功能

        首先判断信息是否为空,然后创建变量object填写相应属性后执行JSON.stringify将object转换为json对象后传给后端,由后端对信息作出相应处理

function sendMessage(message) {
    if (message === "" || message == null) {
        alert("信息不能为空~");
        return;
    }
    let object = {};
    object.code = 2;
    object.username = myNick;
    object.msg = message;
    object.sendUserId = me.userId;
    $('#sendTextarea').val("");
    send(JSON.stringify(object));
}

五.界面展示

1.前端界面

 

2.服务端界面

 

 作者:NP542

...全文
300 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

566

社区成员

发帖
与我相关
我的任务
社区描述
软件工程教学新范式,强化专项技能训练+基于项目的学习PBL。Git仓库:https://gitee.com/mengning997/se
软件工程 高校
社区管理员
  • 码农孟宁
加入社区
  • 近7日
  • 近30日
  • 至今

试试用AI创作助手写篇文章吧