This commit is contained in:
wujiawei 2023-02-12 09:10:47 +08:00 committed by wujiawei
parent 315631c7e3
commit f5d027cc06
24 changed files with 1191 additions and 9 deletions

View File

@ -1,6 +1,4 @@
原理分析
内网穿透的实现过程主要分三步
原理分析 内网穿透的实现过程主要分三步
1、启动服务端这时服务端监听了两个端口1600116002可根据启动参数修改
@ -14,10 +12,6 @@
这三步最终形成了(访客-代理-客户端-真实服务)完整的通道。
启动服务端 java -jar cc-server.jar 16001 16002
启动服务端
java -jar cc-server.jar 16001 16002
启动客户端
java -jar cc-client.jar 127.0.0.1 16001 8080
启动客户端 java -jar cc-client.jar 127.0.0.1 16001 8080

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>netty-proxy</artifactId>
<groupId>com.wu</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>netty-proxy-client</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.wu</groupId>
<artifactId>netty-proxy-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>top.wu2020</groupId>
<artifactId>wu-framework-web</artifactId>
<version>1.1.9-JDK1.8-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,17 @@
package com.luck.client;
public class ClientStart {
public static void main(String[] args) throws Exception {
if (null != args && args.length == 3) {
int realPort = Integer.parseInt(args[2]);
int serverPort = Integer.parseInt(args[1]);
String serverIp = args[0];
Constant.serverIp = serverIp;
Constant.serverPort = serverPort;
Constant.realPort = realPort;
}
// 连接代理服务
ProxySocket.connectProxyServer();
}
}

View File

@ -0,0 +1,87 @@
package com.luck.client;
import io.netty.channel.Channel;
import io.netty.util.AttributeKey;
import io.netty.util.internal.StringUtil;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class Constant {
/**
* 绑定访客id
*/
public static final AttributeKey<String> VID = AttributeKey.newInstance("vid");
/**
* 代理服务channel
*/
public static Channel proxyChannel = null;
/**
* 访客代理服务channel
*/
public static Map<String, Channel> vpc = new ConcurrentHashMap<>();
/**
* 访客真实服务channel
*/
public static Map<String, Channel> vrc = new ConcurrentHashMap<>();
/**
* 真实服务端口
*/
public static int realPort = 8080;
/**
* 服务端口
*/
public static int serverPort = 16001;
/**
* 服务IP
*/
public static String serverIp = "127.0.0.1";
/**
* 清除连接
*
* @param vid 访客ID
*/
public static void clearvpcvrc(String vid) {
if (StringUtil.isNullOrEmpty(vid)) {
return;
}
Channel clientChannel = vpc.get(vid);
if (null != clientChannel) {
clientChannel.attr(VID).set(null);
vpc.remove(vid);
}
Channel visitorChannel = vrc.get(vid);
if (null != visitorChannel) {
visitorChannel.attr(VID).set(null);
vrc.remove(vid);
}
}
/**
* 清除关闭连接
*
* @param vid 访客ID
*/
public static void clearvpcvrcAndClose(String vid) {
if (StringUtil.isNullOrEmpty(vid)) {
return;
}
Channel clientChannel = vpc.get(vid);
if (null != clientChannel) {
clientChannel.attr(VID).set(null);
vpc.remove(vid);
clientChannel.close();
}
Channel visitorChannel = vrc.get(vid);
if (null != visitorChannel) {
visitorChannel.attr(VID).set(null);
vrc.remove(vid);
visitorChannel.close();
}
}
}

View File

