javaFX使用netty写一个群组聊天demo

1.新建一个javaFX项目(选择模块可以全选,如果有部分模块报错可以maven删除,特别是eu.hansolo.tilesfx,好像是做游戏的,但是应该需要jdk是17,其他版本均报错)

在这里插入图片描述

2.maven引入加入netty,加入打包的依赖(目前采用非模块化打包,因为netty没有模块化的依赖,需要手动转模块化)完整依赖如下

 <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.68.Final</version>
        </dependency>
.....省略项目创建时所带依赖

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
                <configuration>
                    <source>21</source>
                    <target>21</target>
                </configuration>
            </plugin>
            <!--            <plugin>-->
            <!--                <groupId>org.openjfx</groupId>-->
            <!--                <artifactId>javafx-maven-plugin</artifactId>-->
            <!--                <version>0.0.8</version>-->
            <!--                <executions>-->
            <!--                    <execution>-->
            <!--                        &lt;!&ndash; Default configuration for running with: mvn clean javafx:run &ndash;&gt;-->
            <!--                        <id>default-cli</id>-->
            <!--                        <configuration>-->
            <!--                            <mainClass>com.qiankun.im/com.qiankun.im.HelloApplication</mainClass>-->
            <!--                            <launcher>app</launcher>-->
            <!--                            <jlinkZipName>app</jlinkZipName>-->
            <!--                            <jlinkImageName>app</jlinkImageName>-->
            <!--                            <noManPages>true</noManPages>-->
            <!--                            <stripDebug>true</stripDebug>-->
            <!--                            <noHeaderFiles>true</noHeaderFiles>-->
            <!--                        </configuration>-->
            <!--                    </execution>-->
            <!--                </executions>-->
            <!--            </plugin>-->

            <plugin>
                <groupId>io.github.fvarrui</groupId>
                <artifactId>javapackager</artifactId>
                <version>1.7.5</version>
                <configuration>
                    <name>hello</name>
                    <!--生成jre-->
                    <bundleJre>true</bundleJre>
                    <!--程序入口文件-->
                    <mainClass>com.qiankun.im.Main</mainClass>
                    <!--生成安装程序-->
                    <generateInstaller>false</generateInstaller>
                </configuration>
                <executions>
                    <execution>
                        <id>bundling-for-windows</id>
                        <phase>package</phase>
                        <goals>
                            <goal>package</goal>
                        </goals>
                        <configuration>
                            <!--平台-->
                            <platform>windows</platform>
                            <!--生成压缩包-->
                            <createZipball>true</createZipball>

                            <winConfig>
                                <!--生成安装程序-->
                                <generateSetup>true</generateSetup>
                                <!--生成Msi-->
                                <generateMsi>true</generateMsi>
                                <!--标头类型 console-->
                                <headerType>gui</headerType>

                                <wrapJar>true</wrapJar>
                                <!--文件版本-->
                                <fileVersion>1.0.0.0</fileVersion>
                                <!--txt文件版本-->
                                <txtFileVersion>${project.version}</txtFileVersion>
                                <!--产品版本-->
                                <productVersion>1.0.0.0</productVersion>
                                <!--txt产品版本-->
                                <txtProductVersion>${project.version}</txtProductVersion>
                                <!--文件描述-->
                                <fileDescription>${project.description}</fileDescription>
                                <!--产品名称-->
                                <productName>${project.name}</productName>
                                <!--内部名称-->
                                <internalName>${project.name}</internalName>
                                <!--原始文件名-->
                                <originalFilename>${project.name}.exe</originalFilename>
                                <!--exe创建工具 winrun4j  launch4j-->
                                <exeCreationTool>winrun4j</exeCreationTool>
                                <!--设置模式-->
                                <setupMode>installForAllUsers</setupMode>
                                <!--设置语言-->
                                <setupLanguages>
                                    <ChineseSimplified>compiler:Languages\ChineseSimplified.isl</ChineseSimplified>
                                </setupLanguages>
                                <!--禁用目录页-->
                                <disableDirPage>false</disableDirPage>
                                <!--禁用程序组页面-->
                                <disableProgramGroupPage>false</disableProgramGroupPage>
                                <!--禁用已完成页面-->
                                <disableFinishedPage>false</disableFinishedPage>
                                <!--禁用安装后运行-->
                                <disableRunAfterInstall>false</disableRunAfterInstall>
                                <!--禁用欢迎页面-->
                                <disableWelcomePage>false</disableWelcomePage>
                                <!--创建桌面图标任务-->
                                <createDesktopIconTask>true</createDesktopIconTask>

                                <!--https://github.com/fvarrui/JavaPackager/blob/master/docs/windows-specific-properties.md-->
                                <icoFile>${project.basedir}/src/main/resources/img/icon.ico</icoFile>
                            </winConfig>
                        </configuration>
                    </execution>
                </executions>
            </plugin>


        </plugins>
    </build>

