Skip to content

Commit

Permalink
更新说明文档,并将default无端口相关的类进行单例化,避免过多的重复类创建
Browse files Browse the repository at this point in the history
  • Loading branch information
Pluto-Whong committed Apr 28, 2021
1 parent eae1407 commit 6353a77
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 104 deletions.
63 changes: 50 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,16 @@
## 打包使用

服务端-ServerApp打包:
```
修改ServerApp.java中serviceIp为公网服务器的IP

```shell
# 修改ServerApp.java中serviceIp为公网服务器的IP

mvn clean compile package -PserverApp
```

客户端-ClientApp打包:
```

```shell
mvn clean compile package -PclientApp
```

Expand All @@ -33,15 +36,22 @@ mvn clean compile package -PclientApp
4. 用浏览器访问serviceIp:listenPort便可以访问到内网的tomcat

## 参数解释
ServerApp:
CommonConstants:

|字段|解释|
|:-:|:-|
|serviceIp|公网服务器的IP|
|servicePort|服务端的控制端口,主要用来与客户端进行指令交互|
|listenPort|服务端的监听端口,也就是部署完成后,在外网访问 serviceIp:listenPort 的方式对内网应用进行访问|
|aesKey|交互密钥key,保证数据的秘密性,可以查看 SecretInteractiveModel.java 中的fullMessage和checkAutograph中确认密钥的使用方式。<br>如果你使用了secretAll方式进行部署,这个key还是数据加密的key,可以在 SecretPassway.java 中确认密钥的使用方式|
|destIp|要开放到公网的内网应用所在机器的IP|
|destPort|要开放到公网的内网应用的端口|
|aesKey|交互密钥key,保证数据的秘密性,可以查看 SecretInteractiveModel.java 中的fullMessage和checkAutograph中确认密钥的使用方式。<br>如果你使用了secretAll方式进行部署,这个key还是数据加密的key,可以在 SecretPassway.java 中确认密钥的使用方式<br>注意使用长度,windows版本的java只能用最大128长度的密钥|
|tokenKey|交互签名key,签名同aesKey|

ServerApp:

|字段|解释|
|:-:|:-|
|sslKeyStorePath|ssl证书的路径,默认方法只支持pkcs12的证书格式,使用这个证书可以做到https协议转http协议|
|sslKeyStorePassword|证书密码|
|createServerSocket|创建socket的方式,主要针对普通socket和sslSocket的方式进行封装,结合ssl证书使用|
Expand All @@ -50,15 +60,42 @@ ClientApp:

|字段|解释|
|:-:|:-|
|destIp|要开放到公网的内网应用所在机器的IP|
|destPort|要开放到公网的内网应用的端口|
|serviceIp|复用ServerApp的字段,所以要保证部署包和内网包的参数一致|
|servicePort|复用ServerApp的字段,所以要保证部署包和内网包的参数一致|
|listenPort|复用ServerApp的字段,所以要保证部署包和内网包的参数一致|
|aesKey|复用ServerApp的字段,所以要保证部署包和内网包的参数一致|
|tokenKey|复用ServerApp的字段,所以要保证部署包和内网包的参数一致|
|#secretHttpRoute:routes|http方式,根据不同host路由选择不同的目标应用|

## 内网穿透思路

因NAT网内CLIENT可以正常连接到SERVER端,并且能够保持一段时间的长连接,则由CLIENT发起连接,建立SOCKET对,在SERVER收到外部请求时,可以通过已经建立好的SOCKET将数据传输给CLIENT,CLIENT使用相同的方式将数据发送给指定的网络程序,网络程序回发数据后则按原路返回给请求方。
![时序图](./doc/sequence.svg)
![时序图](./doc/sequence.svg)

## 相关技术

