用Netty实现简单的聊天:一对一匹配聊天

1.交互演示

开启一个用户等待另一个用户

另一个用户登录

聊天

 

2. code

  • main

public class One2OneMatchingMain {
    public static void main(String[] args) {
        new NettyService(11111, new One2OneMatchingChannelInitializer()).start();
    }
}
  • NettyService

public class NettyService {

    private final int port;
    private final ChannelInitializer<Channel> handler;

    public NettyService(int port, ChannelInitializer<Channel> handler) {
        this.port = port;
        this.handler = handler;
    }

    public void start() {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(handler);
            Channel channel = serverBootstrap.bind(port).sync().channel();
            channel.closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
  • 自定义的ChannelInitializer

public class One2OneMatchingChannelInitializer extends MyChannelInitializer {
    @Override
    protected SimpleChannelInboundHandler<Object> getHandler() {
        return new One2OneMatchingHandler();
    }
}

public abstract class MyChannelInitializer extends ChannelInitializer<Channel> {

    @Override
    protected void initChannel(Channel channel) throws Exception {
        ChannelPipeline pipeline = channel.pipeline();
        pipeline.addLast("http-codec", new HttpServerCodec()); // Http消息编码解码
        pipeline.addLast("aggregator", new HttpObjectAggregator(65536)); // Http消息组装
        pipeline.addLast("http-chunked", new ChunkedWriteHandler()); // WebSocket通信支持
        pipeline.addLast("handler", getHandler());
    }

    protected abstract SimpleChannelInboundHandler<Object> getHandler();
}
  • 自定义Handler

public class One2OneMatchingHandler extends SimpleChannelInboundHandler<Object> {

    private WebSocketServerHandshaker handshaker;
    private ChannelHandlerContext ctx;
    private String sessionId;
    private String name;

    @Override
    protected void messageReceived(ChannelHandlerContext ctx, Object o) throws Exception {
        if (o instanceof FullHttpRequest) { // 传统的HTTP接入
            handleHttpRequest(ctx, (FullHttpRequest) o);
        } else if (o instanceof WebSocketFrame) { // WebSocket接入
            handleWebSocketFrame(ctx, (WebSocketFrame) o);
        }
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }

    @Override
    public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
        super.close(ctx, promise);
        //关闭连接将移除该用户消息
        Mage mage = new Mage();
        mage.setName(this.name);
        mage.setMessage("20002");
        //将用户下线信息发送给为下线用户
        String table = One2OneMatchingInformation.login.get(this.sessionId);
        ConcurrentMap<String, One2OneMatchingInformation> cmap = One2OneMatchingInformation.map.get(table);
        if (cmap != null) {
            cmap.forEach((id, iom) -> {
                try {
                    if (id != this.sessionId) iom.sead(mage);
                } catch (Exception e) {
                    System.err.println(e);
                }
            });
        }
        One2OneMatchingInformation.login.remove(this.sessionId);
        One2OneMatchingInformation.map.remove(table);
    }

    /**
     * 处理Http请求,完成WebSocket握手<br/>
     * 注意:WebSocket连接第一次请求使用的是Http
     * @param ctx
     * @param request
     * @throws Exception
     */
    private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
        // 如果HTTP解码失败,返回HTTP异常
        if (!request.getDecoderResult().isSuccess() || (!"websocket".equals(request.headers().get("Upgrade")))) {
            sendHttpResponse(ctx, request, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
            return;
        }

        // 正常WebSocket的Http连接请求,构造握手响应返回
        WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory("ws://" + request.headers().get(HttpHeaders.Names.HOST), null, false);
        handshaker = wsFactory.newHandshaker(request);
        if (handshaker == null) { // 无法处理的websocket版本
            WebSocketServerHandshakerFactory.sendUnsupportedWebSocketVersionResponse(ctx.channel());
        } else { // 向客户端发送websocket握手,完成握手
            handshaker.handshake(ctx.channel(), request);
            // 记录管道处理上下文,便于服务器推送数据到客户端
            this.ctx = ctx;
        }
    }

    /**
     * Http返回
     * @param ctx
     * @param request
     * @param response
     */
    private static void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest request, FullHttpResponse response) {
        // 返回应答给客户端
        if (response.getStatus().code() != 200) {
            ByteBuf buf = Unpooled.copiedBuffer(response.getStatus().toString(), CharsetUtil.UTF_8);
            response.content().writeBytes(buf);
            buf.release();
            HttpHeaders.setContentLength(response, response.content().readableBytes());
        }

        // 如果是非Keep-Alive,关闭连接
        ChannelFuture f = ctx.channel().writeAndFlush(response);
        if (!HttpHeaders.isKeepAlive(request) || response.getStatus().code() != 200) {
            f.addListener(ChannelFutureListener.CLOSE);
        }
    }