@ -0,0 +1,98 @@
package com.luck.client;
import com.luck.msg.MyMsg;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOption;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.internal.StringUtil;
public class ProxyHandler extends SimpleChannelInboundHandler<MyMsg> {
@Override
public void channelRead0(ChannelHandlerContext ctx, MyMsg myMsg) {
// 客户端读取到代理过来的数据了
byte type = myMsg.getType();
String vid = new String(myMsg.getData());
switch (type) {
case MyMsg.TYPE_HEARTBEAT:
break;
case MyMsg.TYPE_CONNECT:
RealSocket.connectRealServer(vid);
break;
case MyMsg.TYPE_DISCONNECT:
Constant.clearvpcvrcAndClose(vid);
break;
case MyMsg.TYPE_TRANSFER:
// 把数据转到真实服务
ByteBuf buf = ctx.alloc().buffer(myMsg.getData().length);
buf.writeBytes(myMsg.getData());
String visitorId = ctx.channel().attr(Constant.VID).get();
Channel rchannel = Constant.vrc.get(visitorId);
if (null != rchannel) {
rchannel.writeAndFlush(buf);
}
break;
default:
// 操作有误
}
// 客户端发数据到真实服务了
}
@Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
String vid = ctx.channel().attr(Constant.VID).get();
if (StringUtil.isNullOrEmpty(vid)) {
super.channelWritabilityChanged(ctx);
return;
}
Channel realChannel = Constant.vrc.get(vid);
if (realChannel != null) {
realChannel.config().setOption(ChannelOption.AUTO_READ, ctx.channel().isWritable());
}
super.channelWritabilityChanged(ctx);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
String vid = ctx.channel().attr(Constant.VID).get();
if (StringUtil.isNullOrEmpty(vid)) {
super.channelInactive(ctx);
return;
}
Channel realChannel = Constant.vrc.get(vid);
if (realChannel != null && realChannel.isActive()) {
realChannel.close();
}
super.channelInactive(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
cause.printStackTrace();
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) evt;
switch (event.state()) {
case READER_IDLE:
ctx.channel().close();
break;
case WRITER_IDLE:
MyMsg myMsg = new MyMsg();
myMsg.setType(MyMsg.TYPE_HEARTBEAT);
ctx.channel().writeAndFlush(myMsg);
break;
case ALL_IDLE:
break;
}
}
}
}

View File

@ -0,0 +1,101 @@
package com.luck.client;
import com.luck.msg.MyMsg;
import com.luck.msg.MyMsgDecoder;
import com.luck.msg.MyMsgEncoder;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.internal.StringUtil;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ProxySocket {
/**
* 重连代理服务
*/
private static final ScheduledExecutorService reconnectExecutor = Executors.newSingleThreadScheduledExecutor();
private static EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
public static Channel connectProxyServer() throws Exception {
reconnectExecutor.scheduleAtFixedRate(() -> {
try {
connectProxyServer(null);
} catch (Exception e) {
e.printStackTrace();
}
}, 3, 3, TimeUnit.SECONDS);
return connectProxyServer(null);
}
public static Channel connectProxyServer(String vid) throws Exception {
if (StringUtil.isNullOrEmpty(vid)) {
if (Constant.proxyChannel == null || !Constant.proxyChannel.isActive()) {
newConnect(null);
}
return null;
} else {
Channel channel = Constant.vpc.get(vid);
if (null == channel) {
newConnect(vid);
channel = Constant.vpc.get(vid);
}
return channel;
}
}
private static void newConnect(String vid) throws InterruptedException {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new MyMsgDecoder(Integer.MAX_VALUE, 0, 4, -4, 0));
pipeline.addLast(new MyMsgEncoder());
pipeline.addLast(new IdleStateHandler(40, 8, 0));
pipeline.addLast(new ProxyHandler());
}
});
bootstrap.connect(Constant.serverIp, Constant.serverPort).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
// 客户端链接代理服务器成功
Channel channel = future.channel();
if (StringUtil.isNullOrEmpty(vid)) {
// 告诉服务端这条连接是client的连接
MyMsg myMsg = new MyMsg();
myMsg.setType(MyMsg.TYPE_CONNECT);
myMsg.setData("client".getBytes());
channel.writeAndFlush(myMsg);
Constant.proxyChannel = channel;
} else {
// 告诉服务端这条连接是vid的连接
MyMsg myMsg = new MyMsg();
myMsg.setType(MyMsg.TYPE_CONNECT);
myMsg.setData(vid.getBytes());
channel.writeAndFlush(myMsg);
// 客户端绑定通道关系
Constant.vpc.put(vid, channel);
channel.attr(Constant.VID).set(vid);
Channel realChannel = Constant.vrc.get(vid);
if (null != realChannel) {
realChannel.config().setOption(ChannelOption.AUTO_READ, true);
}
}
}
}
});
}
}

View File

