first commit
This commit is contained in:
commit
2138f97bd7
68
pom.xml
Normal file
68
pom.xml
Normal 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>
|
||||||
7
src/main/java/com/demo/Main.java
Normal file
7
src/main/java/com/demo/Main.java
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package com.demo;
|
||||||
|
|
||||||
|
public class Main {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SecretChat.serving();
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/main/java/com/demo/Message.java
Normal file
29
src/main/java/com/demo/Message.java
Normal 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}}");
|
||||||
|
}
|
||||||
|
}
|
||||||
140
src/main/java/com/demo/SecretChat.java
Normal file
140
src/main/java/com/demo/SecretChat.java
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
18
src/main/java/com/demo/TwoWayMap.java
Normal file
18
src/main/java/com/demo/TwoWayMap.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user