├── .gitignore ├── build.gradle ├── settings.gradle └── src └── main └── java └── com └── linling └── netty ├── chunk ├── MyClient.java ├── MyClientChunkHandler.java ├── MyClientHandler.java ├── MyClientInitializer.java └── MyIdleStateHandler.java ├── heartbeat ├── client │ ├── MyChatClient.java │ ├── MyChatClientHandler.java │ ├── MyChatClientIdleHandler.java │ └── MyChatClientInitializer.java ├── codec │ ├── ChatProtocol.java │ ├── ChatProtocolState.java │ ├── MyChatDecoder.java │ └── MyChatEncoder.java ├── common │ └── MyChatContants.java ├── heartbeat │ └── MyHeartbeat.java └── server │ ├── MyChatServer.java │ ├── MyChatServerHandler.java │ ├── MyChatServerIdleHandler.java │ └── MyChatServerInitializer.java └── trafficshaping ├── MyClient.java ├── MyClientHandler.java ├── MyClientInitializer.java ├── MyServer.java ├── MyServerChunkHandler.java ├── MyServerCommonHandler.java ├── MyServerInitializer.java ├── oom ├── MyServerHandlerForOOM.java └── MyServerHandlerForSolveOOM.java └── plain └── MyServerHandlerForPlain.java /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle/ 2 | build/ 3 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | group 'com.linling' 2 | version '1.0' 3 | 4 | apply plugin: 'java' 5 | 6 | sourceCompatibility = 1.8 7 | 8 | repositories { 9 | mavenCentral() 10 | } 11 | 12 | dependencies { 13 | compile ( 14 | "io.netty:netty-all:4.1.15.Final" 15 | ) 16 | 17 | testCompile ( 18 | "junit:junit:4.12" 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'netty_module_function' 2 | 3 | -------------------------------------------------------------------------------- /src/main/java/com/linling/netty/chunk/MyClient.java: -------------------------------------------------------------------------------- 1 | package com.linling.netty.chunk; 2 | 3 | import com.linling.netty.trafficshaping.MyServerInitializer; 4 | import io.netty.bootstrap.Bootstrap; 5 | import io.netty.channel.ChannelFuture; 6 | import io.netty.channel.EventLoopGroup; 7 | import io.netty.channel.nio.NioEventLoopGroup; 8 | import io.netty.channel.socket.nio.NioSocketChannel; 9 | 10 | public class MyClient { 11 | 12 | public static void main(String[] args) throws Exception { 13 | 14 | EventLoopGroup group = new NioEventLoopGroup(); 15 | 16 | try { 17 | Bootstrap client = new Bootstrap(); 18 | client.group(group).channel(NioSocketChannel.class).handler(new MyClientInitializer()); 19 | 20 | ChannelFuture channelFuture = client.connect("118.89.229.31", 8899).sync(); 21 | channelFuture.channel().closeFuture().sync(); 22 | 23 | } finally { 24 | group.shutdownGracefully(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/linling/netty/chunk/MyClientChunkHandler.java: -------------------------------------------------------------------------------- 1 | package com.linling.netty.chunk; 2 | 3 | import com.sun.xml.internal.messaging.saaj.util.ByteInputStream; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.buffer.ByteBufInputStream; 6 | import io.netty.channel.*; 7 | import io.netty.handler.stream.ChunkedNioStream; 8 | import io.netty.handler.stream.ChunkedStream; 9 | import io.netty.util.ReferenceCountUtil; 10 | 11 | import java.nio.charset.Charset; 12 | 13 | public class MyClientChunkHandler extends ChannelOutboundHandlerAdapter { 14 | 15 | Charset charset = Charset.forName("utf-8"); 16 | @Override 17 | public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { 18 | if(msg instanceof ByteBuf) { 19 | ByteBuf buf = (ByteBuf)msg; 20 | ByteInputStream in = new ByteInputStream(); 21 | byte[] data = null; 22 | if(buf.hasArray()) { 23 | System.out.println("+++ is array"); 24 | data = buf.array(); 25 | } else { 26 | System.out.println("--- is direct"); 27 | data = new byte[buf.readableBytes()]; 28 | buf.writeBytes(data); 29 | 30 | } 31 | System.out.println("===== data length : " + data.length); 32 | in.setBuf(data); 33 | 34 | // 第一种方式:使用 ByteInputStream 35 | ChunkedStream stream = new ChunkedStream(in); 36 | 37 | // 第二种方式:使用 ByteBufInputStream 38 | // ByteBufInputStream byteBufInputStream = new ByteBufInputStream(buf); 39 | // ChunkedStream stream = new ChunkedStream(byteBufInputStream); 40 | 41 | ChannelProgressivePromise progressivePromise = ctx.channel().newProgressivePromise(); 42 | progressivePromise.addListener(new ChannelProgressiveFutureListener(){ 43 | @Override 44 | public void operationProgressed(ChannelProgressiveFuture future, long progress, long total) throws Exception { 45 | // System.out.println("数据正在发送中。。。"); 46 | } 47 | 48 | @Override 49 | public void operationComplete(ChannelProgressiveFuture future) throws Exception { 50 | if(future.isSuccess()){ 51 | promise.setSuccess(); 52 | } else { 53 | promise.setFailure(future.cause()); 54 | } 55 | System.out.println("数据已经发送完了!"); 56 | } 57 | }); 58 | 59 | ReferenceCountUtil.release(msg); 60 | ctx.write(stream, progressivePromise); 61 | } else { 62 | super.write(ctx, msg, promise); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/linling/netty/chunk/MyClientHandler.java: -------------------------------------------------------------------------------- 1 | package com.linling.netty.chunk; 2 | 3 | import io.netty.channel.ChannelFuture; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.ChannelPromise; 6 | import io.netty.channel.SimpleChannelInboundHandler; 7 | import io.netty.handler.timeout.IdleStateEvent; 8 | import io.netty.util.concurrent.Future; 9 | import io.netty.util.concurrent.GenericFutureListener; 10 | 11 | import java.time.LocalDateTime; 12 | 13 | public class MyClientHandler extends SimpleChannelInboundHandler { 14 | 15 | private String tempString; 16 | 17 | public MyClientHandler() { 18 | StringBuilder builder = new StringBuilder(); 19 | for (int i = 0; i < 1024 * 1024; i++) { 20 | builder.append("abcdefghijklmnopqrstuvwxyz"); 21 | } 22 | tempString = builder.toString(); 23 | } 24 | 25 | 26 | @Override 27 | protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { 28 | System.out.println(LocalDateTime.now().toString() + "----" + ctx.channel().remoteAddress().toString() + "----" + msg.length()); 29 | } 30 | 31 | @Override 32 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 33 | sendData(ctx); 34 | } 35 | 36 | private void sendData(ChannelHandlerContext ctx) { 37 | if (!ctx.channel().isActive()) 38 | { 39 | System.out.println("channel inactive..."); 40 | ctx.close(); 41 | return; 42 | } 43 | 44 | System.out.println("send a pack of data ..."); 45 | 46 | long tickCount = System.currentTimeMillis(); 47 | 48 | 49 | ChannelFuture future = ctx.writeAndFlush(tempString); 50 | ChannelPromise promise = (ChannelPromise)future; 51 | promise.addListener(new GenericFutureListener>() { 52 | @Override 53 | public void operationComplete(Future future) throws Exception { 54 | System.out.println("send completed. isSuccess : " + future.isSuccess()); 55 | if(!future.isSuccess()) { 56 | future.cause().printStackTrace(); 57 | } 58 | System.out.println("Time elapse:" + (System.currentTimeMillis() - tickCount)); 59 | // sendData(ctx); 60 | } 61 | }); 62 | 63 | 64 | 65 | 66 | } 67 | 68 | @Override 69 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 70 | cause.printStackTrace(); 71 | ctx.close(); 72 | } 73 | 74 | @Override 75 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 76 | //System.out.println(LocalDateTime.now().toString()); 77 | if (evt == IdleStateEvent.READER_IDLE_STATE_EVENT) { 78 | System.out.println("READER_IDLE_STATE_EVENT"); 79 | } else if (evt == IdleStateEvent.WRITER_IDLE_STATE_EVENT){ 80 | // for heartbit 81 | System.out.println("WRITER_IDLE_STATE_EVENT----" + LocalDateTime.now().toString()); 82 | //ctx.writeAndFlush("ACK"); 83 | } else if (evt == IdleStateEvent.ALL_IDLE_STATE_EVENT) { 84 | //System.out.println("ALL_IDLE_STATE_EVENT"); 85 | } else if (evt == IdleStateEvent.FIRST_READER_IDLE_STATE_EVENT) { 86 | System.out.println("FIRST_READER_IDLE_STATE_EVENT"); 87 | } else if (evt == IdleStateEvent.FIRST_WRITER_IDLE_STATE_EVENT) { 88 | //System.out.println("FIRST_WRITER_IDLE_STATE_EVENT"); 89 | } else if (evt == IdleStateEvent.FIRST_ALL_IDLE_STATE_EVENT) { 90 | //System.out.println("FIRST_ALL_IDLE_STATE_EVENT"); 91 | } 92 | //super.userEventTriggered(ctx, evt); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/com/linling/netty/chunk/MyClientInitializer.java: -------------------------------------------------------------------------------- 1 | package com.linling.netty.chunk; 2 | 3 | import io.netty.channel.ChannelInitializer; 4 | import io.netty.channel.ChannelPipeline; 5 | import io.netty.channel.socket.SocketChannel; 6 | import io.netty.handler.codec.LengthFieldBasedFrameDecoder; 7 | import io.netty.handler.codec.LengthFieldPrepender; 8 | import io.netty.handler.codec.string.StringDecoder; 9 | import io.netty.handler.codec.string.StringEncoder; 10 | import io.netty.handler.stream.ChunkedWriteHandler; 11 | import io.netty.handler.timeout.IdleStateHandler; 12 | import io.netty.util.CharsetUtil; 13 | 14 | import java.nio.ByteOrder; 15 | import java.util.concurrent.TimeUnit; 16 | 17 | public class MyClientInitializer extends ChannelInitializer { 18 | 19 | @Override 20 | protected void initChannel(SocketChannel ch) throws Exception { 21 | ChannelPipeline pipeline = ch.pipeline(); 22 | pipeline.addFirst("idleStateHandler", new IdleStateHandler(9, 1, 11, TimeUnit.SECONDS)); 23 | pipeline.addLast("lengthFieldBasedFrameDecoder", new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN, Integer.MAX_VALUE, 24 | 0, 4, 0, 4, true)); 25 | pipeline.addLast("lengthFieldPrepender", new LengthFieldPrepender(ByteOrder.LITTLE_ENDIAN, 4, 0, false)); 26 | pipeline.addLast("chunkedWriteHandler", new ChunkedWriteHandler()); 27 | pipeline.addLast("myClientChunkHandler", new MyClientChunkHandler()); 28 | pipeline.addLast("stringEncoder", new StringEncoder(CharsetUtil.UTF_8)); 29 | pipeline.addLast("stringDecoder", new StringDecoder(CharsetUtil.UTF_8)); 30 | pipeline.addLast("myClientHandler", new MyClientHandler()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/linling/netty/chunk/MyIdleStateHandler.java: -------------------------------------------------------------------------------- 1 | package com.linling.netty.chunk; 2 | 3 | import io.netty.channel.*; 4 | import io.netty.handler.timeout.IdleState; 5 | import io.netty.handler.timeout.IdleStateEvent; 6 | import io.netty.handler.timeout.IdleStateHandler; 7 | 8 | import java.util.concurrent.ScheduledFuture; 9 | import java.util.concurrent.TimeUnit; 10 | 11 | /** 12 | * copy的一份IdleStateHandler,方便打印日志、调试。。。 13 | */ 14 | public class MyIdleStateHandler extends ChannelDuplexHandler { 15 | private static final long MIN_TIMEOUT_NANOS = TimeUnit.MILLISECONDS.toNanos(1); 16 | 17 | int count; 18 | // Not create a new ChannelFutureListener per write operation to reduce GC pressure. 19 | private final ChannelFutureListener writeListener = new ChannelFutureListener() { 20 | @Override 21 | public void operationComplete(ChannelFuture future) throws Exception { 22 | count++; 23 | System.out.println("============= writeListener ============ count : " + count); 24 | lastWriteTime = ticksInNanos(); 25 | firstWriterIdleEvent = firstAllIdleEvent = true; 26 | 27 | } 28 | }; 29 | 30 | private final boolean observeOutput; 31 | private final long readerIdleTimeNanos; 32 | private final long writerIdleTimeNanos; 33 | private final long allIdleTimeNanos; 34 | 35 | private ScheduledFuture readerIdleTimeout; 36 | private long lastReadTime; 37 | private boolean firstReaderIdleEvent = true; 38 | 39 | private ScheduledFuture writerIdleTimeout; 40 | private long lastWriteTime; 41 | private boolean firstWriterIdleEvent = true; 42 | 43 | private ScheduledFuture allIdleTimeout; 44 | private boolean firstAllIdleEvent = true; 45 | 46 | private byte state; // 0 - none, 1 - initialized, 2 - destroyed 47 | private boolean reading; 48 | 49 | private long lastChangeCheckTimeStamp; 50 | private int lastMessageHashCode; 51 | private long lastPendingWriteBytes; 52 | 53 | /** 54 | * Creates a new instance firing {@link IdleStateEvent}s. 55 | * 56 | * @param readerIdleTimeSeconds 57 | * an {@link IdleStateEvent} whose state is {@link IdleState#READER_IDLE} 58 | * will be triggered when no read was performed for the specified 59 | * period of time. Specify {@code 0} to disable. 60 | * @param writerIdleTimeSeconds 61 | * an {@link IdleStateEvent} whose state is {@link IdleState#WRITER_IDLE} 62 | * will be triggered when no write was performed for the specified 63 | * period of time. Specify {@code 0} to disable. 64 | * @param allIdleTimeSeconds 65 | * an {@link IdleStateEvent} whose state is {@link IdleState#ALL_IDLE} 66 | * will be triggered when neither read nor write was performed for 67 | * the specified period of time. Specify {@code 0} to disable. 68 | */ 69 | public MyIdleStateHandler( 70 | int readerIdleTimeSeconds, 71 | int writerIdleTimeSeconds, 72 | int allIdleTimeSeconds) { 73 | 74 | this(readerIdleTimeSeconds, writerIdleTimeSeconds, allIdleTimeSeconds, 75 | TimeUnit.SECONDS); 76 | } 77 | 78 | public MyIdleStateHandler( 79 | long readerIdleTime, long writerIdleTime, long allIdleTime, 80 | TimeUnit unit) { 81 | this(false, readerIdleTime, writerIdleTime, allIdleTime, unit); 82 | } 83 | 84 | /** 85 | * Creates a new instance firing {@link IdleStateEvent}s. 86 | * 87 | * @param observeOutput 88 | * whether or not the consumption of {@code bytes} should be taken into 89 | * consideration when assessing write idleness. The default is {@code false}. 90 | * @param readerIdleTime 91 | * an {@link IdleStateEvent} whose state is {@link IdleState#READER_IDLE} 92 | * will be triggered when no read was performed for the specified 93 | * period of time. Specify {@code 0} to disable. 94 | * @param writerIdleTime 95 | * an {@link IdleStateEvent} whose state is {@link IdleState#WRITER_IDLE} 96 | * will be triggered when no write was performed for the specified 97 | * period of time. Specify {@code 0} to disable. 98 | * @param allIdleTime 99 | * an {@link IdleStateEvent} whose state is {@link IdleState#ALL_IDLE} 100 | * will be triggered when neither read nor write was performed for 101 | * the specified period of time. Specify {@code 0} to disable. 102 | * @param unit 103 | * the {@link TimeUnit} of {@code readerIdleTime}, 104 | * {@code writeIdleTime}, and {@code allIdleTime} 105 | */ 106 | public MyIdleStateHandler(boolean observeOutput, 107 | long readerIdleTime, long writerIdleTime, long allIdleTime, 108 | TimeUnit unit) { 109 | if (unit == null) { 110 | throw new NullPointerException("unit"); 111 | } 112 | 113 | this.observeOutput = observeOutput; 114 | 115 | if (readerIdleTime <= 0) { 116 | readerIdleTimeNanos = 0; 117 | } else { 118 | readerIdleTimeNanos = Math.max(unit.toNanos(readerIdleTime), MIN_TIMEOUT_NANOS); 119 | } 120 | if (writerIdleTime <= 0) { 121 | writerIdleTimeNanos = 0; 122 | } else { 123 | writerIdleTimeNanos = Math.max(unit.toNanos(writerIdleTime), MIN_TIMEOUT_NANOS); 124 | } 125 | if (allIdleTime <= 0) { 126 | allIdleTimeNanos = 0; 127 | } else { 128 | allIdleTimeNanos = Math.max(unit.toNanos(allIdleTime), MIN_TIMEOUT_NANOS); 129 | } 130 | } 131 | 132 | /** 133 | * Return the readerIdleTime that was given when instance this class in milliseconds. 134 | * 135 | */ 136 | public long getReaderIdleTimeInMillis() { 137 | return TimeUnit.NANOSECONDS.toMillis(readerIdleTimeNanos); 138 | } 139 | 140 | /** 141 | * Return the writerIdleTime that was given when instance this class in milliseconds. 142 | * 143 | */ 144 | public long getWriterIdleTimeInMillis() { 145 | return TimeUnit.NANOSECONDS.toMillis(writerIdleTimeNanos); 146 | } 147 | 148 | /** 149 | * Return the allIdleTime that was given when instance this class in milliseconds. 150 | * 151 | */ 152 | public long getAllIdleTimeInMillis() { 153 | return TimeUnit.NANOSECONDS.toMillis(allIdleTimeNanos); 154 | } 155 | 156 | @Override 157 | public void handlerAdded(ChannelHandlerContext ctx) throws Exception { 158 | if (ctx.channel().isActive() && ctx.channel().isRegistered()) { 159 | // channelActive() event has been fired already, which means this.channelActive() will 160 | // not be invoked. We have to initialize here instead. 161 | initialize(ctx); 162 | } else { 163 | // channelActive() event has not been fired yet. this.channelActive() will be invoked 164 | // and initialization will occur there. 165 | } 166 | } 167 | 168 | @Override 169 | public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { 170 | destroy(); 171 | } 172 | 173 | @Override 174 | public void channelRegistered(ChannelHandlerContext ctx) throws Exception { 175 | // Initialize early if channel is active already. 176 | if (ctx.channel().isActive()) { 177 | initialize(ctx); 178 | } 179 | super.channelRegistered(ctx); 180 | } 181 | 182 | @Override 183 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 184 | // This method will be invoked only if this handler was added 185 | // before channelActive() event is fired. If a user adds this handler 186 | // after the channelActive() event, initialize() will be called by beforeAdd(). 187 | initialize(ctx); 188 | super.channelActive(ctx); 189 | } 190 | 191 | @Override 192 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 193 | destroy(); 194 | super.channelInactive(ctx); 195 | } 196 | 197 | @Override 198 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 199 | if (readerIdleTimeNanos > 0 || allIdleTimeNanos > 0) { 200 | reading = true; 201 | firstReaderIdleEvent = firstAllIdleEvent = true; 202 | } 203 | ctx.fireChannelRead(msg); 204 | } 205 | 206 | @Override 207 | public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 208 | if ((readerIdleTimeNanos > 0 || allIdleTimeNanos > 0) && reading) { 209 | lastReadTime = ticksInNanos(); 210 | reading = false; 211 | } 212 | ctx.fireChannelReadComplete(); 213 | } 214 | 215 | @Override 216 | public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { 217 | // Allow writing with void promise if handler is only configured for read timeout events. 218 | if (writerIdleTimeNanos > 0 || allIdleTimeNanos > 0) { 219 | ctx.write(msg, promise.unvoid()).addListener(writeListener); 220 | } else { 221 | ctx.write(msg, promise); 222 | } 223 | } 224 | 225 | private void initialize(ChannelHandlerContext ctx) { 226 | // Avoid the case where destroy() is called before scheduling timeouts. 227 | // See: https://github.com/netty/netty/issues/143 228 | switch (state) { 229 | case 1: 230 | case 2: 231 | return; 232 | } 233 | 234 | state = 1; 235 | initOutputChanged(ctx); 236 | 237 | lastReadTime = lastWriteTime = ticksInNanos(); 238 | if (readerIdleTimeNanos > 0) { 239 | readerIdleTimeout = schedule(ctx, new MyIdleStateHandler.ReaderIdleTimeoutTask(ctx), 240 | readerIdleTimeNanos, TimeUnit.NANOSECONDS); 241 | } 242 | if (writerIdleTimeNanos > 0) { 243 | writerIdleTimeout = schedule(ctx, new MyIdleStateHandler.WriterIdleTimeoutTask(ctx), 244 | writerIdleTimeNanos, TimeUnit.NANOSECONDS); 245 | } 246 | if (allIdleTimeNanos > 0) { 247 | allIdleTimeout = schedule(ctx, new MyIdleStateHandler.AllIdleTimeoutTask(ctx), 248 | allIdleTimeNanos, TimeUnit.NANOSECONDS); 249 | } 250 | } 251 | 252 | /** 253 | * This method is visible for testing! 254 | */ 255 | long ticksInNanos() { 256 | return System.nanoTime(); 257 | } 258 | 259 | /** 260 | * This method is visible for testing! 261 | */ 262 | ScheduledFuture schedule(ChannelHandlerContext ctx, Runnable task, long delay, TimeUnit unit) { 263 | return ctx.executor().schedule(task, delay, unit); 264 | } 265 | 266 | private void destroy() { 267 | state = 2; 268 | 269 | if (readerIdleTimeout != null) { 270 | readerIdleTimeout.cancel(false); 271 | readerIdleTimeout = null; 272 | } 273 | if (writerIdleTimeout != null) { 274 | writerIdleTimeout.cancel(false); 275 | writerIdleTimeout = null; 276 | } 277 | if (allIdleTimeout != null) { 278 | allIdleTimeout.cancel(false); 279 | allIdleTimeout = null; 280 | } 281 | } 282 | 283 | /** 284 | * Is called when an {@link IdleStateEvent} should be fired. This implementation calls 285 | * {@link ChannelHandlerContext#fireUserEventTriggered(Object)}. 286 | */ 287 | protected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) throws Exception { 288 | ctx.fireUserEventTriggered(evt); 289 | } 290 | 291 | /** 292 | * Returns a {@link IdleStateEvent}. 293 | */ 294 | protected IdleStateEvent newIdleStateEvent(IdleState state, boolean first) { 295 | switch (state) { 296 | case ALL_IDLE: 297 | return first ? IdleStateEvent.FIRST_ALL_IDLE_STATE_EVENT : IdleStateEvent.ALL_IDLE_STATE_EVENT; 298 | case READER_IDLE: 299 | return first ? IdleStateEvent.FIRST_READER_IDLE_STATE_EVENT : IdleStateEvent.READER_IDLE_STATE_EVENT; 300 | case WRITER_IDLE: 301 | return first ? IdleStateEvent.FIRST_WRITER_IDLE_STATE_EVENT : IdleStateEvent.WRITER_IDLE_STATE_EVENT; 302 | default: 303 | throw new IllegalArgumentException("Unhandled: state=" + state + ", first=" + first); 304 | } 305 | } 306 | 307 | /** 308 | * @see #hasOutputChanged(ChannelHandlerContext, boolean) 309 | */ 310 | private void initOutputChanged(ChannelHandlerContext ctx) { 311 | if (observeOutput) { 312 | Channel channel = ctx.channel(); 313 | Channel.Unsafe unsafe = channel.unsafe(); 314 | ChannelOutboundBuffer buf = unsafe.outboundBuffer(); 315 | 316 | if (buf != null) { 317 | lastMessageHashCode = System.identityHashCode(buf.current()); 318 | lastPendingWriteBytes = buf.totalPendingWriteBytes(); 319 | } 320 | } 321 | } 322 | 323 | /** 324 | * Returns {@code true} if and only if the {@link IdleStateHandler} was constructed 325 | * with {@link #observeOutput} enabled and there has been an observed change in the 326 | * {@link ChannelOutboundBuffer} between two consecutive calls of this method. 327 | * 328 | * https://github.com/netty/netty/issues/6150 329 | */ 330 | private boolean hasOutputChanged(ChannelHandlerContext ctx, boolean first) { 331 | if (observeOutput) { 332 | 333 | // We can take this shortcut if the ChannelPromises that got passed into write() 334 | // appear to complete. It indicates "change" on message level and we simply assume 335 | // that there's change happening on byte level. If the user doesn't observe channel 336 | // writability events then they'll eventually OOME and there's clearly a different 337 | // problem and idleness is least of their concerns. 338 | if (lastChangeCheckTimeStamp != lastWriteTime) { 339 | lastChangeCheckTimeStamp = lastWriteTime; 340 | 341 | // But this applies only if it's the non-first call. 342 | if (!first) { 343 | return true; 344 | } 345 | } 346 | 347 | Channel channel = ctx.channel(); 348 | Channel.Unsafe unsafe = channel.unsafe(); 349 | ChannelOutboundBuffer buf = unsafe.outboundBuffer(); 350 | 351 | if (buf != null) { 352 | int messageHashCode = System.identityHashCode(buf.current()); 353 | long pendingWriteBytes = buf.totalPendingWriteBytes(); 354 | 355 | if (messageHashCode != lastMessageHashCode || pendingWriteBytes != lastPendingWriteBytes) { 356 | lastMessageHashCode = messageHashCode; 357 | lastPendingWriteBytes = pendingWriteBytes; 358 | 359 | if (!first) { 360 | return true; 361 | } 362 | } 363 | } 364 | } 365 | 366 | return false; 367 | } 368 | 369 | private abstract static class AbstractIdleTask implements Runnable { 370 | 371 | private final ChannelHandlerContext ctx; 372 | 373 | AbstractIdleTask(ChannelHandlerContext ctx) { 374 | this.ctx = ctx; 375 | } 376 | 377 | @Override 378 | public void run() { 379 | if (!ctx.channel().isOpen()) { 380 | return; 381 | } 382 | 383 | run(ctx); 384 | } 385 | 386 | protected abstract void run(ChannelHandlerContext ctx); 387 | } 388 | 389 | private final class ReaderIdleTimeoutTask extends MyIdleStateHandler.AbstractIdleTask { 390 | 391 | ReaderIdleTimeoutTask(ChannelHandlerContext ctx) { 392 | super(ctx); 393 | } 394 | 395 | @Override 396 | protected void run(ChannelHandlerContext ctx) { 397 | long nextDelay = readerIdleTimeNanos; 398 | if (!reading) { 399 | nextDelay -= ticksInNanos() - lastReadTime; 400 | } 401 | 402 | if (nextDelay <= 0) { 403 | // Reader is idle - set a new timeout and notify the callback. 404 | readerIdleTimeout = schedule(ctx, this, readerIdleTimeNanos, TimeUnit.NANOSECONDS); 405 | 406 | boolean first = firstReaderIdleEvent; 407 | firstReaderIdleEvent = false; 408 | 409 | try { 410 | IdleStateEvent event = newIdleStateEvent(IdleState.READER_IDLE, first); 411 | channelIdle(ctx, event); 412 | } catch (Throwable t) { 413 | ctx.fireExceptionCaught(t); 414 | } 415 | } else { 416 | // Read occurred before the timeout - set a new timeout with shorter delay. 417 | readerIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS); 418 | } 419 | } 420 | } 421 | 422 | private final class WriterIdleTimeoutTask extends MyIdleStateHandler.AbstractIdleTask { 423 | 424 | WriterIdleTimeoutTask(ChannelHandlerContext ctx) { 425 | super(ctx); 426 | } 427 | 428 | @Override 429 | protected void run(ChannelHandlerContext ctx) { 430 | System.out.println("######### WriterIdleTimeoutTask is run "); 431 | long lastWriteTime = MyIdleStateHandler.this.lastWriteTime; 432 | long nextDelay = writerIdleTimeNanos - (ticksInNanos() - lastWriteTime); 433 | if (nextDelay <= 0) { 434 | // Writer is idle - set a new timeout and notify the callback. 435 | writerIdleTimeout = schedule(ctx, this, writerIdleTimeNanos, TimeUnit.NANOSECONDS); 436 | 437 | boolean first = firstWriterIdleEvent; 438 | firstWriterIdleEvent = false; 439 | 440 | try { 441 | if (hasOutputChanged(ctx, first)) { 442 | return; 443 | } 444 | 445 | IdleStateEvent event = newIdleStateEvent(IdleState.WRITER_IDLE, first); 446 | channelIdle(ctx, event); 447 | } catch (Throwable t) { 448 | ctx.fireExceptionCaught(t); 449 | } 450 | } else { 451 | // Write occurred before the timeout - set a new timeout with shorter delay. 452 | writerIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS); 453 | } 454 | } 455 | } 456 | 457 | private final class AllIdleTimeoutTask extends MyIdleStateHandler.AbstractIdleTask { 458 | 459 | AllIdleTimeoutTask(ChannelHandlerContext ctx) { 460 | super(ctx); 461 | } 462 | 463 | @Override 464 | protected void run(ChannelHandlerContext ctx) { 465 | 466 | long nextDelay = allIdleTimeNanos; 467 | if (!reading) { 468 | nextDelay -= ticksInNanos() - Math.max(lastReadTime, lastWriteTime); 469 | } 470 | if (nextDelay <= 0) { 471 | // Both reader and writer are idle - set a new timeout and 472 | // notify the callback. 473 | allIdleTimeout = schedule(ctx, this, allIdleTimeNanos, TimeUnit.NANOSECONDS); 474 | 475 | boolean first = firstAllIdleEvent; 476 | firstAllIdleEvent = false; 477 | 478 | try { 479 | if (hasOutputChanged(ctx, first)) { 480 | return; 481 | } 482 | 483 | IdleStateEvent event = newIdleStateEvent(IdleState.ALL_IDLE, first); 484 | channelIdle(ctx, event); 485 | } catch (Throwable t) { 486 | ctx.fireExceptionCaught(t); 487 | } 488 | } else { 489 | // Either read or write occurred before the timeout - set a new 490 | // timeout with shorter delay. 491 | allIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS); 492 | } 493 | } 494 | } 495 | } 496 | -------------------------------------------------------------------------------- /src/main/java/com/linling/netty/heartbeat/client/MyChatClient.java: -------------------------------------------------------------------------------- 1 | package com.linling.netty.heartbeat.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.EventLoopGroup; 7 | import io.netty.channel.nio.NioEventLoopGroup; 8 | import io.netty.channel.socket.nio.NioSocketChannel; 9 | 10 | import java.io.BufferedReader; 11 | import java.io.InputStreamReader; 12 | import java.net.InetSocketAddress; 13 | import java.util.concurrent.ExecutorService; 14 | import java.util.concurrent.Executors; 15 | 16 | public class MyChatClient { 17 | 18 | private ExecutorService executor; 19 | // private Future result; 20 | private static BufferedReader bufferedReader; 21 | private PrintTask printTask; 22 | 23 | public static void main(String[] args) throws Exception { 24 | MyChatClient chatClient = new MyChatClient(); 25 | EventLoopGroup group = new NioEventLoopGroup(); 26 | bufferedReader = new BufferedReader(new InputStreamReader(System.in)); 27 | try { 28 | chatClient.connect(group); 29 | } finally { 30 | // group.shutdownGracefully(); 31 | } 32 | 33 | Runtime.getRuntime().addShutdownHook(new Thread(() -> group.shutdownGracefully())); 34 | } 35 | 36 | public void connect(EventLoopGroup group) { 37 | try{ 38 | if (null == executor) { 39 | executor = Executors.newSingleThreadExecutor(); 40 | 41 | } 42 | 43 | Bootstrap client = new Bootstrap(); 44 | client.group(group).channel(NioSocketChannel.class).handler(new MyChatClientInitializer(this)); 45 | 46 | // ======= 说明 ======== 47 | /** 48 | * 这种写法在重连接的时候回抛 "io.netty.util.concurrent.BlockingOperationException: DefaultChannelPromise@d21e95e(incomplete)"异常 49 | * 解决方法:不能在ChannelHandler中调用 ChannelFuture.sync() 。通过注册Listener来实现功能 50 | */ 51 | // ChannelFuture future = client.connect(new InetSocketAddress("127.0.0.1", 5566)).sync(); 52 | // ======= 说明 ======== 53 | 54 | //192.168.1.102 55 | client.remoteAddress(new InetSocketAddress("127.0.0.1", 5566)); 56 | client.connect().addListener((ChannelFuture future) -> { 57 | if(future.isSuccess()) { 58 | 59 | // ======= 说明 ======== 60 | // 这个 死循环 导致了走到了channelRegistered, 后面的channelActive流程就被它堵塞了,以至于没往下走。。。 61 | /*while (!readerExit) { 62 | BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); 63 | future.channel().writeAndFlush(bufferedReader.readLine()); 64 | }*/ 65 | // ======= 说明 ======== 66 | 67 | if(printTask == null) { 68 | printTask = new PrintTask(future.channel(), bufferedReader); 69 | executor.submit(printTask); 70 | } else { 71 | printTask.setFuture(future.channel()); 72 | } 73 | 74 | 75 | 76 | } 77 | }); 78 | } catch (Exception e) { 79 | e.printStackTrace(); 80 | } 81 | 82 | System.out.println("=============="); 83 | } 84 | 85 | class PrintTask implements Runnable { 86 | 87 | // private volatile ChannelFuture future; 88 | private volatile Channel channel; 89 | private final BufferedReader bufferedReader; 90 | 91 | public PrintTask(Channel channel, BufferedReader bufferedReader) { 92 | this.bufferedReader = bufferedReader; 93 | this.channel = channel; 94 | } 95 | 96 | @Override 97 | public void run() { 98 | while (true) { 99 | try{ 100 | String line = bufferedReader.readLine(); 101 | channel.writeAndFlush(line); 102 | } catch (Exception e) { 103 | e.printStackTrace(); 104 | break; 105 | } 106 | } 107 | } 108 | 109 | public void setFuture(Channel channel) { 110 | this.channel = channel; 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/com/linling/netty/heartbeat/client/MyChatClientHandler.java: -------------------------------------------------------------------------------- 1 | package com.linling.netty.heartbeat.client; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.channel.SimpleChannelInboundHandler; 5 | 6 | public class MyChatClientHandler extends SimpleChannelInboundHandler { 7 | 8 | @Override 9 | protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { 10 | System.out.println(msg); 11 | } 12 | 13 | @Override 14 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 15 | System.out.println(ctx.channel() + " is inactive"); 16 | super.channelInactive(ctx); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/linling/netty/heartbeat/client/MyChatClientIdleHandler.java: -------------------------------------------------------------------------------- 1 | package com.linling.netty.heartbeat.client; 2 | 3 | import com.linling.netty.heartbeat.heartbeat.MyHeartbeat; 4 | import io.netty.channel.Channel; 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 | 10 | import java.util.concurrent.TimeUnit; 11 | 12 | import static com.linling.netty.heartbeat.common.MyChatContants.RETRY_LIMIT; 13 | 14 | public class MyChatClientIdleHandler extends ChannelInboundHandlerAdapter { 15 | 16 | private final MyChatClient chatClient; 17 | 18 | public MyChatClientIdleHandler(MyChatClient chatClient) { 19 | this.chatClient = chatClient; 20 | } 21 | 22 | private int retryCount; 23 | 24 | @Override 25 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 26 | if(evt instanceof IdleStateEvent) { 27 | IdleStateEvent event = (IdleStateEvent)evt; 28 | if(event.state() == IdleState.READER_IDLE) { 29 | if(++retryCount > RETRY_LIMIT) { 30 | System.out.println("server " + ctx.channel().remoteAddress() + " is inactive to close"); 31 | closeAndReconnection(ctx.channel()); 32 | } else { 33 | System.out.println("send ping package to " + ctx.channel().remoteAddress()); 34 | ctx.writeAndFlush(MyHeartbeat.getHeartbeatPingBuf()); 35 | } 36 | } 37 | } else { 38 | super.userEventTriggered(ctx, evt); 39 | } 40 | } 41 | 42 | private void closeAndReconnection(Channel channel) { 43 | channel.close(); 44 | channel.eventLoop().schedule(() -> { 45 | System.out.println("========== 尝试重连接 =========="); 46 | chatClient.connect(channel.eventLoop()); 47 | }, 10L, TimeUnit.SECONDS); 48 | } 49 | 50 | @Override 51 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 52 | retryCount=0; 53 | super.channelRead(ctx, msg); 54 | } 55 | 56 | @Override 57 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 58 | System.out.println(ctx.channel() + " 已连上. 可以开始聊天..."); 59 | super.channelActive(ctx); 60 | } 61 | 62 | @Override 63 | public void channelRegistered(ChannelHandlerContext ctx) throws Exception { 64 | System.out.println("channelRegistered : " + ctx.channel()); 65 | super.channelRegistered(ctx); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/linling/netty/heartbeat/client/MyChatClientInitializer.java: -------------------------------------------------------------------------------- 1 | package com.linling.netty.heartbeat.client; 2 | 3 | import com.linling.netty.heartbeat.codec.MyChatDecoder; 4 | import com.linling.netty.heartbeat.codec.MyChatEncoder; 5 | import io.netty.channel.ChannelInitializer; 6 | import io.netty.channel.socket.SocketChannel; 7 | import io.netty.handler.codec.string.StringDecoder; 8 | import io.netty.handler.codec.string.StringEncoder; 9 | import io.netty.handler.timeout.IdleStateHandler; 10 | 11 | import java.nio.charset.Charset; 12 | import java.util.concurrent.TimeUnit; 13 | 14 | import static com.linling.netty.heartbeat.common.MyChatContants.CLIENT_READ_TIME; 15 | 16 | 17 | public class MyChatClientInitializer extends ChannelInitializer { 18 | 19 | final MyChatClient chatClient; 20 | 21 | Charset charset = Charset.forName("utf-8"); 22 | 23 | public MyChatClientInitializer(MyChatClient chatClient) { 24 | this.chatClient = chatClient; 25 | } 26 | 27 | @Override 28 | protected void initChannel(SocketChannel ch) throws Exception { 29 | ch.pipeline() 30 | .addLast("idleStateHandler", new IdleStateHandler(CLIENT_READ_TIME, 0, 0, TimeUnit.SECONDS)) 31 | .addLast("myChatClientIdleHandler", new MyChatClientIdleHandler(chatClient)) 32 | .addLast("myChatDecoder", new MyChatDecoder()) 33 | .addLast("myChatEncoder", new MyChatEncoder()) 34 | .addLast("stringDecoder", new StringDecoder(charset)) 35 | .addLast("stringEncoder", new StringEncoder(charset)) 36 | .addLast("myChatClientHandler", new MyChatClientHandler()); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/linling/netty/heartbeat/codec/ChatProtocol.java: -------------------------------------------------------------------------------- 1 | package com.linling.netty.heartbeat.codec; 2 | 3 | public class ChatProtocol { 4 | 5 | public static final byte MAGIC_MESSAGE = 1; 6 | public static final byte MAGIC_HEARTBEAT_PING = 2; 7 | public static final byte MAGIC_HEARTBEAT_PONG = 3; 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/linling/netty/heartbeat/codec/ChatProtocolState.java: -------------------------------------------------------------------------------- 1 | package com.linling.netty.heartbeat.codec; 2 | 3 | public enum ChatProtocolState { 4 | MESSAGE_TYPE,MESSAGE_PACKAGE,MESSAGE_DATA 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/linling/netty/heartbeat/codec/MyChatDecoder.java: -------------------------------------------------------------------------------- 1 | package com.linling.netty.heartbeat.codec; 2 | 3 | import com.linling.netty.heartbeat.heartbeat.MyHeartbeat; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.handler.codec.ReplayingDecoder; 7 | 8 | import java.util.List; 9 | 10 | import static com.linling.netty.heartbeat.codec.ChatProtocolState.MESSAGE_DATA; 11 | import static com.linling.netty.heartbeat.codec.ChatProtocolState.MESSAGE_PACKAGE; 12 | import static com.linling.netty.heartbeat.codec.ChatProtocolState.MESSAGE_TYPE; 13 | 14 | /** 15 | * 自定义消息格式: | MAGIC | LENGTH | BODY | 16 | * MAGIC(byte) :消息类型。{@link ChatProtocol#MAGIC_MESSAGE}表示消息类型;{@link ChatProtocol#MAGIC_HEARTBEAT_PING}表示PING心跳包;{@link ChatProtocol#MAGIC_HEARTBEAT_PONG}表示PONG心跳包 17 | * LENGTH(int32) :消息长度 18 | * BODY(byte[]) :消息体 19 | */ 20 | public class MyChatDecoder extends ReplayingDecoder { 21 | 22 | public MyChatDecoder() { 23 | super(MESSAGE_TYPE); 24 | } 25 | 26 | byte magic; 27 | 28 | @Override 29 | protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { 30 | 31 | int length = 0; 32 | switch (state()) { 33 | case MESSAGE_TYPE: 34 | magic = in.readByte(); 35 | checkpoint(MESSAGE_PACKAGE); 36 | case MESSAGE_PACKAGE: 37 | length = parseMessage(ctx, in); 38 | if(0 == length) { 39 | break; 40 | } 41 | case MESSAGE_DATA: 42 | ByteBuf body = in.readBytes(length); 43 | out.add(body); 44 | checkpoint(MESSAGE_TYPE); 45 | } 46 | 47 | } 48 | 49 | private int parseMessage(ChannelHandlerContext ctx, ByteBuf in){ 50 | switch (magic) { 51 | case ChatProtocol.MAGIC_MESSAGE: 52 | int length = in.readInt(); 53 | checkpoint(MESSAGE_DATA); 54 | return length; 55 | case ChatProtocol.MAGIC_HEARTBEAT_PING: 56 | System.out.println("收到 " + ctx.channel().remoteAddress() + " 的 ping 包,返回一个 pong 包。"); 57 | ctx.writeAndFlush(MyHeartbeat.getHeartbeatPongBuf()); 58 | checkpoint(MESSAGE_TYPE); 59 | break; 60 | case ChatProtocol.MAGIC_HEARTBEAT_PONG: 61 | System.out.println("收到 " + ctx.channel().remoteAddress() + " 的 pong 包。"); 62 | checkpoint(MESSAGE_TYPE); 63 | break; 64 | } 65 | return 0; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/linling/netty/heartbeat/codec/MyChatEncoder.java: -------------------------------------------------------------------------------- 1 | package com.linling.netty.heartbeat.codec; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.MessageToByteEncoder; 6 | 7 | public class MyChatEncoder extends MessageToByteEncoder { 8 | 9 | @Override 10 | protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception { 11 | out.writeByte(ChatProtocol.MAGIC_MESSAGE); 12 | out.writeInt(msg.readableBytes()); 13 | out.writeBytes(msg); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/linling/netty/heartbeat/common/MyChatContants.java: -------------------------------------------------------------------------------- 1 | package com.linling.netty.heartbeat.common; 2 | 3 | public class MyChatContants { 4 | 5 | public static final int RETRY_LIMIT = 3; 6 | 7 | public static final long CLIENT_READ_TIME = 10; 8 | 9 | public static final long SERVER_READ_TIME = 15 * RETRY_LIMIT; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/linling/netty/heartbeat/heartbeat/MyHeartbeat.java: -------------------------------------------------------------------------------- 1 | package com.linling.netty.heartbeat.heartbeat; 2 | 3 | import com.linling.netty.heartbeat.codec.ChatProtocol; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.buffer.Unpooled; 6 | 7 | public class MyHeartbeat { 8 | 9 | private static final ByteBuf HEARTBEAT_PING_BUF; 10 | private static final ByteBuf HEARTBEAT_PONG_BUF; 11 | 12 | static { 13 | ByteBuf pingBuf = Unpooled.buffer(); 14 | pingBuf.writeByte(ChatProtocol.MAGIC_HEARTBEAT_PING); 15 | HEARTBEAT_PING_BUF = Unpooled.unreleasableBuffer(pingBuf).asReadOnly(); 16 | 17 | ByteBuf pongBuf = Unpooled.buffer(); 18 | pongBuf.writeByte(ChatProtocol.MAGIC_HEARTBEAT_PONG); 19 | HEARTBEAT_PONG_BUF = Unpooled.unreleasableBuffer(pongBuf).asReadOnly(); 20 | } 21 | 22 | public static ByteBuf getHeartbeatPingBuf() { 23 | return HEARTBEAT_PING_BUF; 24 | } 25 | 26 | public static ByteBuf getHeartbeatPongBuf() { 27 | return HEARTBEAT_PONG_BUF; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/linling/netty/heartbeat/server/MyChatServer.java: -------------------------------------------------------------------------------- 1 | package com.linling.netty.heartbeat.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 | public class MyChatServer { 12 | 13 | public static void main(String[] args) throws Exception { 14 | EventLoopGroup boss = new NioEventLoopGroup(); 15 | EventLoopGroup work = new NioEventLoopGroup(); 16 | 17 | try { 18 | ServerBootstrap server = new ServerBootstrap(); 19 | server.group(boss, work).channel(NioServerSocketChannel.class).handler(new LoggingHandler(LogLevel.INFO)).childHandler(new MyChatServerInitializer()); 20 | ChannelFuture future = server.bind(5566).sync(); 21 | future.channel().closeFuture().sync(); 22 | } finally { 23 | boss.shutdownGracefully(); 24 | work.shutdownGracefully(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/linling/netty/heartbeat/server/MyChatServerHandler.java: -------------------------------------------------------------------------------- 1 | package com.linling.netty.heartbeat.server; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.SimpleChannelInboundHandler; 6 | import io.netty.channel.group.ChannelGroup; 7 | import io.netty.channel.group.DefaultChannelGroup; 8 | import io.netty.util.concurrent.GlobalEventExecutor; 9 | 10 | public class MyChatServerHandler extends SimpleChannelInboundHandler { 11 | 12 | private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); 13 | 14 | @Override 15 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 16 | Channel channel = ctx.channel(); 17 | String msg = channel.remoteAddress() + " 上线!"; 18 | System.out.println(msg); 19 | channelGroup.writeAndFlush(msg); 20 | channelGroup.add(channel); 21 | } 22 | 23 | @Override 24 | protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { 25 | Channel channel = ctx.channel(); 26 | System.out.println("收到" + channel.remoteAddress() + " 消息 : " + msg); 27 | String sendMsg = "[" + channel.remoteAddress() + "] : " + msg; 28 | channelGroup.writeAndFlush(sendMsg, c -> !channel.remoteAddress().equals(c.remoteAddress())); 29 | String myMsg = "[自己] : " + msg; 30 | channel.writeAndFlush(myMsg); 31 | } 32 | 33 | @Override 34 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 35 | Channel channel = ctx.channel(); 36 | String msg = channel.remoteAddress() + " 下线!"; 37 | System.out.println(msg); 38 | channelGroup.writeAndFlush(msg); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/linling/netty/heartbeat/server/MyChatServerIdleHandler.java: -------------------------------------------------------------------------------- 1 | package com.linling.netty.heartbeat.server; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.channel.ChannelInboundHandlerAdapter; 5 | import io.netty.handler.timeout.IdleStateEvent; 6 | 7 | import static io.netty.handler.timeout.IdleState.READER_IDLE; 8 | 9 | public class MyChatServerIdleHandler extends ChannelInboundHandlerAdapter { 10 | 11 | 12 | @Override 13 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 14 | if(evt instanceof IdleStateEvent) { 15 | IdleStateEvent event = (IdleStateEvent)evt; 16 | if (event.state() == READER_IDLE) { 17 | System.out.println("client " + ctx.channel().remoteAddress() + " is inactive to close"); 18 | ctx.channel().close(); 19 | } 20 | } else { 21 | super.userEventTriggered(ctx, evt); 22 | } 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/linling/netty/heartbeat/server/MyChatServerInitializer.java: -------------------------------------------------------------------------------- 1 | package com.linling.netty.heartbeat.server; 2 | 3 | import com.linling.netty.heartbeat.codec.MyChatDecoder; 4 | import com.linling.netty.heartbeat.codec.MyChatEncoder; 5 | import io.netty.channel.ChannelInitializer; 6 | import io.netty.channel.socket.SocketChannel; 7 | import io.netty.handler.codec.string.StringDecoder; 8 | import io.netty.handler.codec.string.StringEncoder; 9 | import io.netty.handler.timeout.IdleStateHandler; 10 | 11 | import java.nio.charset.Charset; 12 | import java.util.concurrent.TimeUnit; 13 | 14 | import static com.linling.netty.heartbeat.common.MyChatContants.SERVER_READ_TIME; 15 | 16 | public class MyChatServerInitializer extends ChannelInitializer { 17 | 18 | Charset charset = Charset.forName("utf-8"); 19 | 20 | @Override 21 | protected void initChannel(SocketChannel ch) throws Exception { 22 | ch.pipeline() 23 | .addLast("idleStateHandler", new IdleStateHandler(SERVER_READ_TIME, 0, 0, TimeUnit.SECONDS)) 24 | .addLast("myChatServerIdleHandler", new MyChatServerIdleHandler()) 25 | .addLast("myChatDecoder", new MyChatDecoder()) 26 | .addLast("myChatEncoder", new MyChatEncoder()) 27 | .addLast("stringDecoder", new StringDecoder(charset)) 28 | .addLast("stringEncoder", new StringEncoder(charset)) 29 | .addLast("myChatServerHandler", new MyChatServerHandler()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/linling/netty/trafficshaping/MyClient.java: -------------------------------------------------------------------------------- 1 | package com.linling.netty.trafficshaping; 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 | public class MyClient { 10 | 11 | public static void main(String[] args) throws Exception { 12 | 13 | EventLoopGroup group = new NioEventLoopGroup(); 14 | 15 | try { 16 | Bootstrap client = new Bootstrap(); 17 | client.group(group).channel(NioSocketChannel.class).handler(new MyClientInitializer()); 18 | 19 | ChannelFuture channelFuture = client.connect("127.0.0.1", 5566).sync(); 20 | channelFuture.channel().closeFuture().sync(); 21 | } finally { 22 | group.shutdownGracefully(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/linling/netty/trafficshaping/MyClientHandler.java: -------------------------------------------------------------------------------- 1 | package com.linling.netty.trafficshaping; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.channel.SimpleChannelInboundHandler; 5 | 6 | import java.util.UUID; 7 | 8 | public class MyClientHandler extends SimpleChannelInboundHandler { 9 | 10 | @Override 11 | protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { 12 | System.out.println("receive server msg. length : " + msg.length()); 13 | ctx.write(UUID.randomUUID().toString(), ctx.voidPromise()); 14 | } 15 | 16 | @Override 17 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 18 | cause.printStackTrace(); 19 | ctx.channel().close(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/linling/netty/trafficshaping/MyClientInitializer.java: -------------------------------------------------------------------------------- 1 | package com.linling.netty.trafficshaping; 2 | 3 | import io.netty.channel.ChannelInitializer; 4 | import io.netty.channel.socket.SocketChannel; 5 | import io.netty.handler.codec.LengthFieldBasedFrameDecoder; 6 | import io.netty.handler.codec.LengthFieldPrepender; 7 | import io.netty.handler.codec.string.StringDecoder; 8 | import io.netty.handler.codec.string.StringEncoder; 9 | import io.netty.handler.traffic.ChannelTrafficShapingHandler; 10 | 11 | import java.nio.charset.Charset; 12 | 13 | public class MyClientInitializer extends ChannelInitializer { 14 | 15 | Charset utf8 = Charset.forName("utf-8"); 16 | 17 | final int M = 1024 * 1024; 18 | 19 | @Override 20 | protected void initChannel(SocketChannel ch) throws Exception { 21 | ChannelTrafficShapingHandler channelTrafficShapingHandler = new ChannelTrafficShapingHandler(10 * M, 1 * M); 22 | ch.pipeline() 23 | .addLast("channelTrafficShapingHandler",channelTrafficShapingHandler) 24 | .addLast("lengthFieldBasedFrameDecoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4, true)) 25 | .addLast("lengthFieldPrepender", new LengthFieldPrepender(4, 0)) 26 | .addLast("stringDecoder", new StringDecoder(utf8)) 27 | .addLast("stringEncoder", new StringEncoder(utf8)) 28 | .addLast("myClientHandler", new MyClientHandler()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/linling/netty/trafficshaping/MyServer.java: -------------------------------------------------------------------------------- 1 | package com.linling.netty.trafficshaping; 2 | 3 | import io.netty.bootstrap.ServerBootstrap; 4 | import io.netty.channel.ChannelFuture; 5 | import io.netty.channel.EventLoopGroup; 6 | import io.netty.channel.WriteBufferWaterMark; 7 | import io.netty.channel.nio.NioEventLoopGroup; 8 | import io.netty.channel.socket.nio.NioServerSocketChannel; 9 | 10 | import static io.netty.channel.ChannelOption.WRITE_BUFFER_WATER_MARK; 11 | 12 | public class MyServer { 13 | 14 | final static int M = 1024 * 1024; 15 | 16 | public static void main(String[] args) throws Exception { 17 | 18 | EventLoopGroup bossGroup = new NioEventLoopGroup(1); 19 | EventLoopGroup workerGroup = new NioEventLoopGroup(); 20 | 21 | try { 22 | ServerBootstrap server = new ServerBootstrap(); 23 | server.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new MyServerInitializer()); 24 | 25 | // server.childOption(WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(30 * M, 50 * M)); 26 | 27 | ChannelFuture channelFuture = server.bind(5566).sync(); 28 | channelFuture.channel().closeFuture().sync(); 29 | } finally { 30 | bossGroup.shutdownGracefully(); 31 | workerGroup.shutdownGracefully(); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/linling/netty/trafficshaping/MyServerChunkHandler.java: -------------------------------------------------------------------------------- 1 | package com.linling.netty.trafficshaping; 2 | 3 | import com.sun.xml.internal.messaging.saaj.util.ByteInputStream; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.channel.*; 6 | import io.netty.handler.stream.ChunkedStream; 7 | import io.netty.util.ReferenceCountUtil; 8 | 9 | public class MyServerChunkHandler extends ChannelOutboundHandlerAdapter { 10 | 11 | @Override 12 | public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { 13 | if(msg instanceof ByteBuf) { 14 | ByteBuf buf = (ByteBuf)msg; 15 | ByteInputStream in = new ByteInputStream(); 16 | byte[] data = null; 17 | if(buf.hasArray()) { 18 | System.out.println("+++ is array"); 19 | data = buf.array().clone(); 20 | } else { 21 | System.out.println("--- is direct"); 22 | data = new byte[buf.readableBytes()]; 23 | buf.writeBytes(data); 24 | 25 | } 26 | // System.out.println("===== data length : " + data.length); 27 | in.setBuf(data); 28 | ChunkedStream stream = new ChunkedStream(in); 29 | 30 | ReferenceCountUtil.release(msg); 31 | ctx.write(stream, promise); 32 | } else { 33 | super.write(ctx, msg, promise); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/linling/netty/trafficshaping/MyServerCommonHandler.java: -------------------------------------------------------------------------------- 1 | package com.linling.netty.trafficshaping; 2 | 3 | import io.netty.channel.*; 4 | 5 | import java.util.Optional; 6 | import java.util.concurrent.atomic.AtomicLong; 7 | import java.util.function.Consumer; 8 | 9 | public abstract class MyServerCommonHandler extends SimpleChannelInboundHandler { 10 | 11 | protected final int M = 1024 * 1024; 12 | protected final int KB = 1024; 13 | protected String tempStr; 14 | protected AtomicLong consumeMsgLength; 15 | protected Runnable counterTask; 16 | private long priorProgress; 17 | protected boolean sentFlag; 18 | 19 | @Override 20 | public void handlerAdded(ChannelHandlerContext ctx) throws Exception { 21 | consumeMsgLength = new AtomicLong(); 22 | counterTask = () -> { 23 | while (true) { 24 | try { 25 | Thread.sleep(1000); 26 | } catch (InterruptedException e) { 27 | 28 | } 29 | 30 | long length = consumeMsgLength.getAndSet(0); 31 | System.out.println("*** rate(KB/S):" + (length / KB)); 32 | } 33 | }; 34 | StringBuilder builder = new StringBuilder(); 35 | for (int i = 0; i < M; i++) { 36 | builder.append("abcdefghijklmnopqrstuvwxyz"); 37 | } 38 | tempStr = builder.toString(); 39 | super.handlerAdded(ctx); 40 | } 41 | 42 | @Override 43 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 44 | sentData(ctx); 45 | new Thread(counterTask).start(); 46 | } 47 | 48 | protected ChannelProgressivePromise getChannelProgressivePromise(ChannelHandlerContext ctx, Consumer completedAction) { 49 | ChannelProgressivePromise channelProgressivePromise = ctx.newProgressivePromise(); 50 | channelProgressivePromise.addListener(new ChannelProgressiveFutureListener(){ 51 | @Override 52 | public void operationProgressed(ChannelProgressiveFuture future, long progress, long total) throws Exception { 53 | consumeMsgLength.addAndGet(progress - priorProgress); 54 | priorProgress = progress; 55 | } 56 | 57 | @Override 58 | public void operationComplete(ChannelProgressiveFuture future) throws Exception { 59 | sentFlag = false; 60 | if(future.isSuccess()){ 61 | System.out.println("成功发送完成!"); 62 | priorProgress -= 26 * M; 63 | Optional.ofNullable(completedAction).ifPresent(action -> action.accept(future)); 64 | } else { 65 | System.out.println("发送失败!!!!!"); 66 | future.cause().printStackTrace(); 67 | } 68 | } 69 | }); 70 | return channelProgressivePromise; 71 | } 72 | 73 | protected abstract void sentData(ChannelHandlerContext ctx); 74 | 75 | @Override 76 | protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { 77 | System.out.println("===== receive client msg : " + msg); 78 | } 79 | 80 | @Override 81 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 82 | cause.printStackTrace(); 83 | ctx.channel().close(); 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/linling/netty/trafficshaping/MyServerInitializer.java: -------------------------------------------------------------------------------- 1 | package com.linling.netty.trafficshaping; 2 | 3 | import com.linling.netty.trafficshaping.oom.MyServerHandlerForOOM; 4 | import com.linling.netty.trafficshaping.oom.MyServerHandlerForSolveOOM; 5 | import com.linling.netty.trafficshaping.plain.MyServerHandlerForPlain; 6 | import io.netty.channel.ChannelInitializer; 7 | import io.netty.channel.socket.SocketChannel; 8 | import io.netty.handler.codec.LengthFieldBasedFrameDecoder; 9 | import io.netty.handler.codec.LengthFieldPrepender; 10 | import io.netty.handler.codec.string.StringDecoder; 11 | import io.netty.handler.codec.string.StringEncoder; 12 | import io.netty.handler.stream.ChunkedWriteHandler; 13 | import io.netty.handler.traffic.GlobalTrafficShapingHandler; 14 | 15 | import java.nio.charset.Charset; 16 | 17 | public class MyServerInitializer extends ChannelInitializer { 18 | 19 | Charset utf8 = Charset.forName("utf-8"); 20 | final int M = 1024 * 1024; 21 | 22 | @Override 23 | protected void initChannel(SocketChannel ch) throws Exception { 24 | 25 | GlobalTrafficShapingHandler globalTrafficShapingHandler = new GlobalTrafficShapingHandler(ch.eventLoop().parent(), 10 * M, 50 * M); 26 | // globalTrafficShapingHandler.setMaxGlobalWriteSize(50 * M); 27 | // globalTrafficShapingHandler.setMaxWriteSize(5 * M); 28 | 29 | ch.pipeline() 30 | .addLast("LengthFieldBasedFrameDecoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4, true)) 31 | .addLast("LengthFieldPrepender", new LengthFieldPrepender(4, 0)) 32 | .addLast("GlobalTrafficShapingHandler", globalTrafficShapingHandler) 33 | .addLast("chunkedWriteHandler", new ChunkedWriteHandler()) 34 | .addLast("myServerChunkHandler", new MyServerChunkHandler()) 35 | .addLast("StringDecoder", new StringDecoder(utf8)) 36 | .addLast("StringEncoder", new StringEncoder(utf8)) 37 | .addLast("myServerHandler", new MyServerHandlerForPlain()); 38 | // .addLast("myServerHandler", new MyServerHandlerForOOM()); 39 | // .addLast("myServerHandler", new MyServerHandlerForSolveOOM()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/linling/netty/trafficshaping/oom/MyServerHandlerForOOM.java: -------------------------------------------------------------------------------- 1 | package com.linling.netty.trafficshaping.oom; 2 | 3 | import com.linling.netty.trafficshaping.MyServerCommonHandler; 4 | import io.netty.channel.ChannelHandlerContext; 5 | 6 | /** 7 | * 因为写操作是一个I/O操作,当你在非NioEventLoop线程上执行了Channel的I/O操作的话,该操作会封装为一个task 被提交至NioEventLoop的任务队列中,以使得I/O操作最终是NioEventLoop线程上得到执行。 8 | 而提交这个任务的流程,仅会对ByteBuf、ByteBufHolder或者FileRegion对象进行真实数据大小的估计(其他情况默认估计大小为8 bytes),并将估计后的数据大小值对该ChannelOutboundBuffer的totalPendingSize属性值进行累加。而totalPendingSize同WriteBufferWaterMark一起来控制着Channel的unwritable。所以,如果你在一个非NioEventLoop线程中不断地发送一个非ByteBuf、ByteBufHolder或者FileRegion对象的大数据包时,最终就会导致NioEventLoop线程在真实执行这些task时发送OOM。 9 | * 【Caused by: io.netty.util.internal.OutOfDirectMemoryError: failed to allocate 81788928 byte(s) of direct memory (used: 916455424, max: 954728448)】 10 | */ 11 | public class MyServerHandlerForOOM extends MyServerCommonHandler { 12 | 13 | @Override 14 | protected void sentData(ChannelHandlerContext ctx) { 15 | 16 | new Thread(() -> { 17 | while (true) { 18 | // System.out.println("send msg"); 19 | ctx.writeAndFlush(tempStr, getChannelProgressivePromise(ctx, null)); 20 | try { 21 | Thread.sleep(500); 22 | } catch (InterruptedException e) { 23 | e.printStackTrace(); 24 | } 25 | } 26 | // ctx.writeAndFlush(tempStr, getChannelProgressivePromise(ctx, null)); 27 | }).start(); 28 | 29 | } 30 | 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/linling/netty/trafficshaping/oom/MyServerHandlerForSolveOOM.java: -------------------------------------------------------------------------------- 1 | package com.linling.netty.trafficshaping.oom; 2 | 3 | import com.linling.netty.trafficshaping.MyServerCommonHandler; 4 | import io.netty.channel.ChannelHandlerContext; 5 | 6 | /** 7 | * 因为写操作是一个I/O操作,当你在非NioEventLoop线程上执行了Channel的I/O操作的话,该操作会封装为一个task 被提交至NioEventLoop的任务队列中,以使得I/O操作最终是NioEventLoop线程上得到执行。 8 | 而提交这个任务的流程,仅会对ByteBuf、ByteBufHolder或者FileRegion对象进行真实数据大小的估计(其他情况默认估计大小为8 bytes),并将估计后的数据大小值对该ChannelOutboundBuffer的totalPendingSize属性值进行累加。而totalPendingSize同WriteBufferWaterMark一起来控制着Channel的unwritable。所以,如果你在一个非NioEventLoop线程中不断地发送一个非ByteBuf、ByteBufHolder或者FileRegion对象的大数据包时,最终就会导致NioEventLoop线程在真实执行这些task时发送OOM。 9 | * 【Caused by: io.netty.util.internal.OutOfDirectMemoryError: failed to allocate 81788928 byte(s) of direct memory (used: 916455424, max: 954728448)】 10 | */ 11 | public class MyServerHandlerForSolveOOM extends MyServerCommonHandler { 12 | 13 | @Override 14 | protected void sentData(ChannelHandlerContext ctx) { 15 | 16 | while (true) { 17 | if(ctx.channel().isWritable()) { 18 | System.out.println("==="); 19 | ctx.writeAndFlush(tempStr, getChannelProgressivePromise(ctx, null)); 20 | } else { 21 | // System.out.println("#######"); 22 | break; 23 | } 24 | } 25 | } 26 | 27 | 28 | @Override 29 | public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception { 30 | if(ctx.channel().isWritable()) { 31 | System.out.println(" ###### 重新开始写数据 ######"); 32 | sentData(ctx); 33 | } else { 34 | // System.out.println(" ===== 写操作暂停 ====="); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/linling/netty/trafficshaping/plain/MyServerHandlerForPlain.java: -------------------------------------------------------------------------------- 1 | package com.linling.netty.trafficshaping.plain; 2 | 3 | import com.linling.netty.trafficshaping.MyServerCommonHandler; 4 | import io.netty.channel.ChannelHandlerContext; 5 | 6 | public class MyServerHandlerForPlain extends MyServerCommonHandler { 7 | 8 | @Override 9 | protected void sentData(ChannelHandlerContext ctx) { 10 | sentFlag = true; 11 | ctx.writeAndFlush(tempStr, getChannelProgressivePromise(ctx, future -> { 12 | if(ctx.channel().isWritable() && !sentFlag) { 13 | sentData(ctx); 14 | } 15 | })); 16 | } 17 | 18 | @Override 19 | public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception { 20 | if(ctx.channel().isWritable()) { 21 | System.out.println(" ###### 重新开始写数据 ######"); 22 | if (!sentFlag) { 23 | System.out.println(" ++++++++ 发送新数据包 ++++++++"); 24 | sentData(ctx); 25 | } 26 | } else { 27 | // System.out.println(" ===== 写暂停 ====="); 28 | } 29 | } 30 | } 31 | --------------------------------------------------------------------------------