@ -0,0 +1,74 @@
package com.luck.client;
import com.luck.msg.MyMsg;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOption;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.internal.StringUtil;
public class RealHandler extends SimpleChannelInboundHandler<ByteBuf> {
@Override
public void channelRead0(ChannelHandlerContext ctx, ByteBuf buf) {
// 客户读取到真实服务数据了
byte[] bytes = new byte[buf.readableBytes()];
buf.readBytes(bytes);
MyMsg myMsg = new MyMsg();
myMsg.setType(MyMsg.TYPE_TRANSFER);
myMsg.setData(bytes);
String vid = ctx.channel().attr(Constant.VID).get();
if (StringUtil.isNullOrEmpty(vid)) {
return;
}
Channel proxyChannel = Constant.vpc.get(vid);
if (null != proxyChannel) {
proxyChannel.writeAndFlush(myMsg);
}
// 客户端发送真实数据到代理了
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
String vid = ctx.channel().attr(Constant.VID).get();
if (StringUtil.isNullOrEmpty(vid)) {
super.channelInactive(ctx);
return;
}
Channel proxyChannel = Constant.vpc.get(vid);
if (proxyChannel != null) {
MyMsg myMsg = new MyMsg();
myMsg.setType(MyMsg.TYPE_DISCONNECT);
myMsg.setData(vid.getBytes());
proxyChannel.writeAndFlush(myMsg);
}
super.channelInactive(ctx);
}
@Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
String vid = ctx.channel().attr(Constant.VID).get();
if (StringUtil.isNullOrEmpty(vid)) {
super.channelWritabilityChanged(ctx);
return;
}
Channel proxyChannel = Constant.vpc.get(vid);
if (proxyChannel != null) {
proxyChannel.config().setOption(ChannelOption.AUTO_READ, ctx.channel().isWritable());
}
super.channelWritabilityChanged(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
}
}

View File

@ -0,0 +1,59 @@
package com.luck.client;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.internal.StringUtil;
public class RealSocket {
static EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
/**
* 连接真实服务
*
* @param vid 访客ID
* @return
*/
public static Channel connectRealServer(String vid) {
if (StringUtil.isNullOrEmpty(vid)) {
return null;
}
Channel channel = Constant.vrc.get(vid);
if (null == channel) {
newConnect(vid);
channel = Constant.vrc.get(vid);
}
return channel;
}
private static void newConnect(String vid) {
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new RealHandler());
}
});
bootstrap.connect("127.0.0.1", Constant.realPort).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
// 客户端链接真实服务成功
future.channel().config().setOption(ChannelOption.AUTO_READ, false);
future.channel().attr(Constant.VID).set(vid);
Constant.vrc.put(vid, future.channel());
ProxySocket.connectProxyServer(vid);
}
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,17 @@
package com.luck.client.controller;
import com.wu.framework.inner.layer.web.EasyController;
import com.wu.framework.response.Result;
import com.wu.framework.response.ResultFactory;
import io.swagger.annotations.Api;
import org.springframework.web.bind.annotation.GetMapping;
@Api(tags = "客户端client")
@EasyController("/client")
public class ClientController {
@GetMapping("/version")
public Result version() {
return ResultFactory.successOf("客户端版本");
}
}

View File

@ -0,0 +1,2 @@
server:
port: 1003

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>netty-proxy</artifactId>
<groupId>com.wu</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>netty-proxy-common</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.74.Final</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,58 @@
package com.luck.msg;
import java.util.Arrays;
public class MyMsg {
/**
* 心跳
*/
public static final byte TYPE_HEARTBEAT = 0X00;
/**
* 连接成功
*/
public static final byte TYPE_CONNECT = 0X01;
/**
* 数据传输
*/
public static final byte TYPE_TRANSFER = 0X02;
/**
* 连接断开
*/
public static final byte TYPE_DISCONNECT = 0X09;
/**
* 数据类型
*/
private byte type;
/**
* 消息传输数据
*/
private byte[] data;
public byte getType() {
return type;
}
public void setType(byte type) {
this.type = type;
}
public byte[] getData() {
return data;
}
public void setData(byte[] data) {
this.data = data;
}
@Override
public String toString() {
return "MyMsg [type=" + type + ", data=" + Arrays.toString(data) + "]";
}
}

View File