    /**
     * 处理Socket请求
     * @param ctx
     * @param frame
     * @throws Exception
     */
    private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception {
        // 判断是否是关闭链路的指令
        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 text =((TextWebSocketFrame)frame).text();
            System.out.println("mage : " + text);
            //消息转成Mage
            Mage mage = Mage.strJson2Mage(text);
            if (mage.getMessage().equals("10001")) {
                if (!One2OneMatchingInformation.login.containsKey(mage.getId())) {
                    One2OneMatchingInformation.login.put(mage.getId(), "");
                    One2OneMatchingInformation.offer(ctx, mage);
                    if (queue.size() >= 2) {
                        String tableId = UUID.randomUUID().toString();
                        One2OneMatchingInformation iom1 = queue.poll().setTableId(tableId);
                        One2OneMatchingInformation iom2 = queue.poll().setTableId(tableId);
                        One2OneMatchingInformation.add(iom1.getChannelHandlerContext(), iom1.getMage());
                        One2OneMatchingInformation.add(iom2.getChannelHandlerContext(), iom2.getMage());
                        iom1.sead(iom2.getMage());
                        iom2.sead(iom1.getMage());
                    }
                } else {//用户已登录
                    mage.setMessage("-10001");
                    sendWebSocket(mage.toJson());
                    ctx.close();
                }
            } else {
                //将用户发送的消息发给所有在同一聊天室内的用户
                One2OneMatchingInformation.map.get(mage.getTable()).forEach((id, iom) -> {
                    try {
                        iom.sead(mage);
                    } catch (Exception e) {
                        System.err.println(e);
                    }
                });
            }
            //记录id 当页面刷新或浏览器关闭时,注销掉此链路
            this.sessionId = mage.getId();
            this.name = mage.getName();
        } else {
            System.err.println("------------------error--------------------------");
        }
    }

    /**
     * WebSocket返回
     */
    public void sendWebSocket(String msg) throws Exception {
        if (this.handshaker == null || this.ctx == null || this.ctx.isRemoved()) {
            throw new Exception("尚未握手成功,无法向客户端发送WebSocket消息");
        }
        //发送消息
        this.ctx.channel().write(new TextWebSocketFrame(msg));
        this.ctx.flush();
    }
}
  • 页面

<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>一对一聊天室</title>
    <script src="jquery.min.js"></script>
</head>
<body>

    <div id="top">请等待匹配</div>

    <div id="bottom">
        <div id="title"></div>
        <div id = 'users'></div>
        <div>
            <input type="text" id="mag"/>
            <input type="button" value="发送" onclick="send()"/>
        </div>
        <div id="sed" style="height: 300px;width: 500px;border:1px solid;"></div>
    </div>
