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