@ -0,0 +1,41 @@
package com.luck.msg;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
public class MyMsgDecoder extends LengthFieldBasedFrameDecoder {
public MyMsgDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment,
int initialBytesToStrip) {
super(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip);
}
public MyMsgDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment,
int initialBytesToStrip, boolean failFast) {
super(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip, failFast);
}
@Override
protected MyMsg decode(ChannelHandlerContext ctx, ByteBuf in2) throws Exception {
ByteBuf in = (ByteBuf) super.decode(ctx, in2);
if (in == null) {
return null;
}
if (in.readableBytes() < 4) {
return null;
}
MyMsg myMsg = new MyMsg();
int dataLength = in.readInt();
byte type = in.readByte();
myMsg.setType(type);
byte[] data = new byte[dataLength - 5];
in.readBytes(data);
myMsg.setData(data);
in.release();
return myMsg;
}
}

View File

@ -0,0 +1,28 @@
package com.luck.msg;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
public class MyMsgEncoder extends MessageToByteEncoder<MyMsg> {
public MyMsgEncoder() {
}
@Override
protected void encode(ChannelHandlerContext ctx, MyMsg msg, ByteBuf out) throws Exception {
int bodyLength = 5;
if (msg.getData() != null) {
bodyLength += msg.getData().length;
}
out.writeInt(bodyLength);
out.writeByte(msg.getType());
if (msg.getData() != null) {
out.writeBytes(msg.getData());
}
}
}

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>netty-proxy</artifactId>
<groupId>com.wu</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>netty-proxy-server</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.wu</groupId>
<artifactId>netty-proxy-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>top.wu2020</groupId>
<artifactId>wu-framework-web</artifactId>
<version>1.1.9-JDK1.8-SNAPSHOT</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,113 @@
package com.luck.server;
import com.luck.msg.MyMsg;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.internal.StringUtil;
public class ClientHandler extends SimpleChannelInboundHandler<MyMsg> {
@Override
public void channelRead0(ChannelHandlerContext ctx, MyMsg myMsg) {
// 代理服务器读到客户端数据了
byte type = myMsg.getType();
switch (type) {
case MyMsg.TYPE_HEARTBEAT:
MyMsg hb = new MyMsg();
hb.setType(MyMsg.TYPE_HEARTBEAT);
ctx.channel().writeAndFlush(hb);
break;
case MyMsg.TYPE_CONNECT:
String vid = new String(myMsg.getData());
if (StringUtil.isNullOrEmpty(vid) || "client".equals(vid)) {
Constant.clientChannel = ctx.channel();
} else {
// 绑定访客和客户端的连接
Channel visitorChannel = Constant.vvc.get(vid);
if (null != visitorChannel) {
ctx.channel().attr(Constant.VID).set(vid);
Constant.vcc.put(vid, ctx.channel());
// 通道绑定完成可以读取访客数据
visitorChannel.config().setOption(ChannelOption.AUTO_READ, true);
}
}
break;
case MyMsg.TYPE_DISCONNECT:
String disVid = new String(myMsg.getData());
Constant.clearVccVvcAndClose(disVid);
break;
case MyMsg.TYPE_TRANSFER:
// 把数据转到用户服务
ByteBuf buf = ctx.alloc().buffer(myMsg.getData().length);
buf.writeBytes(myMsg.getData());
String visitorId = ctx.channel().attr(Constant.VID).get();
Channel vchannel = Constant.vvc.get(visitorId);
if (null != vchannel) {
vchannel.writeAndFlush(buf);
}
break;
default:
// 操作有误
}
// 代理服务器发送数据到用户了
}
@Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
String vid = ctx.channel().attr(Constant.VID).get();
if (StringUtil.isNullOrEmpty(vid)) {
super.channelWritabilityChanged(ctx);
return;
}
Channel visitorChannel = Constant.vvc.get(vid);
if (visitorChannel != null) {
visitorChannel.config().setOption(ChannelOption.AUTO_READ, ctx.channel().isWritable());
}
super.channelWritabilityChanged(ctx);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
String vid = ctx.channel().attr(Constant.VID).get();
if (StringUtil.isNullOrEmpty(vid)) {
super.channelInactive(ctx);
return;
}
Channel visitorChannel = Constant.vvc.get(vid);
if (visitorChannel != null && visitorChannel.isActive()) {
// 数据发送完成后再关闭连接解决http1.0数据传输问题
visitorChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
visitorChannel.close();
} else {
ctx.channel().close();
}
Constant.clearVccVvc(vid);
super.channelInactive(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) evt;
switch (event.state()) {
case READER_IDLE:
ctx.channel().close();
break;
case WRITER_IDLE:
break;
case ALL_IDLE:
break;
}
}
}
}

