├── .gitignore ├── client ├── src │ └── main │ │ ├── resources │ │ ├── config.properties │ │ ├── logback-test.xml │ │ └── logback.xml │ │ └── java │ │ └── com │ │ └── fys │ │ ├── handler │ │ ├── LoginFailHandler.java │ │ ├── LoginHandler.java │ │ ├── ServerStartSuccessHandler.java │ │ ├── ServerStartFailHandler.java │ │ ├── DataConnectionHandler.java │ │ └── CmdDecoder.java │ │ ├── Config.java │ │ ├── AppClient.java │ │ └── DataConnectionClient.java └── pom.xml ├── server ├── src │ ├── main │ │ ├── resources │ │ │ ├── test.toml │ │ │ ├── logback-test.xml │ │ │ ├── config.json │ │ │ └── logback.xml │ │ └── java │ │ │ └── com │ │ │ └── fys │ │ │ ├── conf │ │ │ ├── ServerInfo.java │ │ │ └── ServerWorker.java │ │ │ ├── Config.java │ │ │ ├── handler │ │ │ ├── LoginCmdHandler.java │ │ │ ├── ServerCmdDecoder.java │ │ │ └── FlowManagerHandler.java │ │ │ ├── App.java │ │ │ ├── ServerManager.java │ │ │ └── Server.java │ └── test │ │ └── java │ │ └── com │ │ └── fys │ │ ├── H2Test.java │ │ └── ConfigTest.java └── pom.xml ├── common ├── src │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── fys │ │ │ └── cmd │ │ │ ├── exception │ │ │ └── AuthenticationException.java │ │ │ ├── message │ │ │ ├── Ping.java │ │ │ ├── Pong.java │ │ │ ├── Cmd.java │ │ │ ├── serverToClient │ │ │ │ ├── LoginFailCmd.java │ │ │ │ ├── ServerStartSuccessCmd.java │ │ │ │ └── ServerStartFailCmd.java │ │ │ ├── clientToServer │ │ │ │ └── LoginCmd.java │ │ │ └── DataConnectionCmd.java │ │ │ ├── handler │ │ │ ├── CmdEncoder.java │ │ │ ├── TimeOutHandler.java │ │ │ ├── ExceptionHandler.java │ │ │ ├── TransactionHandler.java │ │ │ ├── PingPongHandler.java │ │ │ └── Rc4Md5Handler.java │ │ │ ├── listener │ │ │ └── ErrorLogListener.java │ │ │ └── util │ │ │ └── CodeUtil.java │ └── test │ │ └── java │ │ └── com │ │ └── fys │ │ └── cmd │ │ ├── message │ │ ├── clientToServer │ │ │ └── LoginCmdTest.java │ │ ├── serverToClient │ │ │ ├── LoginFailCmdTest.java │ │ │ ├── ServerStartSuccessCmdTest.java │ │ │ └── ServerStartFailCmdTest.java │ │ └── DataConnectionCmdTest.java │ │ └── handler │ │ └── Rc4Md5HandlerTest.java └── pom.xml ├── README.md └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .classpath 3 | *~ 4 | *.iml 5 | .idea 6 | *.log 7 | *dependency-reduced-pom.xml 8 | -------------------------------------------------------------------------------- /client/src/main/resources/config.properties: -------------------------------------------------------------------------------- 1 | serverIp=127.0.0.1 2 | serverPort=9050 3 | clientName=hcy_home_pc 4 | token=123456 -------------------------------------------------------------------------------- /server/src/main/resources/test.toml: -------------------------------------------------------------------------------- 1 | [user] 2 | name="huang" 3 | age=25 4 | sex=1 5 | [user] 6 | name="zhang" 7 | age=18 8 | sex=2 9 | 10 | -------------------------------------------------------------------------------- /common/src/main/java/com/fys/cmd/exception/AuthenticationException.java: -------------------------------------------------------------------------------- 1 | package com.fys.cmd.exception; 2 | 3 | /** 4 | * hcy 2020/2/24 5 | */ 6 | public class AuthenticationException extends RuntimeException { 7 | 8 | public static AuthenticationException INSTANCE = new AuthenticationException(); 9 | 10 | public AuthenticationException() { 11 | super("密码不对"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /common/src/main/java/com/fys/cmd/message/Ping.java: -------------------------------------------------------------------------------- 1 | package com.fys.cmd.message; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | 5 | /** 6 | * hcy 2020/2/18 7 | */ 8 | public class Ping implements Cmd { 9 | 10 | private static Ping instance = new Ping(); 11 | 12 | @Override 13 | public void encoderTo(ByteBuf buf) { 14 | buf.writeByte(Cmd.ping); 15 | } 16 | 17 | public static Ping decoderFrom(ByteBuf in) { 18 | return instance; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /common/src/main/java/com/fys/cmd/message/Pong.java: -------------------------------------------------------------------------------- 1 | package com.fys.cmd.message; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | 5 | /** 6 | * hcy 2020/2/18 7 | */ 8 | public class Pong implements Cmd { 9 | 10 | private static Pong instance = new Pong(); 11 | 12 | @Override 13 | public void encoderTo(ByteBuf buf) { 14 | buf.writeByte(Cmd.pong); 15 | } 16 | 17 | public static Pong decoderFrom(ByteBuf in) { 18 | return instance; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /server/src/main/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ${consolePattern} 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /client/src/main/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ${consolePattern} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /common/src/main/java/com/fys/cmd/handler/CmdEncoder.java: -------------------------------------------------------------------------------- 1 | package com.fys.cmd.handler; 2 | 3 | import com.fys.cmd.message.Cmd; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.handler.codec.MessageToByteEncoder; 7 | 8 | /** 9 | * hcy 2020/2/18 10 | */ 11 | public class CmdEncoder extends MessageToByteEncoder { 12 | 13 | //输出 14 | @Override 15 | protected void encode(ChannelHandlerContext ctx, Cmd msg, ByteBuf out) { 16 | msg.encoderTo(out); 17 | } 18 | 19 | 20 | } 21 | -------------------------------------------------------------------------------- /server/src/main/resources/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "server": { 3 | "bindHost": 9050, 4 | "token": "123456" 5 | }, 6 | "clients": [ 7 | { 8 | "clientName": "hcy_home_pc", 9 | "serverWorkers": [ 10 | { 11 | "serverPort": "9051", 12 | "localHost": "0.0.0.0", 13 | "localPort": "81" 14 | }, 15 | { 16 | "serverPort": "9052", 17 | "localHost": "0.0.0.0", 18 | "localPort": "81" 19 | }, 20 | { 21 | "serverPort": "9053", 22 | "localHost": "0.0.0.0", 23 | "localPort": "82" 24 | } 25 | ] 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /common/src/main/java/com/fys/cmd/message/Cmd.java: -------------------------------------------------------------------------------- 1 | package com.fys.cmd.message; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | 5 | /** 6 | * hcy 2020/2/10 7 | * 消息总是由 长度 + data 的形式组成的,不同消息的data形式不同,看具体实现 8 | */ 9 | public interface Cmd { 10 | 11 | byte dataConnectionCmd = 0; 12 | byte ping = 4; 13 | byte pong = 6; 14 | 15 | 16 | interface ServerToClient { 17 | byte serverStartSuccessCmd = 2; 18 | byte serverStartFailCmd = 3; 19 | byte loginFail = 7; 20 | } 21 | 22 | interface ClientToServer { 23 | byte login = 5; 24 | } 25 | 26 | 27 | /* 28 | * 将当前对象序列化到Bytebuf中 29 | * */ 30 | void encoderTo(ByteBuf buf); 31 | 32 | } 33 | -------------------------------------------------------------------------------- /server/src/main/java/com/fys/conf/ServerInfo.java: -------------------------------------------------------------------------------- 1 | package com.fys.conf; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * hcy 2020/3/26 7 | */ 8 | public class ServerInfo { 9 | 10 | private String clientName; 11 | private List serverWorkers; 12 | 13 | public String getClientName() { 14 | return clientName; 15 | } 16 | 17 | public void setClientName(String clientName) { 18 | this.clientName = clientName; 19 | } 20 | 21 | public List getServerWorkers() { 22 | return serverWorkers; 23 | } 24 | 25 | public void setServerWorkers(List serverWorkers) { 26 | this.serverWorkers = serverWorkers; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /client/src/main/java/com/fys/handler/LoginFailHandler.java: -------------------------------------------------------------------------------- 1 | package com.fys.handler; 2 | 3 | import com.fys.cmd.message.serverToClient.LoginFailCmd; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.SimpleChannelInboundHandler; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | /** 10 | * hcy 2020/3/26 11 | */ 12 | public class LoginFailHandler extends SimpleChannelInboundHandler { 13 | private static Logger log = LoggerFactory.getLogger(LoginFailCmd.class); 14 | 15 | @Override 16 | protected void channelRead0(ChannelHandlerContext ctx, LoginFailCmd msg) throws Exception { 17 | log.info(msg.toString()); 18 | ctx.close(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /client/src/main/java/com/fys/handler/LoginHandler.java: -------------------------------------------------------------------------------- 1 | package com.fys.handler; 2 | 3 | import com.fys.AppClient; 4 | import com.fys.Config; 5 | import com.fys.cmd.message.clientToServer.LoginCmd; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.channel.ChannelInboundHandlerAdapter; 8 | 9 | /** 10 | * hcy 2020/5/8 11 | * 连接成功后则发送登陆信息 12 | */ 13 | public class LoginHandler extends ChannelInboundHandlerAdapter { 14 | 15 | @Override 16 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 17 | Config config = ctx.channel().attr(AppClient.key).get(); 18 | ctx.writeAndFlush(new LoginCmd(config.getClientName())); 19 | super.channelActive(ctx); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /common/src/main/java/com/fys/cmd/listener/ErrorLogListener.java: -------------------------------------------------------------------------------- 1 | package com.fys.cmd.listener; 2 | 3 | import io.netty.channel.ChannelFuture; 4 | import io.netty.channel.ChannelFutureListener; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | /** 9 | * hcy 2020/2/22 10 | */ 11 | public class ErrorLogListener implements ChannelFutureListener { 12 | 13 | private static Logger log = LoggerFactory.getLogger(ErrorLogListener.class); 14 | 15 | public static ErrorLogListener INSTANCE = new ErrorLogListener(); 16 | 17 | @Override 18 | public void operationComplete(ChannelFuture future) { 19 | if (!future.isSuccess()) { 20 | log.error("", future.cause()); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /common/src/test/java/com/fys/cmd/message/clientToServer/LoginCmdTest.java: -------------------------------------------------------------------------------- 1 | package com.fys.cmd.message.clientToServer; 2 | 3 | import com.fys.cmd.message.Cmd; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.buffer.Unpooled; 6 | import org.junit.Assert; 7 | import org.junit.Test; 8 | 9 | /** 10 | * hcy 2020/3/25 11 | */ 12 | public class LoginCmdTest { 13 | 14 | @Test 15 | public void test() { 16 | LoginCmd login = new LoginCmd("啦啦啦123abc"); 17 | ByteBuf buffer = Unpooled.buffer(); 18 | login.encoderTo(buffer); 19 | 20 | Assert.assertEquals(Cmd.ClientToServer.login, buffer.readByte()); 21 | LoginCmd decode = LoginCmd.decoderFrom(buffer); 22 | Assert.assertEquals(login,decode); 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /common/src/test/java/com/fys/cmd/message/serverToClient/LoginFailCmdTest.java: -------------------------------------------------------------------------------- 1 | package com.fys.cmd.message.serverToClient; 2 | 3 | import com.fys.cmd.message.Cmd; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.buffer.Unpooled; 6 | import org.junit.Test; 7 | 8 | import static org.junit.Assert.assertEquals; 9 | 10 | /** 11 | * hcy 2020/3/26 12 | */ 13 | public class LoginFailCmdTest { 14 | 15 | @Test 16 | public void encoderTo() { 17 | LoginFailCmd msg = new LoginFailCmd("clientname拉拉", "error msg啦啦啦"); 18 | ByteBuf buffer = Unpooled.buffer(); 19 | msg.encoderTo(buffer); 20 | assertEquals(Cmd.ServerToClient.loginFail, buffer.readByte()); 21 | assertEquals(msg, LoginFailCmd.decoderFrom(buffer)); 22 | 23 | } 24 | } -------------------------------------------------------------------------------- /client/src/main/java/com/fys/handler/ServerStartSuccessHandler.java: -------------------------------------------------------------------------------- 1 | package com.fys.handler; 2 | 3 | import com.fys.cmd.message.serverToClient.ServerStartSuccessCmd; 4 | import io.netty.channel.ChannelHandler; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.channel.SimpleChannelInboundHandler; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | /** 11 | * hcy 2020/2/19 12 | * 此类处理服务器创建成功事件 13 | */ 14 | @ChannelHandler.Sharable 15 | public class ServerStartSuccessHandler extends SimpleChannelInboundHandler { 16 | 17 | private Logger log = LoggerFactory.getLogger(ServerStartSuccessHandler.class); 18 | 19 | @Override 20 | protected void channelRead0(ChannelHandlerContext ctx, ServerStartSuccessCmd msg) { 21 | log.info(msg.toString()); 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /common/src/test/java/com/fys/cmd/message/serverToClient/ServerStartSuccessCmdTest.java: -------------------------------------------------------------------------------- 1 | package com.fys.cmd.message.serverToClient; 2 | 3 | import com.fys.cmd.message.Cmd; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.buffer.Unpooled; 6 | import org.junit.Test; 7 | 8 | import static org.junit.Assert.assertEquals; 9 | 10 | /** 11 | * hcy 2020/2/22 12 | */ 13 | public class ServerStartSuccessCmdTest { 14 | 15 | @Test 16 | public void encoderTo() { 17 | ByteBuf buffer = Unpooled.buffer(); 18 | ServerStartSuccessCmd src = new ServerStartSuccessCmd(70, "127.0.5.7", 50); 19 | src.encoderTo(buffer); 20 | assertEquals(Cmd.ServerToClient.serverStartSuccessCmd, buffer.readByte()); 21 | ServerStartSuccessCmd dec = ServerStartSuccessCmd.decoderFrom(buffer); 22 | assertEquals(src, dec); 23 | buffer.release(); 24 | } 25 | } -------------------------------------------------------------------------------- /client/src/main/java/com/fys/handler/ServerStartFailHandler.java: -------------------------------------------------------------------------------- 1 | package com.fys.handler; 2 | 3 | import com.fys.cmd.message.serverToClient.ServerStartFailCmd; 4 | import io.netty.channel.ChannelHandler; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.channel.SimpleChannelInboundHandler; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | /** 11 | * hcy 2020/2/19 12 | */ 13 | @ChannelHandler.Sharable 14 | public class ServerStartFailHandler extends SimpleChannelInboundHandler { 15 | 16 | private Logger log = LoggerFactory.getLogger(ServerStartFailHandler.class); 17 | 18 | @Override 19 | protected void channelRead0(ChannelHandlerContext ctx, ServerStartFailCmd msg) { 20 | log.error(msg.toString()); 21 | if (ctx.channel().isActive()) { 22 | ctx.close(); 23 | } 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /common/src/test/java/com/fys/cmd/message/serverToClient/ServerStartFailCmdTest.java: -------------------------------------------------------------------------------- 1 | package com.fys.cmd.message.serverToClient; 2 | 3 | import com.fys.cmd.message.Cmd; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.buffer.Unpooled; 6 | import org.junit.Test; 7 | 8 | import java.util.UUID; 9 | 10 | import static org.junit.Assert.assertEquals; 11 | 12 | /** 13 | * hcy 2020/2/22 14 | */ 15 | public class ServerStartFailCmdTest { 16 | 17 | @Test 18 | public void encoderTo() { 19 | ByteBuf buffer = Unpooled.buffer(); 20 | ServerStartFailCmd src = new ServerStartFailCmd(80,"如果是中文会报错吗", 70, UUID.randomUUID().toString()); 21 | src.encoderTo(buffer); 22 | assertEquals(Cmd.ServerToClient.serverStartFailCmd, buffer.readByte()); 23 | ServerStartFailCmd dec = ServerStartFailCmd.decoderFrom(buffer); 24 | assertEquals(src, dec); 25 | buffer.release(); 26 | } 27 | } -------------------------------------------------------------------------------- /common/src/main/java/com/fys/cmd/util/CodeUtil.java: -------------------------------------------------------------------------------- 1 | package com.fys.cmd.util; 2 | 3 | import java.security.MessageDigest; 4 | import java.security.NoSuchAlgorithmException; 5 | 6 | /** 7 | * hcy 2020/4/3 8 | */ 9 | public class CodeUtil { 10 | 11 | public static byte[] md5(byte[] src) { 12 | try { 13 | MessageDigest md = MessageDigest.getInstance("md5"); 14 | return md.digest(src); 15 | } catch (NoSuchAlgorithmException e) { 16 | throw new RuntimeException(e); 17 | } 18 | } 19 | 20 | public static byte[] md5(byte[] src, byte[] src2) { 21 | try { 22 | MessageDigest md = MessageDigest.getInstance("md5"); 23 | md.update(src); 24 | md.update(src2); 25 | return md.digest(); 26 | } catch (NoSuchAlgorithmException e) { 27 | throw new RuntimeException(e); 28 | } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /common/src/main/java/com/fys/cmd/handler/TimeOutHandler.java: -------------------------------------------------------------------------------- 1 | package com.fys.cmd.handler; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.handler.timeout.IdleStateEvent; 5 | import io.netty.handler.timeout.IdleStateHandler; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | /** 10 | * hcy 2020/2/21 11 | */ 12 | public class TimeOutHandler extends IdleStateHandler { 13 | 14 | private static Logger log = LoggerFactory.getLogger(TimeOutHandler.class); 15 | 16 | public TimeOutHandler(int readerIdleTimeSeconds, int writerIdleTimeSeconds, int allIdleTimeSeconds) { 17 | super(readerIdleTimeSeconds, writerIdleTimeSeconds, allIdleTimeSeconds); 18 | } 19 | 20 | 21 | @Override 22 | public final void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) throws Exception { 23 | log.debug("超时关闭连接:{},Event:{}", ctx, evt); 24 | ctx.flush().close(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /common/src/test/java/com/fys/cmd/message/DataConnectionCmdTest.java: -------------------------------------------------------------------------------- 1 | package com.fys.cmd.message; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.Unpooled; 5 | import org.junit.Test; 6 | 7 | import static org.junit.Assert.assertEquals; 8 | 9 | /** 10 | * hcy 2020/2/23 11 | */ 12 | public class DataConnectionCmdTest { 13 | 14 | @Test 15 | public void encoderTo() { 16 | ByteBuf buffer = Unpooled.buffer(); 17 | DataConnectionCmd src = new DataConnectionCmd( 90, "127.0.2.8", 80, System.nanoTime()); 18 | src.encoderTo(buffer); 19 | assertEquals(Cmd.dataConnectionCmd, buffer.readByte()); 20 | DataConnectionCmd dec = DataConnectionCmd.decoderFrom(buffer); 21 | assertEquals(src.getLocalHost(), dec.getLocalHost()); 22 | assertEquals(src.getLocalPort(), dec.getLocalPort()); 23 | assertEquals(src.getServerPort(), dec.getServerPort()); 24 | assertEquals(src.getToken(), dec.getToken()); 25 | buffer.release(); 26 | } 27 | } -------------------------------------------------------------------------------- /server/src/test/java/com/fys/H2Test.java: -------------------------------------------------------------------------------- 1 | package com.fys; 2 | 3 | import org.h2.jdbcx.JdbcConnectionPool; 4 | import org.h2.tools.Server; 5 | 6 | import java.sql.Connection; 7 | import java.sql.DriverManager; 8 | import java.sql.SQLException; 9 | 10 | /** 11 | * hcy 2020/2/26 12 | */ 13 | public class H2Test { 14 | 15 | public static void main(String[] args) throws SQLException, ClassNotFoundException { 16 | 17 | 18 | JdbcConnectionPool cp = JdbcConnectionPool.create("jdbc:h2:~/test", "", ""); 19 | Connection conn = cp.getConnection(); 20 | conn.close(); 21 | cp.dispose(); 22 | 23 | // Class.forName("org.h2.Driver"); 24 | // Connection conn = DriverManager.getConnection("jdbc:h2:~/test", "sa", ""); 25 | // 26 | // Server.createWebServer().start(); 27 | 28 | //默认8082 端口 29 | 30 | Class.forName("org.h2.Driver"); 31 | Connection conn1 = DriverManager.getConnection("jdbc:h2:tcp:localhost:9092/~/test", "sa", ""); 32 | 33 | } 34 | 35 | 36 | } -------------------------------------------------------------------------------- /server/src/test/java/com/fys/ConfigTest.java: -------------------------------------------------------------------------------- 1 | package com.fys; 2 | 3 | import com.fys.conf.ServerInfo; 4 | import com.fys.conf.ServerWorker; 5 | import org.junit.Assert; 6 | import org.junit.Test; 7 | 8 | import java.io.IOException; 9 | 10 | /** 11 | * hcy 2020/3/26 12 | */ 13 | public class ConfigTest { 14 | 15 | @Test 16 | public void testConf() throws IOException { 17 | Config.init("config.json"); 18 | Assert.assertEquals("0.0.0.0", Config.bindHost); 19 | Assert.assertEquals(9050, Config.bindPort); 20 | ServerInfo hcy_home_pc = Config.getServerInfo("hcy_home_pc"); 21 | System.out.println(hcy_home_pc.getClientName()); 22 | for (ServerWorker serverWorker : hcy_home_pc.getServerWorkers()) { 23 | System.out.println("------"); 24 | System.out.println(serverWorker.getServerPort()); 25 | System.out.println(serverWorker.getLocalHost()); 26 | System.out.println(serverWorker.getLocalPort()); 27 | } 28 | 29 | } 30 | 31 | 32 | } 33 | -------------------------------------------------------------------------------- /common/src/test/java/com/fys/cmd/handler/Rc4Md5HandlerTest.java: -------------------------------------------------------------------------------- 1 | package com.fys.cmd.handler; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.ByteBufUtil; 5 | import io.netty.buffer.Unpooled; 6 | import io.netty.channel.embedded.EmbeddedChannel; 7 | import org.junit.Assert; 8 | import org.junit.Test; 9 | 10 | /** 11 | * hcy 2020/4/3 12 | */ 13 | public class Rc4Md5HandlerTest { 14 | 15 | @Test 16 | public void decode() { 17 | 18 | EmbeddedChannel eb = new EmbeddedChannel(new Rc4Md5Handler("123456")); 19 | //出站将数据加密 20 | ByteBuf origin = Unpooled.wrappedBuffer("abcdefghijklmn".getBytes()); 21 | //将副本写出channel 22 | ByteBuf src = origin.copy(); 23 | Assert.assertTrue(eb.writeOutbound(src)); 24 | //出站rc4加密后的数据 25 | Object o = eb.readOutbound(); 26 | 27 | //进站,解密数据 28 | Assert.assertTrue(eb.writeInbound(o)); 29 | //读取解密后的数据 30 | ByteBuf buff = eb.readInbound(); 31 | Assert.assertTrue(ByteBufUtil.equals(origin, buff)); 32 | Assert.assertFalse(eb.finish()); 33 | eb.close(); 34 | } 35 | } -------------------------------------------------------------------------------- /common/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | com.remotePort 7 | remotePortMapping 8 | 1.0-SNAPSHOT 9 | 10 | 11 | 4.0.0 12 | 13 | common 14 | common 15 | 16 | 17 | 18 | io.netty 19 | netty-all 20 | 21 | 22 | ch.qos.logback 23 | logback-classic 24 | 25 | 26 | junit 27 | junit 28 | test 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /client/src/main/java/com/fys/handler/DataConnectionHandler.java: -------------------------------------------------------------------------------- 1 | package com.fys.handler; 2 | 3 | import com.fys.AppClient; 4 | import com.fys.Config; 5 | import com.fys.DataConnectionClient; 6 | import com.fys.cmd.message.DataConnectionCmd; 7 | import io.netty.channel.ChannelHandlerContext; 8 | import io.netty.channel.SimpleChannelInboundHandler; 9 | import io.netty.util.Attribute; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | 14 | public class DataConnectionHandler extends SimpleChannelInboundHandler { 15 | 16 | private Logger log = LoggerFactory.getLogger(DataConnectionHandler.class); 17 | 18 | /* 19 | * 需要注意这个ctx是managerChannel的,即使开启失败也不能关闭此ctx 20 | * */ 21 | @Override 22 | protected void channelRead0(ChannelHandlerContext ctx, DataConnectionCmd msg) { 23 | Attribute conf = ctx.channel().attr(AppClient.key); 24 | Config config = conf.get(); 25 | log.debug("收到服务端DataConnection,{} -> {}:{}", msg.getServerPort(), msg.getLocalHost(), msg.getLocalPort()); 26 | new DataConnectionClient(config, msg).start(); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /common/src/main/java/com/fys/cmd/handler/ExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.fys.cmd.handler; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.ChannelHandler; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.channel.ChannelInboundHandlerAdapter; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | /** 11 | * hcy 2020/2/22 12 | */ 13 | @ChannelHandler.Sharable 14 | public class ExceptionHandler extends ChannelInboundHandlerAdapter { 15 | 16 | private static Logger log = LoggerFactory.getLogger(ExceptionHandler.class); 17 | public static ExceptionHandler INSTANCE = new ExceptionHandler(); 18 | 19 | public static final String NAME = "ExceptionHandler"; 20 | 21 | private ExceptionHandler() { 22 | } 23 | 24 | @Override 25 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 26 | Channel channel = ctx.channel(); 27 | if ("Connection reset by peer".equals(cause.getMessage())) { 28 | log.error("收尾:Connection reset by peer local:{},remote:{}", channel.localAddress(), channel.remoteAddress()); 29 | return; 30 | } 31 | log.error("收尾local:" + channel.localAddress() + " remote" + channel.remoteAddress(), cause); 32 | ctx.close(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /server/src/main/java/com/fys/conf/ServerWorker.java: -------------------------------------------------------------------------------- 1 | package com.fys.conf; 2 | 3 | /** 4 | * hcy 2020/3/2 5 | */ 6 | public class ServerWorker { 7 | 8 | private Integer serverPort; 9 | private String localHost; 10 | private Integer localPort; 11 | 12 | public ServerWorker() { 13 | } 14 | 15 | public ServerWorker(Integer serverPort, String localHost, Integer localPort) { 16 | this.serverPort = serverPort; 17 | this.localHost = localHost; 18 | this.localPort = localPort; 19 | } 20 | 21 | public Integer getServerPort() { 22 | return serverPort; 23 | } 24 | 25 | public void setServerPort(Integer serverPort) { 26 | this.serverPort = serverPort; 27 | } 28 | 29 | public String getLocalHost() { 30 | return localHost; 31 | } 32 | 33 | public void setLocalHost(String localHost) { 34 | this.localHost = localHost; 35 | } 36 | 37 | public Integer getLocalPort() { 38 | return localPort; 39 | } 40 | 41 | public void setLocalPort(Integer localPort) { 42 | this.localPort = localPort; 43 | } 44 | 45 | 46 | @Override 47 | public String toString() { 48 | if (serverPort == null) { 49 | return "尚未配置的服务映射"; 50 | } 51 | return serverPort + " ---> " + localHost + ":" + localPort; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /client/src/main/java/com/fys/Config.java: -------------------------------------------------------------------------------- 1 | package com.fys; 2 | 3 | import java.io.FileInputStream; 4 | import java.io.InputStream; 5 | import java.nio.file.Files; 6 | import java.nio.file.Paths; 7 | import java.util.Properties; 8 | 9 | /** 10 | * hcy 2020/2/18 11 | */ 12 | public class Config { 13 | 14 | private String serverIp; 15 | private int serverPort; 16 | private String clientName; 17 | private String token; 18 | 19 | public static Config INSTANCE = new Config(); 20 | 21 | private Config() { 22 | try { 23 | InputStream in = ClassLoader.getSystemResourceAsStream("config.properties"); 24 | if (in == null) { 25 | if (Files.notExists(Paths.get("config.properties"))) { 26 | throw new RuntimeException("找不到配置文件: config.properties"); 27 | } 28 | in = new FileInputStream("config.properties"); 29 | } 30 | 31 | Properties prop = new Properties(); 32 | prop.load(in); 33 | serverIp = prop.getProperty("serverIp"); 34 | clientName = prop.getProperty("clientName"); 35 | token = prop.getProperty("token"); 36 | serverPort = Integer.parseInt(prop.getProperty("serverPort")); 37 | in.close(); 38 | } catch (Exception e) { 39 | e.printStackTrace(); 40 | } 41 | } 42 | 43 | public String getServerIp() { 44 | return serverIp; 45 | } 46 | 47 | public int getServerPort() { 48 | return serverPort; 49 | } 50 | 51 | public String getClientName() { 52 | return clientName; 53 | } 54 | 55 | public String getToken() { 56 | return token; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /client/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | ${consolePattern} 11 | 12 | 13 | 14 | 15 | ${filePattern} 16 | 17 | client_info_log.log 18 | 19 | client_info_log_%d{yyyy-MM-dd}.%i.gz 20 | 10MB 21 | 5 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | ${filePattern} 33 | 34 | client_debug_log.log 35 | 36 | client_debug_log_%d{yyyy-MM-dd}.%i.gz 37 | 10MB 38 | 5 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### 这是一个java实现的内网穿透工具。 2 | 它能实现通过服务器做跳板来穿透到内网机器上。 3 | 他能实现客户端服务端数据传输加密 4 | 他能实现多客户端同时使用 5 | 他能实现同时映射多个端口 6 | 7 | ### 客户端用法 8 | 9 | 客户端默认会查找jar文件相同目录下的'config.properties'的配置文件, 10 | 配置文件在resources目录下,按照这个模板配置。 11 | 12 | ```properties 13 | #服务器ip 14 | serverIp=127.0.0.1 15 | #服务器端口 16 | serverPort=9050 17 | #客户端名 18 | clientName=hcy_home_pc 19 | #密码 20 | token=123456 21 | ``` 22 | 23 | 24 | 25 | 26 | 27 | ### 服务端用法配置 28 | 29 | 服务端需要绑定一个管理端口用于客户端管理,再绑定一个端口用于映射。 30 | 请在jar文件相同目录下创建一个 config.json的文件,配置如下。 31 | 32 | ```json 33 | { 34 | "server": { 35 | "bindHost": 9050, 36 | "token": "123456" 37 | }, 38 | "clients": [ 39 | { 40 | "clientName": "hcy_home_pc", 41 | "serverWorkers": [ 42 | { 43 | "serverPort": "9051", 44 | "localHost": "0.0.0.0", 45 | "localPort": "81" 46 | }, 47 | { 48 | "serverPort": "9052", 49 | "localHost": "0.0.0.0", 50 | "localPort": "81" 51 | }, 52 | { 53 | "serverPort": "9053", 54 | "localHost": "0.0.0.0", 55 | "localPort": "82" 56 | } 57 | ] 58 | } 59 | ] 60 | } 61 | ``` 62 | 63 | 64 | - 服务器只允许具有相同Token的客户端连接 65 | - 服务器只能为配置在clients节点下的client服务,如果Token正确client下找不到对应名称的client,也是不能工作的 66 | - 所有配置只在服务器上配置,客户端只能决定连接还是断开 67 | 68 | 69 | 70 | ##### 只有配置了映射关系服务器才会监听该端口,并且要保证服务器的指定端口是没有被使用的。 71 | 72 | 73 | 74 | ### 配置window远程桌面示例 75 | 即在家里远程桌面连接, 就可以连接到公司电脑了。 76 | ```json 77 | 78 | { 79 | "server": { 80 | "bindHost": 9050, 81 | "token": "123456" 82 | }, 83 | "clients": [ 84 | { 85 | "clientName": "hcy_home_pc", 86 | "serverWorkers": [ 87 | { 88 | "serverPort": "9051", 89 | "localHost": "0.0.0.0", 90 | "localPort": 3389 91 | } 92 | ] 93 | } 94 | ] 95 | } 96 | ``` 97 | 98 | 99 | ## 一定要注意,因为用到了随机数,启动java时要使用伪随机数,不然linux的随机数是堵塞的 -------------------------------------------------------------------------------- /common/src/main/java/com/fys/cmd/message/serverToClient/LoginFailCmd.java: -------------------------------------------------------------------------------- 1 | package com.fys.cmd.message.serverToClient; 2 | 3 | import com.fys.cmd.message.Cmd; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.buffer.ByteBufUtil; 6 | 7 | import java.util.Objects; 8 | 9 | import static java.nio.charset.StandardCharsets.UTF_8; 10 | 11 | /** 12 | * hcy 2020/3/26 13 | */ 14 | public class LoginFailCmd implements Cmd { 15 | private String clientName; 16 | private String failMsg; 17 | 18 | public LoginFailCmd(String clientName, String failMsg) { 19 | this.clientName = clientName; 20 | this.failMsg = failMsg; 21 | } 22 | 23 | @Override 24 | public void encoderTo(ByteBuf buf) { 25 | buf.writeByte(ServerToClient.loginFail); 26 | buf.writeInt(ByteBufUtil.utf8Bytes(clientName)); 27 | buf.writeCharSequence(clientName, UTF_8); 28 | buf.writeInt(ByteBufUtil.utf8Bytes(failMsg)); 29 | buf.writeCharSequence(failMsg, UTF_8); 30 | } 31 | 32 | public static LoginFailCmd decoderFrom(ByteBuf in) { 33 | CharSequence clientName = in.readCharSequence(in.readInt(), UTF_8); 34 | CharSequence failMsg = in.readCharSequence(in.readInt(), UTF_8); 35 | return new LoginFailCmd(clientName.toString(), failMsg.toString()); 36 | } 37 | 38 | @Override 39 | public boolean equals(Object o) { 40 | if (this == o) return true; 41 | if (o == null || getClass() != o.getClass()) return false; 42 | LoginFailCmd that = (LoginFailCmd) o; 43 | return Objects.equals(clientName, that.clientName) && 44 | Objects.equals(failMsg, that.failMsg); 45 | } 46 | 47 | @Override 48 | public int hashCode() { 49 | return Objects.hash(clientName, failMsg); 50 | } 51 | 52 | @Override 53 | public String toString() { 54 | return "登录失败:[clientName:+" + clientName + ",+FailMsg:" + failMsg + "]"; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /common/src/main/java/com/fys/cmd/message/clientToServer/LoginCmd.java: -------------------------------------------------------------------------------- 1 | package com.fys.cmd.message.clientToServer; 2 | 3 | import com.fys.cmd.exception.AuthenticationException; 4 | import com.fys.cmd.message.Cmd; 5 | import io.netty.buffer.ByteBuf; 6 | import io.netty.buffer.ByteBufUtil; 7 | 8 | import java.util.Objects; 9 | 10 | import static java.nio.charset.StandardCharsets.UTF_8; 11 | 12 | /** 13 | * hcy 2020/3/25 14 | * 登录 15 | */ 16 | public class LoginCmd implements Cmd { 17 | 18 | private String clientName; 19 | 20 | public LoginCmd(String clientName) { 21 | this.clientName = clientName; 22 | } 23 | 24 | // flag 长度 clientName md5 25 | @Override 26 | public void encoderTo(ByteBuf buf) { 27 | buf.writeByte(ClientToServer.login); 28 | buf.writeInt(ByteBufUtil.utf8Bytes(clientName)); 29 | buf.writeCharSequence(clientName, UTF_8); 30 | } 31 | 32 | public static LoginCmd decoderFrom(ByteBuf in) { 33 | int clientNameLength = in.readInt(); 34 | if (clientNameLength > 50) { 35 | throw new AuthenticationException(); 36 | } 37 | CharSequence clientName = in.readCharSequence(clientNameLength, UTF_8); 38 | return new LoginCmd(clientName.toString()); 39 | } 40 | 41 | public String getClientName() { 42 | return clientName; 43 | } 44 | 45 | @Override 46 | public boolean equals(Object o) { 47 | if (this == o) return true; 48 | if (o == null || getClass() != o.getClass()) return false; 49 | LoginCmd loginCmd = (LoginCmd) o; 50 | return Objects.equals(clientName, loginCmd.clientName); 51 | } 52 | 53 | @Override 54 | public int hashCode() { 55 | 56 | return Objects.hash(clientName); 57 | } 58 | 59 | @Override 60 | public String toString() { 61 | return "LoginCmd{" + 62 | "clientName='" + clientName + '\'' + 63 | '}'; 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /client/src/main/java/com/fys/handler/CmdDecoder.java: -------------------------------------------------------------------------------- 1 | package com.fys.handler; 2 | 3 | import com.fys.cmd.message.Cmd; 4 | import com.fys.cmd.message.DataConnectionCmd; 5 | import com.fys.cmd.message.Pong; 6 | import com.fys.cmd.message.serverToClient.LoginFailCmd; 7 | import com.fys.cmd.message.Ping; 8 | import com.fys.cmd.message.serverToClient.ServerStartFailCmd; 9 | import com.fys.cmd.message.serverToClient.ServerStartSuccessCmd; 10 | import io.netty.buffer.ByteBuf; 11 | import io.netty.channel.ChannelHandlerContext; 12 | import io.netty.handler.codec.ReplayingDecoder; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | import java.util.List; 17 | 18 | /** 19 | * hcy 2020/2/18 20 | */ 21 | public class CmdDecoder extends ReplayingDecoder { 22 | 23 | private static Logger log = LoggerFactory.getLogger(CmdDecoder.class); 24 | 25 | @Override 26 | protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) { 27 | byte flag = in.readByte(); 28 | if (flag == Cmd.ping) { 29 | out.add(Ping.decoderFrom(in)); 30 | return; 31 | } 32 | if (flag == Cmd.pong) { 33 | out.add(Pong.decoderFrom(in)); 34 | return; 35 | } 36 | if (flag == Cmd.dataConnectionCmd) { 37 | out.add(DataConnectionCmd.decoderFrom(in)); 38 | return; 39 | } 40 | if (flag == Cmd.ServerToClient.serverStartSuccessCmd) { 41 | out.add(ServerStartSuccessCmd.decoderFrom(in)); 42 | return; 43 | } 44 | if (flag == Cmd.ServerToClient.serverStartFailCmd) { 45 | out.add(ServerStartFailCmd.decoderFrom(in)); 46 | return; 47 | } 48 | if (flag == Cmd.ServerToClient.loginFail) { 49 | out.add(LoginFailCmd.decoderFrom(in)); 50 | return; 51 | } 52 | log.error("无法识别服务端发送的指令,指令:{}", flag); 53 | in.skipBytes(actualReadableBytes()); 54 | ctx.close(); 55 | } 56 | 57 | 58 | } 59 | -------------------------------------------------------------------------------- /common/src/main/java/com/fys/cmd/handler/TransactionHandler.java: -------------------------------------------------------------------------------- 1 | package com.fys.cmd.handler; 2 | 3 | import io.netty.channel.*; 4 | import io.netty.util.ReferenceCountUtil; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | /** 9 | * 转换数据用的handler,将读到的数据传入out中 10 | * hcy 2020/2/10 11 | */ 12 | public class TransactionHandler extends ChannelInboundHandlerAdapter { 13 | 14 | private static Logger log = LoggerFactory.getLogger(TransactionHandler.class); 15 | private boolean autoRead; 16 | private Channel out; 17 | 18 | //输出管道,是否自动读取 19 | public TransactionHandler(Channel out, boolean autoRead) { 20 | this.out = out; 21 | this.autoRead = autoRead; 22 | } 23 | 24 | @Override 25 | public void channelRead(final ChannelHandlerContext ctx, Object msg) { 26 | boolean autoRelease = true; 27 | try { 28 | if (out.isActive()) { 29 | ChannelFuture f = out.writeAndFlush(msg); 30 | f.addListener(ChannelFutureListener.CLOSE_ON_FAILURE); 31 | //非自动读则手动读取一次 32 | if (!autoRead) { 33 | f.addListener(future -> { 34 | if (future.isSuccess()) { 35 | ctx.read(); 36 | } else { 37 | log.error("透传handler写入失败in:" + ctx + ",to:" + out, future.cause()); 38 | ctx.close(); 39 | } 40 | }); 41 | } 42 | autoRelease = false; 43 | } 44 | } finally { 45 | if (autoRelease) 46 | ReferenceCountUtil.release(msg); 47 | } 48 | } 49 | 50 | //如果输入的channel被关闭了,则手动关闭输出的管道 51 | @Override 52 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 53 | if (out.isActive()) { 54 | log.debug("[{}]被关闭了,所以同步关闭另一侧", ctx.name()); 55 | out.flush().close(); 56 | } else { 57 | log.debug("[{}]被关闭了,另一侧已经被关闭了,不处理", ctx.name()); 58 | } 59 | super.channelInactive(ctx); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /common/src/main/java/com/fys/cmd/message/serverToClient/ServerStartSuccessCmd.java: -------------------------------------------------------------------------------- 1 | package com.fys.cmd.message.serverToClient; 2 | 3 | import com.fys.cmd.message.Cmd; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.buffer.ByteBufUtil; 6 | 7 | import java.util.Objects; 8 | 9 | import static java.nio.charset.StandardCharsets.UTF_8; 10 | 11 | /** 12 | * hcy 2020/2/19 13 | */ 14 | public class ServerStartSuccessCmd implements Cmd { 15 | 16 | private int serverPort; 17 | private String localHost; 18 | private int localPort; 19 | 20 | public ServerStartSuccessCmd(int serverPort, String localHost, int localPort) { 21 | this.serverPort = serverPort; 22 | this.localHost = localHost; 23 | this.localPort = localPort; 24 | } 25 | 26 | @Override 27 | public void encoderTo(ByteBuf buf) { 28 | buf.writeByte(ServerToClient.serverStartSuccessCmd); 29 | buf.writeShort(serverPort); 30 | buf.writeShort(ByteBufUtil.utf8Bytes(localHost)); 31 | buf.writeCharSequence(localHost, UTF_8); 32 | buf.writeShort(localPort); 33 | } 34 | 35 | public static ServerStartSuccessCmd decoderFrom(ByteBuf in) { 36 | int serverPort = in.readUnsignedShort(); 37 | int localHostLength = in.readUnsignedShort(); 38 | CharSequence localHost = in.readCharSequence(localHostLength, UTF_8); 39 | int localPort = in.readUnsignedShort(); 40 | return new ServerStartSuccessCmd(serverPort, localHost.toString(), localPort); 41 | } 42 | 43 | @Override 44 | public boolean equals(Object o) { 45 | if (this == o) return true; 46 | if (o == null || getClass() != o.getClass()) return false; 47 | ServerStartSuccessCmd that = (ServerStartSuccessCmd) o; 48 | return serverPort == that.serverPort && 49 | localPort == that.localPort && 50 | Objects.equals(localHost, that.localHost); 51 | } 52 | 53 | @Override 54 | public int hashCode() { 55 | 56 | return Objects.hash(serverPort, localHost, localPort); 57 | } 58 | 59 | @Override 60 | public String toString() { 61 | return "Server[" + serverPort + "->" + localHost + ":" + localPort + "]开启成功"; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /server/src/main/java/com/fys/Config.java: -------------------------------------------------------------------------------- 1 | package com.fys; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.fys.conf.ServerInfo; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.io.FileInputStream; 10 | import java.io.FileNotFoundException; 11 | import java.io.IOException; 12 | import java.io.InputStream; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | /** 17 | * hcy 2020/2/18 18 | */ 19 | public class Config { 20 | 21 | private static Logger log = LoggerFactory.getLogger(Config.class); 22 | public static String bindHost = "0.0.0.0"; 23 | public static int bindPort; 24 | public static String token; 25 | //等待客户端数据连接的超时时间 26 | public static int timeOut = 5; 27 | 28 | private static List serverInfos = new ArrayList<>(); 29 | 30 | public static void init(String configPath) throws IOException { 31 | log.info("准备读取配置文件"); 32 | ObjectMapper mapper = new ObjectMapper(); 33 | InputStream input = getResource(configPath); 34 | JsonNode config = mapper.readTree(input); 35 | bindPort = config.at("/server/bindHost").intValue(); 36 | token = config.at("/server/token").textValue(); 37 | 38 | for (JsonNode node : config.get("clients")) { 39 | ServerInfo serverInfo = mapper.treeToValue(node, ServerInfo.class); 40 | serverInfos.add(serverInfo); 41 | } 42 | } 43 | 44 | public static ServerInfo getServerInfo(String clientName) { 45 | for (ServerInfo serverInfo : serverInfos) { 46 | if (clientName.equals(serverInfo.getClientName())) { 47 | return serverInfo; 48 | } 49 | } 50 | return null; 51 | } 52 | 53 | 54 | private static InputStream getResource(String resourceName) { 55 | if (resourceName == null) { 56 | return null; 57 | } 58 | InputStream input = ClassLoader.getSystemClassLoader().getResourceAsStream(resourceName); 59 | if (input != null) { 60 | return input; 61 | } 62 | try { 63 | return new FileInputStream(resourceName); 64 | } catch (FileNotFoundException e) { 65 | return null; 66 | } 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /common/src/main/java/com/fys/cmd/handler/PingPongHandler.java: -------------------------------------------------------------------------------- 1 | package com.fys.cmd.handler; 2 | 3 | import com.fys.cmd.message.Ping; 4 | import com.fys.cmd.message.Pong; 5 | import io.netty.channel.ChannelFutureListener; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.channel.SimpleChannelInboundHandler; 8 | import io.netty.handler.timeout.IdleState; 9 | import io.netty.handler.timeout.IdleStateEvent; 10 | import io.netty.handler.timeout.IdleStateHandler; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | 15 | public class PingPongHandler extends IdleStateHandler { 16 | 17 | private static Logger log = LoggerFactory.getLogger(PingPongHandler.class); 18 | //读超时时间 19 | private static int writeTimeOut = 10; 20 | //写超时时间 21 | private static int readTimeout = writeTimeOut * 4 + 2; 22 | 23 | public PingPongHandler() { 24 | super(readTimeout, writeTimeOut, 0); 25 | } 26 | 27 | /** 28 | * 此处添加连个handler来处理ping 和 pong消息 29 | * 对于pong消息,直接丢弃 30 | * 对于ping消息,需要回复pong 31 | */ 32 | @Override 33 | public void handlerAdded(ChannelHandlerContext ctx) throws Exception { 34 | super.handlerAdded(ctx); 35 | ctx.pipeline().addAfter(ctx.name(), null, new SimpleChannelInboundHandler() { 36 | @Override 37 | protected void channelRead0(ChannelHandlerContext ctx, Pong msg) { 38 | log.debug("收到Pong"); 39 | } 40 | }); 41 | ctx.pipeline().addAfter(ctx.name(), null, new SimpleChannelInboundHandler() { 42 | @Override 43 | protected void channelRead0(ChannelHandlerContext ctx, Ping msg) { 44 | log.debug("收到Ping"); 45 | ctx.writeAndFlush(new Pong()).addListener(ChannelFutureListener.CLOSE_ON_FAILURE); 46 | } 47 | }); 48 | } 49 | 50 | /* 51 | * 长时间没写(写超时),则发送Ping 52 | * 长时间没读(读超时),则断开连接 53 | * */ 54 | @Override 55 | protected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) { 56 | if (evt.state() == IdleState.WRITER_IDLE) { 57 | log.debug("发送Ping"); 58 | ctx.writeAndFlush(new Ping()).addListener(ChannelFutureListener.CLOSE_ON_FAILURE); 59 | } else { 60 | log.info("读超时断开连接:{}", ctx.channel().remoteAddress()); 61 | ctx.flush().close(); 62 | } 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /server/src/main/java/com/fys/handler/LoginCmdHandler.java: -------------------------------------------------------------------------------- 1 | package com.fys.handler; 2 | 3 | import com.fys.Config; 4 | import com.fys.ServerManager; 5 | import com.fys.cmd.message.clientToServer.LoginCmd; 6 | import com.fys.cmd.message.serverToClient.LoginFailCmd; 7 | import com.fys.cmd.message.serverToClient.ServerStartFailCmd; 8 | import com.fys.cmd.message.serverToClient.ServerStartSuccessCmd; 9 | import com.fys.conf.ServerInfo; 10 | import com.fys.conf.ServerWorker; 11 | import io.netty.channel.ChannelFutureListener; 12 | import io.netty.channel.ChannelHandlerContext; 13 | import io.netty.channel.SimpleChannelInboundHandler; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | 17 | /** 18 | * hcy 2020/3/25 19 | */ 20 | public class LoginCmdHandler extends SimpleChannelInboundHandler { 21 | 22 | private static Logger log = LoggerFactory.getLogger(LoginCmdHandler.class); 23 | 24 | @Override 25 | protected void channelRead0(ChannelHandlerContext ctx, LoginCmd msg) { 26 | ServerInfo serverInfo = Config.getServerInfo(msg.getClientName()); 27 | //确定配置中存在此客户端名 28 | if (serverInfo == null) { 29 | LoginFailCmd cmd = new LoginFailCmd(msg.getClientName(), "无法识别客户端名"); 30 | log.info(cmd.toString()); 31 | ctx.writeAndFlush(cmd).addListener(ChannelFutureListener.CLOSE); 32 | return; 33 | } 34 | 35 | log.info("{} 连接上来了", serverInfo.getClientName()); 36 | 37 | //开启配置中的映射服务,并在成功或失败后发送消息 38 | for (ServerWorker sw : serverInfo.getServerWorkers()) { 39 | ServerManager.startServers(sw, ctx.channel()).addListener(f -> { 40 | if (f.isSuccess()) { 41 | ServerStartSuccessCmd successCmd = new ServerStartSuccessCmd(sw.getServerPort(), sw.getLocalHost(), sw.getLocalPort()); 42 | log.info(successCmd.toString()); 43 | ctx.writeAndFlush(successCmd).addListener(ChannelFutureListener.CLOSE_ON_FAILURE); 44 | } else { 45 | ServerStartFailCmd failCmd = new ServerStartFailCmd(sw.getServerPort(), sw.getLocalHost(), sw.getLocalPort(), f.cause().toString()); 46 | log.error(failCmd.toString()); 47 | if (ctx.channel().isActive()) { 48 | ctx.writeAndFlush(failCmd).addListener(ChannelFutureListener.CLOSE); 49 | } 50 | } 51 | }); 52 | } 53 | 54 | } 55 | 56 | 57 | } 58 | -------------------------------------------------------------------------------- /server/src/main/java/com/fys/App.java: -------------------------------------------------------------------------------- 1 | package com.fys; 2 | 3 | import com.fys.cmd.handler.CmdEncoder; 4 | import com.fys.cmd.handler.Rc4Md5Handler; 5 | import com.fys.cmd.handler.TimeOutHandler; 6 | import com.fys.handler.ServerCmdDecoder; 7 | import io.netty.bootstrap.ServerBootstrap; 8 | import io.netty.channel.*; 9 | import io.netty.channel.nio.NioEventLoopGroup; 10 | import io.netty.channel.socket.nio.NioServerSocketChannel; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | import java.io.IOException; 15 | 16 | public class App { 17 | 18 | private static Logger log = LoggerFactory.getLogger(App.class); 19 | public static EventLoopGroup boss = new NioEventLoopGroup(1); 20 | public static EventLoopGroup work = new NioEventLoopGroup(1); 21 | 22 | /* 23 | * -c config.properties 24 | * */ 25 | public static void main(String[] args) throws IOException { 26 | Config.init("config.json"); 27 | ServerBootstrap sb = new ServerBootstrap(); 28 | sb.group(boss, work) 29 | .channel(NioServerSocketChannel.class) 30 | .option(ChannelOption.SO_RCVBUF, 32 * 1024) 31 | .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 6000) 32 | .childOption(ChannelOption.SO_RCVBUF, 128 * 1024) 33 | .childOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, 6000) 34 | .childOption(ChannelOption.TCP_NODELAY, true) 35 | .childHandler(new ChannelInitializer() { 36 | @Override 37 | protected void initChannel(Channel ch) throws Exception { 38 | ch.pipeline().addLast(new Rc4Md5Handler(Config.token)); 39 | ch.pipeline().addLast(new CmdEncoder()); 40 | //控制超时,防止链接上来但不发送消息任何的连接 41 | ch.pipeline().addLast(new TimeOutHandler(0, 0, 360)); 42 | ch.pipeline().addLast(new ServerCmdDecoder()); 43 | } 44 | }) 45 | .bind(Config.bindHost, Config.bindPort) 46 | .addListener((ChannelFutureListener) future -> { 47 | if (future.isSuccess()) { 48 | log.info("服务端在端口:{}启动成功", Config.bindPort); 49 | } else { 50 | log.error("服务端在端口:" + Config.bindPort + "启动失败", future.cause()); 51 | stop(); 52 | } 53 | }); 54 | } 55 | 56 | private static void stop() { 57 | boss.shutdownGracefully(); 58 | work.shutdownGracefully(); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /server/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | ${consolePattern} 11 | 12 | 13 | 14 | 15 | ${filePattern} 16 | 17 | server_info_log.log 18 | 19 | server_info_log_%d{yyyy-MM-dd}.%i.gz 20 | 10MB 21 | 5 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | %d{yyyy-MM-dd HH:mm:ss} %-5p %C-%m%n 33 | 34 | flowInfo.log 35 | 36 | flowInfo_%d{yyyy-MM-dd}.%i.gz 37 | 10MB 38 | 5 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | ${filePattern} 49 | 50 | server_debug_log.log 51 | 52 | server_debug_log_%d{yyyy-MM-dd}.%i.gz 53 | 10MB 54 | 5 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /common/src/main/java/com/fys/cmd/message/serverToClient/ServerStartFailCmd.java: -------------------------------------------------------------------------------- 1 | package com.fys.cmd.message.serverToClient; 2 | 3 | import com.fys.cmd.message.Cmd; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.buffer.ByteBufUtil; 6 | 7 | import java.util.Objects; 8 | 9 | import static java.nio.charset.StandardCharsets.UTF_8; 10 | 11 | /** 12 | * 告诉客户端,无法创建server,并返回无法创建的原因 13 | */ 14 | public class ServerStartFailCmd implements Cmd { 15 | 16 | private int serverPort; 17 | private String localHost; 18 | private int localPort; 19 | private String failMsg; 20 | 21 | public ServerStartFailCmd(int serverPort, String localHost, int localPort, String failMsg) { 22 | this.serverPort = serverPort; 23 | this.localHost = localHost; 24 | this.localPort = localPort; 25 | this.failMsg = failMsg; 26 | } 27 | 28 | @Override 29 | public void encoderTo(ByteBuf buf) { 30 | buf.writeByte(ServerToClient.serverStartFailCmd); 31 | buf.writeShort(serverPort); 32 | buf.writeShort(ByteBufUtil.utf8Bytes(localHost)); 33 | buf.writeCharSequence(localHost, UTF_8); 34 | buf.writeShort(localPort); 35 | buf.writeShort(ByteBufUtil.utf8Bytes(failMsg)); 36 | buf.writeCharSequence(failMsg, UTF_8); 37 | } 38 | 39 | public static ServerStartFailCmd decoderFrom(ByteBuf in) { 40 | int serverPort = in.readUnsignedShort(); 41 | int localHostLength = in.readUnsignedShort(); 42 | CharSequence localHost = in.readCharSequence(localHostLength, UTF_8); 43 | int localPort = in.readUnsignedShort(); 44 | int msgLength = in.readUnsignedShort(); 45 | CharSequence charSequence = in.readCharSequence(msgLength, UTF_8); 46 | return new ServerStartFailCmd(serverPort, localHost.toString(), localPort, charSequence.toString()); 47 | } 48 | 49 | @Override 50 | public boolean equals(Object o) { 51 | if (this == o) return true; 52 | if (o == null || getClass() != o.getClass()) return false; 53 | ServerStartFailCmd that = (ServerStartFailCmd) o; 54 | return serverPort == that.serverPort && 55 | localPort == that.localPort && 56 | Objects.equals(localHost, that.localHost) && 57 | Objects.equals(failMsg, that.failMsg); 58 | } 59 | 60 | @Override 61 | public int hashCode() { 62 | 63 | return Objects.hash(serverPort, localHost, localPort, failMsg); 64 | } 65 | 66 | @Override 67 | public String toString() { 68 | return "Server[" + serverPort + "->" + localHost + ":" + localPort + "]开启失败,因为" + failMsg; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /common/src/main/java/com/fys/cmd/message/DataConnectionCmd.java: -------------------------------------------------------------------------------- 1 | package com.fys.cmd.message; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | 5 | import java.util.Objects; 6 | 7 | import static java.nio.charset.StandardCharsets.UTF_8; 8 | 9 | /** 10 | * hcy 2020/2/18 11 | * 此类需要作为ServerManager里面map的键,注意要写hashcode 和 equals方法 12 | */ 13 | public final class DataConnectionCmd implements Cmd { 14 | 15 | private int serverPort; 16 | private String localHost; 17 | private int localPort; 18 | private long token; 19 | 20 | public DataConnectionCmd(int serverPort, String localHost, int localPort, long token) { 21 | this.serverPort = serverPort; 22 | this.localHost = localHost; 23 | this.localPort = localPort; 24 | this.token = token; 25 | } 26 | 27 | @Override 28 | public void encoderTo(ByteBuf buf) { 29 | buf.writeByte(Cmd.dataConnectionCmd); //flag 30 | buf.writeShort(serverPort); 31 | buf.writeShort(localHost.length()); 32 | buf.writeCharSequence(localHost, UTF_8); 33 | buf.writeShort(localPort); 34 | buf.writeLong(token); 35 | } 36 | 37 | public static DataConnectionCmd decoderFrom(ByteBuf in) { 38 | int serverPort = in.readUnsignedShort(); 39 | CharSequence localHost = in.readCharSequence(in.readShort(), UTF_8); 40 | int localPort = in.readUnsignedShort(); 41 | long token = in.readLong(); 42 | return new DataConnectionCmd(serverPort, localHost.toString(), localPort, token); 43 | } 44 | 45 | public int getServerPort() { 46 | return serverPort; 47 | } 48 | 49 | public int getLocalPort() { 50 | return localPort; 51 | } 52 | 53 | public String getLocalHost() { 54 | return localHost; 55 | } 56 | 57 | public long getToken() { 58 | return token; 59 | } 60 | 61 | @Override 62 | public String toString() { 63 | return "DataConnectionCmd{" + 64 | "serverPort=" + serverPort + 65 | ", localHost='" + localHost + '\'' + 66 | ", localPort=" + localPort + 67 | ", token=" + token + 68 | '}'; 69 | } 70 | 71 | 72 | @Override 73 | public boolean equals(Object o) { 74 | if (this == o) return true; 75 | if (o == null || getClass() != o.getClass()) return false; 76 | DataConnectionCmd cmd = (DataConnectionCmd) o; 77 | return serverPort == cmd.serverPort && 78 | localPort == cmd.localPort && 79 | token == cmd.token && 80 | Objects.equals(localHost, cmd.localHost); 81 | } 82 | 83 | @Override 84 | public int hashCode() { 85 | return Objects.hash(serverPort, localHost, localPort, token); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /server/src/main/java/com/fys/handler/ServerCmdDecoder.java: -------------------------------------------------------------------------------- 1 | package com.fys.handler; 2 | 3 | import com.fys.ServerManager; 4 | import com.fys.cmd.handler.ExceptionHandler; 5 | import com.fys.cmd.handler.PingPongHandler; 6 | import com.fys.cmd.handler.Rc4Md5Handler; 7 | import com.fys.cmd.message.Cmd; 8 | import com.fys.cmd.message.DataConnectionCmd; 9 | import com.fys.cmd.message.clientToServer.LoginCmd; 10 | import com.fys.cmd.message.Pong; 11 | import com.fys.cmd.message.Ping; 12 | import io.netty.buffer.ByteBuf; 13 | import io.netty.channel.Channel; 14 | import io.netty.channel.ChannelHandlerContext; 15 | import io.netty.handler.codec.ReplayingDecoder; 16 | import org.slf4j.Logger; 17 | import org.slf4j.LoggerFactory; 18 | 19 | import java.util.List; 20 | 21 | /** 22 | * hcy 2020/2/10 23 | * length,code,data 24 | * 此类处理服务端和客户端managerChannel 25 | */ 26 | public class ServerCmdDecoder extends ReplayingDecoder { 27 | 28 | private static Logger log = LoggerFactory.getLogger(ServerCmdDecoder.class); 29 | 30 | //防止多次登录 31 | private boolean addHandler = false; 32 | 33 | @Override 34 | protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) { 35 | byte flag = in.readByte(); 36 | 37 | //新建数据连接 38 | if (flag == Cmd.dataConnectionCmd) { 39 | DataConnectionCmd cmd = DataConnectionCmd.decoderFrom(in); 40 | log.debug("获取客户端连接:{}", cmd); 41 | ctx.pipeline().remove(this); 42 | ServerManager.addConnection(cmd, ctx.channel()); 43 | return; 44 | } 45 | 46 | //收到客户端pong,只有管理连接才能收到pong,数据连接不会发送Ping也就不会收到Pong 47 | if (flag == Cmd.pong) { 48 | out.add(Pong.decoderFrom(in)); 49 | return; 50 | } 51 | //收到客户端的ping 52 | if (flag == Cmd.ping) { 53 | out.add(Ping.decoderFrom(in)); 54 | return; 55 | } 56 | 57 | //登录 58 | if (flag == Cmd.ClientToServer.login) { 59 | LoginCmd login = LoginCmd.decoderFrom(in); 60 | if (!addHandler) { 61 | ctx.pipeline().addLast(new PingPongHandler()); 62 | ctx.pipeline().addLast(new LoginCmdHandler()); 63 | ctx.pipeline().addLast(ExceptionHandler.INSTANCE); 64 | addHandler = !addHandler; 65 | } 66 | out.add(login); 67 | return; 68 | } 69 | 70 | //无法识别的指令 71 | log.error("无法识别客户端:{} 发送的指令,指令:{}", ctx.channel().remoteAddress(), flag); 72 | in.skipBytes(actualReadableBytes()); 73 | ctx.close(); 74 | 75 | } 76 | 77 | @Override 78 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 79 | Channel channel = ctx.channel(); 80 | if ("Connection reset by peer".equals(cause.getMessage())) { 81 | log.error("Connection reset by peer local:{},remote:{}", channel.localAddress(), channel.remoteAddress()); 82 | return; 83 | } 84 | log.error("ServerCmdDecoder:" + channel.localAddress() + " remote" + channel.remoteAddress(), cause); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.remotePort 7 | remotePortMapping 8 | pom 9 | 1.0-SNAPSHOT 10 | 11 | 12 | common 13 | client 14 | server 15 | 16 | 17 | remotePortMapping 18 | 19 | 20 | UTF-8 21 | 1.8 22 | 1.8 23 | 24 | 25 | 26 | 27 | 28 | com.remotePort 29 | common 30 | 1.0-SNAPSHOT 31 | 32 | 33 | io.netty 34 | netty-all 35 | 4.1.45.Final 36 | 37 | 38 | ch.qos.logback 39 | logback-classic 40 | 1.2.3 41 | 42 | 43 | com.fasterxml.jackson.core 44 | jackson-databind 45 | 2.10.3 46 | 47 | 48 | junit 49 | junit 50 | 4.11 51 | test 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | maven-resources-plugin 61 | 3.1.0 62 | 63 | 64 | maven-compiler-plugin 65 | 3.8.1 66 | 67 | ${project.build.sourceEncoding} 68 | ${maven.compiler.source} 69 | ${maven.compiler.target} 70 | 71 | 72 | 73 | maven-jar-plugin 74 | 3.0.2 75 | 76 | 77 | maven-shade-plugin 78 | 3.2.1 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /server/src/main/java/com/fys/handler/FlowManagerHandler.java: -------------------------------------------------------------------------------- 1 | package com.fys.handler; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelFutureListener; 5 | import io.netty.channel.ChannelHandler; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.handler.codec.MessageToMessageCodec; 8 | import io.netty.util.concurrent.ScheduledFuture; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import java.text.DecimalFormat; 13 | import java.util.List; 14 | import java.util.concurrent.TimeUnit; 15 | import java.util.concurrent.atomic.AtomicLong; 16 | 17 | /** 18 | * hcy 2020/2/20 19 | * 流量统计,定时输出流量信息 20 | */ 21 | @ChannelHandler.Sharable 22 | public class FlowManagerHandler extends MessageToMessageCodec { 23 | 24 | private static Logger log = LoggerFactory.getLogger(FlowManagerHandler.class); 25 | private static final int[] ds = {0, 1024, 1024 * 1024, 1024 * 1024 * 1024}; 26 | private static final String[] dw = {"B", "KB", "MB", "GB"}; 27 | private static final DecimalFormat format = new DecimalFormat(".##"); 28 | 29 | private AtomicLong decodeFlow = new AtomicLong(0); 30 | private AtomicLong encodeFlow = new AtomicLong(0); 31 | 32 | public String serverName; 33 | private String encodeName, decodeName; 34 | 35 | public FlowManagerHandler(String serverName, String encodeName, String decodeName) { 36 | this.serverName = serverName; 37 | this.encodeName = encodeName; 38 | this.decodeName = decodeName; 39 | } 40 | 41 | @Override 42 | public void handlerAdded(ChannelHandlerContext ctx) throws Exception { 43 | ScheduledFuture scheduledFuture = ctx.executor().scheduleAtFixedRate(new Runnable() { 44 | @Override 45 | public void run() { 46 | 47 | 48 | String decode = ""; 49 | for (int i = ds.length - 1; i >= 0; i--) { 50 | if (decodeFlow.doubleValue() > ds[i]) { 51 | decode = format.format(decodeFlow.doubleValue() / ds[i]) + dw[i]; 52 | break; 53 | } 54 | } 55 | 56 | String encode = ""; 57 | for (int i = ds.length - 1; i >= 0; i--) { 58 | if (encodeFlow.doubleValue() > ds[i]) { 59 | encode = format.format(encodeFlow.doubleValue() / ds[i]) + dw[i]; 60 | break; 61 | } 62 | } 63 | 64 | log.info("{}:{}流量:{},{}流量:{}", serverName, decodeName, decode, encodeName, encode); 65 | } 66 | }, 30, 60, TimeUnit.SECONDS); 67 | 68 | //这里添加关闭定时的监听 69 | ctx.channel().closeFuture().addListener((ChannelFutureListener) future -> scheduledFuture.cancel(true)); 70 | super.handlerAdded(ctx); 71 | } 72 | 73 | @Override 74 | protected void encode(ChannelHandlerContext ctx, ByteBuf msg, List out) throws Exception { 75 | encodeFlow.addAndGet(msg.readableBytes()); 76 | out.add(msg.readRetainedSlice(msg.readableBytes())); 77 | } 78 | 79 | @Override 80 | protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List out) throws Exception { 81 | decodeFlow.addAndGet(msg.readableBytes()); 82 | out.add(msg.readRetainedSlice(msg.readableBytes())); 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /client/src/main/java/com/fys/AppClient.java: -------------------------------------------------------------------------------- 1 | package com.fys; 2 | 3 | import com.fys.cmd.handler.CmdEncoder; 4 | import com.fys.cmd.handler.ExceptionHandler; 5 | import com.fys.cmd.handler.PingPongHandler; 6 | import com.fys.cmd.handler.Rc4Md5Handler; 7 | import com.fys.cmd.message.clientToServer.LoginCmd; 8 | import com.fys.handler.*; 9 | import io.netty.bootstrap.Bootstrap; 10 | import io.netty.channel.*; 11 | import io.netty.channel.nio.NioEventLoopGroup; 12 | import io.netty.channel.socket.nio.NioSocketChannel; 13 | import io.netty.util.AttributeKey; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | 17 | import java.util.concurrent.TimeUnit; 18 | 19 | /** 20 | * 客户端启动类 21 | */ 22 | public class AppClient { 23 | 24 | private static Logger log = LoggerFactory.getLogger(AppClient.class); 25 | public static EventLoopGroup work = new NioEventLoopGroup(1); 26 | public static AttributeKey key = AttributeKey.newInstance("config"); 27 | 28 | private Config config = Config.INSTANCE; 29 | 30 | public static void main(String[] args) { 31 | AppClient appClient = new AppClient(); 32 | appClient.start(); 33 | } 34 | 35 | private void start() { 36 | createManagerConnection() 37 | .addListener((ChannelFutureListener) future -> { 38 | if (future.isSuccess()) { 39 | log.info("连接成功{}:{}等待服务端验证", config.getServerIp(), config.getServerPort()); 40 | //断开连接10s后会重试 41 | future.channel().closeFuture().addListener(f -> { 42 | log.info("检测到连接断开了10后重连", f.cause()); 43 | work.schedule(this::start, 10, TimeUnit.SECONDS); 44 | }); 45 | } else { 46 | log.error("连接失败,5秒后重试:{}", future.cause().toString()); 47 | work.schedule(this::start, 5, TimeUnit.SECONDS); 48 | } 49 | }); 50 | } 51 | 52 | 53 | private ChannelFuture createManagerConnection() { 54 | Bootstrap b = new Bootstrap(); 55 | return b.group(work) 56 | .channel(NioSocketChannel.class) 57 | .remoteAddress(config.getServerIp(), config.getServerPort()) 58 | .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 4000) 59 | .option(ChannelOption.TCP_NODELAY, true) 60 | //将配置类存入channel 61 | .attr(key, config) 62 | .handler(new ChannelInitializer() { 63 | @Override 64 | protected void initChannel(Channel ch) { 65 | ch.pipeline().addLast(new Rc4Md5Handler(config.getToken())); 66 | ch.pipeline().addLast(new CmdEncoder()); 67 | 68 | ch.pipeline().addLast(new CmdDecoder()); 69 | ch.pipeline().addLast(new PingPongHandler()); 70 | ch.pipeline().addLast(new LoginHandler()); 71 | ch.pipeline().addLast(new DataConnectionHandler()); 72 | ch.pipeline().addLast(new ServerStartSuccessHandler()); 73 | ch.pipeline().addLast(new ServerStartFailHandler()); 74 | ch.pipeline().addLast(new LoginFailHandler()); 75 | ch.pipeline().addLast(ExceptionHandler.INSTANCE); 76 | } 77 | } 78 | ) 79 | .connect(); 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /client/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | com.remotePort 6 | remotePortMapping 7 | 1.0-SNAPSHOT 8 | 9 | 10 | 4.0.0 11 | client 12 | client 13 | 14 | 15 | 16 | com.remotePort 17 | common 18 | 19 | 20 | junit 21 | junit 22 | test 23 | 24 | 25 | 26 | 27 | 28 | 29 | maven-resources-plugin 30 | 31 | 32 | copy-bulid 33 | package 34 | 35 | copy-resources 36 | 37 | 38 | UTF-8 39 | 40 | ${project.build.directory}/ 41 | 42 | 43 | 44 | src/main/resources/ 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | maven-jar-plugin 53 | 54 | 55 | 56 | false 57 | 58 | com.fys.AppClient 59 | 60 | false 61 | 62 | 63 | 64 | 65 | config.properties 66 | logback-test.xml 67 | 68 | 69 | 70 | 71 | maven-shade-plugin 72 | 73 | true 74 | 75 | 76 | ch.qos.logback:logback-core 77 | 78 | ** 79 | 80 | 81 | 82 | 83 | 84 | 85 | package 86 | 87 | shade 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /client/src/main/java/com/fys/DataConnectionClient.java: -------------------------------------------------------------------------------- 1 | package com.fys; 2 | 3 | import com.fys.cmd.handler.*; 4 | import com.fys.cmd.listener.ErrorLogListener; 5 | import com.fys.cmd.message.DataConnectionCmd; 6 | import io.netty.bootstrap.Bootstrap; 7 | import io.netty.channel.*; 8 | import io.netty.channel.socket.nio.NioSocketChannel; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | /** 13 | * hcy 2020/2/17 14 | * 由客户端连接到服务端,用户传输数据 15 | */ 16 | public class DataConnectionClient { 17 | 18 | private static Logger log = LoggerFactory.getLogger(DataConnectionClient.class); 19 | private static EventLoopGroup work = AppClient.work; 20 | private Config config; 21 | private DataConnectionCmd msg; 22 | 23 | public DataConnectionClient(Config config, DataConnectionCmd msg) { 24 | this.config = config; 25 | this.msg = msg; 26 | } 27 | 28 | /* 29 | * 数据连接连接到服务器上时,如果不说明自己是数据连接,服务器是不会主动发数据的,所以这里改成自动读提高性能 30 | * */ 31 | public void start() { 32 | //连接到Local端口成功后,再尝试连接到服务器 33 | createConnectionToLocal(msg.getLocalHost(), msg.getLocalPort()).addListener((ChannelFutureListener) localFuture -> { 34 | if (!localFuture.isSuccess()) { 35 | log.error("Client连接到Local失败", localFuture.cause()); 36 | return; 37 | } 38 | final Channel channelToLocal = localFuture.channel(); 39 | 40 | createConnectionToServer(config.getServerIp(), config.getServerPort()).addListener((ChannelFutureListener) serverFuture -> { 41 | if (serverFuture.isSuccess()) { 42 | Channel channelToServer = serverFuture.channel(); 43 | channelToServer.pipeline().addBefore(ExceptionHandler.NAME, "linkServer", new TransactionHandler(channelToLocal, true)); 44 | channelToLocal.pipeline().addBefore(ExceptionHandler.NAME, "linkLocal", new TransactionHandler(channelToServer, true)); 45 | //发送认证消息,并将Rc4Md5handler移除,因为数据传输阶段不需要加密 46 | channelToServer.writeAndFlush(msg) 47 | .addListener(ErrorLogListener.INSTANCE) 48 | .addListener(ChannelFutureListener.CLOSE_ON_FAILURE); 49 | } else { 50 | log.error("Client连接到Remote失败", serverFuture.cause()); 51 | channelToLocal.close(); 52 | } 53 | }); 54 | }); 55 | } 56 | 57 | //创建一个指向本地端口的连接 58 | private ChannelFuture createConnectionToLocal(String host, int port) { 59 | return newClientBootStrap(work) 60 | .handler(new ChannelInitializer() { 61 | @Override 62 | protected void initChannel(Channel ch) throws Exception { 63 | ch.pipeline().addLast(new TimeOutHandler(0, 0, 180)); 64 | ch.pipeline().addLast(ExceptionHandler.NAME, ExceptionHandler.INSTANCE); 65 | } 66 | }) 67 | .connect(host, port); 68 | } 69 | 70 | //创建一个指向服务器端口的连接 71 | //客户端连接服务器不加超时管理,因为服务器会判断超时主动断开 72 | private ChannelFuture createConnectionToServer(String host, int port) { 73 | return newClientBootStrap(work) 74 | .handler(new ChannelInitializer() { 75 | @Override 76 | protected void initChannel(Channel ch) throws Exception { 77 | ch.pipeline().addLast(new Rc4Md5Handler(config.getToken())); 78 | ch.pipeline().addLast(new CmdEncoder()); 79 | 80 | ch.pipeline().addLast(ExceptionHandler.NAME, ExceptionHandler.INSTANCE); 81 | } 82 | }) 83 | .connect(host, port); 84 | } 85 | 86 | private static Bootstrap newClientBootStrap(EventLoopGroup group) { 87 | return new Bootstrap().group(group) 88 | .channel(NioSocketChannel.class) 89 | .option(ChannelOption.SO_RCVBUF, 128 * 1024) 90 | .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) 91 | .option(ChannelOption.TCP_NODELAY, true); 92 | } 93 | 94 | 95 | } 96 | -------------------------------------------------------------------------------- /server/src/main/java/com/fys/ServerManager.java: -------------------------------------------------------------------------------- 1 | package com.fys; 2 | 3 | import com.fys.cmd.listener.ErrorLogListener; 4 | import com.fys.cmd.message.DataConnectionCmd; 5 | import com.fys.conf.ServerWorker; 6 | import io.netty.channel.Channel; 7 | import io.netty.util.concurrent.DefaultEventExecutor; 8 | import io.netty.util.concurrent.EventExecutor; 9 | import io.netty.util.concurrent.Promise; 10 | import io.netty.util.concurrent.ScheduledFuture; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | import java.util.concurrent.TimeUnit; 17 | import java.util.concurrent.TimeoutException; 18 | 19 | /** 20 | * hcy 2020/2/18 21 | */ 22 | public class ServerManager { 23 | 24 | private static Logger log = LoggerFactory.getLogger(ServerManager.class); 25 | 26 | private static EventExecutor managerEventLoop = new DefaultEventExecutor(); 27 | 28 | //这些promise在等待连接的到来 29 | private static Map> waitConnections = new HashMap<>(); 30 | 31 | /* 32 | * 当客户端登录成功后,遍历映射表,开启所有服务,并绑定到managerChannel的生命周期上 33 | * 开启关闭Server均使用单个eventLoop执行保证按顺序执行 34 | * */ 35 | public static Promise startServers(ServerWorker sw, Channel managerChannel) { 36 | Promise promise = managerEventLoop.newPromise(); 37 | execute(() -> { 38 | if (managerChannel.isActive()) { 39 | Server server = new Server(sw, managerChannel); 40 | server.start(promise); 41 | managerChannel.closeFuture().addListener(future -> { 42 | log.error("managerChannel断开了,准备关闭Server:" + sw.getServerPort(), future.cause()); 43 | execute(server::stop); 44 | }); 45 | } else { 46 | promise.setFailure(new RuntimeException("尚未创建server,但已经断连了")); 47 | } 48 | }); 49 | return promise; 50 | } 51 | 52 | /* 53 | * 接收到客户端连接,为指定serverId的服务添加链接 54 | * */ 55 | public static void addConnection(DataConnectionCmd cmd, Channel channel) { 56 | execute(() -> { 57 | Promise promise = waitConnections.remove(cmd); 58 | if (promise == null) { 59 | log.info("ServerManager.addConnection无法找到Promise,可能promise已被超时取消Channel:{}", channel); 60 | channel.close(); 61 | return; 62 | } 63 | promise.setSuccess(channel); 64 | }); 65 | } 66 | 67 | public static Promise getConnection(Server server) { 68 | Promise promise = managerEventLoop.newPromise(); 69 | ServerWorker sw = server.getServerWorker(); 70 | DataConnectionCmd message = new DataConnectionCmd(sw.getServerPort(), sw.getLocalHost(), sw.getLocalPort(), System.nanoTime()); 71 | server.getManagerChannel().writeAndFlush(message) 72 | .addListener(ErrorLogListener.INSTANCE) 73 | .addListener(future -> { 74 | if (future.isSuccess()) { 75 | execute(() -> { 76 | waitConnections.put(message, promise); 77 | //设置定时任务,超时则设置promise为failure 78 | schedule(() -> { 79 | waitConnections.remove(message); 80 | //说明result还没被设置值,说明还没成功 81 | if (promise.isCancellable()) { 82 | promise.setFailure(new TimeoutException("Promise超时无法获取连接")); 83 | } 84 | }, Config.timeOut, TimeUnit.SECONDS); 85 | }); 86 | } else { 87 | promise.setFailure(future.cause()); 88 | } 89 | }); 90 | return promise; 91 | } 92 | 93 | private static void execute(Runnable runnable) { 94 | if (managerEventLoop.inEventLoop()) { 95 | runnable.run(); 96 | } else { 97 | managerEventLoop.execute(runnable); 98 | } 99 | } 100 | 101 | private static ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { 102 | return managerEventLoop.schedule(command, delay, unit); 103 | } 104 | 105 | 106 | } 107 | -------------------------------------------------------------------------------- /common/src/main/java/com/fys/cmd/handler/Rc4Md5Handler.java: -------------------------------------------------------------------------------- 1 | package com.fys.cmd.handler; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.ByteBufUtil; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.handler.codec.ByteToMessageCodec; 7 | import io.netty.util.ReferenceCountUtil; 8 | 9 | import javax.crypto.Cipher; 10 | import javax.crypto.spec.SecretKeySpec; 11 | import java.security.SecureRandom; 12 | import java.util.List; 13 | 14 | import static com.fys.cmd.util.CodeUtil.md5; 15 | import static java.nio.charset.StandardCharsets.UTF_8; 16 | 17 | /** 18 | * hcy 2020/4/3 19 | */ 20 | public class Rc4Md5Handler extends ByteToMessageCodec { 21 | 22 | private final static SecureRandom random = new SecureRandom(); 23 | private boolean firstDecode = true; 24 | private boolean firstEncode = true; 25 | private Cipher decoderCipher; 26 | private Cipher encoderCipher; 27 | 28 | private String password; 29 | 30 | public Rc4Md5Handler(String password) { 31 | this.password = password; 32 | } 33 | 34 | /* 35 | * 第一次写数据时,生成随机IV 构建cipher 36 | * 使用cipher加密数据 37 | * */ 38 | @Override 39 | protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception { 40 | if (firstEncode) { 41 | byte[] iv = randomIv(); 42 | encoderCipher = Cipher.getInstance("RC4"); 43 | encoderCipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(md5(md5(password.getBytes(UTF_8)), iv), "RC4")); 44 | firstEncode = false; 45 | out.writeBytes(iv); 46 | } 47 | if (!msg.isReadable()) { 48 | return; 49 | } 50 | 51 | //需要加密的明文长度 52 | int dataLength = msg.readableBytes(); 53 | int outputSize = encoderCipher.getOutputSize(dataLength); 54 | 55 | //分配存储密文的buff 56 | ByteBuf outPut = ctx.alloc().ioBuffer(outputSize); 57 | try { 58 | int updateLength = encoderCipher.update(msg.nioBuffer(), outPut.nioBuffer(0, outputSize)); 59 | //忽略处理过的数据 60 | msg.skipBytes(updateLength); 61 | outPut.writerIndex(updateLength); 62 | out.writeBytes(outPut); 63 | } finally { 64 | ReferenceCountUtil.release(outPut); 65 | } 66 | } 67 | 68 | 69 | /* 70 | * 初次读时,前16位作为iv使用,构建cipher,以后再使用则不需要构建 71 | * */ 72 | @Override 73 | protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { 74 | if (firstDecode) { 75 | if (in.readableBytes() < 16) { 76 | return; 77 | } 78 | byte[] iv = readByte(in, 16); 79 | decoderCipher = Cipher.getInstance("RC4"); 80 | byte[] realPassWord = md5(md5(password.getBytes(UTF_8)), iv); 81 | decoderCipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(realPassWord, "RC4")); 82 | firstDecode = false; 83 | } 84 | if (!in.isReadable()) { 85 | return; 86 | } 87 | //密文长度 和 明文长度 88 | int dataLength = in.readableBytes(); 89 | int outputSize = decoderCipher.getOutputSize(dataLength); 90 | //创建buf,将明文解析到此buf 91 | ByteBuf outPut = in.alloc().ioBuffer(outputSize); 92 | try { 93 | int updateLength = decoderCipher.update(in.nioBuffer(), outPut.nioBuffer(0, outputSize)); 94 | in.skipBytes(updateLength); 95 | outPut.writerIndex(updateLength); 96 | //这里增加一次索引,因为finally里面会释放一次 97 | outPut.retain(); 98 | out.add(outPut); 99 | } finally { 100 | ReferenceCountUtil.release(outPut); 101 | } 102 | } 103 | 104 | 105 | private byte[] readByte(ByteBuf in, int length) { 106 | byte[] bytes = ByteBufUtil.getBytes(in, in.readerIndex(), length); 107 | in.skipBytes(bytes.length); 108 | return bytes; 109 | } 110 | 111 | private byte[] randomIv() { 112 | return random.generateSeed(16); 113 | } 114 | 115 | @Override 116 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 117 | if (encoderCipher != null) { 118 | encoderCipher.doFinal(); 119 | } 120 | if (decoderCipher != null) { 121 | decoderCipher.doFinal(); 122 | } 123 | super.channelInactive(ctx); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | com.remotePort 6 | remotePortMapping 7 | 1.0-SNAPSHOT 8 | 9 | 10 | 4.0.0 11 | server 12 | server 13 | 14 | 15 | 16 | com.remotePort 17 | common 18 | 19 | 20 | com.fasterxml.jackson.core 21 | jackson-databind 22 | 23 | 24 | com.h2database 25 | h2 26 | 1.4.200 27 | test 28 | 29 | 30 | junit 31 | junit 32 | test 33 | 34 | 35 | 36 | 37 | 38 | 39 | maven-resources-plugin 40 | 41 | 42 | copy-bulid 43 | package 44 | 45 | copy-resources 46 | 47 | 48 | UTF-8 49 | 50 | ${project.build.directory}/ 51 | 52 | 53 | 54 | src/main/resources/ 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | maven-jar-plugin 63 | 64 | 65 | 66 | false 67 | 68 | com.fys.App 69 | 70 | false 71 | 72 | lib/ 73 | 74 | 75 | 76 | config.json 77 | logback-test.xml 78 | 79 | 80 | 81 | 82 | maven-shade-plugin 83 | 84 | true 85 | 86 | 87 | ch.qos.logback:logback-core 88 | 89 | ** 90 | 91 | 92 | 93 | com.fasterxml.jackson.core:jackson-databind 94 | 95 | ** 96 | 97 | 98 | 99 | 100 | 101 | 102 | package 103 | 104 | shade 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /server/src/main/java/com/fys/Server.java: -------------------------------------------------------------------------------- 1 | package com.fys; 2 | 3 | import com.fys.cmd.handler.ExceptionHandler; 4 | import com.fys.cmd.handler.TimeOutHandler; 5 | import com.fys.cmd.handler.TransactionHandler; 6 | import com.fys.conf.ServerWorker; 7 | import com.fys.handler.FlowManagerHandler; 8 | import io.netty.bootstrap.ServerBootstrap; 9 | import io.netty.channel.*; 10 | import io.netty.channel.socket.nio.NioServerSocketChannel; 11 | import io.netty.util.concurrent.Future; 12 | import io.netty.util.concurrent.GenericFutureListener; 13 | import io.netty.util.concurrent.Promise; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | 17 | /** 18 | * 客户端要求监听某一个端口后创建并启动此类 19 | * 接收外接连接传送到客户端 20 | * 因此此类不会autoRead 21 | * hcy 2020/2/17 22 | */ 23 | public class Server { 24 | 25 | private static Logger log = LoggerFactory.getLogger(Server.class); 26 | private EventLoopGroup boss = App.boss; 27 | private EventLoopGroup work = App.work; 28 | 29 | private ChannelFuture bind; 30 | 31 | private ServerWorker serverWorker; 32 | private Channel managerChannel; 33 | private FlowManagerHandler flowManagerHandler; 34 | 35 | public Server(ServerWorker serverWorker, Channel managerChannel) { 36 | this.serverWorker = serverWorker; 37 | this.managerChannel = managerChannel; 38 | flowManagerHandler = 39 | new FlowManagerHandler("server on " + serverWorker.getServerPort(), "发送到使用者的", "从使用者接收的"); 40 | } 41 | 42 | 43 | public void start(Promise promise) { 44 | ServerBootstrap sb = new ServerBootstrap(); 45 | bind = sb.group(boss, work) 46 | .channel(NioServerSocketChannel.class) 47 | .option(ChannelOption.SO_RCVBUF, 32 * 1024) 48 | .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 6000) 49 | .childOption(ChannelOption.SO_RCVBUF, 128 * 1024) 50 | .childOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, 6000) 51 | .childOption(ChannelOption.TCP_NODELAY, true) 52 | .childOption(ChannelOption.AUTO_READ, false) 53 | .childHandler(new ChannelInitializer() { 54 | @Override 55 | protected void initChannel(Channel ch) { 56 | ch.pipeline().addLast(new TimeOutHandler(0, 0, 180)); 57 | ch.pipeline().addLast(flowManagerHandler); 58 | ch.pipeline().addLast(new ServerHandler(Server.this)); 59 | ch.pipeline().addLast(ExceptionHandler.INSTANCE); 60 | } 61 | }).bind(Config.bindHost, serverWorker.getServerPort()); 62 | 63 | //添加服务开启成功失败事件 64 | bind.addListener((ChannelFutureListener) future -> { 65 | if (future.isSuccess()) { 66 | promise.setSuccess(this); 67 | } else { 68 | promise.setFailure(future.cause()); 69 | } 70 | }); 71 | } 72 | 73 | public void stop() { 74 | if (managerChannel != null && managerChannel.isActive()) { 75 | managerChannel.close(); 76 | } 77 | if (bind != null && bind.channel().isActive()) { 78 | bind.channel().close().addListener(f -> log.info("Server在端口:{}关闭成功", serverWorker.getServerPort())); 79 | } 80 | } 81 | 82 | public ServerWorker getServerWorker() { 83 | return serverWorker; 84 | } 85 | 86 | public Channel getManagerChannel() { 87 | return managerChannel; 88 | } 89 | 90 | //当外网用户连接到监听的端口后,将打开与客户端的连接,并传输数据 91 | private static class ServerHandler extends ChannelInboundHandlerAdapter { 92 | 93 | private Server server; 94 | 95 | ServerHandler(Server server) { 96 | this.server = server; 97 | } 98 | 99 | @Override 100 | public void channelActive(ChannelHandlerContext userConnection) { 101 | Promise promise = ServerManager.getConnection(server); 102 | promise.addListener((GenericFutureListener>) future -> { 103 | if (future.isSuccess()) { 104 | Channel clientChannel = future.getNow(); 105 | clientChannel.pipeline().addLast("linkClient", new TransactionHandler(userConnection.channel(), true)); 106 | clientChannel.pipeline().addLast(ExceptionHandler.INSTANCE); 107 | userConnection.pipeline().replace(this, "linkUser", new TransactionHandler(clientChannel, true)); 108 | userConnection.channel().config().setAutoRead(true); 109 | } else { 110 | //promise失败可能是:超时没结果被定时任务取消的 111 | log.error("服务端获取对客户端的连接失败,关闭userConnection", future.cause()); 112 | userConnection.close(); 113 | } 114 | }); 115 | } 116 | } 117 | } 118 | --------------------------------------------------------------------------------