springboot实现桌面聊天程序(二):项目搭建--客户端与服务器通信

我们的聊天程序服务器使用springboot和使用netty进行通讯。客户端使用javafx和netty。服务器项目和客户端的项目分别搭建两个项目: cc_chat_server和cc_chat.

一,服务器项目搭建

使用idea搭建,并引入maven依赖:

<properties>
        <java.version>1.8</java.version>
        <mybatis-plus.version>3.5.1</mybatis-plus.version>
        <netty.version>4.1.53.Final</netty.version>
        <hutool.version>5.8.15</hutool.version>
        <crypto.version>5.3.6.RELEASE</crypto.version>
        <fastjson.version>1.2.79</fastjson.version>
        <msgpack.version>0.9.1</msgpack.version>
    </properties>




    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
        </dependency>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

        <dependency>
            <groupId>org.msgpack</groupId>
            <artifactId>msgpack-core</artifactId>
            <version>${msgpack.version}</version>
        </dependency>

        <dependency>
            <groupId>com.github.ben-manes.caffeine</groupId>
            <artifactId>caffeine</artifactId>
        </dependency>


        <dependency>
            <groupId>com.chen</groupId>
            <artifactId>cc_chat_protocol</artifactId>
            <version>1.0-SNAPSHOT</version>
            <exclusions>
                <exclusion>
                    <artifactId>lombok</artifactId>
                    <groupId>org.projectlombok</groupId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-crypto</artifactId>
            <version>${crypto.version}</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>${hutool.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <!--排除logback-->
                <exclusion>
                    <artifactId>spring-boot-starter-logging</artifactId>
                    <groupId>org.springframework.boot</groupId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>

        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>${netty.version}</version>
        </dependency>

        <!-- 添加log4j2 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>


    </dependencies>

1,创建netty服务

netty是一个高性能的网络框架,使用netty我们可以很容易搭建一个网络服务器:

package com.chen.chatServer.netty;

/**
 * @author @Chenxc
 * @date 2023/8/19 10:02
 **/
@Component
public class ChatNettyServer{

    private Logger logger = LogManager.getLogger(ChatNettyServer.class);
   @Value("${netty.port}")
    private int port;
    private NioEventLoopGroup boss = null;
    private NioEventLoopGroup workers = null;
    private ServerBootstrap sb = null;


    @Lookup
    public ChatNettyServerHandler getHandler(){
        return null;
    }

    private void init(){
      logger.info("正在启动ChatNettyServer。。。。");
       try{
           boss = new NioEventLoopGroup(2);
           workers = new NioEventLoopGroup();
           sb = new ServerBootstrap();
           sb.group(boss, workers)
                   .channel(NioServerSocketChannel.class)
                   .option(ChannelOption.SO_BACKLOG, 1024)
                   .childHandler(new ChannelInitializer<SocketChannel>() {
                       @Override
                       protected void initChannel(SocketChannel ch) throws Exception {
                           ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(10*1024*1024,0,4,0,4));
                           ch.pipeline().addLast(new MsgPackDecoder());
                           ch.pipeline().addLast(new LengthFieldPrepender(4));
                           ch.pipeline().addLast(new MsgPackEncoder());
                           ch.pipeline().addLast("readTimeoutHandler",new ReadTimeoutHandler(60));
                           ch.pipeline().addLast(getHandler());
                       }
                   });
          sb.bind(port).sync().channel().closeFuture().sync();
       }catch (Exception e){
           logger.error("netty服务器ChatNettyServer启动异常:{}"+e.toString());
       }finally {
            if(null != boss){
                boss.shutdownGracefully();
            }
            if(null != workers){
                workers.shutdownGracefully();
            }
       }
   }

   private void stopServer(){
       if(null != boss){
           boss.shutdownGracefully();
       }
       if(null != workers){
           workers.shutdownGracefully();
       }
   }

   public void startNettyServer(){
        init();
   }

   public void stopNettyServer(){
       stopServer();
   }

}

