/* * Copyright 2012 The Netty Project * * The Netty Project licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package io.netty.testsuite.transport.udt; import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.group.ChannelGroup; import io.netty.channel.group.DefaultChannelGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.udt.UdtChannel; import io.netty.channel.udt.nio.NioUdtProvider; import io.netty.handler.codec.DelimiterBasedFrameDecoder; import io.netty.handler.codec.Delimiters; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import io.netty.util.CharsetUtil; import io.netty.util.concurrent.DefaultThreadFactory; import io.netty.util.concurrent.GlobalEventExecutor; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.concurrent.ThreadFactory; import static org.junit.Assert.*; /** * Verify UDT connect/disconnect life cycle. */ public class UDTClientServerConnectionTest { static class Client implements Runnable { static final Logger log = LoggerFactory.getLogger(Client.class); final String host; final int port; volatile Channel channel; volatile boolean isRunning; volatile boolean isShutdown; Client(final String host, final int port) { this.host = host; this.port = port; } @Override public void run() { final Bootstrap boot = new Bootstrap(); final ThreadFactory clientFactory = new DefaultThreadFactory("client"); final NioEventLoopGroup connectGroup = new NioEventLoopGroup(1, clientFactory, NioUdtProvider.BYTE_PROVIDER); try { boot.group(connectGroup) .channelFactory(NioUdtProvider.BYTE_CONNECTOR) .handler(new ChannelInitializer<UdtChannel>() { @Override protected void initChannel(final UdtChannel ch) throws Exception { final ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter())); pipeline.addLast("decoder", new StringDecoder( CharsetUtil.UTF_8)); pipeline.addLast("encoder", new StringEncoder( CharsetUtil.UTF_8)); pipeline.addLast("handler", new ClientHandler()); } }); channel = boot.connect(host, port).sync().channel(); isRunning = true; log.info("Client ready."); waitForRunning(false); log.info("Client closing..."); channel.close().sync(); isShutdown = true; log.info("Client is done."); } catch (final Throwable e) { log.error("Client failed.", e); } finally { connectGroup.shutdownGracefully().syncUninterruptibly(); } } void shutdown() { isRunning = false; } void waitForActive(final boolean isActive) throws Exception { for (int k = 0; k < WAIT_COUNT; k++) { Thread.sleep(WAIT_SLEEP); final ClientHandler handler = channel.pipeline().get( ClientHandler.class); if (handler != null && isActive == handler.isActive) { return; } } } void waitForRunning(final boolean isRunning) throws Exception { for (int k = 0; k < WAIT_COUNT; k++) { if (isRunning == this.isRunning) { return; } Thread.sleep(WAIT_SLEEP); } } private void waitForShutdown() throws Exception { for (int k = 0; k < WAIT_COUNT; k++) { if (isShutdown) { return; } Thread.sleep(WAIT_SLEEP); } } } static class ClientHandler extends SimpleChannelInboundHandler<Object> { static final Logger log = LoggerFactory.getLogger(ClientHandler.class); volatile boolean isActive; @Override public void channelActive(final ChannelHandlerContext ctx) throws Exception { isActive = true; log.info("Client active {}", ctx.channel()); super.channelActive(ctx); } @Override public void channelInactive(final ChannelHandlerContext ctx) throws Exception { isActive = false; log.info("Client inactive {}", ctx.channel()); super.channelInactive(ctx); } @Override public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) throws Exception { log.warn("Client unexpected exception from downstream.", cause); ctx.close(); } @Override public void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { log.info("Client received: " + msg); } } static class Server implements Runnable { static final Logger log = LoggerFactory.getLogger(Server.class); final ChannelGroup group = new DefaultChannelGroup("server group", GlobalEventExecutor.INSTANCE); final String host; final int port; volatile Channel channel; volatile boolean isRunning; volatile boolean isShutdown; Server(final String host, final int port) { this.host = host; this.port = port; } @Override public void run() { final ServerBootstrap boot = new ServerBootstrap(); final ThreadFactory acceptFactory = new DefaultThreadFactory("accept"); final ThreadFactory serverFactory = new DefaultThreadFactory("server"); final NioEventLoopGroup acceptGroup = new NioEventLoopGroup(1, acceptFactory, NioUdtProvider.BYTE_PROVIDER); final NioEventLoopGroup connectGroup = new NioEventLoopGroup(1, serverFactory, NioUdtProvider.BYTE_PROVIDER); try { boot.group(acceptGroup, connectGroup) .channelFactory(NioUdtProvider.BYTE_ACCEPTOR) .childHandler(new ChannelInitializer<UdtChannel>() { @Override protected void initChannel(final UdtChannel ch) throws Exception { final ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter())); pipeline.addLast("decoder", new StringDecoder( CharsetUtil.UTF_8)); pipeline.addLast("encoder", new StringEncoder( CharsetUtil.UTF_8)); pipeline.addLast("handler", new ServerHandler( group)); } }); channel = boot.bind(port).sync().channel(); isRunning = true; log.info("Server ready."); waitForRunning(false); log.info("Server closing acceptor..."); channel.close().sync(); log.info("Server closing connectors..."); group.close().sync(); isShutdown = true; log.info("Server is done."); } catch (final Throwable e) { log.error("Server failure.", e); } finally { acceptGroup.shutdownGracefully(); connectGroup.shutdownGracefully(); acceptGroup.terminationFuture().syncUninterruptibly(); connectGroup.terminationFuture().syncUninterruptibly(); } } void shutdown() { isRunning = false; } void waitForActive(final boolean isActive) throws Exception { for (int k = 0; k < WAIT_COUNT; k++) { Thread.sleep(WAIT_SLEEP); if (isActive) { for (final Channel channel : group) { final ServerHandler handler = channel.pipeline().get( ServerHandler.class); if (handler != null && handler.isActive) { return; } } } else { if (group.isEmpty()) { return; } } } } void waitForRunning(final boolean isRunning) throws Exception { for (int k = 0; k < WAIT_COUNT; k++) { if (isRunning == this.isRunning) { return; } Thread.sleep(WAIT_SLEEP); } } void waitForShutdown() throws Exception { for (int k = 0; k < WAIT_COUNT; k++) { if (isShutdown) { return; } Thread.sleep(WAIT_SLEEP); } } } static class ServerHandler extends SimpleChannelInboundHandler<Object> { static final Logger log = LoggerFactory.getLogger(ServerHandler.class); final ChannelGroup group; volatile boolean isActive; ServerHandler(final ChannelGroup group) { this.group = group; } @Override public void channelActive(final ChannelHandlerContext ctx) throws Exception { group.add(ctx.channel()); isActive = true; log.info("Server active : {}", ctx.channel()); super.channelActive(ctx); } @Override public void channelInactive(final ChannelHandlerContext ctx) throws Exception { group.remove(ctx.channel()); isActive = false; log.info("Server inactive: {}", ctx.channel()); super.channelInactive(ctx); } @Override public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) { log.warn("Server close on exception.", cause); ctx.close(); } @Override public void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { log.info("Server received: " + msg); } } static final Logger log = LoggerFactory .getLogger(UDTClientServerConnectionTest.class); /** * Maximum wait time is 5 seconds. * <p> * wait-time = {@code WAIT_COUNT} * {@value #WAIT_SLEEP} */ static final int WAIT_COUNT = 50; static final int WAIT_SLEEP = 100; /** * Verify UDT client/server connect and disconnect. */ @Test public void connection() throws Exception { final String host = "localhost"; final int port = 1234; log.info("Starting server."); final Server server = new Server(host, port); final Thread serverTread = new Thread(server, "server-*"); serverTread.start(); server.waitForRunning(true); assertTrue(server.isRunning); log.info("Starting client."); final Client client = new Client(host, port); final Thread clientThread = new Thread(client, "client-*"); clientThread.start(); client.waitForRunning(true); assertTrue(client.isRunning); log.info("Wait till connection is active."); client.waitForActive(true); server.waitForActive(true); log.info("Verify connection is active."); assertEquals("group must have one", 1, server.group.size()); log.info("Stopping client."); client.shutdown(); client.waitForShutdown(); assertTrue(client.isShutdown); log.info("Wait till connection is inactive."); client.waitForActive(false); server.waitForActive(false); log.info("Verify connection is inactive."); assertEquals("group must be empty", 0, server.group.size()); log.info("Stopping server."); server.shutdown(); server.waitForShutdown(); assertTrue(server.isShutdown); log.info("Finished server."); } }