View File

@ -0,0 +1,82 @@
package com.luck.server;
import io.netty.channel.Channel;
import io.netty.util.AttributeKey;
import io.netty.util.internal.StringUtil;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class Constant {
/**
* 绑定channel_id
*/
public static final AttributeKey<String> VID = AttributeKey.newInstance("vid");
/**
* 客户端服务channel
*/
public static Channel clientChannel = null;
/**
* 访客客户服务channel
*/
public static Map<String, Channel> vcc = new ConcurrentHashMap<>();
/**
* 访客访客服务channel
*/
public static Map<String, Channel> vvc = new ConcurrentHashMap<>();
/**
* 服务代理端口
*/
public static int visitorPort = 16002;
/**
* 服务端口
*/
public static int serverPort = 16001;
/**
* 清除连接
*
* @param vid 访客ID
*/
public static void clearVccVvc(String vid) {
if (StringUtil.isNullOrEmpty(vid)) {
return;
}
Channel clientChannel = vcc.get(vid);
if (null != clientChannel) {
clientChannel.attr(VID).set(null);
vcc.remove(vid);
}
Channel visitorChannel = vvc.get(vid);
if (null != visitorChannel) {
visitorChannel.attr(VID).set(null);
vvc.remove(vid);
}
}
/**
* 清除关闭连接
*
* @param vid 访客ID
*/
public static void clearVccVvcAndClose(String vid) {
if (StringUtil.isNullOrEmpty(vid)) {
return;
}
Channel clientChannel = vcc.get(vid);
if (null != clientChannel) {
clientChannel.attr(VID).set(null);
vcc.remove(vid);
clientChannel.close();
}
Channel visitorChannel = vvc.get(vid);
if (null != visitorChannel) {
visitorChannel.attr(VID).set(null);
vvc.remove(vid);
visitorChannel.close();
}
}
}

View File

@ -0,0 +1,61 @@
package com.luck.server;
import com.luck.msg.MyMsgDecoder;
import com.luck.msg.MyMsgEncoder;
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.timeout.IdleStateHandler;
public class ServerSocket {
private static EventLoopGroup bossGroup = new NioEventLoopGroup();
private static EventLoopGroup workerGroup = new NioEventLoopGroup();
private static ChannelFuture channelFuture;
/**
* 启动服务端
*
* @throws Exception
*/
public static void startServer() throws Exception {
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new MyMsgDecoder(Integer.MAX_VALUE, 0, 4, -4, 0));
pipeline.addLast(new MyMsgEncoder());
pipeline.addLast(new IdleStateHandler(40, 10, 0));
pipeline.addLast(new ClientHandler());
}
});
channelFuture = b.bind(Constant.serverPort).sync();
channelFuture.addListener((ChannelFutureListener) channelFuture -> {
// 服务器已启动
});
channelFuture.channel().closeFuture().sync();
} finally {
shutdown();
// 服务器已关闭
}
}
public static void shutdown() {
if (channelFuture != null) {
channelFuture.channel().close().syncUninterruptibly();
}
if ((bossGroup != null) && (!bossGroup.isShutdown())) {
bossGroup.shutdownGracefully();
}
if ((workerGroup != null) && (!workerGroup.isShutdown())) {
workerGroup.shutdownGracefully();
}
}
}

View File

@ -0,0 +1,17 @@
package com.luck.server;
public class ServerStart {
public static void main(String[] args) throws Exception {
if (null != args && args.length == 2) {
int visitorPort = Integer.parseInt(args[1]);
int serverPort = Integer.parseInt(args[0]);
Constant.visitorPort = visitorPort;
Constant.serverPort = serverPort;
}
// 启动访客服务端用于接收访客请求
VisitorSocket.startServer();
// 启动代理服务端用于接收客户端请求
ServerSocket.startServer();
}
}

View File