3.目录如下(创建项目后,有modou-info.java直接删除)

在这里插入图片描述

4.javaFx完整客户端代码

package com.qiankun.im.controller;



import com.qiankun.im.message.MessageObserver;
import com.qiankun.im.message.MessageSubject;
import com.qiankun.im.netty.NettyClient;
import io.netty.util.internal.StringUtil;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;

public class HelloController implements MessageObserver {
    @FXML
    private TextArea viewTextArea;
    @FXML
    private Label sendText;
    @FXML
    private TextField inputField;

    NettyClient nettyClient;

    public void setNettyClient(NettyClient nettyClient) {
        this.nettyClient = nettyClient;
    }



    // 添加一个构造函数来接收传递进来的对象
    @FXML
    protected void initialize() {
        //添加进观察者模型
        MessageSubject.addObserver(this);
        inputField.setOnKeyReleased(event -> {
            if (event.getCode() == KeyCode.ENTER) {
                // 处理按下回车键的逻辑
                onHelloButtonClick();
            }
        });
    }

    @FXML
    protected void onHelloButtonClick() {

        String text = inputField.getText();
        if (!StringUtil.isNullOrEmpty(text)) {
            sendText.setText("发送成功");
            nettyClient.sendMessage(text);
            inputField.setText("");
        } else {
            sendText.setText("内容为空");
        }

    }

    @Override
    public void update(String message) {

        Platform.runLater(() -> {
            viewTextArea.appendText(  message + "\n");
        });

    }
}
package com.qiankun.im.controller;

import com.qiankun.im.netty.NettyClient;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;

import java.io.IOException;

public class HelloApplication extends Application {

    private NettyClient nettyClient=new NettyClient();
    @Override
    public void start(Stage stage) throws IOException {
        FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml"));
        Scene scene = new Scene(fxmlLoader.load(), 320, 240);
        stage.setTitle("IM!");
        stage.setScene(scene);
        // 你的对象,例如 NettyClient
        HelloController helloController = fxmlLoader.getController();
        helloController.setNettyClient(nettyClient);
        stage.show();

    }
    public void stop() throws Exception {
        super.stop();
        // 在应用程序关闭时关闭 NettyClient
        if (nettyClient != null) {
            nettyClient.close();
        }

    }


    public static void main(String[] args) {
        launch();
    }
}
package com.qiankun.im.message;

public interface MessageObserver {
    void update(String message);
}

package com.qiankun.im.message;

import java.util.ArrayList;
import java.util.List;


public  class MessageSubject  {

    private static final List<MessageObserver> observers = new ArrayList<>();

    public static void addObserver(MessageObserver observer) {
        observers.add(observer);
    }

    public static void removeObserver(MessageObserver observer) {
        observers.remove(observer);
    }

    public  static void notifyObservers(String message) {
        for (MessageObserver observer : observers) {
            observer.update(message);
        }
    }
}

package com.qiankun.im.netty;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;

public class NettyClient {

    Channel channel;
    EventLoopGroup eventLoopGroup = new NioEventLoopGroup();

