websocket netty 实现点对点聊天(即时通讯)

转自: https://blog.csdn.net/weixin_39168678/article/details/79453585

websocket netty 实现点对点聊天(即时通讯)

效果图

首先用h5画一个简单的聊天页面,分别录入发送人的id,接受者的id,和需要发的消息,以及消息展示框
这里写图片描述

然后分别用小明,小红和小刚登录

这里写图片描述

这里写图片描述

这里写图片描述

现在小明给小红发消息,小红收的到,但小刚收不到实现这里写图片描述点对点聊天

这里写图片描述

这里写图片描述

首先,先看前台代码

h5

<body>
<div id="content" class="row-center">
<div id="chat-box" class="row-center">

</div>
<div id="input-box">
 <input class="chat-input" id="chat-input" placeholder="message"></input>
 <input id="myid" placeholder="myid">
 <button id="login-button" onclick="login()">登录</button>
 <input id="friendid" placeholder="friendid">
<button class="chat-button" id="send" onclick="send()">发送</button>
</div> 
</div>
</body>

js 控制

<script type="text/javascript">
var ipaddress="127.0.0.1";
//新建socket对象
window.socket = new WebSocket("ws://"+ipaddress+":9999/ws");
//监听netty服务器消息并打印到页面上
socket.onmessage = function(event) {
      var datas=event.data.split(",");
      console.log("服务器消息===="+datas);
      $("#chat-box").text(datas);
      }
//将发送人接收人的id和要发生的消息发送出去
function send(){
    console.log($("#chat-input").val())
    var data=$("#myid").val()+","+$("#friendid").val()+","+$("#chat-input").val()
    socket.send(data)
}     
//登录事件
function login(){
    var data=$("#myid").val();socket.send(data);
}
</script>

netty服务端启动类

public class imServer {
    private io.netty.channel.Channel channel;
    private  EventLoopGroup bossGroup = new NioEventLoopGroup();
    private  EventLoopGroup workerGroup = new NioEventLoopGroup();
    private static int port=9999;
    //线程池设计的定时任务类
    public imServer(int port) {

    }
    public void run() throws Exception {
        try {
            //创建ServerBootstrap实例
            ServerBootstrap b = new ServerBootstrap();  
           //设置并绑定Reactor线程池
            b.group(bossGroup, workerGroup)
            //设置并绑定服务端Channel
             .channel(NioServerSocketChannel.class)  
             .childHandler(new ChannelInitializer<SocketChannel>(){
                    @Override
                    public void initChannel(SocketChannel ch) throws Exception {
                        ChannelPipeline pipeline = ch.pipeline();
                        pipeline.addLast("http-codec", new HttpServerCodec()); 
                        pipeline.addLast("aggregator", new HttpObjectAggregator(65536)); // Http消息组装  
                        pipeline.addLast("http-chunked", new ChunkedWriteHandler()); // WebSocket通信支持  
                        pipeline.addLast(new SocketHandler());//自定义处理类

                    }
             })
             .option(ChannelOption.SO_BACKLOG, 128)  
             .childOption(ChannelOption.SO_KEEPALIVE, true);
//          System.out.println("WebsocketChatServer Start:" + port);
            try {
                ChannelFuture f = b.bind(port).sync();//// 服务器异步创建绑定

                channel = f.channel();
                f.channel().closeFuture().sync();
            } catch (InterruptedException e) {

            }
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
            channel.closeFuture().syncUninterruptibly();
//          System.out.println("WebsocketChatServer Stop:" + port);
        }
    }
    public static void main(String[] args) throws Exception {
        new imServer(port).run();
    }
}

这些看不懂的自行百度

自定义处理类

/**
 * @author oj
 * 消息处理类
 */
public class SocketHandler extends ChannelInboundHandlerAdapter {

    public static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    private WebSocketServerHandshaker handshaker;
    private final String wsUri = "/ws";
    //websocket握手升级绑定页面 
     String wsFactoryUri = ""; 
    /*
     * 握手建立
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        Channel incoming = ctx.channel();
        channels.add(incoming);
    }

    /*
     * 握手取消
     */
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {  
        Channel incoming = ctx.channel();
        channels.remove(incoming);
    }

