简易 IM 双向通信电脑端 GUI 应用——基于 Netty、JavaFX、多线程技术等

简易 IM 双向通信电脑端 GUI 应用——基于 Netty、JavaFX、多线程技术等

说明

  这是一个使用 Netty 来实现 IM 双向通信的 demo 项目。通信双方的客户端 GUI 界面均是使用 JavaFX 来实现的。

  本 demo 项目已完成的工作有:

  1. 通信双方可互为发送方、接收方。

  2. 在文本框中,可以点击 发送 按钮来发送消息,也可以使用 Enter,而在文本中另起一行需要使用组合键 Ctrl + Enter 来完成。

  3. 通信过程是由其它线程在后台完成,不会阻塞 UI 线程。

  4. 通信双方的通信是使用 Netty 来实现的,已解决 Netty 传输过程中的半包、粘包问题。

  5. 实现对 Java 对象的透明传输。

    通信时可以传输 Java 对象,而不限制为简单的文本数据。在发送端、接收端可以借助传输载体,通过对 Java 对象的序列化和反序列化来实现对 Java 对象的透明传输。

  6. 本项目使用的传输载体有:

    • JSON

运行效果

在这里插入图片描述


在这里插入图片描述


在这里插入图片描述

核心代码

  • 客户端核心代码
package org.wangpai.demo.im.client;

import com.fasterxml.jackson.core.JsonProcessingException;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.MessageToMessageEncoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
import java.util.List;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.wangpai.demo.im.protocol.Message;
import org.wangpai.demo.im.protocol.Protocol;
import org.wangpai.demo.im.util.json.JsonUtil;

/**
 * @since 2021-12-1
 */
@Accessors(chain = true)
public class Client {
    @Setter
    private String ip;

    @Setter
    private int port;

    private Channel channel;

    private EventLoopGroup workerLoopGroup = new NioEventLoopGroup();

    public Client start() {
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(workerLoopGroup);
        bootstrap.channel(NioSocketChannel.class);
        // 设置接收端的 IP 和端口号,但实际上,自己作为发送端也会为自己自动生成一个端口号
        bootstrap.remoteAddress(ip, port);
        bootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
        bootstrap.handler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) {
                // 最外层编码器。为了帮助接收端解决粘包、半包问题
                ch.pipeline().addLast(new LengthFieldPrepender(Protocol.HEAD_LENGTH));
                // 将 String 数据转化为二进制数据
                ch.pipeline().addLast(new StringEncoder(CharsetUtil.UTF_8));
                // 将 Java 对象转化为 String 数据(JSON 数据)
                ch.pipeline().addLast(new MessageToMessageEncoder<Message>() {
                    @Override
                    protected void encode(ChannelHandlerContext ctx, Message message, List<Object> out)
                            throws JsonProcessingException {
                        out.add(JsonUtil.pojo2Json(message));
                    }
                });
            }
        });

        ChannelFuture future = bootstrap.connect();
        future.addListener((ChannelFuture futureListener) -> {
            if (futureListener.isSuccess()) {
                System.out.println("客户端连接成功"); // FIXME:日志
            } else {
                System.out.println("客户端连接失败"); // FIXME:日志
            }
        });
        try {
            future.sync();
        } catch (Exception exception) {
            exception.printStackTrace(); // FIXME:日志
        }
        this.channel = future.channel();

        return this;
    }

    public void send(Message message) {
        channel.writeAndFlush(message);
    }

    public void send(String msg) {
        var message = new Message();
        message.setMsg(msg);

        this.send(message);
    }

    public void destroy() {
        this.workerLoopGroup.shutdownGracefully();
    }

    private Client() {
        super();
    }

    public static Client getInstance() {
        return new Client();
    }
}

  • 服务器端核心代码
package org.wangpai.demo.im.server;

import com.fasterxml.jackson.core.JsonProcessingException;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.MessageToMessageDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.util.CharsetUtil;
import java.util.List;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.wangpai.demo.im.protocol.Message;
import org.wangpai.demo.im.protocol.Protocol;
import org.wangpai.demo.im.util.json.JsonUtil;
import org.wangpai.demo.im.view.MainFace;

/**
 * @since 2021-12-1
 */
@Accessors(chain = true)
public class Server {
    @Setter
    private int port;

    @Setter
    private MainFace mainFace;

    private EventLoopGroup bossLoopGroup = new NioEventLoopGroup(1);

    private EventLoopGroup workerLoopGroup = new NioEventLoopGroup();

    public Server start() {
        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(this.bossLoopGroup, this.workerLoopGroup);
        bootstrap.channel(NioServerSocketChannel.class);
        bootstrap.localAddress(port);
        bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
        bootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
        bootstrap.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);

        bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) {
                // 最外层解码器。可解决粘包、半包问题
                ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024, 0,
                        Protocol.HEAD_LENGTH, 0, Protocol.HEAD_LENGTH));
                // 将二进制数据解码成 String 数据
                ch.pipeline().addLast(new StringDecoder(CharsetUtil.UTF_8));
                // 将 String 数据(JSON 数据)解码成 Java 对象
                ch.pipeline().addLast(new MessageToMessageDecoder<String>() {
                    @Override
                    protected void decode(ChannelHandlerContext ctx, String msg, List<Object> out)
                            throws JsonProcessingException {
                        out.add(JsonUtil.json2Pojo(msg, Message.class));
                    }
                });
                // 进行对转化后的最终的数据的处理
                ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                    @Override
                    public void channelRead(ChannelHandlerContext ctx, Object obj) {
                        mainFace.receive(((Message) obj).getMsg());
                    }
                });
            }
        });

        try {
            ChannelFuture channelFuture = bootstrap.bind().sync();
            ChannelFuture closeFuture = channelFuture.channel().closeFuture();
            closeFuture.sync();
        } catch (Exception exception) {
            exception.printStackTrace(); // FIXME:日志
        } finally {
            this.workerLoopGroup.shutdownGracefully();
            this.bossLoopGroup.shutdownGracefully();
        }

        return this;
    }

    public void destroy() {
        this.workerLoopGroup.shutdownGracefully();
        this.bossLoopGroup.shutdownGracefully();
    }

    private Server() {
        super();
    }

    public static Server getInstance() {
        return new Server();
    }
}

完整代码

  已上传至 GitHub 中,可免费下载:https://github.com/wangpaiblog/20211213-im_demo-netty_javafx

参考知识

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值