diff --git a/.gitignore b/.gitignore index 0b5792bc..78618cb9 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ # Compiled class file *.class data.db - +data.db-wal # Log file *.log diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/util/ChannelUtil.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/util/ChannelUtil.java new file mode 100644 index 00000000..92c43f61 --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/util/ChannelUtil.java @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2022 aoshiguchen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package fun.asgc.neutrino.core.util; + +import io.netty.channel.Channel; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; + +/** + * + * @author: aoshiguchen + * @date: 2022/9/3 + */ +public class ChannelUtil { + + /** + * 获取ip地址 + * @param channel + * @return + */ + public static String getIP(Channel channel) { + if (null == channel) { + return ""; + } + String ip = ((InetSocketAddress)channel.remoteAddress()).getAddress().getHostAddress(); + if (ip.equals("127.0.0.1") || ip.equals("0:0:0:0:0:0:0:1")) { + //根据网卡取本机配置的IP + InetAddress inet = null; + try { + inet = InetAddress.getLocalHost(); + } catch (UnknownHostException e) { + e.printStackTrace(); + } + ip = inet.getHostAddress(); + } + return ip; + } + +} diff --git a/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/util/HttpUtil.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/util/HttpUtil.java similarity index 97% rename from neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/util/HttpUtil.java rename to neutrino-core/src/main/java/fun/asgc/neutrino/core/util/HttpUtil.java index c80d9987..58823f9b 100644 --- a/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/util/HttpUtil.java +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/util/HttpUtil.java @@ -19,7 +19,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -package fun.asgc.neutrino.proxy.server.util; +package fun.asgc.neutrino.core.util; import fun.asgc.neutrino.core.web.context.HttpRequestWrapper; import io.netty.channel.ChannelHandlerContext; diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/web/HttpRequestHandler.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/web/HttpRequestHandler.java index 1c36b7b7..65017509 100644 --- a/neutrino-core/src/main/java/fun/asgc/neutrino/core/web/HttpRequestHandler.java +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/web/HttpRequestHandler.java @@ -143,6 +143,7 @@ public class HttpRequestHandler { } } catch (Throwable e) { try { + log.error("请求处理异常:{}", requestParser.getUrl(), e); postHandle(requestParser.getRoutePath(), null, null); Object res = exceptionHandler(e); if (null != res) { diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/web/interceptor/InterceptorRegistry.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/web/interceptor/InterceptorRegistry.java index ad04c05d..be2a9636 100644 --- a/neutrino-core/src/main/java/fun/asgc/neutrino/core/web/interceptor/InterceptorRegistry.java +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/web/interceptor/InterceptorRegistry.java @@ -35,11 +35,12 @@ import java.util.List; */ public class InterceptorRegistry { - private final List registrations = new ArrayList(); + private final List registrations = new ArrayList<>(); public InterceptorRegistration addInterceptor(HandlerInterceptor interceptor) { InterceptorRegistration registration = new InterceptorRegistration(interceptor); this.registrations.add(registration); + Collections.sort(this.registrations, INTERCEPTOR_ORDER_COMPARATOR); return registration; } @@ -47,7 +48,6 @@ public class InterceptorRegistry { * Return all registered interceptors. */ public List getInterceptors() { - Collections.sort(this.registrations, INTERCEPTOR_ORDER_COMPARATOR); List result = new ArrayList(this.registrations.size()); for (InterceptorRegistration registration : this.registrations) { result.add(registration.getInterceptor()); diff --git a/neutrino-proxy-client/src/main/java/fun/asgc/neutrino/proxy/client/config/ProxyConfig.java b/neutrino-proxy-client/src/main/java/fun/asgc/neutrino/proxy/client/config/ProxyConfig.java index 416f3fb6..5f7a202c 100644 --- a/neutrino-proxy-client/src/main/java/fun/asgc/neutrino/proxy/client/config/ProxyConfig.java +++ b/neutrino-proxy-client/src/main/java/fun/asgc/neutrino/proxy/client/config/ProxyConfig.java @@ -25,7 +25,6 @@ package fun.asgc.neutrino.proxy.client.config; import fun.asgc.neutrino.core.annotation.Configuration; import fun.asgc.neutrino.core.annotation.Init; import fun.asgc.neutrino.core.annotation.Value; -import fun.asgc.neutrino.proxy.core.ProxyClientConfig; import lombok.Data; /** diff --git a/neutrino-proxy-client/src/main/java/fun/asgc/neutrino/proxy/client/core/ProxyClientRunner.java b/neutrino-proxy-client/src/main/java/fun/asgc/neutrino/proxy/client/core/ProxyClientRunner.java index c1237ec1..3b2bd9a4 100644 --- a/neutrino-proxy-client/src/main/java/fun/asgc/neutrino/proxy/client/core/ProxyClientRunner.java +++ b/neutrino-proxy-client/src/main/java/fun/asgc/neutrino/proxy/client/core/ProxyClientRunner.java @@ -113,10 +113,10 @@ public class ProxyClientRunner implements ApplicationRunner { @Override public void operationComplete(ChannelFuture future) throws Exception { if (future.isSuccess()) { - // 连接成功,向服务器发送客户端认证信息(clientKey) + // 连接成功,向服务器发送客户端认证信息(licenseKey) ProxyUtil.setCmdChannel(future.channel()); future.channel().writeAndFlush(ProxyMessage.buildAuthMessage(proxyConfig.getLicenseKey())); - log.info("连接代理服务成功."); + log.info("连接代理服务成功. channelId:{}", future.channel().id().asLongText()); } else { log.info("连接代理服务失败!"); System.exit(-1); diff --git a/neutrino-proxy-client/src/main/java/fun/asgc/neutrino/proxy/client/handler/ProxyMessageDisconnectHandler.java b/neutrino-proxy-client/src/main/java/fun/asgc/neutrino/proxy/client/handler/ProxyMessageDisconnectHandler.java index 66852888..e916f0f3 100644 --- a/neutrino-proxy-client/src/main/java/fun/asgc/neutrino/proxy/client/handler/ProxyMessageDisconnectHandler.java +++ b/neutrino-proxy-client/src/main/java/fun/asgc/neutrino/proxy/client/handler/ProxyMessageDisconnectHandler.java @@ -48,7 +48,7 @@ public class ProxyMessageDisconnectHandler implements ProxyMessageHandler { @Override public void handle(ChannelHandlerContext ctx, ProxyMessage proxyMessage) { Channel realServerChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get(); - if (realServerChannel != null) { + if (null != realServerChannel) { ctx.channel().attr(Constants.NEXT_CHANNEL).remove(); ProxyUtil.returnProxyChanel(ctx.channel()); realServerChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE); diff --git a/neutrino-proxy-client/src/main/java/fun/asgc/neutrino/proxy/client/util/ProxyUtil.java b/neutrino-proxy-client/src/main/java/fun/asgc/neutrino/proxy/client/util/ProxyUtil.java index 5241a20a..3e7e56f5 100644 --- a/neutrino-proxy-client/src/main/java/fun/asgc/neutrino/proxy/client/util/ProxyUtil.java +++ b/neutrino-proxy-client/src/main/java/fun/asgc/neutrino/proxy/client/util/ProxyUtil.java @@ -57,7 +57,7 @@ public class ProxyUtil { public static void borrowProxyChanel(Bootstrap bootstrap, final ProxyChannelBorrowListener borrowListener) { Channel channel = proxyChannelPool.poll(); - if (channel != null) { + if (null != channel) { borrowListener.success(channel); return; } diff --git a/neutrino-proxy-core/src/main/java/fun/asgc/neutrino/proxy/core/ExceptionEnum.java b/neutrino-proxy-core/src/main/java/fun/asgc/neutrino/proxy/core/ExceptionEnum.java index 232857c6..ec1cc0f5 100644 --- a/neutrino-proxy-core/src/main/java/fun/asgc/neutrino/proxy/core/ExceptionEnum.java +++ b/neutrino-proxy-core/src/main/java/fun/asgc/neutrino/proxy/core/ExceptionEnum.java @@ -34,7 +34,8 @@ import lombok.Getter; @AllArgsConstructor public enum ExceptionEnum { - AUTH_FAILED(1, "认证失败"); + AUTH_FAILED(1, "认证失败"), + CONNECT_FAILED(2, "连接失败"); private Integer code; private String msg; diff --git a/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/base/rest/interceptor/BaseAuthInterceptor.java b/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/base/rest/interceptor/BaseAuthInterceptor.java index acb9688e..40320809 100644 --- a/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/base/rest/interceptor/BaseAuthInterceptor.java +++ b/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/base/rest/interceptor/BaseAuthInterceptor.java @@ -22,6 +22,7 @@ package fun.asgc.neutrino.proxy.server.base.rest.interceptor; import fun.asgc.neutrino.core.util.BeanManager; +import fun.asgc.neutrino.core.util.HttpUtil; import fun.asgc.neutrino.core.util.StringUtil; import fun.asgc.neutrino.core.web.context.HttpContextHolder; import fun.asgc.neutrino.core.web.context.HttpRequestWrapper; @@ -33,7 +34,6 @@ import fun.asgc.neutrino.proxy.server.constant.EnableStatusEnum; import fun.asgc.neutrino.proxy.server.constant.ExceptionConstant; import fun.asgc.neutrino.proxy.server.dal.entity.UserDO; import fun.asgc.neutrino.proxy.server.service.UserService; -import fun.asgc.neutrino.proxy.server.util.HttpUtil; import java.lang.reflect.Method; diff --git a/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/base/rest/interceptor/CorsInterceptor.java b/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/base/rest/interceptor/CorsInterceptor.java index 68ccaddd..50334dd3 100644 --- a/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/base/rest/interceptor/CorsInterceptor.java +++ b/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/base/rest/interceptor/CorsInterceptor.java @@ -42,6 +42,8 @@ public class CorsInterceptor implements HandlerInterceptor { responseWrapper.headers().add("Access-Control-Allow-Headers", "*"); responseWrapper.headers().add("Access-Control-Allow-Credentials", "true"); responseWrapper.headers().add("XDomainRequestAllowed", "1"); + responseWrapper.headers().add("Access-Control-Allow-Headers","Authorize, Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type"); + responseWrapper.headers().add("Access-Control-Allow-Credentials", "true"); } } diff --git a/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/proxy/domain/UserChannelAttachInfo.java b/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/proxy/domain/UserChannelAttachInfo.java index ba4a3e06..cfc8fc7b 100644 --- a/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/proxy/domain/UserChannelAttachInfo.java +++ b/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/proxy/domain/UserChannelAttachInfo.java @@ -34,4 +34,8 @@ import lombok.experimental.Accessors; public class UserChannelAttachInfo { private String userId; private String lanInfo; + /** + * ip地址 + */ + private String ip; } diff --git a/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/service/ProxyMutualService.java b/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/service/ProxyMutualService.java new file mode 100644 index 00000000..6bb644c9 --- /dev/null +++ b/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/service/ProxyMutualService.java @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2022 aoshiguchen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package fun.asgc.neutrino.proxy.server.service; + +import fun.asgc.neutrino.core.annotation.Component; +import fun.asgc.neutrino.core.annotation.NonIntercept; +import fun.asgc.neutrino.proxy.server.proxy.domain.CmdChannelAttachInfo; +import lombok.extern.slf4j.Slf4j; + +/** + * 代理交互服务 + * @author: aoshiguchen + * @date: 2022/9/3 + */ +@Slf4j +@NonIntercept +@Component +public class ProxyMutualService { + + /** + * 绑定服务端端口处理 + * @param attachInfo + * @param serverPort + */ + public void bindServerPort(CmdChannelAttachInfo attachInfo, Integer serverPort) { + // TODO + log.info("绑定服务端端口 licenseId:{},ip:{},lanInfo:{},serverPort:{}", attachInfo.getLicenseId(), attachInfo.getIp(), attachInfo.getClientLanInfo(), serverPort); + } + +} diff --git a/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/util/ProxyUtil.java b/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/util/ProxyUtil.java index d8e32dcd..869b3131 100644 --- a/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/util/ProxyUtil.java +++ b/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/util/ProxyUtil.java @@ -21,10 +21,9 @@ */ package fun.asgc.neutrino.proxy.server.util; +import fun.asgc.neutrino.core.util.ChannelUtil; import fun.asgc.neutrino.core.util.CollectionUtil; -import fun.asgc.neutrino.core.util.StringUtil; import fun.asgc.neutrino.proxy.core.ChannelAttribute; -import fun.asgc.neutrino.proxy.core.Constants; import fun.asgc.neutrino.proxy.server.proxy.domain.CmdChannelAttachInfo; import fun.asgc.neutrino.proxy.server.proxy.domain.ProxyMapping; import fun.asgc.neutrino.proxy.server.proxy.domain.UserChannelAttachInfo; @@ -47,7 +46,7 @@ public class ProxyUtil { /** * license -> 服务端口映射 */ - private static final Map> licenseToServerPortMap = new HashMap<>(); + private static final Map> licenseToServerPortMap = new HashMap<>(); /** * 代理信息映射 */ @@ -59,7 +58,7 @@ public class ProxyUtil { /** * license -> 指令通道映射 */ - private static Map licenseToCmdChannelMap = new ConcurrentHashMap<>(); + private static Map licenseToCmdChannelMap = new ConcurrentHashMap<>(); /** * cmdChannelAttachInfo.getUserChannelMap() 读写锁 @@ -68,30 +67,24 @@ public class ProxyUtil { /** * 初始化代理信息 - * @param licenseKey 客户端licenseKey + * @param licenseId 客户端licenseId * @param proxyMappingList 代理映射集合 */ - public static void initProxyInfo(String licenseKey, List proxyMappingList) { - if (StringUtil.isEmpty(licenseKey)) { - return; - } - licenseToServerPortMap.put(licenseKey, new HashSet<>()); - if (CollectionUtil.isEmpty(proxyMappingList)) { - return; - } + public static void initProxyInfo(Integer licenseId, List proxyMappingList) { + licenseToServerPortMap.put(licenseId, new HashSet<>()); for (ProxyMapping proxyMapping : proxyMappingList) { - licenseToServerPortMap.get(licenseKey).add(proxyMapping.getServerPort()); + licenseToServerPortMap.get(licenseId).add(proxyMapping.getServerPort()); proxyInfoMap.put(proxyMapping.getServerPort(), proxyMapping.getLanInfo()); } } /** * 根据licenseKey获取服务端端口集合 - * @param licenseKey 客户端licenseKey + * @param licenseId licenseId * @return 服务端端口集合 */ - public static Set getServerPortsByLicenseKey(String licenseKey) { - return licenseToServerPortMap.get(licenseKey); + public static Set getServerPortsByLicenseKey(Integer licenseId) { + return licenseToServerPortMap.get(licenseId); } /** @@ -105,28 +98,25 @@ public class ProxyUtil { /** * 添加指令通道相关缓存信息 - * @param licenseKey licenseKey + * @param licenseId licenseId * @param cmdChannel 指令通道 * @param serverPorts 服务端端口集合 */ - public static void addCmdChannel(String licenseKey, Channel cmdChannel, Set serverPorts) { + public static void addCmdChannel(Integer licenseId, Channel cmdChannel, Set serverPorts) { if (CollectionUtil.isEmpty(serverPorts)) { return; } - // 客户端(proxy-client)相对较少,这里同步的比较重 TODO 后续优化 - // 保证服务器对外端口与客户端到服务器的连接关系在临界情况时调用removeChannel(Channel channel)时不出问题 - synchronized (serverPortToCmdChannelMap) { - for (int port : serverPorts) { - serverPortToCmdChannelMap.put(port, cmdChannel); - } + for (int port : serverPorts) { + serverPortToCmdChannelMap.put(port, cmdChannel); } setAttachInfo(cmdChannel, new CmdChannelAttachInfo() + .setIp(ChannelUtil.getIP(cmdChannel)) .setServerPorts(serverPorts) - .setLicenseKey(licenseKey) + .setLicenseId(licenseId) .setUserChannelMap(new HashMap<>(16))); - licenseToCmdChannelMap.put(licenseKey, cmdChannel); + licenseToCmdChannelMap.put(licenseId, cmdChannel); } /** @@ -139,9 +129,9 @@ public class ProxyUtil { } CmdChannelAttachInfo cmdChannelAttachInfo = getAttachInfo(cmdChannel); - Channel channel0 = licenseToCmdChannelMap.remove(cmdChannelAttachInfo.getLicenseKey()); + Channel channel0 = licenseToCmdChannelMap.remove(cmdChannelAttachInfo.getLicenseId()); if (cmdChannel != channel0) { - licenseToCmdChannelMap.put(cmdChannelAttachInfo.getLicenseKey(), cmdChannel); + licenseToCmdChannelMap.put(cmdChannelAttachInfo.getLicenseId(), cmdChannel); } for (int port : cmdChannelAttachInfo.getServerPorts()) { @@ -174,8 +164,8 @@ public class ProxyUtil { return serverPortToCmdChannelMap.get(serverPort); } - public static Channel getCmdChannelByLicenseKey(String licenseKey) { - return licenseToCmdChannelMap.get(licenseKey); + public static Channel getCmdChannelByLicenseId(Integer licenseId) { + return licenseToCmdChannelMap.get(licenseId); } /** @@ -190,6 +180,7 @@ public class ProxyUtil { setAttachInfo(userChannel, new UserChannelAttachInfo() .setUserId(userId) .setLanInfo(lanInfo) + .setIp(ChannelUtil.getIP(userChannel)) ); userChannelMapLock.writeLock().lock(); try { diff --git a/neutrino-proxy-server/src/main/resources/sql/init-structure.sql b/neutrino-proxy-server/src/main/resources/sql/init-structure.sql index c891cd0c..ea52b44f 100644 --- a/neutrino-proxy-server/src/main/resources/sql/init-structure.sql +++ b/neutrino-proxy-server/src/main/resources/sql/init-structure.sql @@ -8,6 +8,7 @@ CREATE TABLE IF NOT EXISTS `user` ( `create_time` INTEGER(20) NOT NULL, `update_time` INTEGER(20) NOT NULL ); +CREATE UNIQUE INDEX IF NOT EXISTS I_login_name ON `user` (login_name ASC); #license表 CREATE TABLE IF NOT EXISTS `license` ( @@ -20,6 +21,7 @@ CREATE TABLE IF NOT EXISTS `license` ( `create_time` INTEGER(20) NOT NULL, `update_time` INTEGER(20) NOT NULL ); +CREATE UNIQUE INDEX IF NOT EXISTS I_key ON `license` (`key` ASC); #用户token表 CREATE TABLE IF NOT EXISTS `user_token` ( @@ -49,6 +51,7 @@ CREATE TABLE IF NOT EXISTS `port_pool` ( `update_time` INTEGER(20) NOT NULL, `create_time` INTEGER(20) NOT NULL ); +CREATE UNIQUE INDEX IF NOT EXISTS I_port ON port_pool (port ASC); #端口映射表 CREATE TABLE IF NOT EXISTS `port_mapping` ( @@ -62,6 +65,7 @@ CREATE TABLE IF NOT EXISTS `port_mapping` ( `create_time` INTEGER(20) NOT NULL, `update_time` INTEGER(20) NOT NULL ); +CREATE UNIQUE INDEX IF NOT EXISTS I_server_port ON port_mapping (server_port ASC); #客户端链接记录表 CREATE TABLE IF NOT EXISTS `client_connect_record` (