@ -0,0 +1,103 @@
package com.luck.server;
import com.luck.msg.MyMsg;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOption;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.internal.StringUtil;
import java.util.UUID;
/**
* description: 访客处理程序
*
* @author 吴佳伟
* @date: 12.2.23 10:21
*/
public class VisitorHandler extends SimpleChannelInboundHandler<ByteBuf> {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// 访客连接上代理服务器了
Channel visitorChannel = ctx.channel();
// 先不读取访客数据
visitorChannel.config().setOption(ChannelOption.AUTO_READ, false);
// 生成访客ID
String vid = UUID.randomUUID().toString();
// 绑定访客通道
visitorChannel.attr(Constant.VID).set(vid);
Constant.vvc.put(vid, visitorChannel);
MyMsg myMsg = new MyMsg();
myMsg.setType(MyMsg.TYPE_CONNECT);
myMsg.setData(vid.getBytes());
Constant.clientChannel.writeAndFlush(myMsg);
super.channelActive(ctx);
}
@Override
public void channelRead0(ChannelHandlerContext ctx, ByteBuf buf) {
String vid = ctx.channel().attr(Constant.VID).get();
if (StringUtil.isNullOrEmpty(vid)) {
return;
}
byte[] bytes = new byte[buf.readableBytes()];
buf.readBytes(bytes);
MyMsg myMsg = new MyMsg();
myMsg.setType(MyMsg.TYPE_TRANSFER);
myMsg.setData(bytes);
// 代理服务器发送数据到客户端了
Channel clientChannel = Constant.vcc.get(vid);
clientChannel.writeAndFlush(myMsg);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
String vid = ctx.channel().attr(Constant.VID).get();
if (StringUtil.isNullOrEmpty(vid)) {
super.channelInactive(ctx);
return;
}
Channel clientChannel = Constant.vcc.get(vid);
if (clientChannel != null && clientChannel.isActive()) {
clientChannel.config().setOption(ChannelOption.AUTO_READ, true);
// 通知客户端访客连接已经断开
MyMsg myMsg = new MyMsg();
myMsg.setType(MyMsg.TYPE_DISCONNECT);
myMsg.setData(vid.getBytes());
clientChannel.writeAndFlush(myMsg);
}
Constant.clearVccVvc(vid);
super.channelInactive(ctx);
}
@Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
Channel visitorChannel = ctx.channel();
String vid = visitorChannel.attr(Constant.VID).get();
if (StringUtil.isNullOrEmpty(vid)) {
super.channelWritabilityChanged(ctx);
return;
}
Channel clientChannel = Constant.vcc.get(vid);
if (clientChannel != null) {
clientChannel.config().setOption(ChannelOption.AUTO_READ, visitorChannel.isWritable());
}
super.channelWritabilityChanged(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}

View File

@ -0,0 +1,37 @@
package com.luck.server;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class VisitorSocket {
private static EventLoopGroup bossGroup = new NioEventLoopGroup();
private static EventLoopGroup workerGroup = new NioEventLoopGroup();
/**
* 启动服务代理
*
* @throws Exception
*/
public static void startServer() throws Exception {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new ChannelDuplexHandler());
pipeline.addLast(new VisitorHandler());
}
});
b.bind(Constant.visitorPort).get();
}
}

View File

@ -0,0 +1,17 @@
package com.luck.server.controller;
import com.wu.framework.inner.layer.web.EasyController;
import com.wu.framework.response.Result;
import com.wu.framework.response.ResultFactory;
import io.swagger.annotations.Api;
import org.springframework.web.bind.annotation.GetMapping;
@Api(tags = "服务端")
@EasyController("/server")
public class ServerController {
@GetMapping("/version")
public Result version() {
return ResultFactory.successOf("服务端端版本");
}
}

View File

@ -0,0 +1,2 @@
server:
port: 1003

68
pom.xml Normal file
View File

@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.wu</groupId>
<artifactId>netty-proxy</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>netty-proxy</name>
<description>netty-proxy</description>
<properties>
<java.version>19</java.version>
</properties>
<modules>
<module>netty-proxy-common</module>
<module>netty-proxy-server</module>
<module>netty-proxy-client</module>
</modules>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<repositories>
<repository>
<id>oss.snapshots</id>
<name>oss.sonatype.org</name>
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<build>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>