2,netty服务器处理器

用于处理netty接受到请求:

package com.chen.chatServer.netty;
/**
 * @author @Chenxc
 * @date 2023/8/19 10:39
 **/
@Component()
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ChatNettyServerHandler extends ChannelInboundHandlerAdapter {
    private Logger logger = LogManager.getLogger(ChatNettyServerHandler.class);

    @Autowired
    private RequestHandler requestHandler;
    @Autowired
    private UserService userService;

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        logger.info("用户{}上线",ctx.channel().remoteAddress());
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        logger.info("用户{}下线",ctx.channel().remoteAddress());
        ClientCache.remove(ctx);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
       
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {

    }

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

}

3,启动netty服务

我们使用springboot搭建项目,所以需要在启动springboot时启动netty服务。实现

org.springframework.boot.CommandLineRunner
即可

/**
 * @author @Chenxc
 * @date 2023/8/19 10:26
 **/
@Component
public class StartNettyServer implements CommandLineRunner {
    @Autowired
    private ChatNettyServer chatNettyServer;
    @Override
    public void run(String... args) throws Exception {
        chatNettyServer.startNettyServer();
    }
}

这样在启动springboot时启动netty服务

二,客户端项目搭建

客户端使用javafx和netty。项目使用的jdk版本时1.8,所以Javafx在jdk1.8中是自带的。
maven依赖:

  <dependencies>
    <dependency>
      <groupId>com.chen</groupId>
      <artifactId>cc_chat_protocol</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>

    <dependency>
      <groupId>cn.hutool</groupId>
      <artifactId>hutool-all</artifactId>
      <version>5.8.15</version>
    </dependency>

    <dependency>
      <groupId>io.netty</groupId>
      <artifactId>netty-all</artifactId>
      <version>4.1.53.Final</version>
    </dependency>

    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>2.0.0-alpha2</version>
    </dependency>

    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-core</artifactId>
      <version>2.11.1</version>
    </dependency>

    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.79</version>
    </dependency>


    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-slf4j-impl</artifactId>
      <version>2.10.0</version>
    </dependency>

    <!--异步日志依赖-->
    <dependency>
      <groupId>com.lmax</groupId>
      <artifactId>disruptor</artifactId>
      <version>3.3.4</version>
    </dependency>

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>org.msgpack</groupId>
      <artifactId>msgpack-core</artifactId>
      <version>0.9.1</version>
    </dependency>

  </dependencies>

1,创建netty客户端

package com.chen.netty;

/** netty聊天客户端
 * @author @Chenxc
 * @date 2023/8/19 0:14
 **/
public class ChatNettyClient {
    public static final Logger LOGGER = LogManager.getLogger(ChatNettyClient.class);
    private static NioEventLoopGroup group;
    public volatile static boolean ONLINE = false;
    public volatile static ChannelHandlerContext context;


    private static void init(){
        try {
            group = new NioEventLoopGroup();
            Bootstrap bootstrap = new Bootstrap();
            Channel channel = bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(10*1024*1024,0,4,0,4));
                            ch.pipeline().addLast(new MsgPackDecoder());
                            ch.pipeline().addLast(new LengthFieldPrepender(4));
                            ch.pipeline().addLast(new MsgPackEncoder());
                            ch.pipeline().addLast("readTimeoutHandler",new ReadTimeoutHandler(30));
                            ch.pipeline().addLast(new ChatNettyHandler());
                        }
                    }).connect(Config.HOST, Config.NETTY_PORT).sync().channel();
            channel.closeFuture().sync();
        }catch (Exception e){
            LOGGER.error("连接服务器错误:{}",e.toString());
        }finally {
            if(null != group){
                group.shutdownGracefully();
            }
        }
    }

    public static void connectServer(){
        ThreadPool.getPool().execute(()->{init();});
    }

    public static void disconnect(){
        if(null != group){
            group.shutdownGracefully();
        }
    }

}

2,netty客户端处理器

用于处理netty客户端接受到返回:

