first commit

This commit is contained in:
chuzhongzai 2024-01-30 18:20:10 +08:00
commit 2138f97bd7
5 changed files with 262 additions and 0 deletions

68
pom.xml Normal file
View File

@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.demo</groupId>
<artifactId>SecertChat</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.101.Final</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>0.9.28</version>
<configuration>
<mainClass>com.demo.Main</mainClass>
<imageName>SecretChat</imageName>
<quickBuild>true</quickBuild>
<buildArgs>
<arg>--gc=G1</arg>
<arg>-H:+ReportExceptionStackTraces</arg>
</buildArgs>
<metadataRepository>
<enabled>true</enabled>
</metadataRepository>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>21</source>
<target>21</target>
<compilerArgs>--enable-preview</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,7 @@
package com.demo;
public class Main {
public static void main(String[] args) {
SecretChat.serving();
}
}

View File

@ -0,0 +1,29 @@
package com.demo;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import static java.lang.StringTemplate.STR;
public class Message {
//表示前端与后端建立连接并发送聊天id
public static final int INIT = 1;
//表示前端发送的聊天id无法在等待队列找到
public static final int WAIT = 11;
//表示前端发送的聊天id可以在等待队列找到
public static final int JOIN = 12;
//表示前端取消等待
public static final int CANCEL = 13;
//表示对方断开连接
public static final int DISCONNECT = 20;
//表示转发该消息
public static final int CONTENT = 30;
public static TextWebSocketFrame pairDisconnected(){
return new TextWebSocketFrame(STR."{\"type\":\{Message.DISCONNECT}}");
}
}

View File

@ -0,0 +1,140 @@
package com.demo;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler;
import java.util.HashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class SecretChat {
//等待队列
static TwoWayMap<String> pairCodeWithChannelId;
//已连接
static HashMap<String, Channel> channelId2Channel;
//聊天配对
static TwoWayMap<String> channelIdWithChannelId;
static ScheduledExecutorService scheduler;
static ObjectMapper objectMapper = new ObjectMapper();
public static void serving(){
pairCodeWithChannelId = new TwoWayMap<>();
channelId2Channel = new HashMap<>();
channelIdWithChannelId = new TwoWayMap<>();
scheduler = Executors.newScheduledThreadPool(1);
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new HttpServerCodec()); // HTTP 协议解析用于握手阶段
pipeline.addLast(new HttpObjectAggregator(65536)); // HTTP 协议解析用于握手阶段
pipeline.addLast(new WebSocketServerCompressionHandler()); // WebSocket 数据压缩扩展
pipeline.addLast(new WebSocketServerProtocolHandler("/", null, true)); // WebSocket 握手控制帧处理
pipeline.addLast(new MyWebSocketServerHandler());
}
});
ChannelFuture sync = b.bind(7777).sync();
sync.channel().closeFuture().sync();
}catch (Exception e){
e.printStackTrace();
}finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
static class MyWebSocketServerHandler extends SimpleChannelInboundHandler<WebSocketFrame> {
@Override
public void channelActive(ChannelHandlerContext ctx){
channelId2Channel.put(ctx.channel().id().asShortText(), ctx.channel());
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame frame){
if (frame instanceof TextWebSocketFrame textWebSocketFrame) { // 此处仅处理 Text Frame
JsonNode node;
try {
node = objectMapper.readTree(textWebSocketFrame.text());
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
//说明不是合法消息
if(!node.has("type"))
return;
switch (node.get("type").asInt()){
case Message.INIT -> {
String pairCode = node.get("pairCode").asText();
if(pairCodeWithChannelId.containsKey(pairCode)){ //如果等待队列存在该id,则建立连接
String preChannelId = pairCodeWithChannelId.remove(pairCode);
String channelId = ctx.channel().id().asShortText();
channelIdWithChannelId.put(preChannelId, channelId);
ObjectNode joinNode = objectMapper.createObjectNode();
joinNode.put("type", Message.JOIN);
TextWebSocketFrame joinMessage = new TextWebSocketFrame(joinNode.toString());
channelId2Channel.get(preChannelId).writeAndFlush(joinMessage.copy());
ctx.channel().writeAndFlush(joinMessage);
} else { //否则加入等待队列
pairCodeWithChannelId.put(pairCode, ctx.channel().id().asShortText());
scheduler.schedule(() -> {
pairCodeWithChannelId.remove(pairCode);
}, 60, TimeUnit.SECONDS);
ObjectNode joinNode = objectMapper.createObjectNode();
joinNode.put("type", Message.WAIT);
TextWebSocketFrame joinMessage = new TextWebSocketFrame(joinNode.toString());
ctx.channel().writeAndFlush(joinMessage);
}
}
case Message.CONTENT -> channelId2Channel.get(channelIdWithChannelId.get(ctx.channel().id().asShortText())).writeAndFlush(new TextWebSocketFrame(node.get("content").asText()));
case Message.CANCEL -> pairCodeWithChannelId.remove(node.get("pairCode").asText());
}
}
}
@Override
public void channelUnregistered(ChannelHandlerContext ctx){
channelId2Channel.remove(ctx.channel().id().asShortText());
//建立连接的一般分为两种:1.正在等待 2.已经建立聊天
if(pairCodeWithChannelId.containsKey(ctx.channel().id().asShortText())){
pairCodeWithChannelId.remove(ctx.channel().id().asShortText());
} else {
String channelId = channelIdWithChannelId.remove(ctx.channel().id().asShortText());
if(channelId != null && channelId2Channel.get(channelId) != null)
channelId2Channel.remove(channelId).writeAndFlush(Message.pairDisconnected());
}
}
}
}

View File

@ -0,0 +1,18 @@
package com.demo;
import java.util.HashMap;
public class TwoWayMap<String> extends HashMap<String, String> {
@Override
public String put(String key, String value) {
super.put(key, value);
return super.put(value, key);
}
@Override
public String remove(Object key) {
String result = super.remove(key);
super.remove(result);
return result;
}
}