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

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

一,服务器项目搭建

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

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




    <dependencies>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-mailartifactId>
        dependency>


        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-validationartifactId>
        dependency>

        <dependency>
            <groupId>org.msgpackgroupId>
            <artifactId>msgpack-coreartifactId>
            <version>${msgpack.version}version>
        dependency>

        <dependency>
            <groupId>com.github.ben-manes.caffeinegroupId>
            <artifactId>caffeineartifactId>
        dependency>


        <dependency>
            <groupId>com.chengroupId>
            <artifactId>cc_chat_protocolartifactId>
            <version>1.0-SNAPSHOTversion>
            <exclusions>
                <exclusion>
                    <artifactId>lombokartifactId>
                    <groupId>org.projectlombokgroupId>
                exclusion>
            exclusions>
        dependency>

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

        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>fastjsonartifactId>
            <version>${fastjson.version}version>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-thymeleafartifactId>
        dependency>

        <dependency>
            <groupId>cn.hutoolgroupId>
            <artifactId>hutool-allartifactId>
            <version>${hutool.version}version>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-devtoolsartifactId>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
            <exclusions>
                
                <exclusion>
                    <artifactId>spring-boot-starter-loggingartifactId>
                    <groupId>org.springframework.bootgroupId>
                exclusion>
            exclusions>
        dependency>

        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
        dependency>

        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>

        <dependency>
            <groupId>com.baomidougroupId>
            <artifactId>mybatis-plus-boot-starterartifactId>
            <version>${mybatis-plus.version}version>
        dependency>

        <dependency>
            <groupId>io.nettygroupId>
            <artifactId>netty-allartifactId>
            <version>${netty.version}version>
        dependency>

        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-log4j2artifactId>
        dependency>


        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        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.chengroupId>
      <artifactId>cc_chat_protocolartifactId>
      <version>1.0-SNAPSHOTversion>
    dependency>

    <dependency>
      <groupId>cn.hutoolgroupId>
      <artifactId>hutool-allartifactId>
      <version>5.8.15version>
    dependency>

    <dependency>
      <groupId>io.nettygroupId>
      <artifactId>netty-allartifactId>
      <version>4.1.53.Finalversion>
    dependency>

    <dependency>
      <groupId>org.slf4jgroupId>
      <artifactId>slf4j-apiartifactId>
      <version>2.0.0-alpha2version>
    dependency>

    <dependency>
      <groupId>org.apache.logging.log4jgroupId>
      <artifactId>log4j-coreartifactId>
      <version>2.11.1version>
    dependency>

    <dependency>
      <groupId>com.alibabagroupId>
      <artifactId>fastjsonartifactId>
      <version>1.2.79version>
    dependency>


    <dependency>
      <groupId>org.apache.logging.log4jgroupId>
      <artifactId>log4j-slf4j-implartifactId>
      <version>2.10.0version>
    dependency>

    
    <dependency>
      <groupId>com.lmaxgroupId>
      <artifactId>disruptorartifactId>
      <version>3.3.4version>
    dependency>

    <dependency>
      <groupId>junitgroupId>
      <artifactId>junitartifactId>
      <version>4.11version>
      <scope>testscope>
    dependency>

    <dependency>
      <groupId>org.msgpackgroupId>
      <artifactId>msgpack-coreartifactId>
      <version>0.9.1version>
    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;
        }
    }
}

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

你可能感兴趣的:(桌面聊天项目,spring,boot,服务器,后端,java)