    public NettyClient() {

        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class)
                    .handler(new TcpClientInitializer());
                    !!!!!!!这里换成自己的服务器地址!!!!!!!
            ChannelFuture future = bootstrap.connect("127.0.0.1", 70).sync();
            future.addListener((ChannelFutureListener) s -> {
                if (s.isSuccess()) {
                    // 连接成功,可以发送消息
                    System.err.println("连接服务器成功");
                } else {
                    // 连接失败
                    System.err.println("连接服务器失败: " + s.cause().getMessage());
                }
            });
            this.channel = future.channel();
        } catch (Exception e) {
            e.printStackTrace(); // 添加异常信息打印,以便排查连接问题
        }
    }

    public void sendMessage(String message) {
        try {
            if (channel != null && channel.isActive()) {
                channel.writeAndFlush(message);
            } else {
                System.err.println("Channel is not active. Unable to send message: " + message);
            }
        } catch (Exception e) {
            e.printStackTrace(); // 添加异常信息打印,以便排查发送消息时的问题
        }
    }


    public void close() {
        // 关闭连接
        if (channel != null) {
            try {
                channel.close().sync(); // sync() 等待通道关闭
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        eventLoopGroup.shutdownGracefully();
    }
}

package com.qiankun.im.netty;



import com.qiankun.im.message.MessageSubject;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

public class TcpClientHandler extends SimpleChannelInboundHandler<String> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        System.out.println("客户端收到来自"+ctx.channel().remoteAddress()+"的消息:"+msg);
        MessageSubject.notifyObservers(msg);
       // ctx.channel().writeAndFlush("我是客户端");
    }
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush("客户端请求连接");
    }

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


}

package com.qiankun.im.netty;



import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;

public class TcpClientInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,4,0,4));
        pipeline.addLast(new LengthFieldPrepender(4));
        pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
        pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
        pipeline.addLast(new TcpClientHandler());
    }
}

package com.qiankun.im;

import com.qiankun.im.controller.HelloApplication;
import javafx.application.Application;

public class Main {
    public static void main(String[] args) {
        Application.launch(HelloApplication.class);
    }
}

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<VBox alignment="CENTER" spacing="20.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/17.0.2-ea" fx:controller="com.qiankun.im.controller.HelloController">
    <padding>
        <Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
    </padding>
    <Label text="消息接收区" />
    <TextArea fx:id="viewTextArea" editable="false" wrapText="true" />

    <Label fx:id="sendText" />
    <TextField fx:id="inputField" prefHeight="35.0" prefWidth="518.0" promptText="回车/点击发送发送按钮" />
    <Button onAction="#onHelloButtonClick" text="发送" />
</VBox>

5.服务端代码

package com.inter.demo;


import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class SocketServer {
    public static void main(String[] args) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {

            // 创建TCP服务器的引导程序
            ServerBootstrap tcpBootstrap = new ServerBootstrap();
            //参数为两个事件组 前面的用来接收请求 后面的用来处理
            tcpBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    //childHandler 设置请求到了之后进行处理的处理器
                    .childHandler(new TcpServerInitializer());

            // 创建UDP服务器的引导程序
         
            // 绑定TCP端口
            ChannelFuture tcpFuture = tcpBootstrap.bind(70).sync();
            // 绑定UDP端口
       

            // 等待服务器关闭
            tcpFuture.channel().closeFuture().sync();
       


        } finally {
            //关闭事件组
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
package com.inter.demo;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;


public class TcpServerInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
        pipeline.addLast(new LengthFieldPrepender(4));
        pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
        pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
        pipeline.addLast(new TcpServerHandler());
    }

}
package com.inter.demo;

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
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.util.concurrent.EventExecutorGroup;
import io.netty.util.concurrent.GlobalEventExecutor;

public class TcpServerHandler extends SimpleChannelInboundHandler<String> {
    // 创建一个 ChannelGroup 来跟踪活动连接
    private static final  ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
//    private static final ChannelGroup channelGroup = ChannelGroupManager.getChannelGroup();

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        System.out.println("TCP服务端收到来自"+ctx.channel().remoteAddress()+"的"+msg);
        //ctx.channel().writeAndFlush(ctx.channel().id().asLongText());
        channelGroup.writeAndFlush(ctx.channel().id()+":"+msg);
    }

    // 在连接建立时添加 Channel 到 ChannelGroup
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        Channel incoming = ctx.channel();
        channelGroup.add(incoming);
        System.out.println("新连接:" + incoming.id());
        // 打印当前连接数
        System.out.println("当前活动连接数:" + channelGroup.size());
    }

    // 在连接关闭时从 ChannelGroup 中移除 Channel
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        Channel incoming = ctx.channel();
        channelGroup.remove(incoming);
    }

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

6.点击maven的package打包,运行如下

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值