</body>
<script>
    var user;
    var socket;
    $(function() {
        var random = Math.ceil(Math.random()*1000);
        $('#bottom').hide();
        user = {
            id:"id_" + random,
            name:"name_" + random,
            pwd:"pwd_" + random
        };
        console.log(user);
        var temp = typeof(user);
        console.log(temp);
        inChat();
    });

    //进入聊天室
    function inChat() {
        if (!window.WebSocket) {
            window.WebSocket = window.MozWebSocket;
        }
        if (window.WebSocket) {
            //获取h5 socket
            socket = new WebSocket("ws://127.0.0.1:11111/");
            //接收消息
            socket.onmessage = function(data){
                console.log("socket.onmessage:")
                console.log(data);
                var mage = JSON.parse(data.data);
                console.log(mage.message);
                if (mage.message == '10001') {//10001为上线
                    $('#top').hide();
                    $('#bottom').show();
                    $('#title').text('chat' + mage.table);
                    $('#users').append('<span>'+ mage.name + '\t</span>');
                    user.table = mage.table;
                } else if (mage.message == '20002') {//对方下线
                    $('#bottom').hide();
                    $('#top').show();
                    $('#top').text('对方已下线');
                    socket.close();
                } else if (mage.message == '-10001') {//已经在线
                    $('#bottom').hide();
                    $('#top').text('已经在线!');
                } else {//用户发的消息
                    $('#sed').append('<span>'+ mage.name + ' : ' + mage.message + '</span><br/>');
                }
            }
            //webSocket的链接
            socket.onopen = function(data) {
                $('#top').text('链接成功,请等待匹配');
                console.log("socket.onopen:")
                console.log(data);
                user.table = '';
                user.message = '10001';
                delete user.pwd;
                console.log(user);
                //链接成功后发送用户信息进入聊天室
                socket.send(JSON.stringify(user));
            }
            //webSocket关闭
            socket.onclose = function(data) {
                console.log("socket.onclose:")
                console.log(data);
            }
            //webSocket错误信息
            socket.onerror = function(data) {
                console.log("socket.onerror:")
                console.log(data);
            }
        } else {
            alert("抱歉,您的浏览器不支持WebSocket协议!");
        }
    }

    //发送消息
    function send() {
        user.message = $('#mag').val();
        socket.send(JSON.stringify(user));
    }
</script>
</html>

 

源码 https://github.com/li-ze-lin/easy-chatroom

  • 2
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
Java实现一对一聊天Netty是一种基于NIO的客户端/服务器框架,用于快速开发可维护的高性能协议服务器和客户端。Netty是一个异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。Netty提供了一种新的方式来处理网络应用程序,使开发人员可以专注于业务逻辑而不是网络通信。Netty的主要特点包括: 1. 异步事件驱动:Netty使用异步事件驱动模型,这意味着它可以处理大量的并发连接,而不会导致线程堵塞。 2. 高性能:Netty使用NIO,这意味着它可以处理大量的并发连接,而不会导致线程堵塞。 3. 可扩展性:Netty的设计非常灵活,可以轻松地添加新的协议和功能。 4. 易于使用:Netty提供了简单易用的API,使开发人员可以快速开发高性能的网络应用程序。 5. 支持多种协议:Netty支持多种协议,包括HTTP、WebSocket、TCP和UDP等。 在Java实现一对一聊天Netty,可以使用Netty提供的API来实现。具体实现步骤如下: 1. 创建一个ServerBootstrap实例,用于启动服务器。 2. 配置ServerBootstrap实例,包括设置端口号、设置Channel类型、设置ChannelHandler等。 3. 创建一个ChannelInitializer实例,用于初始化ChannelPipeline。 4. 在ChannelInitializer实例中添加ChannelHandler,包括编码器、解码器、业务逻辑处理器等。 5. 启动服务器,等待客户端连接。 6. 创建一个Bootstrap实例,用于启动客户端。 7. 配置Bootstrap实例,包括设置远程地址、设置Channel类型、设置ChannelHandler等。 8. 创建一个ChannelInitializer实例,用于初始化ChannelPipeline。 9. 在ChannelInitializer实例中添加ChannelHandler,包括编码器、解码器、业务逻辑处理器等。 10. 启动客户端,连接服务器。 11. 客户端和服务器之间可以进行一对一聊天

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值