|技术|体现点|
|:-:|:-|
|Socket|核心技术概念|
|NIO|nio.NioHallows,使用Selector作为注册监听器(多路复用),有事件唤起后会创建子线程进行异步处理|
|TCP粘包、拆包的解决|channel.LengthChannel,此处用的是一个大端序列的长度加消息内容的方式|
|线程管控|clientside.ClientControlThread、serverside.client.ClientServiceThread、serverside.listen.ServerListenThread作为独立管控及子线程异步处理的主要体现,亦可通过executor包下相关类进行追踪|
|HTTP路由|api.socketpart.HttpRouteSocketPart#routeHost,一个简单的对host头部字段的处理应用|
|消息加密|对AES、MD5联合的使用示例,channel.SecretInteractiveChannel、model.SecretInteractiveModel,对实际消息进行加密,增加辅助字段保证消息的真实性、准确性和完整性|
|计数门闩|utils.CountWaitLatch,类CountDownLatch,增加了countUp,不只受初始化的值决定,可以增加、减少,主要用来解决nio.NioHallows唤醒后批量channel注册的问题(等下,怎么感觉可以用读写锁来解决呢?)|
|线程池|虽然用了线程池,但默认的是Executors.newCachedThreadPool()来生成的,具体的还是需要根据机器来自定义线程池,主要还是对子线程的管控体现吧|

## 设计模式的使用

|设计模式|体现点|
|:-:|:-|
|单例模式|nio.NioHallows等|
|建造者模式|adapter的构建过程,三大核心Thread的config设置过程|
|适配器模式|IClientAdapter、IClientServiceAdapter、SimplePassway#write|
|桥接模式|三大核心Thread的config|
|组合模式|InteractiveSimpleClientAdapter#addMessageHandler、InteractiveProcessHandler|
|装饰器模式|InteractiveSimpleClientAdapter#addMessageHandler、InteractiveProcessHandler、ReadAheadPassValueAdapter#handlerList、HttpRouteClientConfig|
|外观模式|NatcrossExecutor|
|享元模式|SimplePassway#obtainByteBuffer|
|代理模式|channel下各个类|
|责任链模式|InteractiveProcessHandler、InteractiveSimpleClientAdapter#procMethod|
|中介者模式|api.IBelongControl|
|策略模式|三大核心Thread的config,适配不同的模式|
|模板模式|ISecret、AbsSocketPart、IHttpRouting、Channel、IExecutor等等一系列的接口定义|
|数据访问模式|InteractiveChannel|
|传输对象模式|日常|
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class DefaultReadAheadPassValueAdapter extends ReadAheadPassValueAdapter<