    /*
     * channelAction
     *
     * channel 通道 action 活跃的
     *
     * 当客户端主动链接服务端的链接后,这个通道就是活跃的了。 
     *
     */
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
//      System.out.println(ctx.channel().localAddress().toString() + " 通道已激活!");
    }
    /*
     * channelInactive
     *
     * channel 通道 Inactive 不活跃的
     *
     * 当客户端主动断开服务端的链接后,这个通道就是不活跃的。 
     *
     */
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
//      System.out.println(ctx.channel().localAddress().toString() + " 通道不活跃!");
    }

    /*
     * 功能:读取 h5页面发送过来的信息
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (msg instanceof FullHttpRequest) {// 如果是HTTP请求,进行HTTP操作
            handleHttpRequest(ctx, (FullHttpRequest) msg);
        } else if (msg instanceof WebSocketFrame) {// 如果是Websocket请求,则进行websocket操作
            handleWebSocketFrame(ctx, (WebSocketFrame) msg);
        }
    }
    /*
     * 功能:读空闲时移除Channel
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent evnet = (IdleStateEvent) evt;
            // 判断Channel是否读空闲, 读空闲时移除Channel
            if (evnet.state().equals(IdleState.READER_IDLE)) {                
                UserInfoManager.removeChannel(ctx.channel());
            }
        }
        ctx.fireUserEventTriggered(evt);
    }

    /*
     * 功能:处理HTTP的代码
     */
    private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) throws UnsupportedEncodingException {
        // 如果HTTP解码失败,返回HHTP异常
        if (req instanceof HttpRequest) {
            HttpMethod method = req.getMethod();
            // 如果是websocket请求就握手升级
            if (wsUri.equalsIgnoreCase(req.getUri())) {
                System.out.println(" req instanceof HttpRequest");
                WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(
                        wsFactoryUri, null, false);
                handshaker = wsFactory.newHandshaker(req);
                if (handshaker == null) {
                    WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
                } else {
                    handshaker.handshake(ctx.channel(), req);
                }
            }

        }
    }

    /*
     * 处理Websocket的代码
     */
    private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
        // 判断是否是关闭链路的指令
        //System.out.println("websocket get");
        if (frame instanceof CloseWebSocketFrame) {
            handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());
            return;
        }
        // 判断是否是Ping消息
        if (frame instanceof PingWebSocketFrame) {
            ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
            return;
        }
        // 文本消息,不支持二进制消息
        if (frame instanceof TextWebSocketFrame) {
            // 返回应答消息
            String requestmsg = ((TextWebSocketFrame) frame).text();
            System.out.println("websocket消息======"+requestmsg);
            String[] array= requestmsg.split(",");
            // 将通道加入通道管理器
            UserInfoManager.addChannel(ctx.channel(),array[0]);
            UserInfo userInfo = UserInfoManager.getUserInfo(ctx.channel());
            if (array.length== 3) {
            // 将信息返回给h5
            String sendid=array[0];String friendid=array[1];String messageid=array[2];
            UserInfoManager.broadcastMess(friendid,messageid,sendid);
            }
        }
    }
    /**
     * 功能:服务端发生异常的操作
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}

channel管理类

 private static ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(true);

    private static ConcurrentMap<Channel, UserInfo> userInfos = new ConcurrentHashMap<>();
    /**
     * 登录注册 channel
     *
     *  
     */
    public static void addChannel(Channel channel,String uid) {
        String remoteAddr = NettyUtil.parseChannelRemoteAddr(channel);
        UserInfo userInfo = new UserInfo();
        userInfo.setUserId(uid);
        userInfo.setAddr(remoteAddr);
        userInfo.setChannel(channel);
        userInfos.put(channel, userInfo);
    }

    /**
     * 普通消息
     *
     * @param message
     */
    public static void broadcastMess(String uid,String message,String sender) {
        if (!BlankUtil.isBlank(message)) {
            try {
                rwLock.readLock().lock();
                Set<Channel> keySet = userInfos.keySet();
                for (Channel ch : keySet) {
                    UserInfo userInfo = userInfos.get(ch);
                    if (!userInfo.getUserId().equals(uid) ) continue;
                    String backmessage=sender+","+message;
                    ch.writeAndFlush(new TextWebSocketFrame(backmessage));
                  /*  responseToClient(ch,message);*/
                }
            } finally {
                rwLock.readLock().unlock();
            }
        }
    }

channel实体类

public class UserInfo {
    private String userId;  // UID
    private String addr;    // 地址
    private Channel channel;// 通道

    public String getAddr() {
        return addr;
    }

    public void setAddr(String addr) {
        this.addr = addr;
    }

    public Channel getChannel() {
        return channel;
    }

    public void setChannel(Channel channel) {
        this.channel = channel;
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }
}

大体思路

每个客户端通过websocket连接到后台服务器都是一条通道,只要每个人都登录的时候就将channel信息注册保存,当需要向具体某个人发消息的时候,只需要遍历出收件人的channel再发送信息即可

注册channel

 public static void addChannel(Channel channel,String uid) {
        String remoteAddr = NettyUtil.parseChannelRemoteAddr(channel);
        UserInfo userInfo = new UserInfo();
        userInfo.setUserId(uid);
        userInfo.setAddr(remoteAddr);
        userInfo.setChannel(channel);
        userInfos.put(channel, userInfo);
    }

遍历出收件人的channel

    public static void broadcastMess(String uid,String message,String sender) {
        if (!BlankUtil.isBlank(message)) {
            try {
                Set<Channel> keySet = userInfos.keySet();
                for (Channel ch : keySet) {
                    UserInfo userInfo = userInfos.get(ch);
                    if (!userInfo.getUserId().equals(uid) ) continue;
                    String backmessage=sender+","+message;
                    ch.writeAndFlush(new TextWebSocketFrame(backmessage));
                  /*  responseToClient(ch,message);*/
                }
            } finally {

            }
        }
    }

--------------------- 本文来自 白马湖小龙王 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/weixin_39168678/article/details/79453585?utm_source=copy

原文博主提供的源代码下载: 链接:https://pan.baidu.com/s/1O2oe1JNigW_-r_qMwem_HQ 密码:tg5k

猜你喜欢

转载自blog.csdn.net/hemeinvyiqiluoben/article/details/82962813