我们的聊天程序服务器使用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>
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();
}
}
用于处理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();
}
}
我们使用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>
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();
}
}
}
用于处理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();
}
}
因为使用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;
}
}
}
这样我们的项目客户端与服务器通信就可以了。