public DefaultReadAheadPassValueAdapter(IClientServiceConfig<InteractiveModel, InteractiveModel> config) {
super(config);
this.addLast(new DefaultInteractiveProcessHandler());
this.addLast(DefaultInteractiveProcessHandler.INSTANCE);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public class ReadAheadPassValueAdapter<R, W> implements IClientServiceAdapter {
/**
* 客户端服务配置
*/
private IClientServiceConfig<R, W> config;
private final IClientServiceConfig<R, W> config;

public ReadAheadPassValueAdapter(IClientServiceConfig<R, W> config) {
this.config = config;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@
* @since 2021-04-26 17:22:31
*/
public class DefaultInteractiveProcessHandler extends InteractiveProcessHandler {

public static final DefaultInteractiveProcessHandler INSTANCE = new DefaultInteractiveProcessHandler();

public DefaultInteractiveProcessHandler() {
this.addLast(new ClientControlProcess());
this.addLast(new ClientConnectProcess());
}
public DefaultInteractiveProcessHandler() {
this.addLast(ClientControlProcess.INSTANCE);
this.addLast(ClientConnectProcess.INSTANCE);
}

}
Original file line number Diff line number Diff line change
@@ -1,60 +1,62 @@
package person.pluto.natcross2.serverside.client.process;

import person.pluto.natcross2.channel.SocketChannel;
import person.pluto.natcross2.common.CommonFormat;
import person.pluto.natcross2.model.InteractiveModel;
import person.pluto.natcross2.model.NatcrossResultModel;
import person.pluto.natcross2.model.enumeration.InteractiveTypeEnum;
import person.pluto.natcross2.model.enumeration.NatcrossResultEnum;
import person.pluto.natcross2.model.interactive.ClientConnectModel;
import person.pluto.natcross2.serverside.listen.ListenServerControl;
import person.pluto.natcross2.serverside.listen.ServerListenThread;

/**
*
* <p>
* 请求建立隧道处理器
* </p>
*
* @author Pluto
* @since 2020-01-08 16:48:25
*/
public class ClientConnectProcess implements IProcess {

@Override
public boolean wouldProc(InteractiveModel recvInteractiveModel) {
InteractiveTypeEnum interactiveTypeEnum = InteractiveTypeEnum
.getEnumByName(recvInteractiveModel.getInteractiveType());
return InteractiveTypeEnum.CLIENT_CONNECT.equals(interactiveTypeEnum);
}

@Override
public boolean processMothed(SocketChannel<? extends InteractiveModel, ? super InteractiveModel> socketChannel,
InteractiveModel recvInteractiveModel) throws Exception {
ClientConnectModel clientConnectModel = recvInteractiveModel.getData().toJavaObject(ClientConnectModel.class);
Integer listenPort = CommonFormat.getSocketPortByPartKey(clientConnectModel.getSocketPartKey());

ServerListenThread serverListenThread = ListenServerControl.get(listenPort);

if (serverListenThread == null) {
socketChannel.writeAndFlush(InteractiveModel.of(recvInteractiveModel.getInteractiveSeq(),
InteractiveTypeEnum.COMMON_REPLY, NatcrossResultEnum.NO_HAS_SERVER_LISTEN.toResultModel()));
return false;
}

// 回复设置成功,如果doSetPartClient没有找到对应的搭档,则直接按关闭处理
socketChannel.writeAndFlush(InteractiveModel.of(recvInteractiveModel.getInteractiveSeq(),
InteractiveTypeEnum.COMMON_REPLY, NatcrossResultModel.ofSuccess()));

boolean doSetPartClient = serverListenThread.doSetPartClient(clientConnectModel.getSocketPartKey(),
socketChannel.getSocket());
if (doSetPartClient) {
// 若设置成功,则上层无需关闭
return true;
} else {
// 若设置失败,则由上层关闭
return false;
}
}

}
package person.pluto.natcross2.serverside.client.process;

import person.pluto.natcross2.channel.SocketChannel;
import person.pluto.natcross2.common.CommonFormat;
import person.pluto.natcross2.model.InteractiveModel;
import person.pluto.natcross2.model.NatcrossResultModel;
import person.pluto.natcross2.model.enumeration.InteractiveTypeEnum;
import person.pluto.natcross2.model.enumeration.NatcrossResultEnum;
import person.pluto.natcross2.model.interactive.ClientConnectModel;
import person.pluto.natcross2.serverside.listen.ListenServerControl;
import person.pluto.natcross2.serverside.listen.ServerListenThread;

/**
*
* <p>
* 请求建立隧道处理器
* </p>
*
* @author Pluto
* @since 2020-01-08 16:48:25
*/
public class ClientConnectProcess implements IProcess {

public static final ClientConnectProcess INSTANCE = new ClientConnectProcess();

@Override
public boolean wouldProc(InteractiveModel recvInteractiveModel) {
InteractiveTypeEnum interactiveTypeEnum = InteractiveTypeEnum
.getEnumByName(recvInteractiveModel.getInteractiveType());
return InteractiveTypeEnum.CLIENT_CONNECT.equals(interactiveTypeEnum);
}

@Override
public boolean processMothed(SocketChannel<? extends InteractiveModel, ? super InteractiveModel> socketChannel,
InteractiveModel recvInteractiveModel) throws Exception {
ClientConnectModel clientConnectModel = recvInteractiveModel.getData().toJavaObject(ClientConnectModel.class);
Integer listenPort = CommonFormat.getSocketPortByPartKey(clientConnectModel.getSocketPartKey());

ServerListenThread serverListenThread = ListenServerControl.get(listenPort);

if (serverListenThread == null) {
socketChannel.writeAndFlush(InteractiveModel.of(recvInteractiveModel.getInteractiveSeq(),
InteractiveTypeEnum.COMMON_REPLY, NatcrossResultEnum.NO_HAS_SERVER_LISTEN.toResultModel()));
return false;
}

// 回复设置成功,如果doSetPartClient没有找到对应的搭档,则直接按关闭处理
socketChannel.writeAndFlush(InteractiveModel.of(recvInteractiveModel.getInteractiveSeq(),
InteractiveTypeEnum.COMMON_REPLY, NatcrossResultModel.ofSuccess()));

boolean doSetPartClient = serverListenThread.doSetPartClient(clientConnectModel.getSocketPartKey(),
socketChannel.getSocket());
if (doSetPartClient) {
// 若设置成功,则上层无需关闭
return true;
} else {
// 若设置失败,则由上层关闭
return false;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,30 +20,32 @@
*/
public class ClientControlProcess implements IProcess {

@Override
public boolean wouldProc(InteractiveModel recvInteractiveModel) {
InteractiveTypeEnum interactiveTypeEnum = InteractiveTypeEnum
.getEnumByName(recvInteractiveModel.getInteractiveType());
return InteractiveTypeEnum.CLIENT_CONTROL.equals(interactiveTypeEnum);
}

@Override
public boolean processMothed(SocketChannel<? extends InteractiveModel, ? super InteractiveModel> socketChannel,
InteractiveModel recvInteractiveModel) throws Exception {
ClientControlModel clientControlModel = recvInteractiveModel.getData().toJavaObject(ClientControlModel.class);
ServerListenThread serverListenThread = ListenServerControl.get(clientControlModel.getListenPort());

if (serverListenThread == null) {
socketChannel.writeAndFlush(InteractiveModel.of(recvInteractiveModel.getInteractiveSeq(),
InteractiveTypeEnum.COMMON_REPLY, NatcrossResultEnum.NO_HAS_SERVER_LISTEN.toResultModel()));
return false;
}

socketChannel.writeAndFlush(InteractiveModel.of(recvInteractiveModel.getInteractiveSeq(),
InteractiveTypeEnum.COMMON_REPLY, NatcrossResultModel.ofSuccess()));

serverListenThread.setControlSocket(socketChannel.getSocket());
return true;
}
public static final ClientControlProcess INSTANCE = new ClientControlProcess();

@Override
public boolean wouldProc(InteractiveModel recvInteractiveModel) {
InteractiveTypeEnum interactiveTypeEnum = InteractiveTypeEnum
.getEnumByName(recvInteractiveModel.getInteractiveType());
return InteractiveTypeEnum.CLIENT_CONTROL.equals(interactiveTypeEnum);
}

@Override
public boolean processMothed(SocketChannel<? extends InteractiveModel, ? super InteractiveModel> socketChannel,
InteractiveModel recvInteractiveModel) throws Exception {
ClientControlModel clientControlModel = recvInteractiveModel.getData().toJavaObject(ClientControlModel.class);
ServerListenThread serverListenThread = ListenServerControl.get(clientControlModel.getListenPort());

if (serverListenThread == null) {
socketChannel.writeAndFlush(InteractiveModel.of(recvInteractiveModel.getInteractiveSeq(),
InteractiveTypeEnum.COMMON_REPLY, NatcrossResultEnum.NO_HAS_SERVER_LISTEN.toResultModel()));
return false;
}

socketChannel.writeAndFlush(InteractiveModel.of(recvInteractiveModel.getInteractiveSeq(),
InteractiveTypeEnum.COMMON_REPLY, NatcrossResultModel.ofSuccess()));

serverListenThread.setControlSocket(socketChannel.getSocket());
return true;
}

}

0 comments on commit 6353a77

Please sign in to comment.