package com.chen.netty;
/**
 * @author @Chenxc
 * @date 2023/8/19 10:28
 **/
public class ChatNettyHandler extends ChannelInboundHandlerAdapter {
    public static final Logger LOGGER = LogManager.getLogger(ChatNettyHandler.class);

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        LOGGER.info("成功连接");
        SendUtil.reLogin(UserUtil.currentUser);
        ChatNettyClient.context = ctx;
        ChatNettyClient.ONLINE = true;
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        LOGGER.error("与服务器断开连接,正在重新连接...");
        ChatNettyClient.context = null;
        ChatNettyClient.ONLINE = false;
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {

    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        LOGGER.info("连接异常");
        ChatNettyClient.context = null;
        ChatNettyClient.ONLINE = false;
        System.err.println(cause);
        ctx.close();
    }
}

3,启动netty客户端

因为使用javafx,我们只需要创建一个启动类并继承

javafx.application.Application

并重写start(Stage primaryStage)方法即可。

package com.chen;
/**
 * Hello world!
 *
 */

public class App extends Application {
    public static final Logger LOGGER =LogManager.getLogger(App.class);
    public static Stage stage;
    private static final int W = 380;
    private static  final int H = 480;

    public static void main( String[] args ) {
        launch(args);
    }


    @Override
    public void start(Stage primaryStage) throws Exception {
        stage = primaryStage;
        String fxml = "fxml/login.fxml";
        URL location = App.class.getClassLoader().getResource(fxml);
        InputStream inputStream = App.class.getClassLoader().getResourceAsStream(fxml);
        Pane pane = loadFXML(stage, location, inputStream);
        if (pane == null) {
            return;
        }
        stage.setWidth(W);
        stage.setHeight(H);
        primaryStage.getIcons().add(new Image("img/logo.png"));
        popupWindow(pane, stage, "cc聊天");
        connectNetty();
        stage.setOnCloseRequest(event -> {
            ChatNettyClient.disconnect();
            System.exit(0);
    }

    private void connectNetty() {
        Timer timer = new Timer();
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                if (!ChatNettyClient.ONLINE) {
                    ChatNettyClient.connectServer();
                } else {
                    SendUtil.sendHeart();
                }
            }
        };
        timer.scheduleAtFixedRate(timerTask,0,6000);
    }

    private static void showStage(Pane root, Stage stage, Stage parent, String stageName) {
        Scene scene = new Scene(root);
        //if (null != App.stage && null != stage && !App.stage.equals(stage)) {
        stage.setTitle(stageName);
        //}
        stage.setScene(scene);
        stage.centerOnScreen();
        stage.setResizable(false);
        if (parent != null && !stage.equals(parent)) {
            stage.initModality(Modality.WINDOW_MODAL);
            //stage.initOwner(parent);
        }
        stage.show();
    }


    public static void popupWindow(Pane root, Stage stage, String stageName) {
        showStage(root, stage, App.stage, stageName);
    }
    /**
     * 加载fxml
     * @param stage
     * @param location
     * @param in
     * @return
     */
    public static Pane loadFXML(Stage stage, URL location, InputStream in) {
        FXMLLoader loader = new FXMLLoader();
        //loader.setControllerFactory(context::getBean);
        loader.setBuilderFactory(new JavaFXBuilderFactory());
        loader.setLocation(location);
        try {
            Pane pane = (Pane) loader.load(in);
            FxPageInterface page = (FxPageInterface) loader.getController();
            page.setMain(stage);
            File tempFile = new File(location.getPath().trim());
            String fileName = tempFile.getName();
            pages.put(fileName, page);
            if (loader.getController() instanceof UploadCallBackInterface) {
                pagesUpload.put(fileName, loader.getController());
            }
            if (loader.getController() instanceof UserAvatarInLogin) {
                getAvatar.put(fileName, loader.getController());
            }
            return pane;
        } catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }
    }
}

这样我们的项目客户端与服务器通信就可以了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值