├── .gitignore ├── README.md ├── codenow-im-chat └── pom.xml ├── codenow-rpc └── pom.xml ├── java-network-programming ├── README.md └── pom.xml ├── netty-boot ├── dependency-reduced-pom.xml ├── pom.xml └── src │ └── main │ ├── java │ └── fun │ │ └── codenow │ │ └── netty │ │ ├── NettyBootServer.java │ │ ├── handler │ │ ├── ApplicationLoggingHandler.java │ │ ├── HeartBeatResponseHandler.java │ │ └── ServerChannelInitializer.java │ │ └── message │ │ └── CustomMessageProto.java │ └── resources │ └── log4j.properties ├── netty-common ├── pom.xml └── src │ └── main │ └── java │ └── fun │ └── codenow │ └── netty │ └── common │ ├── CustomMessageProto.java │ └── codec │ ├── CustomDecode.java │ └── CustomEncode.java ├── netty-demo ├── pom.xml └── src │ └── main │ ├── java │ └── fun │ │ └── codenow │ │ └── netty │ │ └── socket │ │ ├── NettySocketApplication.java │ │ ├── coder │ │ └── protobuf │ │ │ ├── ClientOne.java │ │ │ └── ServerOne.java │ │ ├── demo │ │ ├── EchoClient.java │ │ ├── EchoClientHandler.java │ │ ├── EchoServer.java │ │ ├── EchoServerHandler.java │ │ └── WebSocketServer.java │ │ ├── heart │ │ ├── CustomHeartBeatClientHandler.java │ │ ├── CustomHeartBeatInitializer.java │ │ ├── CustomHeartBeathandler.java │ │ ├── CustomHeartbeatClientInitializer.java │ │ ├── HeartbeatClient.java │ │ └── HeartbeatServer.java │ │ ├── heartbeat │ │ ├── CustomProtocol.java │ │ ├── HeartBeatServer.java │ │ ├── HeartBeatSimpleHandle.java │ │ ├── HeartbeatDecoder.java │ │ ├── HeartbeatEncode.java │ │ ├── HeartbeatInitializer.java │ │ ├── NettySocketHolder.java │ │ ├── client │ │ │ ├── CustomerHandleInitializer.java │ │ │ ├── EchoClientHandle.java │ │ │ └── HeartbeatClient.java │ │ └── util │ │ │ └── SpringBeanFactory.java │ │ ├── inaction │ │ ├── Client.java │ │ ├── ClientHandler.java │ │ ├── Server.java │ │ └── ServerHandler.java │ │ ├── protobuf │ │ ├── SubscribeReqProto.java │ │ └── SubscribeRespProto.java │ │ ├── protocol │ │ ├── custom │ │ │ └── CustomProtocolServer.java │ │ ├── http │ │ │ └── HttpNettyServer.java │ │ └── websocket │ │ │ └── WebSocketNettyServer.java │ │ ├── subscribebook │ │ ├── SubscribeBookClient.java │ │ ├── SubscribeBookClientHandler.java │ │ ├── SubscribeBookServer.java │ │ └── SubscribeBookServerHandler.java │ │ ├── test │ │ ├── EchoClientTest.java │ │ └── EchoServerTest.java │ │ ├── test2 │ │ ├── EchoClientTest2.java │ │ ├── EchoClientTest2ChannelHandler.java │ │ ├── EchoServer2ChannelHanlder.java │ │ └── EchoServerTest2.java │ │ ├── timeserver │ │ ├── TimeClient.java │ │ ├── TimeClientHandler.java │ │ ├── TimeServer.java │ │ ├── TimeServerHandler.java │ │ └── TimeServerInitializer.java │ │ └── uptime │ │ ├── UptimeClient.java │ │ ├── UptimeClientHandler.java │ │ ├── UptimeServer.java │ │ └── UptimeServerHandler.java │ └── resources │ └── application.yml ├── netty-file-system └── pom.xml ├── netty-heartbeat ├── heartbeat-client │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── fun │ │ │ └── codenow │ │ │ └── netty │ │ │ └── heartbeat │ │ │ └── client │ │ │ ├── HeartBeatClientApplication.java │ │ │ ├── client │ │ │ ├── Client.java │ │ │ └── CustomClientChannelInitializer.java │ │ │ └── handler │ │ │ └── HeartBeatClientHandler.java │ │ └── resources │ │ └── application.yml ├── heartbeat-server │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── fun │ │ │ └── codenow │ │ │ └── netty │ │ │ └── heartbeat │ │ │ └── server │ │ │ ├── HeartBeatServerApplication.java │ │ │ ├── handler │ │ │ └── HeartBeatServerHandler.java │ │ │ └── server │ │ │ ├── CustomServerChannelInitializer.java │ │ │ └── Server.java │ │ └── resources │ │ └── application.yml └── pom.xml ├── netty-mqtt ├── pom.xml └── src │ └── main │ └── java │ └── fun │ └── codenow │ └── mqtt │ ├── ChannelClosedException.java │ ├── MqttChannelHandler.java │ ├── MqttClient.java │ ├── MqttClientCallback.java │ ├── MqttClientConfig.java │ ├── MqttClientImpl.java │ ├── MqttConnectResult.java │ ├── MqttHandler.java │ ├── MqttIncomingQos2Publish.java │ ├── MqttLastWill.java │ ├── MqttPendingPublish.java │ ├── MqttPendingSubscription.java │ ├── MqttPendingUnsubscription.java │ ├── MqttPingHandler.java │ ├── MqttSubscription.java │ └── RetransmissionHandler.java ├── netty-private-protocol ├── pom.xml └── src │ └── main │ ├── java │ └── fun │ │ └── codenow │ │ └── netty │ │ └── privateprotocol │ │ ├── NettyPrivateProtocolApplication.java │ │ ├── client │ │ ├── ClientChannelInitializer.java │ │ ├── HeartBeatRequestHandler.java │ │ └── privateProtocoClient.java │ │ ├── codec │ │ ├── CustomDecode.java │ │ └── CustomEncode.java │ │ ├── protobuf │ │ └── CustomMessageProto.java │ │ ├── server │ │ ├── HeartBeatResponseHandler.java │ │ ├── PrivateProtocolServer.java │ │ └── ServerChannelInitializer.java │ │ └── struct │ │ └── MessageType.java │ └── resources │ └── application.yml ├── netty-websocket ├── pom.xml └── src │ └── main │ ├── java │ └── fun │ │ └── codenow │ │ └── netty │ │ └── websocket │ │ └── server │ │ ├── ServerChannelInitializer.java │ │ ├── ServerHandler.java │ │ ├── WebSocketServer.java │ │ └── WebSocketServerHandler.java │ └── resources │ └── templates │ └── WebSocketServer.html └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | # maven # 2 | target 3 | 4 | logs 5 | 6 | # windows # 7 | Thumbs.db 8 | 9 | # Mac # 10 | .DS_Store 11 | 12 | # eclipse # 13 | .settings 14 | .project 15 | .classpath 16 | .log 17 | **.log 18 | *.class 19 | 20 | # idea # 21 | .idea 22 | *.iml 23 | 24 | # Package Files # 25 | *.jar 26 | *.war 27 | *.ear 28 | /target 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # codenow-netty 2 | 3 | Netty是一个基于Java NIO的网络编程框架,提供了一套高效的、事件驱动的异步网络通信机制。简化了网络应用程序的开发过程,提供了可靠的、高性能的网络传输。 4 | 5 | 作为高性能的网络通信框架,Netty被广泛应用于各种开源组件中,比如Dubbo、RocketMQ、Elasticsearch、gRPC、Kafka、Hadoop…… 6 | 7 | 8 | 本项目主要是为了更深入的理解netty、rpc等框架的实现原理,基于Netty实现 9 | 10 | - [x] Echo服务器 11 | 12 | - [x] 心跳检测 13 | 14 | - [x] 自定义编解码机制 15 | 16 | - [x] 断连重连 17 | 18 | - [x] 握手和安全认证 19 | 20 | - [x] 私有协议栈开发 21 | 22 | - [ ] 通信聊天系统 23 | 24 | - [ ] WebSocket服务器 25 | 26 | - [ ] 文件服务器 27 | 28 | - [ ] 手写RPC框架 -------------------------------------------------------------------------------- /codenow-im-chat/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | codenow-netty 7 | fun.codenow 8 | 1.0.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | codenow-im-chat 13 | 14 | 15 | -------------------------------------------------------------------------------- /codenow-rpc/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | codenow-netty 7 | fun.codenow 8 | 1.0.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | codenow-rpc 13 | 14 | 15 | -------------------------------------------------------------------------------- /java-network-programming/README.md: -------------------------------------------------------------------------------- 1 | Java 网络编程 基础 2 | 3 | Java原生的网络类库 -------------------------------------------------------------------------------- /java-network-programming/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | codenow-netty 7 | fun.codenow 8 | 1.0.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | java-network-programming 13 | 14 | 15 | -------------------------------------------------------------------------------- /netty-boot/dependency-reduced-pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | fun.codenow.netty 5 | netty-boot 6 | 1.0.2 7 | 8 | 9 | 10 | maven-shade-plugin 11 | 3.2.4 12 | 13 | 14 | package 15 | 16 | shade 17 | 18 | 19 | 20 | 21 | fun.codenow.netty.NettyBootServer 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | UTF-8 32 | 1.8 33 | UTF-8 34 | 1.8 35 | 36 | 37 | -------------------------------------------------------------------------------- /netty-boot/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | fun.codenow.netty 8 | netty-boot 9 | 1.0.2 10 | jar 11 | 12 | 13 | UTF-8 14 | UTF-8 15 | 16 | 1.8 17 | 1.8 18 | 19 | 20 | 21 | io.netty 22 | netty-all 23 | 4.1.54.Final 24 | 25 | 26 | 27 | com.google.protobuf 28 | protobuf-java 29 | 3.14.0 30 | 31 | 32 | 33 | com.google.code.gson 34 | gson 35 | 2.8.6 36 | 37 | 38 | org.slf4j 39 | slf4j-api 40 | 2.0.0-alpha1 41 | 42 | 43 | 44 | org.slf4j 45 | slf4j-simple 46 | 2.0.0-alpha1 47 | 48 | 49 | 50 | org.projectlombok 51 | lombok 52 | 1.18.20 53 | 54 | 55 | 56 | 57 | 58 | 59 | org.apache.maven.plugins 60 | maven-shade-plugin 61 | 3.2.4 62 | 63 | 64 | package 65 | 66 | shade 67 | 68 | 69 | 70 | 71 | fun.codenow.netty.NettyBootServer 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /netty-boot/src/main/java/fun/codenow/netty/NettyBootServer.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty; 2 | 3 | import fun.codenow.netty.handler.ApplicationLoggingHandler; 4 | import fun.codenow.netty.handler.ServerChannelInitializer; 5 | import io.netty.bootstrap.ServerBootstrap; 6 | import io.netty.channel.ChannelFuture; 7 | import io.netty.channel.EventLoopGroup; 8 | import io.netty.channel.nio.NioEventLoopGroup; 9 | import io.netty.channel.socket.nio.NioServerSocketChannel; 10 | import io.netty.handler.logging.LogLevel; 11 | 12 | import java.net.InetSocketAddress; 13 | 14 | /** 15 | * @Author Jack Wu 16 | * @Description 17 | * @Version V1.0 18 | * @Date2021/6/30 11:03 19 | **/ 20 | public class NettyBootServer { 21 | public static void main(String[] args) throws InterruptedException { 22 | EventLoopGroup bossGroup=new NioEventLoopGroup(); 23 | EventLoopGroup workerGroup=new NioEventLoopGroup(); 24 | ServerBootstrap serverBootstrap=new ServerBootstrap(); 25 | serverBootstrap 26 | .group(bossGroup,workerGroup) 27 | .channel(NioServerSocketChannel.class) 28 | .localAddress(new InetSocketAddress(8888)) 29 | .handler(new ApplicationLoggingHandler(LogLevel.INFO)) 30 | .childHandler(new ServerChannelInitializer()); 31 | //.option() 32 | try { 33 | ChannelFuture channelFuture= serverBootstrap.bind().sync(); 34 | channelFuture.channel().closeFuture().sync(); 35 | } finally { 36 | bossGroup.shutdownGracefully().sync(); 37 | workerGroup.shutdownGracefully().sync(); 38 | } 39 | 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /netty-boot/src/main/java/fun/codenow/netty/handler/HeartBeatResponseHandler.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.handler; 2 | 3 | import com.google.gson.Gson; 4 | import fun.codenow.netty.message.CustomMessageProto; 5 | import io.netty.buffer.ByteBuf; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.channel.ChannelInboundHandlerAdapter; 8 | import io.netty.handler.timeout.IdleState; 9 | import io.netty.handler.timeout.IdleStateEvent; 10 | import lombok.extern.slf4j.Slf4j; 11 | 12 | /** 13 | * @Author Jack Wu 14 | * @Description 15 | * @Version V1.0 16 | * @Date2021/6/30 15:52 17 | **/ 18 | @Slf4j 19 | public class HeartBeatResponseHandler extends ChannelInboundHandlerAdapter { 20 | @Override 21 | public void channelRegistered(ChannelHandlerContext ctx) throws Exception { 22 | super.channelRegistered(ctx); 23 | } 24 | 25 | @Override 26 | public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { 27 | super.channelUnregistered(ctx); 28 | } 29 | 30 | @Override 31 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 32 | super.channelActive(ctx); 33 | } 34 | 35 | @Override 36 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 37 | super.channelInactive(ctx); 38 | } 39 | 40 | @Override 41 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 42 | CustomMessageProto.CustomMessage message= (CustomMessageProto.CustomMessage) msg; 43 | if (message.getHeader().getType().getNumber()== CustomMessageProto.CustomMessage.CustomHeader.MessgeType.PING_VALUE){ 44 | log.info("接收心跳消息:{}"+message); 45 | CustomMessageProto.CustomMessage.Builder heartBeatRespMsg=CustomMessageProto.CustomMessage.newBuilder() 46 | .setHeader( 47 | CustomMessageProto.CustomMessage.CustomHeader.newBuilder() 48 | .setTypeValue(0xABEF) 49 | .setType(CustomMessageProto.CustomMessage.CustomHeader.MessgeType.PONG) 50 | ); 51 | ctx.channel().writeAndFlush(heartBeatRespMsg); 52 | }else { 53 | log.info("消息未处理:{}"+message); 54 | } 55 | 56 | } 57 | 58 | @Override 59 | public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 60 | super.channelReadComplete(ctx); 61 | } 62 | 63 | @Override 64 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 65 | //超过40s没接收心跳消息则强制断开连接,从列表移除 66 | if (evt instanceof IdleStateEvent){ 67 | IdleStateEvent event= (IdleStateEvent) evt; 68 | if (event.state().equals(IdleState.READER_IDLE)){ 69 | //判断上一次心跳时间,如果超过40s则直接断开连接 70 | //否则尝试发送心跳 71 | log.info("已经5秒没有收到客户端心跳消息了,发送心跳检测给客户端"); 72 | CustomMessageProto.CustomMessage.Builder heartBeatRespMsg=CustomMessageProto.CustomMessage.newBuilder() 73 | .setHeader( 74 | CustomMessageProto.CustomMessage.CustomHeader.newBuilder() 75 | .setTypeValue(0xABEF) 76 | .setType(CustomMessageProto.CustomMessage.CustomHeader.MessgeType.PONG) 77 | ); 78 | ctx.channel().writeAndFlush(heartBeatRespMsg); 79 | } 80 | } 81 | super.userEventTriggered(ctx, evt); 82 | } 83 | 84 | @Override 85 | public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception { 86 | super.channelWritabilityChanged(ctx); 87 | } 88 | 89 | @Override 90 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 91 | log.error("心跳检测发生异常:"); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /netty-boot/src/main/java/fun/codenow/netty/handler/ServerChannelInitializer.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.handler; 2 | 3 | import fun.codenow.netty.message.CustomMessageProto; 4 | import io.netty.channel.ChannelInitializer; 5 | import io.netty.channel.socket.SocketChannel; 6 | import io.netty.handler.codec.protobuf.ProtobufDecoder; 7 | import io.netty.handler.codec.protobuf.ProtobufEncoder; 8 | import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder; 9 | import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender; 10 | import io.netty.handler.timeout.IdleStateHandler; 11 | 12 | import java.util.concurrent.TimeUnit; 13 | 14 | /** 15 | * @Author Jack Wu 16 | * @Description 17 | * @Version V1.0 18 | * @Date2021/6/30 15:51 19 | **/ 20 | public class ServerChannelInitializer extends ChannelInitializer { 21 | @Override 22 | protected void initChannel(SocketChannel socketChannel) throws Exception { 23 | socketChannel.pipeline() 24 | .addLast(new IdleStateHandler(10,0,0, TimeUnit.SECONDS)) 25 | 26 | .addLast(new ProtobufVarint32FrameDecoder()) 27 | .addLast(new ProtobufDecoder(CustomMessageProto.CustomMessage.getDefaultInstance())) 28 | .addLast(new ProtobufVarint32LengthFieldPrepender()) 29 | .addLast(new ProtobufEncoder()) 30 | .addLast(new HeartBeatResponseHandler()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /netty-boot/src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | # Set root logger level to DEBUG and its only appender to A1. 2 | log4j.rootLogger=DEBUG, A1 3 | 4 | # A1 is set to be a ConsoleAppender. 5 | log4j.appender.A1=org.apache.log4j.ConsoleAppender 6 | 7 | # A1 uses PatternLayout. 8 | log4j.appender.A1.layout=org.apache.log4j.PatternLayout 9 | log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n 10 | -------------------------------------------------------------------------------- /netty-common/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | codenow-netty 7 | fun.codenow 8 | 1.0.0-SNAPSHOT 9 | 10 | 4.0.0 11 | jar 12 | 13 | netty-common 14 | 15 | 16 | 17 | com.google.protobuf 18 | protobuf-java 19 | 20 | 21 | -------------------------------------------------------------------------------- /netty-common/src/main/java/fun/codenow/netty/common/codec/CustomDecode.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.common.codec; 2 | 3 | /** 4 | * @Author Jack Wu 5 | * @Description 6 | * @Version V1.0 7 | * @Date2020/12/11 15:19 8 | **/ 9 | public class CustomDecode { 10 | } 11 | -------------------------------------------------------------------------------- /netty-common/src/main/java/fun/codenow/netty/common/codec/CustomEncode.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.common.codec; 2 | 3 | /** 4 | * @Author Jack Wu 5 | * @Description 6 | * @Version V1.0 7 | * @Date2020/12/11 15:19 8 | **/ 9 | public class CustomEncode { 10 | } 11 | -------------------------------------------------------------------------------- /netty-demo/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | codenow-netty 7 | fun.codenow 8 | 1.0.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | netty-demo 13 | 14 | 15 | 16 | org.springframework.boot 17 | spring-boot-starter-web 18 | 19 | 20 | org.springframework.boot 21 | spring-boot-starter-tomcat 22 | 23 | 24 | 25 | 26 | 27 | io.netty 28 | netty-all 29 | 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-jetty 34 | 35 | 36 | 37 | org.projectlombok 38 | lombok 39 | 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-starter-websocket 44 | 45 | 46 | 47 | com.google.code.gson 48 | gson 49 | 2.8.5 50 | 51 | 52 | 53 | com.google.protobuf 54 | protobuf-java 55 | 56 | 57 | 58 | 59 | 60 | 61 | org.springframework.boot 62 | spring-boot-maven-plugin 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/NettySocketApplication.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | /** 7 | * @Author Jack Wu 8 | * @Description 9 | * @Version V1.0 10 | * @Date2020/11/24 18:03 11 | **/ 12 | @SpringBootApplication 13 | public class NettySocketApplication { 14 | public static void main(String[] args) { 15 | SpringApplication.run(NettySocketApplication.class); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/coder/protobuf/ClientOne.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.coder.protobuf; 2 | 3 | /** 4 | * @Author Jack Wu 5 | * @Description 6 | * @Version V1.0 7 | * @Date2020/12/1 11:50 8 | **/ 9 | public class ClientOne { 10 | } 11 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/coder/protobuf/ServerOne.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.coder.protobuf; 2 | 3 | import io.netty.bootstrap.ServerBootstrap; 4 | import io.netty.channel.Channel; 5 | import io.netty.channel.ChannelInitializer; 6 | import io.netty.channel.EventLoopGroup; 7 | import io.netty.channel.nio.NioEventLoopGroup; 8 | import io.netty.channel.socket.nio.NioServerSocketChannel; 9 | 10 | import java.net.InetSocketAddress; 11 | 12 | /** 13 | * @Author Jack Wu 14 | * @Description 15 | * @Version V1.0 16 | * @Date2020/12/1 11:50 17 | **/ 18 | public class ServerOne { 19 | public static void main(String[] args) { 20 | EventLoopGroup bossEventGroup=new NioEventLoopGroup(); 21 | EventLoopGroup workerEventGroup=new NioEventLoopGroup(); 22 | 23 | ServerBootstrap serverBootstrap=new ServerBootstrap(); 24 | serverBootstrap 25 | .group(bossEventGroup,workerEventGroup) 26 | .channel(NioServerSocketChannel.class) 27 | .localAddress(new InetSocketAddress(8686)) 28 | .childHandler(new ChannelInitializer() { 29 | @Override 30 | protected void initChannel(Channel channel) throws Exception { 31 | 32 | } 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/demo/EchoClient.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.demo; 2 | 3 | import io.netty.bootstrap.Bootstrap; 4 | import io.netty.channel.Channel; 5 | import io.netty.channel.ChannelFuture; 6 | import io.netty.channel.ChannelInitializer; 7 | import io.netty.channel.EventLoopGroup; 8 | import io.netty.channel.nio.NioEventLoopGroup; 9 | import io.netty.channel.socket.SocketChannel; 10 | import io.netty.channel.socket.nio.NioSocketChannel; 11 | 12 | import java.net.InetSocketAddress; 13 | 14 | /** 15 | * @Author Jack Wu 16 | * @Description 17 | * 18 | * 创建一个 Bootstrap 来初始化客户端 19 | * 一个 NioEventLoopGroup 实例被分配给处理该事件的处理,这包括创建新的连接和处理入站和出站数据 20 | * 创建一个 InetSocketAddress 以连接到服务器 21 | * 连接好服务器之时,将安装一个 EchoClientHandler 在 pipeline 22 | * 之后 Bootstrap.connect()被调用连接到远程的 - 本例就是 echo(回声)服务器。 23 | * 24 | * @Version V1.0 25 | * @Date2020/11/25 16:43 26 | **/ 27 | public class EchoClient { 28 | public static void main(String[] args) { 29 | EventLoopGroup group=new NioEventLoopGroup(); 30 | Bootstrap bootstrap=new Bootstrap(); 31 | 32 | bootstrap 33 | .group(group) 34 | .channel(NioSocketChannel.class) 35 | .remoteAddress( new InetSocketAddress(8999)) 36 | .handler(new ChannelInitializer() { //5 37 | @Override 38 | public void initChannel(SocketChannel ch) 39 | throws Exception { 40 | ch.pipeline().addLast( 41 | new EchoClientHandler()); 42 | } 43 | }); 44 | try { 45 | ChannelFuture channelFuture=bootstrap.connect().sync(); 46 | channelFuture.channel().closeFuture().sync(); 47 | } catch (InterruptedException e) { 48 | e.printStackTrace(); 49 | }finally { 50 | try { 51 | group.shutdownGracefully().sync(); 52 | } catch (InterruptedException e) { 53 | e.printStackTrace(); 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/demo/EchoClientHandler.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.demo; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.Unpooled; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.channel.SimpleChannelInboundHandler; 7 | import io.netty.util.CharsetUtil; 8 | 9 | /** 10 | * @Author Jack Wu 11 | * @Description 12 | * 13 | * 客户端的工作内容: 14 | * 连接服务器 15 | * 发送信息 16 | * 发送的每个信息,等待和接收从服务器返回的同样的信息 17 | * 关闭连接 18 | * 19 | * 用 ChannelHandler 实现客户端逻辑 20 | * 跟写服务器一样,我们提供 ChannelInboundHandler 来处理数据。下面例子,我们用 SimpleChannelInboundHandler 来处理所有的任务,需要覆盖三个方法: 21 | * 22 | * channelActive() - 服务器的连接被建立后调用 23 | * channelRead0() - 数据后从服务器接收到调用 24 | * exceptionCaught() - 捕获一个异常时调用 25 | * 26 | * 27 | * @Version V1.0 28 | * @Date2020/11/25 17:11 29 | **/ 30 | public class EchoClientHandler extends SimpleChannelInboundHandler { 31 | @Override 32 | public void channelActive(ChannelHandlerContext ctx) { 33 | ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!", 34 | CharsetUtil.UTF_8)); 35 | } 36 | 37 | @Override 38 | public void channelRead0(ChannelHandlerContext ctx, 39 | ByteBuf in) { 40 | System.out.println("Client received: " + in.toString(CharsetUtil.UTF_8)); 41 | } 42 | 43 | @Override 44 | public void exceptionCaught(ChannelHandlerContext ctx, 45 | Throwable cause) { 46 | cause.printStackTrace(); 47 | ctx.close(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/demo/EchoServer.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.demo; 2 | 3 | import io.netty.bootstrap.ServerBootstrap; 4 | import io.netty.channel.ChannelFuture; 5 | import io.netty.channel.ChannelInitializer; 6 | import io.netty.channel.nio.NioEventLoopGroup; 7 | import io.netty.channel.socket.SocketChannel; 8 | import io.netty.channel.socket.nio.NioServerSocketChannel; 9 | 10 | import java.net.InetSocketAddress; 11 | 12 | /** 13 | * @Author Jack Wu 14 | * @Description 15 | * 16 | * 1.设置端口值(抛出一个 NumberFormatException 如果该端口参数的格式不正确) 17 | * 18 | * 2.呼叫服务器的 start() 方法 19 | * 20 | * 3.创建 EventLoopGroup 21 | * 22 | * 4.创建 ServerBootstrap 23 | * 24 | * 5.指定使用 NIO 的传输 Channel 25 | * 26 | * 6.设置 socket 地址使用所选的端口 27 | * 28 | * 7.添加 EchoServerHandler 到 Channel 的 ChannelPipeline 29 | * 30 | * 8.绑定的服务器;sync 等待服务器关闭 31 | * 32 | * 9.关闭 channel 和 块,直到它被关闭 33 | * 34 | * 10.关机的 EventLoopGroup,释放所有资源。 35 | * 36 | * @Version V1.0 37 | * @Date2020/11/25 16:58 38 | **/ 39 | public class EchoServer { 40 | public static void main(String[] args) throws InterruptedException { 41 | NioEventLoopGroup nioEventLoopGroup=new NioEventLoopGroup(); 42 | 43 | ServerBootstrap serverBootstrap=new ServerBootstrap(); 44 | serverBootstrap 45 | .group(nioEventLoopGroup) 46 | .channel(NioServerSocketChannel.class) 47 | .localAddress(new InetSocketAddress(8999)) 48 | .childHandler(new ChannelInitializer() { 49 | @Override 50 | public void initChannel(SocketChannel ch) 51 | throws Exception { 52 | ch.pipeline().addLast( 53 | new EchoServerHandler()); 54 | } 55 | }); 56 | try { 57 | ChannelFuture f = serverBootstrap.bind().sync(); 58 | System.out.println(EchoServer.class.getName() + " started and listen on " + f.channel().localAddress()); 59 | f.channel().closeFuture().sync(); 60 | } catch (InterruptedException e) { 61 | e.printStackTrace(); 62 | } finally { 63 | nioEventLoopGroup.shutdownGracefully().sync(); 64 | } 65 | 66 | 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/demo/EchoServerHandler.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.demo; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.Unpooled; 5 | import io.netty.channel.ChannelFutureListener; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.channel.ChannelInboundHandlerAdapter; 8 | import io.netty.util.CharsetUtil; 9 | import lombok.extern.slf4j.Slf4j; 10 | 11 | /** 12 | * @Author Jack Wu 13 | * @Description 14 | * 15 | * 一个Echo服务器 需要: 16 | * handler:这个组件实现了服务器的业务逻辑,决定了连接创建后和接收到信息后该如何处理 17 | * Bootstrapping: 这个是配置服务器的启动代码。最少需要设置服务器绑定的端口,用来监听连接请求。 18 | * 19 | * 20 | * 实现 ChannelInboundHandler 接口 21 | * 22 | * 继承ChannelInboundHandlerAdapter (提供了默认 ChannelInboundHandler 的实现) 23 | * 24 | * channelRead() - 每个信息入站都会调用 25 | * channelReadComplete() - 通知处理器最后的 26 | * channelread() 是当前批处理中的最后一条消息时调用 27 | * exceptionCaught()- 读操作时捕获到异常时调用 28 | * 29 | * @Version V1.0 30 | * @Date2020/11/25 16:43 31 | **/ 32 | @Slf4j 33 | public class EchoServerHandler extends ChannelInboundHandlerAdapter { 34 | public EchoServerHandler() { 35 | super(); 36 | } 37 | 38 | @Override 39 | public void channelRegistered(ChannelHandlerContext ctx) throws Exception { 40 | super.channelRegistered(ctx); 41 | } 42 | 43 | @Override 44 | public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { 45 | super.channelUnregistered(ctx); 46 | } 47 | 48 | @Override 49 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 50 | super.channelActive(ctx); 51 | } 52 | 53 | @Override 54 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 55 | super.channelInactive(ctx); 56 | } 57 | 58 | @Override 59 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 60 | ByteBuf byteBuf=(ByteBuf) msg; 61 | log.info("channelRead 调用:{}",byteBuf.toString(CharsetUtil.UTF_8)); 62 | ctx.write(byteBuf); 63 | } 64 | 65 | @Override 66 | public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 67 | ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE); 68 | } 69 | 70 | @Override 71 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 72 | super.userEventTriggered(ctx, evt); 73 | } 74 | 75 | @Override 76 | public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception { 77 | super.channelWritabilityChanged(ctx); 78 | } 79 | 80 | @Override 81 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 82 | log.error("Echo 抛出异常,{}",cause.getMessage()); 83 | ctx.close(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/demo/WebSocketServer.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.demo; 2 | 3 | import com.google.gson.Gson; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.eclipse.jetty.util.ConcurrentHashSet; 6 | import org.springframework.stereotype.Component; 7 | 8 | import javax.websocket.*; 9 | import javax.websocket.server.PathParam; 10 | import javax.websocket.server.ServerEndpoint; 11 | import java.util.Collections; 12 | import java.util.Set; 13 | import java.util.concurrent.ConcurrentHashMap; 14 | 15 | /** 16 | * @Author Jack Wu 17 | * @Description 18 | * @Version V1.0 19 | * @Date2020/11/25 10:14 20 | **/ 21 | @ServerEndpoint("/socket/{connectionId}") 22 | @Component 23 | @Slf4j 24 | public class WebSocketServer { 25 | private static Set connectionIdSet= new ConcurrentHashSet(); 26 | private static ConcurrentHashMap socketSessionPools=new ConcurrentHashMap<>(); 27 | 28 | @OnOpen 29 | public void onSocketOpen(Session session, @PathParam(value = "connectionId") String connectionId){ 30 | socketSessionPools.put(connectionId,session); 31 | connectionIdSet.add(connectionId); 32 | log.info("当前在线人数:{}",connectionIdSet.size()); 33 | log.info("所有connctionId:{}",new Gson().toJson(connectionIdSet)); 34 | } 35 | 36 | @OnMessage 37 | public void onMessage(String message, @PathParam(value = "connectionId") String connectionId){ 38 | log.info("收到客户端{}的消息:{}",connectionId,message); 39 | //回复消息 40 | sendMessageBySessionId(connectionId,"服务端已接收消息:"+message); 41 | } 42 | 43 | @OnClose 44 | public void onClose( @PathParam(value = "connectionId") String connectionId){ 45 | socketSessionPools.remove(connectionId); 46 | connectionIdSet.remove(connectionId); 47 | log.info("客户端{}断开连接,当前连接数:{}",connectionId,connectionIdSet.size()); 48 | } 49 | 50 | @OnError 51 | public void OnError(Session session, Throwable throwable, @PathParam(value = "connectionId") String connectionId){ 52 | log.error("socket连接异常,connectionId:{},errormessage:{}",connectionId,throwable.getMessage()); 53 | } 54 | 55 | /** 56 | * send message by connectionId 57 | */ 58 | public void sendMessageBySessionId(String connectionId,String message){ 59 | Session session=socketSessionPools.get(connectionId); 60 | session.getAsyncRemote().sendText(message); 61 | } 62 | /** 63 | * send message to all connectionId 64 | */ 65 | public void sendMessageToAll(String message){ 66 | for (Session session: socketSessionPools.values()){ 67 | if (session!=null){ 68 | synchronized (session){ 69 | session.getAsyncRemote().sendText(message); 70 | } 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/heart/CustomHeartBeatClientHandler.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.heart; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.channel.SimpleChannelInboundHandler; 5 | 6 | /** 7 | * @Author Jack Wu 8 | * @Description 9 | * @Version V1.0 10 | * @Date2020/11/27 17:19 11 | **/ 12 | public class CustomHeartBeatClientHandler extends SimpleChannelInboundHandler { 13 | @Override 14 | protected void channelRead0(ChannelHandlerContext channelHandlerContext, String string) throws Exception { 15 | System.out.println("客户端收到:"+string); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/heart/CustomHeartBeatInitializer.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.heart; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.ChannelInitializer; 5 | import io.netty.channel.ChannelPipeline; 6 | import io.netty.handler.codec.string.StringDecoder; 7 | import io.netty.handler.codec.string.StringEncoder; 8 | import io.netty.handler.timeout.IdleStateHandler; 9 | 10 | import java.util.concurrent.TimeUnit; 11 | 12 | /** 13 | * @Author Jack Wu 14 | * @Description 15 | * @Version V1.0 16 | * @Date2020/11/27 17:08 17 | **/ 18 | public class CustomHeartBeatInitializer extends ChannelInitializer { 19 | @Override 20 | protected void initChannel(Channel channel) throws Exception { 21 | ChannelPipeline pipeline=channel.pipeline(); 22 | pipeline.addLast("decoder",new StringDecoder()); 23 | pipeline.addLast("encoder",new StringEncoder()); 24 | pipeline.addLast(new IdleStateHandler(2,2,2, TimeUnit.SECONDS)); 25 | pipeline.addLast(new CustomHeartBeathandler()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/heart/CustomHeartBeathandler.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.heart; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.channel.SimpleChannelInboundHandler; 5 | import io.netty.handler.timeout.IdleStateEvent; 6 | 7 | /** 8 | * @Author Jack Wu 9 | * @Description 10 | * @Version V1.0 11 | * @Date2020/11/26 18:04 12 | **/ 13 | public class CustomHeartBeathandler extends SimpleChannelInboundHandler { 14 | int readIdleTimes=0; 15 | @Override 16 | protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception { 17 | System.out.println(" Server HeartBeat received message:"+s); 18 | channelHandlerContext.writeAndFlush(" client reply : get message "+s); 19 | 20 | } 21 | @Override 22 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 23 | IdleStateEvent event= (IdleStateEvent) evt; 24 | String eventType=null; 25 | switch (event.state()){ 26 | case WRITER_IDLE: 27 | eventType=" 读空闲"; 28 | readIdleTimes++; 29 | break; 30 | case READER_IDLE: 31 | eventType="写空闲"; 32 | break; 33 | case ALL_IDLE: 34 | eventType="读写空闲"; 35 | break; 36 | } 37 | System.out.println("eventType:"+eventType); 38 | if (readIdleTimes>3){ 39 | System.out.println("读空闲超过三次,关闭连接"); 40 | ctx.channel().writeAndFlush("you are out"); 41 | /*ctx.channel().close().sync();*/ 42 | } 43 | } 44 | 45 | @Override 46 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 47 | System.out.println("= = = "+ctx.channel().remoteAddress()+"is active = = ="); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/heart/CustomHeartbeatClientInitializer.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.heart; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.ChannelInitializer; 6 | import io.netty.channel.ChannelPipeline; 7 | import io.netty.handler.codec.string.StringDecoder; 8 | import io.netty.handler.codec.string.StringEncoder; 9 | 10 | /** 11 | * @Author Jack Wu 12 | * @Description 13 | * @Version V1.0 14 | * @Date2020/11/27 17:20 15 | **/ 16 | public class CustomHeartbeatClientInitializer extends ChannelInitializer { 17 | @Override 18 | protected void initChannel(Channel channel) throws Exception { 19 | ChannelPipeline channelPipeline=channel.pipeline(); 20 | channelPipeline.addLast("decoder",new StringDecoder()); 21 | channelPipeline.addLast("encoder",new StringEncoder()); 22 | channelPipeline.addLast(new CustomHeartBeatClientHandler()); 23 | 24 | } 25 | 26 | @Override 27 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 28 | ctx.channel().writeAndFlush(" client is alive"); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/heart/HeartbeatClient.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.heart; 2 | 3 | import io.netty.bootstrap.Bootstrap; 4 | import io.netty.channel.ChannelFuture; 5 | import io.netty.channel.EventLoopGroup; 6 | import io.netty.channel.nio.NioEventLoopGroup; 7 | import io.netty.channel.socket.SocketChannel; 8 | import io.netty.channel.socket.nio.NioSocketChannel; 9 | 10 | import java.net.InetSocketAddress; 11 | 12 | /** 13 | * @Author Jack Wu 14 | * @Description 15 | * @Version V1.0 16 | * @Date2020/11/27 17:14 17 | **/ 18 | public class HeartbeatClient { 19 | private int port; 20 | private String host; 21 | public HeartbeatClient(String host,int port){ 22 | this.port=port; 23 | this.host=host; 24 | } 25 | public void run() throws InterruptedException { 26 | Bootstrap bootstrap=new Bootstrap(); 27 | EventLoopGroup eventLoopGroup=new NioEventLoopGroup(); 28 | bootstrap 29 | .group(eventLoopGroup) 30 | .remoteAddress(new InetSocketAddress(port)) 31 | .channel(NioSocketChannel.class) 32 | .handler(new CustomHeartbeatClientInitializer()); 33 | try { 34 | ChannelFuture channelFuture= bootstrap.connect().sync(); 35 | 36 | /*channelFuture.channel().close().sync();*/ 37 | } finally { 38 | eventLoopGroup.shutdownGracefully().sync(); 39 | } 40 | } 41 | 42 | public static void main(String[] args) throws InterruptedException { 43 | HeartbeatClient heartbeatClient=new HeartbeatClient("localhost",8888); 44 | heartbeatClient.run(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/heart/HeartbeatServer.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.heart; 2 | 3 | import io.netty.bootstrap.ServerBootstrap; 4 | import io.netty.channel.ChannelFuture; 5 | import io.netty.channel.EventLoopGroup; 6 | import io.netty.channel.nio.NioEventLoopGroup; 7 | import io.netty.channel.socket.nio.NioServerSocketChannel; 8 | import io.netty.handler.logging.LogLevel; 9 | import io.netty.handler.logging.LoggingHandler; 10 | 11 | import java.net.InetSocketAddress; 12 | 13 | /** 14 | * @Author Jack Wu 15 | * @Description 16 | * @Version V1.0 17 | * @Date2020/11/26 17:58 18 | **/ 19 | public class HeartbeatServer { 20 | private int port; 21 | private String host; 22 | public HeartbeatServer(int port,String host){ 23 | this.port=port; 24 | this.host=host; 25 | } 26 | 27 | public void run() throws InterruptedException { 28 | ServerBootstrap serverBootstrap=new ServerBootstrap(); 29 | EventLoopGroup bossEventLoopGroup=new NioEventLoopGroup(); 30 | EventLoopGroup workerEventLoopGroup=new NioEventLoopGroup(); 31 | serverBootstrap 32 | .group(bossEventLoopGroup,workerEventLoopGroup) 33 | .channel(NioServerSocketChannel.class) 34 | .localAddress(new InetSocketAddress(port)) 35 | .handler(new LoggingHandler(LogLevel.INFO)) 36 | .childHandler(new CustomHeartBeatInitializer()); 37 | try { 38 | ChannelFuture channelFuture=serverBootstrap.bind().sync(); 39 | channelFuture.channel().closeFuture().sync(); 40 | } finally { 41 | bossEventLoopGroup.shutdownGracefully(); 42 | workerEventLoopGroup.shutdownGracefully(); 43 | } 44 | 45 | 46 | } 47 | public static void main(String[] args) throws InterruptedException { 48 | HeartbeatServer heartbeatServer=new HeartbeatServer(8888,"localhost"); 49 | heartbeatServer.run(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/heartbeat/CustomProtocol.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.heartbeat; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * @Author Jack Wu 7 | * @Description 8 | * @Version V1.0 9 | * @Date2020/12/1 10:21 10 | **/ 11 | 12 | public class CustomProtocol implements Serializable { 13 | private static final long serialVersionUID = 4671171056588401542L; 14 | private long id ; 15 | private String content ; 16 | 17 | public long getId() { 18 | return id; 19 | } 20 | 21 | public void setId(long id) { 22 | this.id = id; 23 | } 24 | 25 | public String getContent() { 26 | return content; 27 | } 28 | 29 | public void setContent(String content) { 30 | this.content = content; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/heartbeat/HeartBeatServer.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.heartbeat; 2 | 3 | import io.netty.bootstrap.ServerBootstrap; 4 | import io.netty.channel.ChannelFuture; 5 | import io.netty.channel.ChannelOption; 6 | import io.netty.channel.EventLoopGroup; 7 | import io.netty.channel.nio.NioEventLoopGroup; 8 | import io.netty.channel.socket.nio.NioServerSocketChannel; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.springframework.stereotype.Component; 11 | 12 | import javax.annotation.PostConstruct; 13 | import javax.annotation.PreDestroy; 14 | import java.net.InetSocketAddress; 15 | 16 | /** 17 | * @Author Jack Wu 18 | * @Description 19 | * @Version V1.0 20 | * @Date2020/12/1 10:08 21 | **/ 22 | @Component 23 | @Slf4j 24 | public class HeartBeatServer { 25 | private EventLoopGroup boss = new NioEventLoopGroup(); 26 | private EventLoopGroup work = new NioEventLoopGroup(); 27 | 28 | 29 | @PostConstruct 30 | public void start() throws InterruptedException { 31 | 32 | ServerBootstrap bootstrap = new ServerBootstrap() 33 | .group(boss, work) 34 | .channel(NioServerSocketChannel.class) 35 | .localAddress(new InetSocketAddress(9696)) 36 | //保持长连接 37 | .childOption(ChannelOption.SO_KEEPALIVE, true) 38 | .childHandler(new HeartbeatInitializer()); 39 | 40 | ChannelFuture future = bootstrap.bind().sync(); 41 | if (future.isSuccess()) { 42 | log.info("启动 Netty 成功"); 43 | } 44 | } 45 | 46 | @PreDestroy 47 | public void destroy() { 48 | boss.shutdownGracefully().syncUninterruptibly(); 49 | work.shutdownGracefully().syncUninterruptibly(); 50 | log.info("关闭 Netty 成功"); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/heartbeat/HeartBeatSimpleHandle.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.heartbeat; 2 | 3 | import io.netty.channel.ChannelFutureListener; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.SimpleChannelInboundHandler; 6 | import io.netty.channel.socket.nio.NioSocketChannel; 7 | import io.netty.handler.timeout.IdleState; 8 | import io.netty.handler.timeout.IdleStateEvent; 9 | import lombok.extern.slf4j.Slf4j; 10 | 11 | import static io.netty.handler.codec.stomp.StompHeaders.HEART_BEAT; 12 | 13 | /** 14 | * @Author Jack Wu 15 | * @Description 16 | * @Version V1.0 17 | * @Date2020/12/1 10:20 18 | **/ 19 | @Slf4j 20 | public class HeartBeatSimpleHandle extends SimpleChannelInboundHandler { 21 | @Override 22 | protected void channelRead0(ChannelHandlerContext channelHandlerContext, CustomProtocol customProtocol) throws Exception { 23 | log.info("收到customProtocol:{}",customProtocol); 24 | NettySocketHolder.put(customProtocol.getId(), (NioSocketChannel) channelHandlerContext.channel()); 25 | } 26 | 27 | @Override 28 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 29 | NettySocketHolder.remove((NioSocketChannel) ctx.channel()); 30 | } 31 | 32 | @Override 33 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 34 | if (evt instanceof IdleStateEvent){ 35 | IdleStateEvent idleStateEvent = (IdleStateEvent) evt ; 36 | 37 | if (idleStateEvent.state() == IdleState.READER_IDLE){ 38 | log.info("已经5秒没有收到信息!"); 39 | log.info("NettySocketHolder MAP{}",NettySocketHolder.getMAP()); 40 | //向客户端发送消息 41 | ctx.writeAndFlush(HEART_BEAT).addListener(ChannelFutureListener.CLOSE_ON_FAILURE) ; 42 | } 43 | } 44 | super.userEventTriggered(ctx, evt); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/heartbeat/HeartbeatDecoder.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.heartbeat; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.ByteToMessageDecoder; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * @Author Jack Wu 11 | * @Description 12 | * @Version V1.0 13 | * @Date2020/12/1 10:20 14 | **/ 15 | public class HeartbeatDecoder extends ByteToMessageDecoder { 16 | @Override 17 | protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List list) throws Exception { 18 | long id = byteBuf.readLong() ; 19 | byte[] bytes = new byte[byteBuf.readableBytes()] ; 20 | byteBuf.readBytes(bytes) ; 21 | String content = new String(bytes) ; 22 | 23 | CustomProtocol customProtocol = new CustomProtocol() ; 24 | customProtocol.setId(id); 25 | customProtocol.setContent(content) ; 26 | list.add(customProtocol) ; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/heartbeat/HeartbeatEncode.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.heartbeat; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.MessageToByteEncoder; 6 | 7 | /** 8 | * @Author Jack Wu 9 | * @Description 10 | * @Version V1.0 11 | * @Date2020/12/1 10:22 12 | **/ 13 | public class HeartbeatEncode extends MessageToByteEncoder { 14 | @Override 15 | protected void encode(ChannelHandlerContext channelHandlerContext, CustomProtocol customProtocol, ByteBuf byteBuf) throws Exception { 16 | byteBuf.writeLong(customProtocol.getId()) ; 17 | byteBuf.writeBytes(customProtocol.getContent().getBytes()) ; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/heartbeat/HeartbeatInitializer.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.heartbeat; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.ChannelInitializer; 5 | import io.netty.handler.timeout.IdleStateHandler; 6 | 7 | /** 8 | * @Author Jack Wu 9 | * @Description 10 | * @Version V1.0 11 | * @Date2020/12/1 10:17 12 | **/ 13 | public class HeartbeatInitializer extends ChannelInitializer { 14 | @Override 15 | protected void initChannel(Channel channel) throws Exception { 16 | channel.pipeline() 17 | //五秒没有收到消息 将IdleStateHandler 添加到 ChannelPipeline 中 18 | .addLast(new IdleStateHandler(5, 0, 0)) 19 | .addLast(new HeartbeatDecoder()) 20 | .addLast(new HeartBeatSimpleHandle()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/heartbeat/NettySocketHolder.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.heartbeat; 2 | 3 | import io.netty.channel.socket.nio.NioSocketChannel; 4 | import java.util.Map; 5 | import java.util.concurrent.ConcurrentHashMap; 6 | 7 | /** 8 | * @Author Jack Wu 9 | * @Description 10 | * @Version V1.0 11 | * @Date2020/12/1 10:30 12 | **/ 13 | public class NettySocketHolder { 14 | private final static Map MAP=new ConcurrentHashMap<>(); 15 | 16 | public static void put(Long id, NioSocketChannel socketChannel) { 17 | MAP.put(id, socketChannel); 18 | } 19 | 20 | public static NioSocketChannel get(Long id) { 21 | return MAP.get(id); 22 | } 23 | 24 | public static Map getMAP() { 25 | return MAP; 26 | } 27 | 28 | public static void remove(NioSocketChannel nioSocketChannel) { 29 | MAP.entrySet().stream().filter(entry -> entry.getValue() == nioSocketChannel).forEach(entry -> MAP.remove(entry.getKey())); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/heartbeat/client/CustomerHandleInitializer.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.heartbeat.client; 2 | 3 | import fun.codenow.netty.socket.heartbeat.HeartbeatEncode; 4 | import io.netty.channel.Channel; 5 | import io.netty.channel.ChannelInitializer; 6 | import io.netty.handler.timeout.IdleStateHandler; 7 | 8 | /** 9 | * @Author Jack Wu 10 | * @Description 11 | * @Version V1.0 12 | * @Date2020/12/1 10:46 13 | **/ 14 | public class CustomerHandleInitializer extends ChannelInitializer { 15 | @Override 16 | protected void initChannel(Channel channel) throws Exception { 17 | channel.pipeline() 18 | //10 秒没发送消息 将IdleStateHandler 添加到 ChannelPipeline 中 19 | .addLast(new IdleStateHandler(0, 10, 0)) 20 | .addLast(new HeartbeatEncode()) 21 | .addLast(new EchoClientHandle()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/heartbeat/client/EchoClientHandle.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.heartbeat.client; 2 | 3 | import fun.codenow.netty.socket.heartbeat.CustomProtocol; 4 | import fun.codenow.netty.socket.heartbeat.HeartBeatServer; 5 | import fun.codenow.netty.socket.heartbeat.util.SpringBeanFactory; 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.channel.ChannelFutureListener; 8 | import io.netty.channel.ChannelHandlerContext; 9 | import io.netty.channel.SimpleChannelInboundHandler; 10 | import io.netty.handler.timeout.IdleState; 11 | import io.netty.handler.timeout.IdleStateEvent; 12 | import io.netty.util.CharsetUtil; 13 | import lombok.extern.slf4j.Slf4j; 14 | import org.springframework.beans.BeanUtils; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | 17 | /** 18 | * @Author Jack Wu 19 | * @Description 20 | * @Version V1.0 21 | * @Date2020/12/1 10:47 22 | **/ 23 | @Slf4j 24 | public class EchoClientHandle extends SimpleChannelInboundHandler { 25 | @Autowired 26 | HeartBeatServer heartBeatServer; 27 | @Override 28 | protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception { 29 | log.info("收到服务端消息:{}"+byteBuf.toString(CharsetUtil.UTF_8)); 30 | } 31 | 32 | @Override 33 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 34 | 35 | if (evt instanceof IdleStateEvent){ 36 | IdleStateEvent idleStateEvent = (IdleStateEvent) evt ; 37 | 38 | if (idleStateEvent.state() == IdleState.WRITER_IDLE){ 39 | log.info("已经 10 秒没有发送信息!"); 40 | //向服务端发送消息 41 | CustomProtocol heartBeat = SpringBeanFactory.getBean("heartBeat", CustomProtocol.class); 42 | ctx.writeAndFlush(heartBeat).addListener(ChannelFutureListener.CLOSE_ON_FAILURE) ; 43 | } 44 | } 45 | super.userEventTriggered(ctx, evt); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/heartbeat/client/HeartbeatClient.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.heartbeat.client; 2 | 3 | import io.netty.bootstrap.Bootstrap; 4 | import io.netty.channel.ChannelFuture; 5 | import io.netty.channel.EventLoopGroup; 6 | import io.netty.channel.nio.NioEventLoopGroup; 7 | import io.netty.channel.socket.SocketChannel; 8 | import io.netty.channel.socket.nio.NioSocketChannel; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.springframework.stereotype.Component; 11 | 12 | import javax.annotation.PostConstruct; 13 | 14 | /** 15 | * @Author Jack Wu 16 | * @Description 17 | * @Version V1.0 18 | * @Date2020/12/1 10:43 19 | **/ 20 | @Component 21 | @Slf4j 22 | public class HeartbeatClient { 23 | private EventLoopGroup group = new NioEventLoopGroup(); 24 | private SocketChannel channel; 25 | 26 | @PostConstruct 27 | public void start() throws InterruptedException { 28 | Bootstrap bootstrap=new Bootstrap(); 29 | bootstrap 30 | .group(group) 31 | .channel(NioSocketChannel.class) 32 | .handler(new CustomerHandleInitializer()); 33 | 34 | ChannelFuture future=bootstrap.connect("192.168.5.93",9696).sync(); 35 | if (future.isSuccess()){ 36 | log.info("启动netty 客户端成功"); 37 | } 38 | channel = (SocketChannel) future.channel(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/heartbeat/util/SpringBeanFactory.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.heartbeat.util; 2 | 3 | import org.springframework.beans.BeansException; 4 | import org.springframework.context.ApplicationContext; 5 | import org.springframework.context.ApplicationContextAware; 6 | import org.springframework.stereotype.Component; 7 | 8 | /** 9 | * @Author Jack Wu 10 | * @Description 11 | * @Version V1.0 12 | * @Date2020/12/1 11:35 13 | **/ 14 | @Component 15 | public class SpringBeanFactory implements ApplicationContextAware { 16 | private static ApplicationContext context; 17 | 18 | public static T getBean(Class c){ 19 | return context.getBean(c); 20 | } 21 | 22 | 23 | public static T getBean(String name,Class clazz){ 24 | return context.getBean(name,clazz); 25 | } 26 | 27 | @Override 28 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 29 | context = applicationContext; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/inaction/Client.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.inaction; 2 | 3 | import io.netty.bootstrap.Bootstrap; 4 | import io.netty.channel.ChannelFuture; 5 | import io.netty.channel.ChannelInitializer; 6 | import io.netty.channel.EventLoopGroup; 7 | import io.netty.channel.nio.NioEventLoopGroup; 8 | import io.netty.channel.socket.SocketChannel; 9 | import io.netty.channel.socket.nio.NioSocketChannel; 10 | 11 | import java.net.InetSocketAddress; 12 | 13 | /** 14 | * @Author Jack Wu 15 | * @Description 16 | * @Version V1.0 17 | * @Date2020/12/3 15:53 18 | **/ 19 | public class Client { 20 | public static void main(String[] args) throws InterruptedException { 21 | EventLoopGroup group=new NioEventLoopGroup(); 22 | Bootstrap bootstrap=new Bootstrap(); 23 | bootstrap 24 | .group(group) 25 | .channel(NioSocketChannel.class) 26 | .remoteAddress(new InetSocketAddress(8888)) 27 | .handler(new ChannelInitializer() { 28 | @Override 29 | protected void initChannel(SocketChannel socketChannel) throws Exception { 30 | socketChannel.pipeline().addLast(new ClientHandler()); 31 | } 32 | }); 33 | try { 34 | ChannelFuture channelFuture=bootstrap.connect().sync(); 35 | channelFuture.channel().closeFuture().sync(); 36 | } finally { 37 | group.shutdownGracefully().sync(); 38 | } 39 | 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/inaction/ClientHandler.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.inaction; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.Unpooled; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.channel.ChannelInboundHandlerAdapter; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | /** 10 | * @Author Jack Wu 11 | * @Description 12 | * @Version V1.0 13 | * @Date2020/12/3 16:04 14 | **/ 15 | @Slf4j 16 | public class ClientHandler extends ChannelInboundHandlerAdapter { 17 | 18 | @Override 19 | public void channelRegistered(ChannelHandlerContext ctx) throws Exception { 20 | log.info(" 日志: ClientHandler 调用channelRegistered"); 21 | super.channelRegistered(ctx); 22 | } 23 | 24 | @Override 25 | public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { 26 | log.info(" 日志: ClientHandler 调用channelUnregistered"); 27 | super.channelUnregistered(ctx); 28 | } 29 | 30 | @Override 31 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 32 | log.info(" 日志: ClientHandler 调用channelActive"); 33 | byte[] req = "client ready".getBytes(); 34 | ByteBuf firstMessage = Unpooled.buffer(req.length); 35 | firstMessage.writeBytes(req); 36 | ctx.writeAndFlush(firstMessage); 37 | } 38 | 39 | @Override 40 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 41 | log.info(" 日志: ClientHandler 调用channelInactive"); 42 | super.channelInactive(ctx); 43 | } 44 | 45 | @Override 46 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 47 | log.info(" 日志: ClientHandler 调用channelRead"); 48 | log.info("msg:" +msg); 49 | ByteBuf buf = (ByteBuf) msg; 50 | byte[] req = new byte[buf.readableBytes()]; 51 | buf.readBytes(req); 52 | String body = new String(req, "UTF-8"); 53 | System.out.println("client 接收到消息 : " + body); 54 | if (body.equalsIgnoreCase("heartbeat request")){ 55 | byte[] respbytes="heartbeat response".getBytes(); 56 | ByteBuf respMsg=Unpooled.buffer(respbytes.length); 57 | respMsg.writeBytes(respbytes); 58 | Thread.sleep(2000); 59 | ctx.writeAndFlush(respMsg); 60 | } 61 | } 62 | 63 | @Override 64 | public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 65 | log.info(" 日志: ClientHandler 调用channelReadComplete"); 66 | super.channelReadComplete(ctx); 67 | } 68 | 69 | @Override 70 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 71 | log.info(" 日志: ClientHandler 调用userEventTriggered"); 72 | super.userEventTriggered(ctx, evt); 73 | } 74 | 75 | @Override 76 | public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception { 77 | log.info(" 日志: ClientHandler 调用channelWritabilityChanged"); 78 | super.channelWritabilityChanged(ctx); 79 | } 80 | 81 | @Override 82 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 83 | log.info(" 日志: ClientHandler 调用exceptionCaught"); 84 | super.exceptionCaught(ctx, cause); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/inaction/Server.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.inaction; 2 | 3 | import io.netty.bootstrap.ServerBootstrap; 4 | import io.netty.channel.ChannelFuture; 5 | import io.netty.channel.ChannelInitializer; 6 | import io.netty.channel.EventLoopGroup; 7 | import io.netty.channel.nio.NioEventLoopGroup; 8 | import io.netty.channel.socket.SocketChannel; 9 | import io.netty.channel.socket.nio.NioServerSocketChannel; 10 | import io.netty.handler.logging.LogLevel; 11 | import io.netty.handler.logging.LoggingHandler; 12 | 13 | import java.net.InetSocketAddress; 14 | 15 | /** 16 | * @Author Jack Wu 17 | * @Description 18 | * @Version V1.0 19 | * @Date2020/12/3 15:53 20 | **/ 21 | public class Server { 22 | public static void main(String[] args) throws InterruptedException { 23 | EventLoopGroup bossGroup=new NioEventLoopGroup(); 24 | EventLoopGroup workerGroup=new NioEventLoopGroup(); 25 | ServerBootstrap serverBootstrap=new ServerBootstrap(); 26 | serverBootstrap 27 | .group(bossGroup,workerGroup) 28 | .channel(NioServerSocketChannel.class) 29 | .localAddress(new InetSocketAddress(8888)) 30 | .handler(new LoggingHandler(LogLevel.INFO)) 31 | .childHandler(new ChannelInitializer() { 32 | @Override 33 | protected void initChannel(SocketChannel socketChannel) throws Exception { 34 | socketChannel.pipeline().addLast(new ServerHandler()); 35 | } 36 | }); 37 | try { 38 | ChannelFuture channelFuture=serverBootstrap.bind().sync(); 39 | channelFuture.channel().closeFuture().sync(); 40 | } finally { 41 | bossGroup.shutdownGracefully().sync(); 42 | workerGroup.shutdownGracefully().sync(); 43 | } 44 | 45 | 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/inaction/ServerHandler.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.inaction; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.Unpooled; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.channel.ChannelInboundHandlerAdapter; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | /** 10 | * @Author Jack Wu 11 | * @Description 12 | * @Version V1.0 13 | * @Date2020/12/3 16:03 14 | **/ 15 | @Slf4j 16 | public class ServerHandler extends ChannelInboundHandlerAdapter { 17 | 18 | @Override 19 | public void channelRegistered(ChannelHandlerContext ctx) throws Exception { 20 | log.info(" 日志: ClientHandler 调用channelRegistered"); 21 | super.channelRegistered(ctx); 22 | } 23 | 24 | @Override 25 | public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { 26 | log.info(" 日志: ClientHandler 调用channelUnregistered"); 27 | super.channelUnregistered(ctx); 28 | } 29 | 30 | @Override 31 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 32 | log.info(" 日志: ClientHandler 调用channelActive"); 33 | } 34 | 35 | @Override 36 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 37 | log.info(" 日志: ClientHandler 调用channelInactive"); 38 | super.channelInactive(ctx); 39 | } 40 | 41 | @Override 42 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 43 | log.info(" 日志: ClientHandler 调用channelRead"); 44 | ByteBuf buf = (ByteBuf) msg; 45 | byte[] req = new byte[buf.readableBytes()]; 46 | buf.readBytes(req); 47 | String body = new String(req, "UTF-8"); 48 | System.out.println("server 接收到消息 : " + body); 49 | if (body.equalsIgnoreCase("heartbeat response")||body.equalsIgnoreCase("client ready")){ 50 | byte[] requestByte="heartbeat request".getBytes(); 51 | ByteBuf requestMsg= Unpooled.buffer(requestByte.length); 52 | requestMsg.writeBytes(requestByte); 53 | Thread.sleep(2000); 54 | ctx.writeAndFlush(requestMsg); 55 | } 56 | 57 | } 58 | 59 | @Override 60 | public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 61 | log.info(" 日志: ClientHandler 调用channelReadComplete"); 62 | super.channelReadComplete(ctx); 63 | } 64 | 65 | @Override 66 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 67 | log.info(" 日志: ClientHandler 调用userEventTriggered"); 68 | super.userEventTriggered(ctx, evt); 69 | } 70 | 71 | @Override 72 | public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception { 73 | log.info(" 日志: ClientHandler 调用channelWritabilityChanged"); 74 | super.channelWritabilityChanged(ctx); 75 | } 76 | 77 | @Override 78 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 79 | log.info(" 日志: ClientHandler 调用exceptionCaught"); 80 | super.exceptionCaught(ctx, cause); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/protocol/custom/CustomProtocolServer.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.protocol.custom; 2 | 3 | /** 4 | * @Author Jack Wu 5 | * @Description 6 | * @Version V1.0 7 | * @Date2020/12/1 17:57 8 | **/ 9 | public class CustomProtocolServer { 10 | } 11 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/protocol/http/HttpNettyServer.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.protocol.http; 2 | 3 | /** 4 | * @Author Jack Wu 5 | * @Description 6 | * @Version V1.0 7 | * @Date2020/12/1 17:56 8 | **/ 9 | public class HttpNettyServer { 10 | } 11 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/protocol/websocket/WebSocketNettyServer.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.protocol.websocket; 2 | 3 | /** 4 | * @Author Jack Wu 5 | * @Description 6 | * @Version V1.0 7 | * @Date2020/12/1 17:56 8 | **/ 9 | public class WebSocketNettyServer { 10 | } 11 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/subscribebook/SubscribeBookClient.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.subscribebook; 2 | 3 | import com.google.protobuf.ByteString; 4 | import com.google.protobuf.CodedOutputStream; 5 | import com.google.protobuf.MessageLite; 6 | import com.google.protobuf.Parser; 7 | import fun.codenow.netty.socket.protobuf.SubscribeRespProto; 8 | import io.netty.bootstrap.Bootstrap; 9 | import io.netty.channel.ChannelFuture; 10 | import io.netty.channel.ChannelInitializer; 11 | import io.netty.channel.ChannelOption; 12 | import io.netty.channel.EventLoopGroup; 13 | import io.netty.channel.nio.NioEventLoopGroup; 14 | import io.netty.channel.socket.SocketChannel; 15 | import io.netty.channel.socket.nio.NioSocketChannel; 16 | import io.netty.handler.codec.protobuf.ProtobufDecoder; 17 | import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder; 18 | import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender; 19 | 20 | import java.io.IOException; 21 | import java.io.OutputStream; 22 | 23 | /** 24 | * @Author Jack Wu 25 | * @Description 26 | * @Version V1.0 27 | * @Date2020/12/2 18:04 28 | **/ 29 | public class SubscribeBookClient { 30 | public static void main(String[] args) throws InterruptedException { 31 | EventLoopGroup eventLoopGroup=new NioEventLoopGroup(); 32 | Bootstrap bootstrap=new Bootstrap(); 33 | bootstrap 34 | .group(eventLoopGroup) 35 | .channel(NioSocketChannel.class) 36 | .option(ChannelOption.TCP_NODELAY,true) 37 | .handler(new ChannelInitializer() { 38 | @Override 39 | protected void initChannel(SocketChannel socketChannel) throws Exception { 40 | socketChannel.pipeline() 41 | .addLast(new ProtobufVarint32FrameDecoder()) 42 | .addLast(new ProtobufDecoder(SubscribeRespProto.SubscribeResp.getDefaultInstance())) 43 | .addLast(new ProtobufVarint32LengthFieldPrepender()) 44 | .addLast(new ProtobufDecoder(new MessageLite() { 45 | @Override 46 | public MessageLite getDefaultInstanceForType() { 47 | return null; 48 | } 49 | 50 | @Override 51 | public boolean isInitialized() { 52 | return false; 53 | } 54 | 55 | @Override 56 | public void writeTo(CodedOutputStream codedOutputStream) throws IOException { 57 | 58 | } 59 | 60 | @Override 61 | public int getSerializedSize() { 62 | return 0; 63 | } 64 | 65 | @Override 66 | public Parser getParserForType() { 67 | return null; 68 | } 69 | 70 | @Override 71 | public ByteString toByteString() { 72 | return null; 73 | } 74 | 75 | @Override 76 | public byte[] toByteArray() { 77 | return new byte[0]; 78 | } 79 | 80 | @Override 81 | public void writeTo(OutputStream outputStream) throws IOException { 82 | 83 | } 84 | 85 | @Override 86 | public void writeDelimitedTo(OutputStream outputStream) throws IOException { 87 | 88 | } 89 | 90 | @Override 91 | public Builder newBuilderForType() { 92 | return null; 93 | } 94 | 95 | @Override 96 | public Builder toBuilder() { 97 | return null; 98 | } 99 | })) 100 | .addLast(new SubscribeBookClientHandler()); 101 | } 102 | }); 103 | try { 104 | ChannelFuture channelFuture=bootstrap.connect("localhost",8888).sync(); 105 | channelFuture.channel().closeFuture().sync(); 106 | } finally { 107 | eventLoopGroup.shutdownGracefully(); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/subscribebook/SubscribeBookClientHandler.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.subscribebook; 2 | 3 | import fun.codenow.netty.socket.protobuf.SubscribeReqProto; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.SimpleChannelInboundHandler; 6 | import lombok.extern.slf4j.Slf4j; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | /** 12 | * @Author Jack Wu 13 | * @Description 14 | * @Version V1.0 15 | * @Date2020/12/2 18:08 16 | **/ 17 | @Slf4j 18 | public class SubscribeBookClientHandler extends SimpleChannelInboundHandler { 19 | @Override 20 | protected void channelRead0(ChannelHandlerContext channelHandlerContext, Object msg) throws Exception { 21 | log.info("客户端打印:接收到消息:{}",msg); 22 | } 23 | 24 | @Override 25 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 26 | ctx.fireChannelActive(); 27 | } 28 | 29 | @Override 30 | public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 31 | ctx.flush(); 32 | } 33 | 34 | @Override 35 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 36 | cause.printStackTrace();; 37 | ctx.close(); 38 | } 39 | 40 | private SubscribeReqProto.SubscribeReq subReq(int i){ 41 | SubscribeReqProto.SubscribeReq.Builder builder=SubscribeReqProto.SubscribeReq.newBuilder(); 42 | builder.setSubReqID(i).setUserName("Lilinfeng").setProductName("Netty Book For Protobuf"); 43 | List address=new ArrayList<>(); 44 | address.add("NanJing YuHuaTai"); 45 | address.add("BeiJing LiuLiChang"); 46 | address.add("ShenZhen HongShuLin"); 47 | builder.setAddress(address.toString()); 48 | 49 | return builder.build(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/subscribebook/SubscribeBookServer.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.subscribebook; 2 | 3 | import fun.codenow.netty.socket.protobuf.SubscribeReqProto; 4 | import io.netty.bootstrap.ServerBootstrap; 5 | import io.netty.channel.ChannelFuture; 6 | import io.netty.channel.ChannelInitializer; 7 | import io.netty.channel.ChannelOption; 8 | import io.netty.channel.EventLoopGroup; 9 | import io.netty.channel.nio.NioEventLoopGroup; 10 | import io.netty.channel.socket.SocketChannel; 11 | import io.netty.channel.socket.nio.NioServerSocketChannel; 12 | import io.netty.handler.codec.protobuf.ProtobufDecoder; 13 | import io.netty.handler.codec.protobuf.ProtobufEncoder; 14 | import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder; 15 | import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender; 16 | import io.netty.handler.logging.LogLevel; 17 | import io.netty.handler.logging.LoggingHandler; 18 | 19 | /** 20 | * @Author Jack Wu 21 | * @Description 22 | * @Version V1.0 23 | * @Date2020/12/2 17:46 24 | **/ 25 | public class SubscribeBookServer { 26 | public static void main(String[] args) throws InterruptedException { 27 | EventLoopGroup bossEventGroup=new NioEventLoopGroup(); 28 | EventLoopGroup workerEventGroup=new NioEventLoopGroup(); 29 | ServerBootstrap serverBootstrap=new ServerBootstrap(); 30 | serverBootstrap 31 | .group(bossEventGroup,workerEventGroup) 32 | .channel(NioServerSocketChannel.class) 33 | .option(ChannelOption.SO_BACKLOG,100) 34 | .handler(new LoggingHandler(LogLevel.INFO)) 35 | .childHandler(new ChannelInitializer() { 36 | @Override 37 | protected void initChannel(SocketChannel socketChannel) throws Exception { 38 | socketChannel.pipeline() 39 | .addLast(new ProtobufVarint32FrameDecoder()) 40 | .addLast(new ProtobufDecoder(SubscribeReqProto.SubscribeReq.getDefaultInstance())) 41 | .addLast(new ProtobufVarint32LengthFieldPrepender()) 42 | .addLast(new ProtobufEncoder()) 43 | .addLast(new SubscribeBookServerHandler()); 44 | } 45 | }); 46 | try { 47 | ChannelFuture channelFuture=serverBootstrap.bind(8888).sync(); 48 | channelFuture.channel().closeFuture().sync(); 49 | } finally { 50 | bossEventGroup.shutdownGracefully().sync(); 51 | workerEventGroup.shutdownGracefully().sync(); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/subscribebook/SubscribeBookServerHandler.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.subscribebook; 2 | 3 | import fun.codenow.netty.socket.protobuf.SubscribeReqProto; 4 | import fun.codenow.netty.socket.protobuf.SubscribeRespProto; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.channel.SimpleChannelInboundHandler; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | /** 10 | * @Author Jack Wu 11 | * @Description 12 | * @Version V1.0 13 | * @Date2020/12/2 17:51 14 | **/ 15 | @Slf4j 16 | public class SubscribeBookServerHandler extends SimpleChannelInboundHandler { 17 | @Override 18 | protected void channelRead0(ChannelHandlerContext channelHandlerContext, Object msg) throws Exception { 19 | SubscribeReqProto.SubscribeReq req= (SubscribeReqProto.SubscribeReq) msg; 20 | if ("Lilinfeng".equalsIgnoreCase(req.getUserName())){ 21 | log.info("打印信息: 收到客户端的订书请求:{}",req.toString()); 22 | channelHandlerContext.writeAndFlush(resp(req.getSubReqID())); 23 | } 24 | } 25 | @Override 26 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 27 | cause.printStackTrace(); 28 | log.error("发生异常,关闭链路,message{}",cause.getMessage()); 29 | } 30 | 31 | private SubscribeRespProto.SubscribeResp resp(int subReqID){ 32 | SubscribeRespProto.SubscribeResp.Builder builder=SubscribeRespProto.SubscribeResp.newBuilder(); 33 | builder.setSubReqID(subReqID); 34 | builder.setRespCode(0); 35 | builder.setDesc("图书下单成功,三天后将邮寄到你的地址"); 36 | return builder.build(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/test/EchoClientTest.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.test; 2 | 3 | import fun.codenow.netty.socket.demo.EchoClientHandler; 4 | import io.netty.bootstrap.Bootstrap; 5 | import io.netty.channel.ChannelFuture; 6 | import io.netty.channel.ChannelInitializer; 7 | import io.netty.channel.nio.NioEventLoopGroup; 8 | import io.netty.channel.socket.SocketChannel; 9 | import io.netty.channel.socket.nio.NioSocketChannel; 10 | 11 | import java.net.InetSocketAddress; 12 | 13 | /** 14 | * @Author Jack Wu 15 | * @Description 16 | * @Version V1.0 17 | * @Date2020/11/26 10:21 18 | **/ 19 | public class EchoClientTest { 20 | public static void main(String[] args) throws InterruptedException { 21 | NioEventLoopGroup nioEventLoopGroup=new NioEventLoopGroup(); 22 | Bootstrap bootstrap=new Bootstrap(); 23 | bootstrap 24 | .group(nioEventLoopGroup) 25 | .channel(NioSocketChannel.class) 26 | /** 27 | * 与服务端的localAddress不同,这里是remoteAddress,配置远程连接 服务器地址和端口 28 | */ 29 | .remoteAddress(new InetSocketAddress(12120)) 30 | .handler(new ChannelInitializer() { 31 | @Override 32 | protected void initChannel(SocketChannel socketChannel) throws Exception { 33 | /** 34 | * 这里配置的Handler是客户端需要使用的Handler 35 | */ 36 | socketChannel.pipeline().addLast(new EchoClientHandler()); 37 | } 38 | }); 39 | try { 40 | /** 41 | * 这里是进行连接,客户端使用connect() 方法进行服务端连接 42 | */ 43 | ChannelFuture channelFuture=bootstrap.connect().sync(); 44 | channelFuture.channel().closeFuture().sync(); 45 | } finally { 46 | nioEventLoopGroup.shutdownGracefully().sync(); 47 | } 48 | 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/test/EchoServerTest.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.test; 2 | 3 | import fun.codenow.netty.socket.demo.EchoServerHandler; 4 | import io.netty.bootstrap.ServerBootstrap; 5 | import io.netty.channel.ChannelFuture; 6 | import io.netty.channel.ChannelInitializer; 7 | import io.netty.channel.nio.NioEventLoopGroup; 8 | import io.netty.channel.socket.SocketChannel; 9 | import io.netty.channel.socket.nio.NioServerSocketChannel; 10 | 11 | import java.net.InetSocketAddress; 12 | 13 | /** 14 | * @Author Jack Wu 15 | * @Description 16 | * @Version V1.0 17 | * @Date2020/11/26 10:21 18 | **/ 19 | public class EchoServerTest { 20 | public static void main(String[] args) throws InterruptedException { 21 | /** 22 | * Bootstrap是应用程序的开始,作用是配置整个netty程序,串联各个组件 23 | */ 24 | ServerBootstrap serverBootstrap=new ServerBootstrap(); 25 | NioEventLoopGroup nioEventLoopGroup=new NioEventLoopGroup(); 26 | serverBootstrap 27 | .group(nioEventLoopGroup) 28 | /** 29 | * Channel 代表一个Socket连接,或者其他和IO操作相关的组件,它和EventLoop一起参加IO处理 30 | */ 31 | .channel(NioServerSocketChannel.class) 32 | .localAddress(new InetSocketAddress(9999)) 33 | /** 34 | * Handler 是为了支持各种协议和处理数据的方式; 主要是处理连接、数据接收、异常、数据转换等事件 35 | * 36 | * ChannelInitializer 用于配置Handler, 它提供ChannelPipeline,并把设置的Handler加到ChannelPipeline 37 | */ 38 | .childHandler(new ChannelInitializer() { 39 | @Override 40 | protected void initChannel(SocketChannel socketChannel) throws Exception { 41 | /** 42 | * ChannelPipeline 一个Netty应用基于ChannelPipeline机制,这种机制依赖于EventLoop和EventLoopGroup 43 | */ 44 | socketChannel.pipeline().addLast(new EchoServerHandler()); 45 | } 46 | }); 47 | try { 48 | /** 49 | * Future和ChannelFuture ,注册监听,当操作成功或失败时自动触发,所有的操作都会返回一个ChannelFuture, 服务端用bind()设置占用的端口号 50 | */ 51 | ChannelFuture channelFuture= serverBootstrap.bind().sync(); 52 | channelFuture.channel().closeFuture().sync(); 53 | } finally { 54 | /** 55 | * 关闭连接 56 | */ 57 | nioEventLoopGroup.shutdownGracefully().sync(); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/test2/EchoClientTest2.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.test2; 2 | 3 | import io.netty.bootstrap.Bootstrap; 4 | import io.netty.channel.ChannelFuture; 5 | import io.netty.channel.ChannelInitializer; 6 | import io.netty.channel.nio.NioEventLoopGroup; 7 | import io.netty.channel.socket.SocketChannel; 8 | 9 | import java.net.InetSocketAddress; 10 | 11 | /** 12 | * @Author Jack Wu 13 | * @Description 14 | * @Version V1.0 15 | * @Date2020/11/27 9:30 16 | **/ 17 | public class EchoClientTest2 { 18 | public static void main(String[] args) throws InterruptedException { 19 | NioEventLoopGroup nioEventLoopGroup=new NioEventLoopGroup(); 20 | Bootstrap bootstrap=new Bootstrap(); 21 | bootstrap.group(nioEventLoopGroup) 22 | .channel(SocketChannel.class) 23 | .remoteAddress(new InetSocketAddress(8777)) 24 | .handler(new ChannelInitializer() { 25 | @Override 26 | protected void initChannel(SocketChannel socketChannel) throws Exception { 27 | 28 | } 29 | }); 30 | try { 31 | ChannelFuture channelFuture= bootstrap.connect().sync(); 32 | channelFuture.channel().close().sync(); 33 | } finally { 34 | nioEventLoopGroup.shutdownGracefully().sync(); 35 | } 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/test2/EchoClientTest2ChannelHandler.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.test2; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.SimpleChannelInboundHandler; 6 | 7 | /** 8 | * @Author Jack Wu 9 | * @Description 10 | * @Version V1.0 11 | * @Date2020/11/27 9:38 12 | **/ 13 | public class EchoClientTest2ChannelHandler extends SimpleChannelInboundHandler { 14 | @Override 15 | protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception { 16 | 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/test2/EchoServer2ChannelHanlder.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.test2; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.channel.ChannelInboundHandlerAdapter; 5 | 6 | /** 7 | * @Author Jack Wu 8 | * @Description 9 | * @Version V1.0 10 | * @Date2020/11/27 9:36 11 | **/ 12 | public class EchoServer2ChannelHanlder extends ChannelInboundHandlerAdapter { 13 | 14 | 15 | @Override 16 | public void channelRegistered(ChannelHandlerContext ctx) throws Exception { 17 | super.channelRegistered(ctx); 18 | } 19 | 20 | @Override 21 | public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { 22 | super.channelUnregistered(ctx); 23 | } 24 | 25 | @Override 26 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 27 | super.channelActive(ctx); 28 | } 29 | 30 | @Override 31 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 32 | super.channelInactive(ctx); 33 | } 34 | 35 | @Override 36 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 37 | super.channelRead(ctx, msg); 38 | } 39 | 40 | @Override 41 | public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 42 | super.channelReadComplete(ctx); 43 | } 44 | 45 | @Override 46 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 47 | super.userEventTriggered(ctx, evt); 48 | } 49 | 50 | @Override 51 | public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception { 52 | super.channelWritabilityChanged(ctx); 53 | } 54 | 55 | @Override 56 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 57 | super.exceptionCaught(ctx, cause); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/test2/EchoServerTest2.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.test2; 2 | 3 | import io.netty.bootstrap.ServerBootstrap; 4 | import io.netty.channel.ChannelFuture; 5 | import io.netty.channel.ChannelInitializer; 6 | import io.netty.channel.nio.NioEventLoopGroup; 7 | import io.netty.channel.socket.ServerSocketChannel; 8 | import io.netty.channel.socket.SocketChannel; 9 | 10 | import java.net.InetSocketAddress; 11 | 12 | /** 13 | * @Author Jack Wu 14 | * @Description 15 | * @Version V1.0 16 | * @Date2020/11/27 9:30 17 | **/ 18 | public class EchoServerTest2 { 19 | public static void main(String[] args) throws InterruptedException { 20 | NioEventLoopGroup nioEventLoopGroup=new NioEventLoopGroup(); 21 | ServerBootstrap serverBootstrap=new ServerBootstrap(); 22 | serverBootstrap 23 | .group(nioEventLoopGroup) 24 | .channel(ServerSocketChannel.class) 25 | .localAddress(new InetSocketAddress(8777)) 26 | .handler(new ChannelInitializer() { 27 | @Override 28 | protected void initChannel(SocketChannel socketChannel) throws Exception { 29 | socketChannel.pipeline().addLast(); 30 | } 31 | }); 32 | try { 33 | ChannelFuture channelFuture= serverBootstrap.bind().sync(); 34 | channelFuture.channel().closeFuture().sync(); 35 | } finally { 36 | nioEventLoopGroup.shutdownGracefully().sync(); 37 | } 38 | 39 | 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/timeserver/TimeClient.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.timeserver; 2 | 3 | import io.netty.bootstrap.Bootstrap; 4 | import io.netty.channel.ChannelFuture; 5 | import io.netty.channel.ChannelInitializer; 6 | import io.netty.channel.ChannelOption; 7 | import io.netty.channel.EventLoopGroup; 8 | import io.netty.channel.nio.NioEventLoopGroup; 9 | import io.netty.channel.socket.SocketChannel; 10 | import io.netty.channel.socket.nio.NioSocketChannel; 11 | 12 | /** 13 | * @Author Jack Wu 14 | * @Description 15 | * @Version V1.0 16 | * @Date2020/12/2 9:42 17 | **/ 18 | public class TimeClient { 19 | public static void main(String[] args) throws InterruptedException { 20 | EventLoopGroup eventLoopGroup=new NioEventLoopGroup(); 21 | Bootstrap bootstrap=new Bootstrap(); 22 | bootstrap 23 | .group(eventLoopGroup) 24 | .channel(NioSocketChannel.class) 25 | .option(ChannelOption.TCP_NODELAY,true) 26 | .handler(new ChannelInitializer() { 27 | @Override 28 | protected void initChannel(SocketChannel socketChannel) throws Exception { 29 | socketChannel.pipeline().addLast(new TimeClientHandler()); 30 | } 31 | }); 32 | try { 33 | ChannelFuture channelFuture= bootstrap.connect("localhost",9995).sync(); 34 | channelFuture.channel().closeFuture().sync(); 35 | } finally { 36 | eventLoopGroup.shutdownGracefully().sync(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/timeserver/TimeClientHandler.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.timeserver; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.Unpooled; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.channel.ChannelInboundHandlerAdapter; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | /** 10 | * @Author Jack Wu 11 | * @Description 12 | * @Version V1.0 13 | * @Date2020/12/2 10:41 14 | **/ 15 | @Slf4j 16 | public class TimeClientHandler extends ChannelInboundHandlerAdapter { 17 | private final ByteBuf firstMessage; 18 | 19 | public TimeClientHandler(){ 20 | byte[] req="QUERY TIME ORDER".getBytes(); 21 | firstMessage= Unpooled.buffer(req.length); 22 | firstMessage.writeBytes(req); 23 | } 24 | 25 | @Override 26 | public void channelRegistered(ChannelHandlerContext ctx) throws Exception { 27 | super.channelRegistered(ctx); 28 | } 29 | 30 | @Override 31 | public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { 32 | super.channelUnregistered(ctx); 33 | } 34 | 35 | @Override 36 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 37 | ctx.writeAndFlush(firstMessage); 38 | } 39 | 40 | @Override 41 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 42 | super.channelInactive(ctx); 43 | } 44 | 45 | @Override 46 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 47 | ByteBuf buf= (ByteBuf) msg; 48 | byte[] req=new byte[buf.readableBytes()]; 49 | buf.readBytes(req); 50 | String body=new String(req,"UTF-8"); 51 | System.out.println("client print , now is :"+body); 52 | } 53 | 54 | @Override 55 | public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 56 | super.channelReadComplete(ctx); 57 | } 58 | 59 | @Override 60 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 61 | super.userEventTriggered(ctx, evt); 62 | } 63 | 64 | @Override 65 | public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception { 66 | super.channelWritabilityChanged(ctx); 67 | } 68 | 69 | @Override 70 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 71 | log.warn("Unexpected exception from downstream, "+cause.getMessage()); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/timeserver/TimeServer.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.timeserver; 2 | 3 | import io.netty.bootstrap.ServerBootstrap; 4 | import io.netty.channel.ChannelFuture; 5 | import io.netty.channel.ChannelOption; 6 | import io.netty.channel.EventLoopGroup; 7 | import io.netty.channel.nio.NioEventLoopGroup; 8 | import io.netty.channel.socket.nio.NioServerSocketChannel; 9 | 10 | /** 11 | * @Author Jack Wu 12 | * @Description 13 | * @Version V1.0 14 | * @Date2020/12/2 9:14 15 | **/ 16 | public class TimeServer { 17 | public static void main(String[] args) throws InterruptedException { 18 | EventLoopGroup bossGroup=new NioEventLoopGroup(); 19 | EventLoopGroup workerGroup=new NioEventLoopGroup(); 20 | ServerBootstrap serverBootstrap=new ServerBootstrap(); 21 | serverBootstrap 22 | .group(bossGroup,workerGroup) 23 | .channel(NioServerSocketChannel.class) 24 | .option(ChannelOption.SO_BACKLOG,1024) 25 | .childHandler(new TimeServerInitializer()); 26 | try { 27 | ChannelFuture channelFuture= serverBootstrap.bind(9995).sync(); 28 | channelFuture.channel().closeFuture().sync(); 29 | } finally { 30 | bossGroup.shutdownGracefully().sync(); 31 | workerGroup.shutdownGracefully().sync(); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/timeserver/TimeServerHandler.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.timeserver; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.Unpooled; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.channel.ChannelInboundHandlerAdapter; 7 | 8 | /** 9 | * @Author Jack Wu 10 | * @Description 11 | * @Version V1.0 12 | * @Date2020/12/2 9:24 13 | **/ 14 | public class TimeServerHandler extends ChannelInboundHandlerAdapter { 15 | public TimeServerHandler() { 16 | super(); 17 | } 18 | 19 | @Override 20 | public void channelRegistered(ChannelHandlerContext ctx) throws Exception { 21 | super.channelRegistered(ctx); 22 | } 23 | 24 | @Override 25 | public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { 26 | super.channelUnregistered(ctx); 27 | } 28 | 29 | @Override 30 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 31 | super.channelActive(ctx); 32 | } 33 | 34 | @Override 35 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 36 | super.channelInactive(ctx); 37 | } 38 | 39 | @Override 40 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 41 | ByteBuf byteBuf= (ByteBuf) msg; 42 | byte[] req=new byte[byteBuf.readableBytes()]; 43 | byteBuf.readBytes(req); 44 | String body=new String(req,"UTF-8"); 45 | System.out.println("Time Server receive order:"+body); 46 | String currentTime="QUERY TIME ORDER".equalsIgnoreCase(body)?String.valueOf(System.currentTimeMillis()):"BAD ORDER"; 47 | ByteBuf resp= Unpooled.copiedBuffer(currentTime.getBytes()); 48 | ctx.write(resp); 49 | } 50 | 51 | @Override 52 | public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 53 | ctx.flush(); 54 | } 55 | 56 | @Override 57 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 58 | super.userEventTriggered(ctx, evt); 59 | } 60 | 61 | @Override 62 | public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception { 63 | super.channelWritabilityChanged(ctx); 64 | } 65 | 66 | @Override 67 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 68 | ctx.close(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/timeserver/TimeServerInitializer.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.timeserver; 2 | 3 | import io.netty.channel.ChannelInitializer; 4 | import io.netty.channel.socket.SocketChannel; 5 | 6 | /** 7 | * @Author Jack Wu 8 | * @Description 9 | * @Version V1.0 10 | * @Date2020/12/2 9:18 11 | **/ 12 | public class TimeServerInitializer extends ChannelInitializer { 13 | @Override 14 | protected void initChannel(SocketChannel socketChannel) throws Exception { 15 | socketChannel.pipeline().addLast(new TimeServerHandler()); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/uptime/UptimeClient.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.uptime; 2 | 3 | /** 4 | * @Author Jack Wu 5 | * @Description 6 | * @Version V1.0 7 | * @Date2020/12/14 13:43 8 | **/ 9 | import io.netty.bootstrap.Bootstrap; 10 | import io.netty.channel.ChannelFuture; 11 | import io.netty.channel.ChannelFutureListener; 12 | import io.netty.channel.ChannelInitializer; 13 | import io.netty.channel.EventLoopGroup; 14 | import io.netty.channel.nio.NioEventLoopGroup; 15 | import io.netty.channel.socket.SocketChannel; 16 | import io.netty.channel.socket.nio.NioSocketChannel; 17 | import io.netty.handler.timeout.IdleStateHandler; 18 | 19 | 20 | /** 21 | * Connects to a server periodically to measure and print the uptime of the 22 | * server. This example demonstrates how to implement reliable reconnection 23 | * mechanism in Netty. 24 | */ 25 | public final class UptimeClient { 26 | 27 | static final String HOST = System.getProperty("host", "127.0.0.1"); 28 | static final int PORT = Integer.parseInt(System.getProperty("port", "8080")); 29 | // Sleep 5 seconds before a reconnection attempt. 30 | static final int RECONNECT_DELAY = Integer.parseInt(System.getProperty("reconnectDelay", "5")); 31 | // Reconnect when the server sends nothing for 10 seconds. 32 | private static final int READ_TIMEOUT = Integer.parseInt(System.getProperty("readTimeout", "10")); 33 | 34 | private static final UptimeClientHandler handler = new UptimeClientHandler(); 35 | private static final Bootstrap bs = new Bootstrap(); 36 | 37 | public static void main(String[] args) throws Exception { 38 | EventLoopGroup group = new NioEventLoopGroup(); 39 | bs.group(group) 40 | .channel(NioSocketChannel.class) 41 | .remoteAddress(HOST, PORT) 42 | .handler(new ChannelInitializer() { 43 | @Override 44 | protected void initChannel(SocketChannel ch) throws Exception { 45 | ch.pipeline().addLast(new IdleStateHandler(READ_TIMEOUT, 0, 0), handler); 46 | } 47 | }); 48 | bs.connect(); 49 | } 50 | 51 | static void connect() { 52 | bs.connect().addListener(new ChannelFutureListener() { 53 | @Override 54 | public void operationComplete(ChannelFuture future) throws Exception { 55 | if (future.cause() != null) { 56 | handler.startTime = -1; 57 | handler.println("Failed to connect: " + future.cause()); 58 | } 59 | } 60 | }); 61 | } 62 | } -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/uptime/UptimeClientHandler.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.uptime; 2 | 3 | 4 | import io.netty.channel.ChannelHandler.Sharable; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.channel.SimpleChannelInboundHandler; 7 | import io.netty.handler.timeout.IdleState; 8 | import io.netty.handler.timeout.IdleStateEvent; 9 | 10 | import java.util.concurrent.TimeUnit; 11 | 12 | /** 13 | * @Author Jack Wu 14 | * @Description 15 | * @Version V1.0 16 | * @Date2020/12/14 13:42 17 | **/ 18 | public class UptimeClientHandler extends SimpleChannelInboundHandler { 19 | long startTime = -1; 20 | 21 | @Override 22 | public void channelActive(ChannelHandlerContext ctx) { 23 | if (startTime < 0) { 24 | startTime = System.currentTimeMillis(); 25 | } 26 | println("Connected to: " + ctx.channel().remoteAddress()); 27 | } 28 | 29 | @Override 30 | public void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { 31 | // Discard received data 32 | } 33 | 34 | @Override 35 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { 36 | if (!(evt instanceof IdleStateEvent)) { 37 | return; 38 | } 39 | 40 | IdleStateEvent e = (IdleStateEvent) evt; 41 | if (e.state() == IdleState.READER_IDLE) { 42 | // The connection was OK but there was no traffic for last period. 43 | println("Disconnecting due to no inbound traffic"); 44 | ctx.close(); 45 | } 46 | } 47 | 48 | @Override 49 | public void channelInactive(final ChannelHandlerContext ctx) { 50 | println("Disconnected from: " + ctx.channel().remoteAddress()); 51 | } 52 | 53 | @Override 54 | public void channelUnregistered(final ChannelHandlerContext ctx) throws Exception { 55 | println("Sleeping for: " + UptimeClient.RECONNECT_DELAY + 's'); 56 | 57 | ctx.channel().eventLoop().schedule(new Runnable() { 58 | @Override 59 | public void run() { 60 | println("Reconnecting to: " + UptimeClient.HOST + ':' + UptimeClient.PORT); 61 | UptimeClient.connect(); 62 | } 63 | }, UptimeClient.RECONNECT_DELAY, TimeUnit.SECONDS); 64 | } 65 | 66 | @Override 67 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 68 | cause.printStackTrace(); 69 | ctx.close(); 70 | } 71 | 72 | void println(String msg) { 73 | if (startTime < 0) { 74 | System.err.format("[SERVER IS DOWN] %s%n", msg); 75 | } else { 76 | System.err.format("[UPTIME: %5ds] %s%n", (System.currentTimeMillis() - startTime) / 1000, msg); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/uptime/UptimeServer.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.uptime; 2 | 3 | /** 4 | * @Author Jack Wu 5 | * @Description 6 | * @Version V1.0 7 | * @Date2020/12/14 13:44 8 | **/ 9 | import io.netty.bootstrap.ServerBootstrap; 10 | import io.netty.channel.ChannelFuture; 11 | import io.netty.channel.ChannelInitializer; 12 | import io.netty.channel.EventLoopGroup; 13 | import io.netty.channel.nio.NioEventLoopGroup; 14 | import io.netty.channel.socket.SocketChannel; 15 | import io.netty.channel.socket.nio.NioServerSocketChannel; 16 | import io.netty.handler.logging.LogLevel; 17 | import io.netty.handler.logging.LoggingHandler; 18 | 19 | /** 20 | * Uptime server is served as a connection server. 21 | * So it simply discards all message received. 22 | */ 23 | public final class UptimeServer { 24 | private static final int PORT = Integer.parseInt(System.getProperty("port", "8080")); 25 | private static final UptimeServerHandler handler = new UptimeServerHandler(); 26 | 27 | private UptimeServer() { 28 | } 29 | 30 | public static void main(String[] args) throws Exception { 31 | 32 | EventLoopGroup bossGroup = new NioEventLoopGroup(1); 33 | EventLoopGroup workerGroup = new NioEventLoopGroup(); 34 | try { 35 | ServerBootstrap b = new ServerBootstrap(); 36 | b.group(bossGroup, workerGroup) 37 | .channel(NioServerSocketChannel.class) 38 | .handler(new LoggingHandler(LogLevel.INFO)) 39 | .childHandler(new ChannelInitializer() { 40 | @Override 41 | public void initChannel(SocketChannel ch) { 42 | ch.pipeline().addLast(handler); 43 | } 44 | }); 45 | 46 | // Bind and start to accept incoming connections. 47 | ChannelFuture f = b.bind(PORT).sync(); 48 | 49 | // Wait until the server socket is closed. 50 | // In this example, this does not happen, but you can do that to gracefully 51 | // shut down your server. 52 | f.channel().closeFuture().sync(); 53 | } finally { 54 | workerGroup.shutdownGracefully(); 55 | bossGroup.shutdownGracefully(); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /netty-demo/src/main/java/fun/codenow/netty/socket/uptime/UptimeServerHandler.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.socket.uptime; 2 | 3 | /** 4 | * @Author Jack Wu 5 | * @Description 6 | * @Version V1.0 7 | * @Date2020/12/14 13:45 8 | **/ 9 | import io.netty.channel.ChannelHandler.Sharable; 10 | import io.netty.channel.ChannelHandlerContext; 11 | import io.netty.channel.SimpleChannelInboundHandler; 12 | 13 | @Sharable 14 | public class UptimeServerHandler extends SimpleChannelInboundHandler { 15 | @Override 16 | public void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { 17 | // discard 18 | } 19 | 20 | @Override 21 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 22 | // Close the connection when an exception is raised. 23 | cause.printStackTrace(); 24 | ctx.close(); 25 | } 26 | } -------------------------------------------------------------------------------- /netty-demo/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8881 -------------------------------------------------------------------------------- /netty-file-system/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | codenow-netty 7 | fun.codenow 8 | 1.0.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | netty-file-system 13 | 14 | 15 | -------------------------------------------------------------------------------- /netty-heartbeat/heartbeat-client/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | netty-heartbeat 7 | fun.codenow 8 | 1.0.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | heartbeat-client 13 | 14 | 15 | 16 | org.springframework.boot 17 | spring-boot-starter-web 18 | 19 | 20 | org.springframework.boot 21 | spring-boot-starter-tomcat 22 | 23 | 24 | 25 | 26 | 27 | io.netty 28 | netty-all 29 | 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-jetty 34 | 35 | 36 | 37 | org.projectlombok 38 | lombok 39 | 40 | 41 | 42 | com.google.code.gson 43 | gson 44 | 2.8.5 45 | 46 | 47 | 48 | com.google.protobuf 49 | protobuf-java 50 | 51 | 52 | 53 | fun.codenow 54 | netty-common 55 | 1.0.0-SNAPSHOT 56 | 57 | 58 | -------------------------------------------------------------------------------- /netty-heartbeat/heartbeat-client/src/main/java/fun/codenow/netty/heartbeat/client/HeartBeatClientApplication.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.heartbeat.client; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | /** 7 | * @Author Jack Wu 8 | * @Description 9 | * 10 | * 初始化时获取应用名称,设置 应用ID,在心跳中加入应用ID和应用名称信息 11 | * 12 | * @Version V1.0 13 | * @Date2020/12/4 14:27 14 | **/ 15 | @SpringBootApplication 16 | public class HeartBeatClientApplication { 17 | public static void main(String[] args) { 18 | SpringApplication.run(HeartBeatClientApplication.class); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /netty-heartbeat/heartbeat-client/src/main/java/fun/codenow/netty/heartbeat/client/client/Client.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.heartbeat.client.client; 2 | 3 | import io.netty.bootstrap.Bootstrap; 4 | import io.netty.channel.Channel; 5 | import io.netty.channel.ChannelFuture; 6 | import io.netty.channel.ChannelFutureListener; 7 | import io.netty.channel.nio.NioEventLoopGroup; 8 | import io.netty.channel.socket.nio.NioSocketChannel; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.stereotype.Component; 12 | 13 | import javax.annotation.PostConstruct; 14 | import javax.annotation.PreDestroy; 15 | import java.util.concurrent.TimeUnit; 16 | 17 | /** 18 | * @Author Jack Wu 19 | * @Description 20 | * @Version V1.0 21 | * @Date2020/12/7 17:22 22 | **/ 23 | @Slf4j 24 | @Component 25 | public class Client { 26 | private NioEventLoopGroup eventLoopGroup; 27 | private Bootstrap bootstrap; 28 | public Channel channel; 29 | private String serverAddr="localhost"; 30 | private int port=8888; 31 | @Autowired 32 | CustomClientChannelInitializer customClientChannelInitializer; 33 | @PostConstruct 34 | public void run() throws InterruptedException { 35 | connect(); 36 | //ChannelFuture channelFuture=bootstrap.connect().sync(); 37 | //channelFuture.channel().closeFuture().sync(); 38 | //客户端自动注册、在断开连接后定期进行重连操作 39 | } 40 | @PreDestroy 41 | public void destroy(){ 42 | eventLoopGroup.shutdownGracefully(); 43 | } 44 | 45 | public void connect() throws InterruptedException { 46 | if (channel!=null&&channel.isActive()){ 47 | return; 48 | } 49 | eventLoopGroup=new NioEventLoopGroup(); 50 | bootstrap=new Bootstrap(); 51 | bootstrap 52 | .group(eventLoopGroup) 53 | .channel(NioSocketChannel.class) 54 | .handler(customClientChannelInitializer); 55 | ChannelFuture channelFuture=bootstrap.connect(serverAddr,port).sync(); 56 | channelFuture.addListener(new ChannelFutureListener() { 57 | @Override 58 | public void operationComplete(ChannelFuture channelFuture) throws Exception { 59 | if (channelFuture.isSuccess()){ 60 | channel=channelFuture.channel(); 61 | log.info("服务器启动成功"); 62 | }else { 63 | log.error("服务器启动失败,正在进行重连……"); 64 | try { 65 | connect(); 66 | } catch (InterruptedException e) { 67 | log.error("连接出现异常,message:{}",e.getMessage()); 68 | } 69 | } 70 | } 71 | }); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /netty-heartbeat/heartbeat-client/src/main/java/fun/codenow/netty/heartbeat/client/client/CustomClientChannelInitializer.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.heartbeat.client.client; 2 | 3 | import fun.codenow.netty.common.CustomMessageProto; 4 | import fun.codenow.netty.heartbeat.client.handler.HeartBeatClientHandler; 5 | import io.netty.channel.ChannelInitializer; 6 | import io.netty.channel.socket.SocketChannel; 7 | import io.netty.handler.codec.protobuf.ProtobufDecoder; 8 | import io.netty.handler.codec.protobuf.ProtobufEncoder; 9 | import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder; 10 | import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender; 11 | import io.netty.handler.timeout.IdleStateHandler; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.stereotype.Component; 14 | 15 | import java.util.concurrent.TimeUnit; 16 | 17 | /** 18 | * @Author Jack Wu 19 | * @Description 20 | * @Version V1.0 21 | * @Date2020/12/11 14:14 22 | **/ 23 | @Component 24 | public class CustomClientChannelInitializer extends ChannelInitializer { 25 | @Autowired 26 | HeartBeatClientHandler heartBeatClientHandler; 27 | @Override 28 | protected void initChannel(SocketChannel socketChannel) throws Exception { 29 | socketChannel 30 | .pipeline() 31 | .addLast(new IdleStateHandler(0,5,0, TimeUnit.SECONDS)) 32 | .addLast(new ProtobufVarint32FrameDecoder()) 33 | .addLast(new ProtobufDecoder(CustomMessageProto.CustomMessage.getDefaultInstance())) 34 | .addLast(new ProtobufVarint32LengthFieldPrepender()) 35 | .addLast(new ProtobufEncoder()) 36 | .addLast(heartBeatClientHandler); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /netty-heartbeat/heartbeat-client/src/main/java/fun/codenow/netty/heartbeat/client/handler/HeartBeatClientHandler.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.heartbeat.client.handler; 2 | 3 | import fun.codenow.netty.common.CustomMessageProto; 4 | import fun.codenow.netty.heartbeat.client.client.Client; 5 | import io.netty.channel.ChannelHandler; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.channel.ChannelInboundHandlerAdapter; 8 | import io.netty.handler.timeout.IdleState; 9 | import io.netty.handler.timeout.IdleStateEvent; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.stereotype.Component; 13 | 14 | import java.util.concurrent.TimeUnit; 15 | 16 | 17 | /** 18 | * @Author Jack Wu 19 | * @Description 20 | * @Version V1.0 21 | * @Date2020/12/11 14:19 22 | **/ 23 | @Slf4j 24 | @Component 25 | @ChannelHandler.Sharable 26 | public class HeartBeatClientHandler extends ChannelInboundHandlerAdapter { 27 | private long reconnectTime=5L; 28 | @Autowired 29 | Client client; 30 | @Override 31 | public void channelRegistered(ChannelHandlerContext ctx) throws Exception { 32 | super.channelRegistered(ctx); 33 | } 34 | 35 | @Override 36 | public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { 37 | //重连 38 | ctx.channel().eventLoop().schedule(new Runnable() { 39 | @Override 40 | public void run() { 41 | try { 42 | client.connect(); 43 | } catch (InterruptedException e) { 44 | log.error("连接服务端出现异常,message:{}",e.getMessage()); 45 | } 46 | } 47 | },reconnectTime, TimeUnit.SECONDS); 48 | super.channelUnregistered(ctx); 49 | } 50 | 51 | @Override 52 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 53 | CustomMessageProto.CustomMessage.Builder heartBeatMsg=CustomMessageProto.CustomMessage.newBuilder() 54 | .setHeader( 55 | CustomMessageProto.CustomMessage.CustomHeader.newBuilder() 56 | .setTypeValue(0xABEF) 57 | .setType(CustomMessageProto.CustomMessage.CustomHeader.MessgeType.PING) 58 | ); 59 | ctx.channel().writeAndFlush(heartBeatMsg); 60 | } 61 | 62 | @Override 63 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 64 | log.warn("连接断开,channelInactive() call "); 65 | super.channelInactive(ctx); 66 | } 67 | 68 | @Override 69 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 70 | CustomMessageProto.CustomMessage message= (CustomMessageProto.CustomMessage) msg; 71 | if (message.getHeader().getType().equals(CustomMessageProto.CustomMessage.CustomHeader.MessgeType.PONG)){ 72 | log.info("接收到心跳消息:{}",message); 73 | } 74 | super.channelRead(ctx, msg); 75 | } 76 | 77 | @Override 78 | public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 79 | super.channelReadComplete(ctx); 80 | } 81 | 82 | @Override 83 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 84 | if (evt instanceof IdleStateEvent){ 85 | log.info("心跳检测触发了"); 86 | IdleStateEvent idleStateEvent= (IdleStateEvent) evt; 87 | if (idleStateEvent.state().equals(IdleState.WRITER_IDLE)){ 88 | log.warn("已经5秒钟没有写操作了,发个心跳消息检测下"); 89 | CustomMessageProto.CustomMessage.Builder heartBeatReqMsg=CustomMessageProto.CustomMessage.newBuilder() 90 | .setHeader( 91 | CustomMessageProto.CustomMessage.CustomHeader.newBuilder() 92 | .setTypeValue(0xABEF) 93 | .setType(CustomMessageProto.CustomMessage.CustomHeader.MessgeType.PING) 94 | ); 95 | ctx.channel().writeAndFlush(heartBeatReqMsg); 96 | } 97 | } 98 | } 99 | 100 | @Override 101 | public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception { 102 | super.channelWritabilityChanged(ctx); 103 | } 104 | 105 | @Override 106 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 107 | log.error("程序异常,{}",cause.getMessage()); 108 | ctx.channel().close(); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /netty-heartbeat/heartbeat-client/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8884 -------------------------------------------------------------------------------- /netty-heartbeat/heartbeat-server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | netty-heartbeat 7 | fun.codenow 8 | 1.0.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | heartbeat-server 13 | 14 | 15 | 16 | org.springframework.boot 17 | spring-boot-starter-web 18 | 19 | 20 | org.springframework.boot 21 | spring-boot-starter-tomcat 22 | 23 | 24 | 25 | 26 | 27 | io.netty 28 | netty-all 29 | 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-jetty 34 | 35 | 36 | 37 | org.projectlombok 38 | lombok 39 | 40 | 41 | 42 | com.google.code.gson 43 | gson 44 | 2.8.5 45 | 46 | 47 | 48 | com.google.protobuf 49 | protobuf-java 50 | 51 | 52 | 53 | fun.codenow 54 | netty-common 55 | 1.0.0-SNAPSHOT 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /netty-heartbeat/heartbeat-server/src/main/java/fun/codenow/netty/heartbeat/server/HeartBeatServerApplication.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.heartbeat.server; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | /** 7 | * @Author Jack Wu 8 | * @Description 9 | * @Version V1.0 10 | * @Date2020/12/4 14:27 11 | **/ 12 | @SpringBootApplication 13 | public class HeartBeatServerApplication { 14 | public static void main(String[] args) { 15 | SpringApplication.run(HeartBeatServerApplication.class); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /netty-heartbeat/heartbeat-server/src/main/java/fun/codenow/netty/heartbeat/server/handler/HeartBeatServerHandler.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.heartbeat.server.handler; 2 | 3 | import fun.codenow.netty.common.CustomMessageProto; 4 | import io.netty.channel.ChannelHandler; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.channel.ChannelInboundHandlerAdapter; 7 | import io.netty.handler.timeout.IdleState; 8 | import io.netty.handler.timeout.IdleStateEvent; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.springframework.stereotype.Component; 11 | 12 | /** 13 | * @Author Jack Wu 14 | * @Description 15 | * @Version V1.0 16 | * @Date2020/12/7 17:56 17 | **/ 18 | @Slf4j 19 | @Component 20 | @ChannelHandler.Sharable 21 | public class HeartBeatServerHandler extends ChannelInboundHandlerAdapter { 22 | @Override 23 | public void channelRegistered(ChannelHandlerContext ctx) throws Exception { 24 | super.channelRegistered(ctx); 25 | } 26 | 27 | @Override 28 | public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { 29 | super.channelUnregistered(ctx); 30 | } 31 | 32 | @Override 33 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 34 | super.channelActive(ctx); 35 | } 36 | 37 | @Override 38 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 39 | super.channelInactive(ctx); 40 | } 41 | 42 | @Override 43 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 44 | CustomMessageProto.CustomMessage message= (CustomMessageProto.CustomMessage) msg; 45 | if (message.getHeader().getType().getNumber()== CustomMessageProto.CustomMessage.CustomHeader.MessgeType.PING_VALUE){ 46 | log.info("接收心跳消息:{}",message); 47 | CustomMessageProto.CustomMessage.Builder heartBeatRespMsg=CustomMessageProto.CustomMessage.newBuilder() 48 | .setHeader( 49 | CustomMessageProto.CustomMessage.CustomHeader.newBuilder() 50 | .setTypeValue(0xABEF) 51 | .setType(CustomMessageProto.CustomMessage.CustomHeader.MessgeType.PONG) 52 | ); 53 | ctx.channel().writeAndFlush(heartBeatRespMsg); 54 | }else { 55 | log.warn("消息未处理:{}",message); 56 | } 57 | } 58 | 59 | @Override 60 | public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 61 | super.channelReadComplete(ctx); 62 | } 63 | 64 | @Override 65 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 66 | //超过40s没接收心跳消息则强制断开连接,从列表移除 67 | if (evt instanceof IdleStateEvent){ 68 | IdleStateEvent event= (IdleStateEvent) evt; 69 | if (event.state().equals(IdleState.READER_IDLE)){ 70 | //判断上一次心跳时间,如果超过40s则直接断开连接 71 | //否则尝试发送心跳 72 | log.info("已经5秒没有收到客户端心跳消息了,发送心跳检测给客户端"); 73 | CustomMessageProto.CustomMessage.Builder heartBeatRespMsg=CustomMessageProto.CustomMessage.newBuilder() 74 | .setHeader( 75 | CustomMessageProto.CustomMessage.CustomHeader.newBuilder() 76 | .setTypeValue(0xABEF) 77 | .setType(CustomMessageProto.CustomMessage.CustomHeader.MessgeType.PONG) 78 | ); 79 | ctx.channel().writeAndFlush(heartBeatRespMsg); 80 | } 81 | } 82 | super.userEventTriggered(ctx, evt); 83 | } 84 | 85 | @Override 86 | public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception { 87 | super.channelWritabilityChanged(ctx); 88 | } 89 | 90 | @Override 91 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 92 | log.error("发生异常,message:{}",cause.getMessage()); 93 | super.exceptionCaught(ctx, cause); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /netty-heartbeat/heartbeat-server/src/main/java/fun/codenow/netty/heartbeat/server/server/CustomServerChannelInitializer.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.heartbeat.server.server; 2 | 3 | import fun.codenow.netty.common.CustomMessageProto; 4 | import fun.codenow.netty.heartbeat.server.handler.HeartBeatServerHandler; 5 | import io.netty.channel.ChannelInitializer; 6 | import io.netty.channel.socket.SocketChannel; 7 | import io.netty.handler.codec.protobuf.ProtobufDecoder; 8 | import io.netty.handler.codec.protobuf.ProtobufEncoder; 9 | import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder; 10 | import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender; 11 | import io.netty.handler.timeout.IdleStateHandler; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.stereotype.Component; 14 | 15 | import java.util.concurrent.TimeUnit; 16 | 17 | /** 18 | * @Author Jack Wu 19 | * @Description 20 | * @Version V1.0 21 | * @Date2020/12/7 17:47 22 | **/ 23 | @Component 24 | public class CustomServerChannelInitializer extends ChannelInitializer { 25 | @Autowired 26 | HeartBeatServerHandler heartBeatServerHandler; 27 | @Override 28 | protected void initChannel(SocketChannel socketChannel) throws Exception { 29 | socketChannel.pipeline() 30 | .addLast(new IdleStateHandler(5,0,0, TimeUnit.SECONDS)) 31 | .addLast(new ProtobufVarint32FrameDecoder()) 32 | .addLast(new ProtobufDecoder(CustomMessageProto.CustomMessage.getDefaultInstance())) 33 | .addLast(new ProtobufVarint32LengthFieldPrepender()) 34 | .addLast(new ProtobufEncoder()) 35 | .addLast(heartBeatServerHandler); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /netty-heartbeat/heartbeat-server/src/main/java/fun/codenow/netty/heartbeat/server/server/Server.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.heartbeat.server.server; 2 | 3 | import io.netty.bootstrap.ServerBootstrap; 4 | import io.netty.channel.ChannelFuture; 5 | import io.netty.channel.nio.NioEventLoopGroup; 6 | import io.netty.channel.socket.nio.NioServerSocketChannel; 7 | import io.netty.handler.logging.LogLevel; 8 | import io.netty.handler.logging.LoggingHandler; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.stereotype.Component; 11 | 12 | import javax.annotation.PostConstruct; 13 | import javax.annotation.PreDestroy; 14 | import java.net.InetSocketAddress; 15 | 16 | /** 17 | * @Author Jack Wu 18 | * @Description 19 | * @Version V1.0 20 | * @Date2020/12/7 17:23 21 | **/ 22 | @Component 23 | public class Server { 24 | private NioEventLoopGroup bossGroup=new NioEventLoopGroup(); 25 | private NioEventLoopGroup workerGroup=new NioEventLoopGroup(); 26 | private ServerBootstrap serverBootstrap=new ServerBootstrap(); 27 | @Autowired 28 | CustomServerChannelInitializer customServerChannelInitializer; 29 | @PostConstruct 30 | public void run() throws InterruptedException { 31 | serverBootstrap 32 | .group(bossGroup,workerGroup) 33 | .channel(NioServerSocketChannel.class) 34 | .localAddress(new InetSocketAddress(8983)) 35 | .handler(new LoggingHandler(LogLevel.INFO)) 36 | .childHandler(customServerChannelInitializer); 37 | ChannelFuture channelFuture=serverBootstrap.bind().sync(); 38 | channelFuture.channel().closeFuture().sync(); 39 | //服务端监控客户端连接是否健康,定期进行健康检查 40 | } 41 | @PreDestroy 42 | public void destroy(){ 43 | bossGroup.shutdownGracefully(); 44 | workerGroup.shutdownGracefully(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /netty-heartbeat/heartbeat-server/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8883 -------------------------------------------------------------------------------- /netty-heartbeat/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | codenow-netty 7 | fun.codenow 8 | 1.0.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | netty-heartbeat 13 | 心跳检测、自定义编解码机制,断连重连、握手和安全认证 14 | pom 15 | 16 | heartbeat-server 17 | heartbeat-client 18 | 19 | 20 | -------------------------------------------------------------------------------- /netty-mqtt/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | codenow-netty 7 | fun.codenow 8 | 1.0.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | netty-mqtt 13 | 14 | 15 | 4.1.66.Final 16 | 28.2-jre 17 | 18 | 19 | 20 | 21 | 22 | io.netty 23 | netty-codec-mqtt 24 | ${netty.version} 25 | 26 | 27 | io.netty 28 | netty-handler 29 | ${netty.version} 30 | 31 | 32 | com.google.code.findbugs 33 | jsr305 34 | 3.0.1 35 | true 36 | 37 | 38 | com.google.guava 39 | guava 40 | ${guava.version} 41 | 42 | 43 | 44 | 45 | 46 | 47 | org.apache.maven.wagon 48 | wagon-ssh 49 | 2.6 50 | 51 | 52 | 53 | 54 | org.apache.maven.plugins 55 | maven-compiler-plugin 56 | 57 | 58 | org.apache.maven.plugins 59 | maven-jar-plugin 60 | 3.1.1 61 | 62 | 63 | 64 | true 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /netty-mqtt/src/main/java/fun/codenow/mqtt/ChannelClosedException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2016-2021 The Thingsboard Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package fun.codenow.mqtt; 17 | 18 | public class ChannelClosedException extends RuntimeException { 19 | 20 | private static final long serialVersionUID = 6266638352424706909L; 21 | 22 | public ChannelClosedException() { 23 | } 24 | 25 | public ChannelClosedException(String message) { 26 | super(message); 27 | } 28 | 29 | public ChannelClosedException(String message, Throwable cause) { 30 | super(message, cause); 31 | } 32 | 33 | public ChannelClosedException(Throwable cause) { 34 | super(cause); 35 | } 36 | 37 | public ChannelClosedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 38 | super(message, cause, enableSuppression, writableStackTrace); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /netty-mqtt/src/main/java/fun/codenow/mqtt/MqttClient.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2016-2021 The Thingsboard Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package fun.codenow.mqtt; 17 | 18 | import io.netty.buffer.ByteBuf; 19 | import io.netty.channel.Channel; 20 | import io.netty.channel.EventLoopGroup; 21 | import io.netty.channel.nio.NioEventLoopGroup; 22 | import io.netty.handler.codec.mqtt.MqttQoS; 23 | import io.netty.util.concurrent.Future; 24 | 25 | public interface MqttClient { 26 | 27 | /** 28 | * Connect to the specified hostname/ip. By default uses port 1883. 29 | * If you want to change the port number, see {@link #connect(String, int)} 30 | * 31 | * @param host The ip address or host to connect to 32 | * @return A future which will be completed when the connection is opened and we received an CONNACK 33 | */ 34 | Future connect(String host); 35 | 36 | /** 37 | * Connect to the specified hostname/ip using the specified port 38 | * 39 | * @param host The ip address or host to connect to 40 | * @param port The tcp port to connect to 41 | * @return A future which will be completed when the connection is opened and we received an CONNACK 42 | */ 43 | Future connect(String host, int port); 44 | 45 | /** 46 | * 47 | * @return boolean value indicating if channel is active 48 | */ 49 | boolean isConnected(); 50 | 51 | /** 52 | * Attempt reconnect to the host that was attempted with {@link #connect(String, int)} method before 53 | * 54 | * @return A future which will be completed when the connection is opened and we received an CONNACK 55 | * @throws IllegalStateException if no previous {@link #connect(String, int)} calls were attempted 56 | */ 57 | Future reconnect(); 58 | 59 | /** 60 | * Retrieve the netty {@link EventLoopGroup} we are using 61 | * @return The netty {@link EventLoopGroup} we use for the connection 62 | */ 63 | EventLoopGroup getEventLoop(); 64 | 65 | /** 66 | * By default we use the netty {@link NioEventLoopGroup}. 67 | * If you change the EventLoopGroup to another type, make sure to change the {@link Channel} class using {@link MqttClientConfig#setChannelClass(Class)} 68 | * If you want to force the MqttClient to use another {@link EventLoopGroup}, call this function before calling {@link #connect(String, int)} 69 | * 70 | * @param eventLoop The new eventloop to use 71 | */ 72 | void setEventLoop(EventLoopGroup eventLoop); 73 | 74 | /** 75 | * Subscribe on the given topic. When a message is received, MqttClient will invoke the {@link MqttHandler#onMessage(String, ByteBuf)} function of the given handler 76 | * 77 | * @param topic The topic filter to subscribe to 78 | * @param handler The handler to invoke when we receive a message 79 | * @return A future which will be completed when the server acknowledges our subscribe request 80 | */ 81 | Future on(String topic, MqttHandler handler); 82 | 83 | /** 84 | * Subscribe on the given topic, with the given qos. When a message is received, MqttClient will invoke the {@link MqttHandler#onMessage(String, ByteBuf)} function of the given handler 85 | * 86 | * @param topic The topic filter to subscribe to 87 | * @param handler The handler to invoke when we receive a message 88 | * @param qos The qos to request to the server 89 | * @return A future which will be completed when the server acknowledges our subscribe request 90 | */ 91 | Future on(String topic, MqttHandler handler, MqttQoS qos); 92 | 93 | /** 94 | * Subscribe on the given topic. When a message is received, MqttClient will invoke the {@link MqttHandler#onMessage(String, ByteBuf)} function of the given handler 95 | * This subscription is only once. If the MqttClient has received 1 message, the subscription will be removed 96 | * 97 | * @param topic The topic filter to subscribe to 98 | * @param handler The handler to invoke when we receive a message 99 | * @return A future which will be completed when the server acknowledges our subscribe request 100 | */ 101 | Future once(String topic, MqttHandler handler); 102 | 103 | /** 104 | * Subscribe on the given topic, with the given qos. When a message is received, MqttClient will invoke the {@link MqttHandler#onMessage(String, ByteBuf)} function of the given handler 105 | * This subscription is only once. If the MqttClient has received 1 message, the subscription will be removed 106 | * 107 | * @param topic The topic filter to subscribe to 108 | * @param handler The handler to invoke when we receive a message 109 | * @param qos The qos to request to the server 110 | * @return A future which will be completed when the server acknowledges our subscribe request 111 | */ 112 | Future once(String topic, MqttHandler handler, MqttQoS qos); 113 | 114 | /** 115 | * Remove the subscription for the given topic and handler 116 | * If you want to unsubscribe from all handlers known for this topic, use {@link #off(String)} 117 | * 118 | * @param topic The topic to unsubscribe for 119 | * @param handler The handler to unsubscribe 120 | * @return A future which will be completed when the server acknowledges our unsubscribe request 121 | */ 122 | Future off(String topic, MqttHandler handler); 123 | 124 | /** 125 | * Remove all subscriptions for the given topic. 126 | * If you want to specify which handler to unsubscribe, use {@link #off(String, MqttHandler)} 127 | * 128 | * @param topic The topic to unsubscribe for 129 | * @return A future which will be completed when the server acknowledges our unsubscribe request 130 | */ 131 | Future off(String topic); 132 | 133 | /** 134 | * Publish a message to the given payload 135 | * @param topic The topic to publish to 136 | * @param payload The payload to send 137 | * @return A future which will be completed when the message is sent out of the MqttClient 138 | */ 139 | Future publish(String topic, ByteBuf payload); 140 | 141 | /** 142 | * Publish a message to the given payload, using the given qos 143 | * @param topic The topic to publish to 144 | * @param payload The payload to send 145 | * @param qos The qos to use while publishing 146 | * @return A future which will be completed when the message is delivered to the server 147 | */ 148 | Future publish(String topic, ByteBuf payload, MqttQoS qos); 149 | 150 | /** 151 | * Publish a message to the given payload, using optional retain 152 | * @param topic The topic to publish to 153 | * @param payload The payload to send 154 | * @param retain true if you want to retain the message on the server, false otherwise 155 | * @return A future which will be completed when the message is sent out of the MqttClient 156 | */ 157 | Future publish(String topic, ByteBuf payload, boolean retain); 158 | 159 | /** 160 | * Publish a message to the given payload, using the given qos and optional retain 161 | * @param topic The topic to publish to 162 | * @param payload The payload to send 163 | * @param qos The qos to use while publishing 164 | * @param retain true if you want to retain the message on the server, false otherwise 165 | * @return A future which will be completed when the message is delivered to the server 166 | */ 167 | Future publish(String topic, ByteBuf payload, MqttQoS qos, boolean retain); 168 | 169 | /** 170 | * Retrieve the MqttClient configuration 171 | * @return The {@link MqttClientConfig} instance we use 172 | */ 173 | MqttClientConfig getClientConfig(); 174 | 175 | 176 | /** 177 | * Construct the MqttClientImpl with additional config. 178 | * This config can also be changed using the {@link #getClientConfig()} function 179 | * 180 | * @param config The config object to use while looking for settings 181 | * @param defaultHandler The handler for incoming messages that do not match any topic subscriptions 182 | */ 183 | static MqttClient create(MqttClientConfig config, MqttHandler defaultHandler){ 184 | return new MqttClientImpl(config, defaultHandler); 185 | } 186 | 187 | /** 188 | * Send disconnect and close channel 189 | * 190 | */ 191 | void disconnect(); 192 | 193 | /** 194 | * Sets the {@see #MqttClientCallback} object for this MqttClient 195 | * @param callback The callback to be set 196 | */ 197 | void setCallback(MqttClientCallback callback); 198 | 199 | } 200 | -------------------------------------------------------------------------------- /netty-mqtt/src/main/java/fun/codenow/mqtt/MqttClientCallback.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2016-2021 The Thingsboard Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package fun.codenow.mqtt; 17 | 18 | public interface MqttClientCallback { 19 | 20 | /** 21 | * This method is called when the connection to the server is lost. 22 | * 23 | * @param cause the reason behind the loss of connection. 24 | */ 25 | void connectionLost(Throwable cause); 26 | 27 | /** 28 | * This method is called when the connection to the server is recovered. 29 | * 30 | */ 31 | void onSuccessfulReconnect(); 32 | } 33 | -------------------------------------------------------------------------------- /netty-mqtt/src/main/java/fun/codenow/mqtt/MqttClientConfig.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2016-2021 The Thingsboard Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package fun.codenow.mqtt; 17 | 18 | import io.netty.channel.Channel; 19 | import io.netty.channel.socket.nio.NioSocketChannel; 20 | import io.netty.handler.codec.mqtt.MqttVersion; 21 | import io.netty.handler.ssl.SslContext; 22 | 23 | import javax.annotation.Nonnull; 24 | import javax.annotation.Nullable; 25 | import java.util.Random; 26 | 27 | public final class MqttClientConfig { 28 | 29 | private final SslContext sslContext; 30 | private final String randomClientId; 31 | 32 | private String clientId; 33 | private int timeoutSeconds = 60; 34 | private MqttVersion protocolVersion = MqttVersion.MQTT_3_1; 35 | @Nullable private String username = null; 36 | @Nullable private String password = null; 37 | private boolean cleanSession = true; 38 | @Nullable private MqttLastWill lastWill; 39 | private Class channelClass = NioSocketChannel.class; 40 | 41 | private boolean reconnect = true; 42 | private long reconnectDelay = 1L; 43 | private int maxBytesInMessage = 8092; 44 | 45 | public MqttClientConfig() { 46 | this(null); 47 | } 48 | 49 | public MqttClientConfig(SslContext sslContext) { 50 | this.sslContext = sslContext; 51 | Random random = new Random(); 52 | String id = "netty-mqtt/"; 53 | String[] options = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".split(""); 54 | for(int i = 0; i < 8; i++){ 55 | id += options[random.nextInt(options.length)]; 56 | } 57 | this.clientId = id; 58 | this.randomClientId = id; 59 | } 60 | 61 | @Nonnull 62 | public String getClientId() { 63 | return clientId; 64 | } 65 | 66 | public void setClientId(@Nullable String clientId) { 67 | if(clientId == null){ 68 | this.clientId = randomClientId; 69 | }else{ 70 | this.clientId = clientId; 71 | } 72 | } 73 | 74 | public int getTimeoutSeconds() { 75 | return timeoutSeconds; 76 | } 77 | 78 | public void setTimeoutSeconds(int timeoutSeconds) { 79 | if(timeoutSeconds != -1 && timeoutSeconds <= 0){ 80 | throw new IllegalArgumentException("timeoutSeconds must be > 0 or -1"); 81 | } 82 | this.timeoutSeconds = timeoutSeconds; 83 | } 84 | 85 | public MqttVersion getProtocolVersion() { 86 | return protocolVersion; 87 | } 88 | 89 | public void setProtocolVersion(MqttVersion protocolVersion) { 90 | if(protocolVersion == null){ 91 | throw new NullPointerException("protocolVersion"); 92 | } 93 | this.protocolVersion = protocolVersion; 94 | } 95 | 96 | @Nullable 97 | public String getUsername() { 98 | return username; 99 | } 100 | 101 | public void setUsername(@Nullable String username) { 102 | this.username = username; 103 | } 104 | 105 | @Nullable 106 | public String getPassword() { 107 | return password; 108 | } 109 | 110 | public void setPassword(@Nullable String password) { 111 | this.password = password; 112 | } 113 | 114 | public boolean isCleanSession() { 115 | return cleanSession; 116 | } 117 | 118 | public void setCleanSession(boolean cleanSession) { 119 | this.cleanSession = cleanSession; 120 | } 121 | 122 | @Nullable 123 | public MqttLastWill getLastWill() { 124 | return lastWill; 125 | } 126 | 127 | public void setLastWill(@Nullable MqttLastWill lastWill) { 128 | this.lastWill = lastWill; 129 | } 130 | 131 | public Class getChannelClass() { 132 | return channelClass; 133 | } 134 | 135 | public void setChannelClass(Class channelClass) { 136 | this.channelClass = channelClass; 137 | } 138 | 139 | public SslContext getSslContext() { 140 | return sslContext; 141 | } 142 | 143 | public boolean isReconnect() { 144 | return reconnect; 145 | } 146 | 147 | public void setReconnect(boolean reconnect) { 148 | this.reconnect = reconnect; 149 | } 150 | 151 | public long getReconnectDelay() { 152 | return reconnectDelay; 153 | } 154 | 155 | /** 156 | * Sets the reconnect delay in seconds. Defaults to 1 second. 157 | * @param reconnectDelay 158 | * @throws IllegalArgumentException if reconnectDelay is smaller than 1. 159 | */ 160 | public void setReconnectDelay(long reconnectDelay) { 161 | if (reconnectDelay <= 0) { 162 | throw new IllegalArgumentException("reconnectDelay must be > 0"); 163 | } 164 | this.reconnectDelay = reconnectDelay; 165 | } 166 | 167 | public int getMaxBytesInMessage() { 168 | return maxBytesInMessage; 169 | } 170 | 171 | /** 172 | * Sets the maximum number of bytes in the message for the {@link io.netty.handler.codec.mqtt.MqttDecoder}. 173 | * Default value is 8092 as specified by Netty. The absolute maximum size is 256MB as set by the MQTT spec. 174 | * 175 | * @param maxBytesInMessage 176 | * @throws IllegalArgumentException if maxBytesInMessage is smaller than 1 or greater than 256_000_000. 177 | */ 178 | public void setMaxBytesInMessage(int maxBytesInMessage) { 179 | if (maxBytesInMessage <= 0 || maxBytesInMessage > 256_000_000) { 180 | throw new IllegalArgumentException("maxBytesInMessage must be > 0 or < 256_000_000"); 181 | } 182 | this.maxBytesInMessage = maxBytesInMessage; 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /netty-mqtt/src/main/java/fun/codenow/mqtt/MqttConnectResult.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2016-2021 The Thingsboard Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package fun.codenow.mqtt; 17 | 18 | import io.netty.channel.ChannelFuture; 19 | import io.netty.handler.codec.mqtt.MqttConnectReturnCode; 20 | 21 | public final class MqttConnectResult { 22 | 23 | private final boolean success; 24 | private final MqttConnectReturnCode returnCode; 25 | private final ChannelFuture closeFuture; 26 | 27 | MqttConnectResult(boolean success, MqttConnectReturnCode returnCode, ChannelFuture closeFuture) { 28 | this.success = success; 29 | this.returnCode = returnCode; 30 | this.closeFuture = closeFuture; 31 | } 32 | 33 | public boolean isSuccess() { 34 | return success; 35 | } 36 | 37 | public MqttConnectReturnCode getReturnCode() { 38 | return returnCode; 39 | } 40 | 41 | public ChannelFuture getCloseFuture() { 42 | return closeFuture; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /netty-mqtt/src/main/java/fun/codenow/mqtt/MqttHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2016-2021 The Thingsboard Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package fun.codenow.mqtt; 17 | 18 | import io.netty.buffer.ByteBuf; 19 | public interface MqttHandler { 20 | 21 | void onMessage(String topic, ByteBuf payload); 22 | } 23 | -------------------------------------------------------------------------------- /netty-mqtt/src/main/java/fun/codenow/mqtt/MqttIncomingQos2Publish.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2016-2021 The Thingsboard Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package fun.codenow.mqtt; 17 | 18 | import io.netty.handler.codec.mqtt.MqttPublishMessage; 19 | 20 | final class MqttIncomingQos2Publish { 21 | 22 | private final MqttPublishMessage incomingPublish; 23 | 24 | MqttIncomingQos2Publish(MqttPublishMessage incomingPublish) { 25 | this.incomingPublish = incomingPublish; 26 | } 27 | 28 | MqttPublishMessage getIncomingPublish() { 29 | return incomingPublish; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /netty-mqtt/src/main/java/fun/codenow/mqtt/MqttLastWill.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2016-2021 The Thingsboard Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package fun.codenow.mqtt; 17 | 18 | import io.netty.handler.codec.mqtt.MqttQoS; 19 | 20 | @SuppressWarnings({"WeakerAccess", "unused", "SimplifiableIfStatement", "StringBufferReplaceableByString"}) 21 | public final class MqttLastWill { 22 | 23 | private final String topic; 24 | private final String message; 25 | private final boolean retain; 26 | private final MqttQoS qos; 27 | 28 | public MqttLastWill(String topic, String message, boolean retain, MqttQoS qos) { 29 | if(topic == null){ 30 | throw new NullPointerException("topic"); 31 | } 32 | if(message == null){ 33 | throw new NullPointerException("message"); 34 | } 35 | if(qos == null){ 36 | throw new NullPointerException("qos"); 37 | } 38 | this.topic = topic; 39 | this.message = message; 40 | this.retain = retain; 41 | this.qos = qos; 42 | } 43 | 44 | public String getTopic() { 45 | return topic; 46 | } 47 | 48 | public String getMessage() { 49 | return message; 50 | } 51 | 52 | public boolean isRetain() { 53 | return retain; 54 | } 55 | 56 | public MqttQoS getQos() { 57 | return qos; 58 | } 59 | 60 | public static Builder builder(){ 61 | return new Builder(); 62 | } 63 | 64 | public static final class Builder { 65 | 66 | private String topic; 67 | private String message; 68 | private boolean retain; 69 | private MqttQoS qos; 70 | 71 | public String getTopic() { 72 | return topic; 73 | } 74 | 75 | public Builder setTopic(String topic) { 76 | if(topic == null){ 77 | throw new NullPointerException("topic"); 78 | } 79 | this.topic = topic; 80 | return this; 81 | } 82 | 83 | public String getMessage() { 84 | return message; 85 | } 86 | 87 | public Builder setMessage(String message) { 88 | if(message == null){ 89 | throw new NullPointerException("message"); 90 | } 91 | this.message = message; 92 | return this; 93 | } 94 | 95 | public boolean isRetain() { 96 | return retain; 97 | } 98 | 99 | public Builder setRetain(boolean retain) { 100 | this.retain = retain; 101 | return this; 102 | } 103 | 104 | public MqttQoS getQos() { 105 | return qos; 106 | } 107 | 108 | public Builder setQos(MqttQoS qos) { 109 | if(qos == null){ 110 | throw new NullPointerException("qos"); 111 | } 112 | this.qos = qos; 113 | return this; 114 | } 115 | 116 | public MqttLastWill build(){ 117 | return new MqttLastWill(topic, message, retain, qos); 118 | } 119 | } 120 | 121 | @Override 122 | public boolean equals(Object o) { 123 | if (this == o) return true; 124 | if (o == null || getClass() != o.getClass()) return false; 125 | 126 | MqttLastWill that = (MqttLastWill) o; 127 | 128 | if (retain != that.retain) return false; 129 | if (!topic.equals(that.topic)) return false; 130 | if (!message.equals(that.message)) return false; 131 | return qos == that.qos; 132 | 133 | } 134 | 135 | @Override 136 | public int hashCode() { 137 | int result = topic.hashCode(); 138 | result = 31 * result + message.hashCode(); 139 | result = 31 * result + (retain ? 1 : 0); 140 | result = 31 * result + qos.hashCode(); 141 | return result; 142 | } 143 | 144 | @Override 145 | public String toString() { 146 | final StringBuilder sb = new StringBuilder("MqttLastWill{"); 147 | sb.append("topic='").append(topic).append('\''); 148 | sb.append(", message='").append(message).append('\''); 149 | sb.append(", retain=").append(retain); 150 | sb.append(", qos=").append(qos.name()); 151 | sb.append('}'); 152 | return sb.toString(); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /netty-mqtt/src/main/java/fun/codenow/mqtt/MqttPendingPublish.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2016-2021 The Thingsboard Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package fun.codenow.mqtt; 17 | 18 | import io.netty.buffer.ByteBuf; 19 | import io.netty.channel.EventLoop; 20 | import io.netty.handler.codec.mqtt.MqttMessage; 21 | import io.netty.handler.codec.mqtt.MqttPublishMessage; 22 | import io.netty.handler.codec.mqtt.MqttQoS; 23 | import io.netty.util.concurrent.Promise; 24 | 25 | import java.util.function.Consumer; 26 | 27 | final class MqttPendingPublish { 28 | 29 | private final int messageId; 30 | private final Promise future; 31 | private final ByteBuf payload; 32 | private final MqttPublishMessage message; 33 | private final MqttQoS qos; 34 | 35 | private final RetransmissionHandler publishRetransmissionHandler = new RetransmissionHandler<>(); 36 | private final RetransmissionHandler pubrelRetransmissionHandler = new RetransmissionHandler<>(); 37 | 38 | private boolean sent = false; 39 | 40 | MqttPendingPublish(int messageId, Promise future, ByteBuf payload, MqttPublishMessage message, MqttQoS qos) { 41 | this.messageId = messageId; 42 | this.future = future; 43 | this.payload = payload; 44 | this.message = message; 45 | this.qos = qos; 46 | 47 | this.publishRetransmissionHandler.setOriginalMessage(message); 48 | } 49 | 50 | int getMessageId() { 51 | return messageId; 52 | } 53 | 54 | Promise getFuture() { 55 | return future; 56 | } 57 | 58 | ByteBuf getPayload() { 59 | return payload; 60 | } 61 | 62 | boolean isSent() { 63 | return sent; 64 | } 65 | 66 | void setSent(boolean sent) { 67 | this.sent = sent; 68 | } 69 | 70 | MqttPublishMessage getMessage() { 71 | return message; 72 | } 73 | 74 | MqttQoS getQos() { 75 | return qos; 76 | } 77 | 78 | void startPublishRetransmissionTimer(EventLoop eventLoop, Consumer sendPacket) { 79 | this.publishRetransmissionHandler.setHandle(((fixedHeader, originalMessage) -> 80 | sendPacket.accept(new MqttPublishMessage(fixedHeader, originalMessage.variableHeader(), this.payload.retain())))); 81 | this.publishRetransmissionHandler.start(eventLoop); 82 | } 83 | 84 | void onPubackReceived() { 85 | this.publishRetransmissionHandler.stop(); 86 | } 87 | 88 | void setPubrelMessage(MqttMessage pubrelMessage) { 89 | this.pubrelRetransmissionHandler.setOriginalMessage(pubrelMessage); 90 | } 91 | 92 | void startPubrelRetransmissionTimer(EventLoop eventLoop, Consumer sendPacket) { 93 | this.pubrelRetransmissionHandler.setHandle((fixedHeader, originalMessage) -> 94 | sendPacket.accept(new MqttMessage(fixedHeader, originalMessage.variableHeader()))); 95 | this.pubrelRetransmissionHandler.start(eventLoop); 96 | } 97 | 98 | void onPubcompReceived() { 99 | this.pubrelRetransmissionHandler.stop(); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /netty-mqtt/src/main/java/fun/codenow/mqtt/MqttPendingSubscription.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2016-2021 The Thingsboard Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package fun.codenow.mqtt; 17 | 18 | import io.netty.channel.EventLoop; 19 | import io.netty.handler.codec.mqtt.MqttSubscribeMessage; 20 | import io.netty.util.concurrent.Promise; 21 | 22 | import java.util.HashSet; 23 | import java.util.Set; 24 | import java.util.function.Consumer; 25 | 26 | final class MqttPendingSubscription { 27 | 28 | private final Promise future; 29 | private final String topic; 30 | private final Set handlers = new HashSet<>(); 31 | private final MqttSubscribeMessage subscribeMessage; 32 | 33 | private final RetransmissionHandler retransmissionHandler = new RetransmissionHandler<>(); 34 | 35 | private boolean sent = false; 36 | 37 | MqttPendingSubscription(Promise future, String topic, MqttSubscribeMessage message) { 38 | this.future = future; 39 | this.topic = topic; 40 | this.subscribeMessage = message; 41 | 42 | this.retransmissionHandler.setOriginalMessage(message); 43 | } 44 | 45 | Promise getFuture() { 46 | return future; 47 | } 48 | 49 | String getTopic() { 50 | return topic; 51 | } 52 | 53 | boolean isSent() { 54 | return sent; 55 | } 56 | 57 | void setSent(boolean sent) { 58 | this.sent = sent; 59 | } 60 | 61 | MqttSubscribeMessage getSubscribeMessage() { 62 | return subscribeMessage; 63 | } 64 | 65 | void addHandler(MqttHandler handler, boolean once){ 66 | this.handlers.add(new MqttPendingHandler(handler, once)); 67 | } 68 | 69 | Set getHandlers() { 70 | return handlers; 71 | } 72 | 73 | void startRetransmitTimer(EventLoop eventLoop, Consumer sendPacket) { 74 | if(this.sent){ //If the packet is sent, we can start the retransmit timer 75 | this.retransmissionHandler.setHandle((fixedHeader, originalMessage) -> 76 | sendPacket.accept(new MqttSubscribeMessage(fixedHeader, originalMessage.variableHeader(), originalMessage.payload()))); 77 | this.retransmissionHandler.start(eventLoop); 78 | } 79 | } 80 | 81 | void onSubackReceived(){ 82 | this.retransmissionHandler.stop(); 83 | } 84 | 85 | final class MqttPendingHandler { 86 | private final MqttHandler handler; 87 | private final boolean once; 88 | 89 | MqttPendingHandler(MqttHandler handler, boolean once) { 90 | this.handler = handler; 91 | this.once = once; 92 | } 93 | 94 | MqttHandler getHandler() { 95 | return handler; 96 | } 97 | 98 | boolean isOnce() { 99 | return once; 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /netty-mqtt/src/main/java/fun/codenow/mqtt/MqttPendingUnsubscription.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2016-2021 The Thingsboard Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package fun.codenow.mqtt; 17 | 18 | import io.netty.channel.EventLoop; 19 | import io.netty.handler.codec.mqtt.MqttUnsubscribeMessage; 20 | import io.netty.util.concurrent.Promise; 21 | 22 | import java.util.function.Consumer; 23 | 24 | final class MqttPendingUnsubscription { 25 | 26 | private final Promise future; 27 | private final String topic; 28 | 29 | private final RetransmissionHandler retransmissionHandler = new RetransmissionHandler<>(); 30 | 31 | MqttPendingUnsubscription(Promise future, String topic, MqttUnsubscribeMessage unsubscribeMessage) { 32 | this.future = future; 33 | this.topic = topic; 34 | 35 | this.retransmissionHandler.setOriginalMessage(unsubscribeMessage); 36 | } 37 | 38 | Promise getFuture() { 39 | return future; 40 | } 41 | 42 | String getTopic() { 43 | return topic; 44 | } 45 | 46 | void startRetransmissionTimer(EventLoop eventLoop, Consumer sendPacket) { 47 | this.retransmissionHandler.setHandle((fixedHeader, originalMessage) -> 48 | sendPacket.accept(new MqttUnsubscribeMessage(fixedHeader, originalMessage.variableHeader(), originalMessage.payload()))); 49 | this.retransmissionHandler.start(eventLoop); 50 | } 51 | 52 | void onUnsubackReceived(){ 53 | this.retransmissionHandler.stop(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /netty-mqtt/src/main/java/fun/codenow/mqtt/MqttPingHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2016-2021 The Thingsboard Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package fun.codenow.mqtt; 17 | 18 | import io.netty.channel.Channel; 19 | import io.netty.channel.ChannelFutureListener; 20 | import io.netty.channel.ChannelHandlerContext; 21 | import io.netty.channel.ChannelInboundHandlerAdapter; 22 | import io.netty.handler.codec.mqtt.MqttFixedHeader; 23 | import io.netty.handler.codec.mqtt.MqttMessage; 24 | import io.netty.handler.codec.mqtt.MqttMessageType; 25 | import io.netty.handler.codec.mqtt.MqttQoS; 26 | import io.netty.handler.timeout.IdleStateEvent; 27 | import io.netty.util.ReferenceCountUtil; 28 | import io.netty.util.concurrent.ScheduledFuture; 29 | 30 | import java.util.concurrent.TimeUnit; 31 | 32 | final class MqttPingHandler extends ChannelInboundHandlerAdapter { 33 | 34 | private final int keepaliveSeconds; 35 | 36 | private ScheduledFuture pingRespTimeout; 37 | 38 | MqttPingHandler(int keepaliveSeconds) { 39 | this.keepaliveSeconds = keepaliveSeconds; 40 | } 41 | 42 | @Override 43 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 44 | if (!(msg instanceof MqttMessage)) { 45 | ctx.fireChannelRead(msg); 46 | return; 47 | } 48 | MqttMessage message = (MqttMessage) msg; 49 | if(message.fixedHeader().messageType() == MqttMessageType.PINGREQ){ 50 | this.handlePingReq(ctx.channel()); 51 | } else if(message.fixedHeader().messageType() == MqttMessageType.PINGRESP){ 52 | this.handlePingResp(); 53 | }else{ 54 | ctx.fireChannelRead(ReferenceCountUtil.retain(msg)); 55 | } 56 | } 57 | 58 | @Override 59 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 60 | super.userEventTriggered(ctx, evt); 61 | 62 | if(evt instanceof IdleStateEvent){ 63 | IdleStateEvent event = (IdleStateEvent) evt; 64 | switch(event.state()){ 65 | case READER_IDLE: 66 | break; 67 | case WRITER_IDLE: 68 | this.sendPingReq(ctx.channel()); 69 | break; 70 | } 71 | } 72 | } 73 | 74 | private void sendPingReq(Channel channel){ 75 | MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PINGREQ, false, MqttQoS.AT_MOST_ONCE, false, 0); 76 | channel.writeAndFlush(new MqttMessage(fixedHeader)); 77 | 78 | if(this.pingRespTimeout != null){ 79 | this.pingRespTimeout = channel.eventLoop().schedule(() -> { 80 | MqttFixedHeader fixedHeader2 = new MqttFixedHeader(MqttMessageType.DISCONNECT, false, MqttQoS.AT_MOST_ONCE, false, 0); 81 | channel.writeAndFlush(new MqttMessage(fixedHeader2)).addListener(ChannelFutureListener.CLOSE); 82 | //TODO: what do when the connection is closed ? 83 | }, this.keepaliveSeconds, TimeUnit.SECONDS); 84 | } 85 | } 86 | 87 | private void handlePingReq(Channel channel){ 88 | MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PINGRESP, false, MqttQoS.AT_MOST_ONCE, false, 0); 89 | channel.writeAndFlush(new MqttMessage(fixedHeader)); 90 | } 91 | 92 | private void handlePingResp(){ 93 | if(this.pingRespTimeout != null && !this.pingRespTimeout.isCancelled() && !this.pingRespTimeout.isDone()){ 94 | this.pingRespTimeout.cancel(true); 95 | this.pingRespTimeout = null; 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /netty-mqtt/src/main/java/fun/codenow/mqtt/MqttSubscription.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2016-2021 The Thingsboard Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package fun.codenow.mqtt; 17 | 18 | import java.util.regex.Pattern; 19 | 20 | final class MqttSubscription { 21 | 22 | private final String topic; 23 | private final Pattern topicRegex; 24 | private final MqttHandler handler; 25 | 26 | private final boolean once; 27 | 28 | private boolean called; 29 | 30 | MqttSubscription(String topic, MqttHandler handler, boolean once) { 31 | if(topic == null){ 32 | throw new NullPointerException("topic"); 33 | } 34 | if(handler == null){ 35 | throw new NullPointerException("handler"); 36 | } 37 | this.topic = topic; 38 | this.handler = handler; 39 | this.once = once; 40 | this.topicRegex = Pattern.compile(topic.replace("+", "[^/]+").replace("#", ".+") + "$"); 41 | } 42 | 43 | String getTopic() { 44 | return topic; 45 | } 46 | 47 | public MqttHandler getHandler() { 48 | return handler; 49 | } 50 | 51 | boolean isOnce() { 52 | return once; 53 | } 54 | 55 | boolean isCalled() { 56 | return called; 57 | } 58 | 59 | boolean matches(String topic){ 60 | return this.topicRegex.matcher(topic).matches(); 61 | } 62 | 63 | @Override 64 | public boolean equals(Object o) { 65 | if (this == o) return true; 66 | if (o == null || getClass() != o.getClass()) return false; 67 | 68 | MqttSubscription that = (MqttSubscription) o; 69 | 70 | return once == that.once && topic.equals(that.topic) && handler.equals(that.handler); 71 | } 72 | 73 | @Override 74 | public int hashCode() { 75 | int result = topic.hashCode(); 76 | result = 31 * result + handler.hashCode(); 77 | result = 31 * result + (once ? 1 : 0); 78 | return result; 79 | } 80 | 81 | void setCalled(boolean called) { 82 | this.called = called; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /netty-mqtt/src/main/java/fun/codenow/mqtt/RetransmissionHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2016-2021 The Thingsboard Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package fun.codenow.mqtt; 17 | 18 | import io.netty.channel.EventLoop; 19 | import io.netty.handler.codec.mqtt.MqttFixedHeader; 20 | import io.netty.handler.codec.mqtt.MqttMessage; 21 | import io.netty.handler.codec.mqtt.MqttMessageType; 22 | import io.netty.handler.codec.mqtt.MqttQoS; 23 | import io.netty.util.concurrent.ScheduledFuture; 24 | 25 | import java.util.concurrent.TimeUnit; 26 | import java.util.function.BiConsumer; 27 | 28 | final class RetransmissionHandler { 29 | 30 | private ScheduledFuture timer; 31 | private int timeout = 10; 32 | private BiConsumer handler; 33 | private T originalMessage; 34 | 35 | void start(EventLoop eventLoop){ 36 | if(eventLoop == null){ 37 | throw new NullPointerException("eventLoop"); 38 | } 39 | if(this.handler == null){ 40 | throw new NullPointerException("handler"); 41 | } 42 | this.timeout = 10; 43 | this.startTimer(eventLoop); 44 | } 45 | 46 | private void startTimer(EventLoop eventLoop){ 47 | this.timer = eventLoop.schedule(() -> { 48 | this.timeout += 5; 49 | boolean isDup = this.originalMessage.fixedHeader().isDup(); 50 | if(this.originalMessage.fixedHeader().messageType() == MqttMessageType.PUBLISH && this.originalMessage.fixedHeader().qosLevel() != MqttQoS.AT_MOST_ONCE){ 51 | isDup = true; 52 | } 53 | MqttFixedHeader fixedHeader = new MqttFixedHeader(this.originalMessage.fixedHeader().messageType(), isDup, this.originalMessage.fixedHeader().qosLevel(), this.originalMessage.fixedHeader().isRetain(), this.originalMessage.fixedHeader().remainingLength()); 54 | handler.accept(fixedHeader, originalMessage); 55 | startTimer(eventLoop); 56 | }, timeout, TimeUnit.SECONDS); 57 | } 58 | 59 | void stop(){ 60 | if(this.timer != null){ 61 | this.timer.cancel(true); 62 | } 63 | } 64 | 65 | void setHandle(BiConsumer runnable) { 66 | this.handler = runnable; 67 | } 68 | 69 | void setOriginalMessage(T originalMessage) { 70 | this.originalMessage = originalMessage; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /netty-private-protocol/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | codenow-netty 7 | fun.codenow 8 | 1.0.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | Netty私有协议开发(长连接、含protobuf编解码、心跳检测、自动重连、基于Ip地址或号段的黑白名单安全认证机制) 13 | 14 | netty-private-protocol 15 | 16 | 17 | 18 | org.springframework.boot 19 | spring-boot-starter-web 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-tomcat 24 | 25 | 26 | 27 | 28 | 29 | io.netty 30 | netty-all 31 | 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-jetty 36 | 37 | 38 | 39 | org.projectlombok 40 | lombok 41 | 42 | 43 | 44 | com.google.code.gson 45 | gson 46 | 2.8.5 47 | 48 | 49 | 50 | com.google.protobuf 51 | protobuf-java 52 | 53 | 54 | 55 | org.jboss.marshalling 56 | jboss-marshalling 57 | 1.4.10.Final 58 | 59 | 60 | org.jboss.marshalling 61 | jboss-marshalling-serial 62 | 1.4.10.Final 63 | 64 | 65 | 66 | 67 | 68 | 69 | org.springframework.boot 70 | spring-boot-maven-plugin 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /netty-private-protocol/src/main/java/fun/codenow/netty/privateprotocol/NettyPrivateProtocolApplication.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.privateprotocol; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | /** 7 | * @Author Jack Wu 8 | * @Description 9 | * @Version V1.0 10 | * @Date2020/12/3 9:34 11 | **/ 12 | @SpringBootApplication 13 | public class NettyPrivateProtocolApplication { 14 | public static void main(String[] args) { 15 | SpringApplication.run(NettyPrivateProtocolApplication.class); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /netty-private-protocol/src/main/java/fun/codenow/netty/privateprotocol/client/ClientChannelInitializer.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.privateprotocol.client; 2 | 3 | import fun.codenow.netty.privateprotocol.protobuf.CustomMessageProto; 4 | import io.netty.channel.ChannelInitializer; 5 | import io.netty.channel.socket.SocketChannel; 6 | import io.netty.handler.codec.protobuf.ProtobufDecoder; 7 | import io.netty.handler.codec.protobuf.ProtobufEncoder; 8 | import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder; 9 | import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender; 10 | import io.netty.handler.timeout.IdleStateHandler; 11 | 12 | import java.util.concurrent.TimeUnit; 13 | 14 | /** 15 | * @Author Jack Wu 16 | * @Description 17 | * @Version V1.0 18 | * @Date2020/12/3 17:15 19 | **/ 20 | public class ClientChannelInitializer extends ChannelInitializer { 21 | @Override 22 | protected void initChannel(SocketChannel socketChannel) throws Exception { 23 | socketChannel.pipeline() 24 | .addLast(new IdleStateHandler(0,4,0, TimeUnit.SECONDS)) 25 | .addLast(new ProtobufVarint32FrameDecoder()) 26 | .addLast(new ProtobufDecoder(CustomMessageProto.CustomMessage.getDefaultInstance())) 27 | .addLast(new ProtobufVarint32LengthFieldPrepender()) 28 | .addLast(new ProtobufEncoder()) 29 | .addLast(new HeartBeatRequestHandler()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /netty-private-protocol/src/main/java/fun/codenow/netty/privateprotocol/client/HeartBeatRequestHandler.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.privateprotocol.client; 2 | 3 | import fun.codenow.netty.privateprotocol.protobuf.CustomMessageProto; 4 | import fun.codenow.netty.privateprotocol.struct.MessageType; 5 | import io.netty.buffer.ByteBuf; 6 | import io.netty.buffer.Unpooled; 7 | import io.netty.channel.ChannelHandlerContext; 8 | import io.netty.channel.ChannelInboundHandlerAdapter; 9 | import io.netty.handler.timeout.IdleState; 10 | import io.netty.handler.timeout.IdleStateEvent; 11 | import lombok.extern.slf4j.Slf4j; 12 | 13 | /** 14 | * @Author Jack Wu 15 | * @Description 16 | * @Version V1.0 17 | * @Date2020/12/3 17:16 18 | **/ 19 | @Slf4j 20 | public class HeartBeatRequestHandler extends ChannelInboundHandlerAdapter { 21 | 22 | @Override 23 | public void channelRegistered(ChannelHandlerContext ctx) throws Exception { 24 | super.channelRegistered(ctx); 25 | } 26 | 27 | @Override 28 | public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { 29 | super.channelUnregistered(ctx); 30 | } 31 | 32 | @Override 33 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 34 | log.info("客户端 active"); 35 | byte[] requestByte="client ready".getBytes(); 36 | ByteBuf requestByteBuf= Unpooled.buffer(requestByte.length); 37 | requestByteBuf.writeBytes(requestByte); 38 | ctx.writeAndFlush(requestByteBuf); 39 | 40 | } 41 | 42 | @Override 43 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 44 | super.channelInactive(ctx); 45 | } 46 | 47 | @Override 48 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 49 | CustomMessageProto.CustomMessage message= (CustomMessageProto.CustomMessage) msg; 50 | if (message.getHeader().getType().getNumber()== MessageType.PONG.value()){ 51 | log.info("接收心跳消息:{}",message); 52 | }else { 53 | log.warn("消息未处理:{}",message); 54 | } 55 | } 56 | 57 | @Override 58 | public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 59 | super.channelReadComplete(ctx); 60 | } 61 | 62 | @Override 63 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 64 | log.warn("触发userEventTriggered 事件"); 65 | 66 | if (evt instanceof IdleStateEvent){ 67 | IdleStateEvent event= (IdleStateEvent) evt; 68 | if (event.state().equals(IdleState.WRITER_IDLE)) { 69 | CustomMessageProto.CustomMessage.Builder heartBeatMsg= 70 | CustomMessageProto.CustomMessage.newBuilder().setHeader( 71 | CustomMessageProto.CustomMessage.CustomHeader.newBuilder() 72 | .setTypeValue(0xABEF) 73 | .setType(CustomMessageProto.CustomMessage.CustomHeader.MessgeType.PING) 74 | ); 75 | ctx.channel().writeAndFlush(heartBeatMsg); 76 | } 77 | } 78 | super.userEventTriggered(ctx, evt); 79 | } 80 | 81 | @Override 82 | public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception { 83 | super.channelWritabilityChanged(ctx); 84 | } 85 | 86 | @Override 87 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 88 | super.exceptionCaught(ctx, cause); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /netty-private-protocol/src/main/java/fun/codenow/netty/privateprotocol/client/privateProtocoClient.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.privateprotocol.client; 2 | 3 | import io.netty.bootstrap.Bootstrap; 4 | import io.netty.channel.ChannelFuture; 5 | import io.netty.channel.EventLoopGroup; 6 | import io.netty.channel.nio.NioEventLoopGroup; 7 | import io.netty.channel.socket.nio.NioSocketChannel; 8 | 9 | import java.net.InetSocketAddress; 10 | 11 | /** 12 | * @Author Jack Wu 13 | * @Description 14 | * @Version V1.0 15 | * @Date2020/12/3 17:08 16 | **/ 17 | public class privateProtocoClient { 18 | public static void main(String[] args) throws InterruptedException { 19 | EventLoopGroup eventLoopGroup=new NioEventLoopGroup(); 20 | Bootstrap bootstrap=new Bootstrap(); 21 | bootstrap 22 | .group(eventLoopGroup) 23 | .channel(NioSocketChannel.class) 24 | .remoteAddress(new InetSocketAddress(8888)) 25 | .handler(new ClientChannelInitializer()); 26 | try { 27 | ChannelFuture channelFuture= bootstrap.connect().sync(); 28 | channelFuture.channel().closeFuture().sync(); 29 | } finally { 30 | eventLoopGroup.shutdownGracefully().sync(); 31 | //资源释放后,尝试重连 32 | } 33 | 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /netty-private-protocol/src/main/java/fun/codenow/netty/privateprotocol/codec/CustomDecode.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.privateprotocol.codec; 2 | 3 | /** 4 | * @Author Jack Wu 5 | * @Description 6 | * @Version V1.0 7 | * @Date2020/12/3 17:47 8 | **/ 9 | public class CustomDecode { 10 | } 11 | -------------------------------------------------------------------------------- /netty-private-protocol/src/main/java/fun/codenow/netty/privateprotocol/codec/CustomEncode.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.privateprotocol.codec; 2 | 3 | /** 4 | * @Author Jack Wu 5 | * @Description 6 | * @Version V1.0 7 | * @Date2020/12/3 17:46 8 | **/ 9 | public class CustomEncode { 10 | } 11 | -------------------------------------------------------------------------------- /netty-private-protocol/src/main/java/fun/codenow/netty/privateprotocol/server/HeartBeatResponseHandler.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.privateprotocol.server; 2 | 3 | import fun.codenow.netty.privateprotocol.protobuf.CustomMessageProto; 4 | import fun.codenow.netty.privateprotocol.struct.MessageType; 5 | import io.netty.buffer.ByteBuf; 6 | import io.netty.buffer.Unpooled; 7 | import io.netty.channel.ChannelHandlerContext; 8 | import io.netty.channel.ChannelInboundHandlerAdapter; 9 | import io.netty.handler.timeout.IdleState; 10 | import io.netty.handler.timeout.IdleStateEvent; 11 | import lombok.extern.slf4j.Slf4j; 12 | 13 | /** 14 | * @Author Jack Wu 15 | * @Description 16 | * @Version V1.0 17 | * @Date2020/12/3 17:13 18 | **/ 19 | @Slf4j 20 | public class HeartBeatResponseHandler extends ChannelInboundHandlerAdapter { 21 | 22 | @Override 23 | public void channelRegistered(ChannelHandlerContext ctx) throws Exception { 24 | super.channelRegistered(ctx); 25 | } 26 | 27 | @Override 28 | public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { 29 | super.channelUnregistered(ctx); 30 | } 31 | 32 | @Override 33 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 34 | super.channelActive(ctx); 35 | } 36 | 37 | @Override 38 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 39 | super.channelInactive(ctx); 40 | } 41 | 42 | @Override 43 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 44 | CustomMessageProto.CustomMessage message= (CustomMessageProto.CustomMessage) msg; 45 | if (message.getHeader().getType().getNumber()== MessageType.PING.value()){ 46 | log.info("接收心跳消息:{}",message); 47 | CustomMessageProto.CustomMessage.Builder heartBeatResp= 48 | CustomMessageProto.CustomMessage.newBuilder() 49 | .setHeader( 50 | CustomMessageProto.CustomMessage.CustomHeader.newBuilder() 51 | .setTypeValue(0xABEF) 52 | .setType(CustomMessageProto.CustomMessage.CustomHeader.MessgeType.PONG) 53 | ); 54 | ctx.channel().writeAndFlush(heartBeatResp); 55 | } 56 | } 57 | 58 | @Override 59 | public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 60 | super.channelReadComplete(ctx); 61 | } 62 | 63 | @Override 64 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 65 | log.warn("触发userEventTriggered 事件"); 66 | if (evt instanceof IdleStateEvent) { 67 | log.warn("已经10秒未收到客户端消息"); 68 | //发送心跳消息 69 | //计数/计时,超过三次则断开连接 70 | IdleStateEvent event= (IdleStateEvent) evt; 71 | if (event.state().equals(IdleState.READER_IDLE)){ 72 | CustomMessageProto.CustomMessage.Builder heartBeatResp= 73 | CustomMessageProto.CustomMessage.newBuilder() 74 | .setHeader( 75 | CustomMessageProto.CustomMessage.CustomHeader.newBuilder() 76 | .setTypeValue(0xABEF) 77 | .setType(CustomMessageProto.CustomMessage.CustomHeader.MessgeType.PONG) 78 | ); 79 | ctx.channel().writeAndFlush(heartBeatResp); 80 | } 81 | } 82 | super.userEventTriggered(ctx, evt); 83 | } 84 | 85 | @Override 86 | public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception { 87 | super.channelWritabilityChanged(ctx); 88 | } 89 | 90 | @Override 91 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 92 | super.exceptionCaught(ctx, cause); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /netty-private-protocol/src/main/java/fun/codenow/netty/privateprotocol/server/PrivateProtocolServer.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.privateprotocol.server; 2 | 3 | import io.netty.bootstrap.ServerBootstrap; 4 | import io.netty.channel.ChannelFuture; 5 | import io.netty.channel.EventLoopGroup; 6 | import io.netty.channel.nio.NioEventLoopGroup; 7 | import io.netty.channel.socket.nio.NioServerSocketChannel; 8 | import io.netty.handler.logging.LogLevel; 9 | import io.netty.handler.logging.LoggingHandler; 10 | 11 | import java.net.InetSocketAddress; 12 | 13 | /** 14 | * @Author Jack Wu 15 | * @Description 16 | * @Version V1.0 17 | * @Date2020/12/3 17:08 18 | **/ 19 | public class PrivateProtocolServer { 20 | public static void main(String[] args) throws InterruptedException { 21 | EventLoopGroup bossGroup=new NioEventLoopGroup(); 22 | EventLoopGroup workerGroup=new NioEventLoopGroup(); 23 | ServerBootstrap serverBootstrap=new ServerBootstrap(); 24 | serverBootstrap 25 | .group(bossGroup,workerGroup) 26 | .channel(NioServerSocketChannel.class) 27 | .localAddress(new InetSocketAddress(8888)) 28 | .handler(new LoggingHandler(LogLevel.INFO)) 29 | .childHandler(new ServerChannelInitializer()); 30 | //.option() 31 | try { 32 | ChannelFuture channelFuture= serverBootstrap.bind().sync(); 33 | channelFuture.channel().closeFuture().sync(); 34 | } finally { 35 | bossGroup.shutdownGracefully().sync(); 36 | workerGroup.shutdownGracefully().sync(); 37 | } 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /netty-private-protocol/src/main/java/fun/codenow/netty/privateprotocol/server/ServerChannelInitializer.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.privateprotocol.server; 2 | 3 | import fun.codenow.netty.privateprotocol.protobuf.CustomMessageProto; 4 | import io.netty.channel.ChannelInitializer; 5 | import io.netty.channel.socket.SocketChannel; 6 | import io.netty.handler.codec.protobuf.ProtobufDecoder; 7 | import io.netty.handler.codec.protobuf.ProtobufEncoder; 8 | import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder; 9 | import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender; 10 | import io.netty.handler.timeout.IdleStateHandler; 11 | 12 | import java.util.concurrent.TimeUnit; 13 | 14 | /** 15 | * @Author Jack Wu 16 | * @Description 17 | * @Version V1.0 18 | * @Date2020/12/3 17:12 19 | **/ 20 | public class ServerChannelInitializer extends ChannelInitializer { 21 | @Override 22 | protected void initChannel(SocketChannel socketChannel) throws Exception { 23 | socketChannel.pipeline() 24 | .addLast(new IdleStateHandler(10,0,0, TimeUnit.SECONDS)) 25 | .addLast(new ProtobufVarint32FrameDecoder()) 26 | .addLast(new ProtobufDecoder(CustomMessageProto.CustomMessage.getDefaultInstance())) 27 | .addLast(new ProtobufVarint32LengthFieldPrepender()) 28 | .addLast(new ProtobufEncoder()) 29 | .addLast(new HeartBeatResponseHandler()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /netty-private-protocol/src/main/java/fun/codenow/netty/privateprotocol/struct/MessageType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2018 Lilinfeng. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package fun.codenow.netty.privateprotocol.struct; 17 | 18 | /** 19 | * @author Lilinfeng 20 | * @date 2014年3月15日 21 | * @version 1.0 22 | */ 23 | public enum MessageType { 24 | 25 | SERVICE_REQ((byte) 0), 26 | SERVICE_RESP((byte) 1), 27 | ONE_WAY((byte) 2), 28 | LOGIN_REQ((byte) 3), 29 | LOGIN_RESP((byte) 4), 30 | PING((byte) 5), 31 | PONG( (byte) 6); 32 | 33 | private byte value; 34 | 35 | private MessageType(byte value) { 36 | this.value = value; 37 | } 38 | 39 | public byte value() { 40 | return this.value; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /netty-private-protocol/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8882 -------------------------------------------------------------------------------- /netty-websocket/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | codenow-netty 7 | fun.codenow 8 | 1.0.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | netty-websocket 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-web 17 | 18 | 19 | org.springframework.boot 20 | spring-boot-starter-tomcat 21 | 22 | 23 | 24 | 25 | 26 | io.netty 27 | netty-all 28 | 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-jetty 33 | 34 | 35 | 36 | org.projectlombok 37 | lombok 38 | 39 | 40 | 41 | com.google.code.gson 42 | gson 43 | 2.8.5 44 | 45 | 46 | 47 | com.google.protobuf 48 | protobuf-java 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /netty-websocket/src/main/java/fun/codenow/netty/websocket/server/ServerChannelInitializer.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.websocket.server; 2 | 3 | import io.netty.channel.ChannelInitializer; 4 | import io.netty.channel.socket.SocketChannel; 5 | import io.netty.handler.codec.http.HttpObjectAggregator; 6 | import io.netty.handler.codec.http.HttpServerCodec; 7 | import io.netty.handler.stream.ChunkedWriteHandler; 8 | 9 | /** 10 | * @Author Jack Wu 11 | * @Description 12 | * @Version V1.0 13 | * @Date2020/12/3 17:12 14 | **/ 15 | public class ServerChannelInitializer extends ChannelInitializer { 16 | @Override 17 | protected void initChannel(SocketChannel socketChannel) throws Exception { 18 | socketChannel.pipeline() 19 | .addLast("http-codec",new HttpServerCodec()) 20 | .addLast("aggregator",new HttpObjectAggregator(65536)) 21 | .addLast("http-chunked",new ChunkedWriteHandler()) 22 | .addLast(new WebSocketServerHandler()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /netty-websocket/src/main/java/fun/codenow/netty/websocket/server/ServerHandler.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.websocket.server; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.Unpooled; 5 | import io.netty.channel.ChannelFuture; 6 | import io.netty.channel.ChannelFutureListener; 7 | import io.netty.channel.ChannelHandlerContext; 8 | import io.netty.channel.ChannelInboundHandlerAdapter; 9 | import io.netty.handler.codec.http.*; 10 | import io.netty.handler.codec.http.websocketx.*; 11 | import io.netty.util.CharsetUtil; 12 | 13 | import java.util.Date; 14 | 15 | /** 16 | * @Author Jack Wu 17 | * @Description 18 | * @Version V1.0 19 | * @Date2020/12/8 10:21 20 | **/ 21 | public class ServerHandler extends ChannelInboundHandlerAdapter { 22 | 23 | private WebSocketServerHandshaker handshaker; 24 | 25 | @Override 26 | public void channelRegistered(ChannelHandlerContext ctx) throws Exception { 27 | super.channelRegistered(ctx); 28 | } 29 | 30 | @Override 31 | public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { 32 | super.channelUnregistered(ctx); 33 | } 34 | 35 | @Override 36 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 37 | super.channelActive(ctx); 38 | } 39 | 40 | @Override 41 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 42 | ctx.channel().id(); 43 | super.channelInactive(ctx); 44 | } 45 | 46 | @Override 47 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 48 | //传统的Http接入 49 | if (msg instanceof FullHttpRequest){ 50 | handleHttpRequest(ctx,(FullHttpRequest)msg); 51 | }else if (msg instanceof WebSocketFrame){ 52 | handleWebSocketFrame(ctx,(WebSocketFrame)msg); 53 | } 54 | } 55 | 56 | @Override 57 | public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 58 | super.channelReadComplete(ctx); 59 | } 60 | 61 | @Override 62 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 63 | super.userEventTriggered(ctx, evt); 64 | } 65 | 66 | @Override 67 | public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception { 68 | super.channelWritabilityChanged(ctx); 69 | } 70 | 71 | @Override 72 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 73 | super.exceptionCaught(ctx, cause); 74 | } 75 | private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame msg) { 76 | // 是否关闭链路的指令 77 | if (msg instanceof CloseWebSocketFrame){ 78 | handshaker.close(ctx.channel(), (CloseWebSocketFrame) msg.retain()); 79 | return; 80 | } 81 | 82 | if (msg instanceof PingWebSocketFrame){ 83 | ctx.channel().write(new PongWebSocketFrame(msg.content().retain())); 84 | return; 85 | } 86 | 87 | //本例仅支持文本传递,不支持二进制消息 88 | if (! (msg instanceof TextWebSocketFrame)){ 89 | throw new UnsupportedOperationException(String.format("%s frame type not supported",msg.getClass().getName())); 90 | } 91 | 92 | String request = ((TextWebSocketFrame)msg).text(); 93 | ctx.channel().write(new TextWebSocketFrame(request+",欢迎使用Netty WebSocket 服务,现在时刻:" + new Date().toString())); 94 | } 95 | 96 | private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request) { 97 | // 如果Http解码失败,返回HTTP异常 98 | if (!request.decoderResult().isSuccess() || (! "websocket".equalsIgnoreCase(request.headers().get("Upgrade")))){ 99 | sendHttpResponse(ctx,request,new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST)); 100 | return; 101 | } 102 | 103 | // 构造握手响应返回,本机测试 104 | WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory("ws://localhost:8080/websocket",null,false); 105 | handshaker = wsFactory.newHandshaker(request); 106 | if (handshaker == null){ 107 | WebSocketServerHandshakerFactory.sendUnsupportedWebSocketVersionResponse(ctx.channel()); 108 | 109 | }else { 110 | handshaker.handshake(ctx.channel(),request); 111 | } 112 | 113 | 114 | } 115 | 116 | private void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest request, DefaultFullHttpResponse response) { 117 | // 返回应答给客户端 118 | if (response.status().code() != 200){ 119 | ByteBuf buf = Unpooled.copiedBuffer(response.status().toString(), CharsetUtil.UTF_8); 120 | response.content().writeBytes(buf); 121 | buf.release(); 122 | response.headers().set("Content-Length",response.content().readableBytes()); 123 | } 124 | 125 | ChannelFuture f = ctx.channel().writeAndFlush(response); 126 | if (!isKeepLive(request) || response.status().code() != 200){ 127 | f.addListener(ChannelFutureListener.CLOSE); 128 | } 129 | 130 | } 131 | 132 | private boolean isKeepLive(FullHttpRequest request) { 133 | return HttpUtil.isKeepAlive(request); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /netty-websocket/src/main/java/fun/codenow/netty/websocket/server/WebSocketServer.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.websocket.server; 2 | 3 | import io.netty.bootstrap.ServerBootstrap; 4 | import io.netty.channel.ChannelFuture; 5 | import io.netty.channel.EventLoopGroup; 6 | import io.netty.channel.nio.NioEventLoopGroup; 7 | import io.netty.channel.socket.nio.NioServerSocketChannel; 8 | import io.netty.handler.logging.LogLevel; 9 | import io.netty.handler.logging.LoggingHandler; 10 | 11 | import java.net.InetSocketAddress; 12 | 13 | /** 14 | * @Author Jack Wu 15 | * @Description 16 | * @Version V1.0 17 | * @Date2020/12/8 10:10 18 | **/ 19 | public class WebSocketServer { 20 | public static void main(String[] args) throws InterruptedException { 21 | EventLoopGroup bossGroup=new NioEventLoopGroup(); 22 | EventLoopGroup workerGroup=new NioEventLoopGroup(); 23 | ServerBootstrap serverBootstrap=new ServerBootstrap(); 24 | serverBootstrap 25 | .group(bossGroup,workerGroup) 26 | .channel(NioServerSocketChannel.class) 27 | .localAddress(new InetSocketAddress(8888)) 28 | .handler(new LoggingHandler(LogLevel.INFO)) 29 | .childHandler(new ServerChannelInitializer()); 30 | //.option() 31 | try { 32 | ChannelFuture channelFuture= serverBootstrap.bind().sync(); 33 | channelFuture.channel().closeFuture().sync(); 34 | } finally { 35 | bossGroup.shutdownGracefully().sync(); 36 | workerGroup.shutdownGracefully().sync(); 37 | } 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /netty-websocket/src/main/java/fun/codenow/netty/websocket/server/WebSocketServerHandler.java: -------------------------------------------------------------------------------- 1 | package fun.codenow.netty.websocket.server; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.Unpooled; 5 | import io.netty.channel.ChannelFuture; 6 | import io.netty.channel.ChannelFutureListener; 7 | import io.netty.channel.ChannelHandlerContext; 8 | import io.netty.channel.SimpleChannelInboundHandler; 9 | import io.netty.handler.codec.http.*; 10 | import io.netty.handler.codec.http.websocketx.*; 11 | import io.netty.util.CharsetUtil; 12 | 13 | import java.util.Date; 14 | 15 | /** 16 | * Created by 2YSP on 2019/5/5. 17 | */ 18 | public class WebSocketServerHandler extends SimpleChannelInboundHandler { 19 | 20 | private WebSocketServerHandshaker handshaker; 21 | 22 | 23 | @Override 24 | protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { 25 | //传统的Http接入 26 | if (msg instanceof FullHttpRequest){ 27 | handleHttpRequest(ctx,(FullHttpRequest)msg); 28 | }else if (msg instanceof WebSocketFrame){ 29 | handleWebSocketFrame(ctx,(WebSocketFrame)msg); 30 | } 31 | 32 | 33 | 34 | } 35 | 36 | @Override 37 | public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 38 | ctx.flush(); 39 | } 40 | 41 | @Override 42 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 43 | cause.printStackTrace(); 44 | ctx.close(); 45 | } 46 | 47 | private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame msg) { 48 | // 是否关闭链路的指令 49 | if (msg instanceof CloseWebSocketFrame){ 50 | handshaker.close(ctx.channel(), (CloseWebSocketFrame) msg.retain()); 51 | return; 52 | } 53 | 54 | if (msg instanceof PingWebSocketFrame){ 55 | ctx.channel().write(new PongWebSocketFrame(msg.content().retain())); 56 | return; 57 | } 58 | 59 | //本例仅支持文本传递,不支持二进制消息 60 | if (! (msg instanceof TextWebSocketFrame)){ 61 | throw new UnsupportedOperationException(String.format("%s frame type not supported",msg.getClass().getName())); 62 | } 63 | 64 | String request = ((TextWebSocketFrame)msg).text(); 65 | ctx.channel().write(new TextWebSocketFrame(request+",欢迎使用Netty WebSocket 服务,现在时刻:" + new Date().toString())); 66 | } 67 | 68 | private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request) { 69 | // 如果Http解码失败,返回HTTP异常 70 | if (!request.decoderResult().isSuccess() || (! "websocket".equalsIgnoreCase(request.headers().get("Upgrade")))){ 71 | sendHttpResponse(ctx,request,new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST)); 72 | return; 73 | } 74 | 75 | // 构造握手响应返回,本机测试 76 | WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory("ws://localhost:8080/websocket",null,false); 77 | handshaker = wsFactory.newHandshaker(request); 78 | if (handshaker == null){ 79 | WebSocketServerHandshakerFactory.sendUnsupportedWebSocketVersionResponse(ctx.channel()); 80 | 81 | }else { 82 | handshaker.handshake(ctx.channel(),request); 83 | } 84 | 85 | 86 | } 87 | 88 | private void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest request, DefaultFullHttpResponse response) { 89 | // 返回应答给客户端 90 | if (response.status().code() != 200){ 91 | ByteBuf buf = Unpooled.copiedBuffer(response.status().toString(), CharsetUtil.UTF_8); 92 | response.content().writeBytes(buf); 93 | buf.release(); 94 | response.headers().set("Content-Length",response.content().readableBytes()); 95 | } 96 | 97 | ChannelFuture f = ctx.channel().writeAndFlush(response); 98 | if (!isKeepLive(request) || response.status().code() != 200){ 99 | f.addListener(ChannelFutureListener.CLOSE); 100 | } 101 | 102 | } 103 | 104 | private boolean isKeepLive(FullHttpRequest request) { 105 | return HttpUtil.isKeepAlive(request); 106 | } 107 | 108 | 109 | } 110 | -------------------------------------------------------------------------------- /netty-websocket/src/main/resources/templates/WebSocketServer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Netty WebSocket 时间服务器 6 | 7 | 8 | 43 | 44 |
45 | 46 |
47 |
48 | 49 |
50 |

服务端返回的应答消息

51 | 52 |
53 | 54 | --------------------------------------------------------------------------------