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>-->
<!-- <!– Default configuration for running with: mvn clean javafx:run –>-->
<!-- <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);
HelloController helloController = fxmlLoader.getController();
helloController.setNettyClient(nettyClient);
stage.show();
}
public void stop() throws Exception {
super.stop();
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();
} 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);
}
@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 {
ServerBootstrap tcpBootstrap = new ServerBootstrap();
tcpBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new TcpServerInitializer());
ChannelFuture tcpFuture = tcpBootstrap.bind(70).sync();
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> {
private static final ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("TCP服务端收到来自"+ctx.channel().remoteAddress()+"的"+msg);
channelGroup.writeAndFlush(ctx.channel().id()+":"+msg);
}
@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());
}
@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打包,运行如下