I try to create a proxy and then pass all the trafic other handlers in Netty. I understand I should manage the references to ByteBuf but I cannot understand how to do it. My example and the exception is below.
Initializer:
public class HexDumpProxyInitializer extends ChannelInitializer<SocketChannel> {
private final SslContext sslCtx;
private final String remoteHost;
private final int remotePort;
public HexDumpProxyInitializer(SslContext sslCtx, String remoteHost, int remotePort) {
this.remoteHost = remoteHost;
this.remotePort = remotePort;
this.sslCtx = sslCtx;
}
public HexDumpProxyInitializer(String remoteHost, int remotePort) {
this.remoteHost = remoteHost;
this.remotePort = remotePort;
this.sslCtx = null;
}
#Override
public void initChannel(SocketChannel ch) {
ChannelPipeline p = ch.pipeline();
if (sslCtx != null) {
p.addLast(sslCtx.newHandler(ch.alloc()));
}
p.addLast(new HexDumpProxyFrontendHandler(remoteHost, remotePort));
p.addLast(new InboundPrinterHandler());
}
}
HexDumpProxyFrontendHandler
public class HexDumpProxyFrontendHandler extends ChannelInboundHandlerAdapter {
private final String remoteHost;
private final int remotePort;
private Channel outboundChannel;
public HexDumpProxyFrontendHandler(String remoteHost, int remotePort) {
this.remoteHost = remoteHost;
this.remotePort = remotePort;
}
#Override
public void channelActive(ChannelHandlerContext ctx) {
final Channel inboundChannel = ctx.channel();
// Start the connection attempt.
Bootstrap b = new Bootstrap();
b.group(inboundChannel.eventLoop())
.channel(ctx.channel().getClass())
.handler(new HexDumpProxyBackendHandler(inboundChannel))
.option(ChannelOption.AUTO_READ, false);
ChannelFuture f = b.connect(remoteHost, remotePort);
outboundChannel = f.channel();
f.addListener(new ChannelFutureListener() {
#Override
public void operationComplete(ChannelFuture future) {
if (future.isSuccess()) {
// connection complete start to read first data
inboundChannel.read();
} else {
// Close the connection if the connection attempt has failed.
inboundChannel.close();
}
}
});
}
#Override
public void channelRead(final ChannelHandlerContext ctx, Object msg) {
if (outboundChannel.isActive()) {
outboundChannel.writeAndFlush(msg).addListener(new ChannelFutureListener() {
#Override
public void operationComplete(ChannelFuture future) {
if (future.isSuccess()) {
// was able to flush out data, start to read the next chunk
ctx.channel().read();
} else {
future.channel().close();
}
}
});
}
ctx.fireChannelRead(msg);
}
#Override
public void channelInactive(ChannelHandlerContext ctx) {
if (outboundChannel != null) {
closeOnFlush(outboundChannel);
}
}
#Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
closeOnFlush(ctx.channel());
}
static void closeOnFlush(Channel ch) {
if (ch.isActive()) {
ch.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
}
}
}
InboundPrinterHandler
public class InboundPrinterHandler extends ChannelInboundHandlerAdapter {
#Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf bb = null;
bb = (ByteBuf) msg;
System.out.println("INBOUND:\n\n"+bb.toString(Charset.defaultCharset()));
System.out.println("\n\n\n");
}
#Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
}
}
The Exception
io.netty.util.IllegalReferenceCountException: refCnt: 0
at io.netty.buffer.AbstractByteBuf.ensureAccessible(AbstractByteBuf.java:1407)
at io.netty.buffer.AbstractByteBuf.checkIndex(AbstractByteBuf.java:1353)
at io.netty.buffer.PooledUnsafeDirectByteBuf.internalNioBuffer(PooledUnsafeDirectByteBuf.java:331)
at io.netty.buffer.ByteBufUtil.decodeString(ByteBufUtil.java:614)
at io.netty.buffer.AbstractByteBuf.toString(AbstractByteBuf.java:1213)
at io.netty.buffer.AbstractByteBuf.toString(AbstractByteBuf.java:1208)
at com.netas.sv.proxy.InboundPrinterHandler.channelRead(InboundPrinterHandler.java:16)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:373)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:359)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:351)
at com.netas.sv.proxy.HexDumpProxyFrontendHandler.channelRead(HexDumpProxyFrontendHandler.java:67)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:373)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:359)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:351)
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1334)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:373)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:359)
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:926)
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:129)
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:651)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:574)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:488)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:450)
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:873)
at io.netty.util.concurrent.DefaultThreadFactory$DefaultRunnableDecorator.run(DefaultThreadFactory.java:144)
at java.lang.Thread.run(Thread.java:745)
if (outboundChannel.isActive()) {
outboundChannel.writeAndFlush(msg).addListener(new ChannelFutureListener() {
// Snip
}
ctx.fireChannelRead(msg);
After you pass away the ByteBuf to the other channel, it is the other channels responsibility to decrement the refcount again. Because the other channel has now decremented the the refcount, it is now unuseable.
The best way to solve this is manually incrementing the value before you pass the traffic to the other channel using .retain():
outboundChannel.writeAndFlush(msg.retain()).addListener(new ChannelFutureListener() {
// Your remaining code
Related
I implemented a basic Server program using Netty. OMS sends group of message strings to the server and the server accepts and displays it to the terminal.I can't receive the message.It is showing the below Exceptions instead.I think the decoded size has exceeded the maximum limit.
If this is correct how to decode any sized string?? That is,is there anyway that dynamically takes the length of the string and receives the exact string from the Buffer?
My Client Code:
public class EchoClient {
private final String host;
private final int port;
public EchoClient(String host, int port) {
this.host = host;
this.port = port;
}
public void start() throws Exception{
EventLoopGroup group = new NioEventLoopGroup();
try{
Bootstrap b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class)
.remoteAddress(new InetSocketAddress(host, port))
.handler(new ChannelInitializer<SocketChannel>(){
#Override
public void initChannel(SocketChannel ch) throws Exception{
ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(204800, 0, 4, 0, 4))
.addLast(new LengthFieldPrepender(4))
.addLast(new EchoClientHandler());
}
});
ChannelFuture future = b.connect().sync();
future.channel().closeFuture().sync();
}
finally {
group.shutdownGracefully().sync();
}
}
public static void main (String [] args) throws Exception {
new EchoClient("127.0.0.1", 11235).start();
}
}
My ClientHandler :
public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf>{
#Override
public void channelActive(ChannelHandlerContext ctx){
System.out.println("Connected");
int i=0;
while(i<100){
ctx.writeAndFlush(Unpooled.copiedBuffer("8=FIX.4.29=0007935=A49=TTDS68AO56=RaviEx34=152=20170427-14:05:04.572108=60141=Y98=010=242\n",
CharsetUtil.UTF_8));
i++;
}
}
#Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
System.out.println("Client received: " + in.toString(CharsetUtil.UTF_8));
}
#Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause){
cause.printStackTrace();
ctx.close();
}
}
My Server:
public class EchoServer{
private final int port;
public EchoServer(int port) {
this.port = port;
}
public void start() throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(group)
.channel(NioServerSocketChannel.class)
.localAddress(new InetSocketAddress(port))
.childHandler(new ChannelInitializer<SocketChannel>() {
#Override
public void initChannel(SocketChannel ch) throws Exception {
System.out.println("New client connected: " + ch.localAddress());
ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(204800, 0, 4, 0, 4))
.addLast(new LengthFieldPrepender(4))
.addLast(new EchoServerHandler());
}
});
ChannelFuture f = b.bind().sync();
f.channel().closeFuture().sync();
}
finally {
group.shutdownGracefully().sync();
}
}
public static void main (String [] args) throws Exception {
new EchoServer(11235).start();
}
}
MyEchoSeverHandler:
public class EchoServerHandler extends ChannelInboundHandlerAdapter{
#Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf in = (ByteBuf) msg;
System.out.println(in.toString(CharsetUtil.UTF_8));
}
#Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
I'm learning Netty and prototyping a simple app which sends an object over TCP. My issue is that when I call Channel.write from the server side with my message, it doesn't seem to reach the handlers in the pipeline. When I send a message from client to server, it works as expected.
Here's the code.
The server:
public class Main {
private int serverPort;
private EventLoopGroup bossGroup;
private EventLoopGroup workerGroup;
private ServerBootstrap boot;
private ChannelFuture future;
private SomeDataChannelDuplexHandler duplex;
private Channel ch;
public Main(int serverPort) {
this.serverPort = serverPort;
}
public void initialise() {
boot = new ServerBootstrap();
bossGroup = new NioEventLoopGroup();
workerGroup = new NioEventLoopGroup();
boot.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
#Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast("idleStateHandler", new IdleStateHandler(0, 0, 2));
// Inbound
ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(65535, 0, 2, 0, 0));
ch.pipeline().addLast(new SomeDataDecoder());
// Outbound
ch.pipeline().addLast(new LengthFieldPrepender(2));
ch.pipeline().addLast(new SomeDataEncoder());
// In-Out
ch.pipeline().addLast(new SomeDataChannelDuplexHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
}
public void sendMessage() {
SomeData fd = new SomeData("hello", "localhost", 1234);
ChannelFuture future = ch.writeAndFlush(fd);
future.addListener(new ChannelFutureListener() {
#Override
public void operationComplete(ChannelFuture future) throws Exception {
if (!future.isSuccess()) {
System.out.println("send error: " + future.cause().toString());
} else {
System.out.println("send message ok");
}
}
});
}
public void startServer(){
try {
future = boot.bind(serverPort)
.sync()
.addListener(new ChannelFutureListener() {
#Override
public void operationComplete(ChannelFuture future) throws Exception {
ch = future.channel();
}
});
} catch (InterruptedException e) {
// log failure
}
}
public void stopServer() {
workerGroup.shutdownGracefully()
.addListener(e -> System.out.println("workerGroup shutdown"));
bossGroup.shutdownGracefully()
.addListener(e -> System.out.println("bossGroup shutdown"));
}
public static void main(String[] args) throws InterruptedException {
Main m = new Main(5000);
m.initialise();
m.startServer();
final Scanner scanner = new Scanner(System.in);
System.out.println("running.");
while (true) {
final String input = scanner.nextLine();
if ("q".equals(input.trim())) {
break;
} else {
m.sendMessage();
}
}
scanner.close();
m.stopServer();
}
}
The duplex channel handler:
public class SomeDataChannelDuplexHandler extends ChannelDuplexHandler {
#Override
public void channelActive(ChannelHandlerContext ctx) {
System.out.println("duplex channel active");
ctx.fireChannelActive();
}
#Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("duplex channelRead");
if (msg instanceof SomeData) {
SomeData sd = (SomeData) msg;
System.out.println("received: " + sd);
} else {
System.out.println("some other object");
}
ctx.fireChannelRead(msg);
}
#Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
#Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) evt;
if (event.state() == IdleState.ALL_IDLE) { // idle for no read and write
System.out.println("idle: " + event.state());
}
}
}
}
And finally the encoder (the decoder is similar):
public class SomeDataEncoder extends MessageToByteEncoder<SomeData> {
#Override
protected void encode(ChannelHandlerContext ctx, SomeData msg, ByteBuf out) throws Exception {
System.out.println("in encoder, msg = " + msg);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(msg.getName());
oos.writeObject(msg.getIp());
oos.writeInt(msg.getPort());
oos.close();
byte[] serialized = bos.toByteArray();
int size = serialized.length;
ByteBuf encoded = ctx.alloc().buffer(size);
encoded.writeBytes(bos.toByteArray());
out.writeBytes(encoded);
}
}
The client side:
public class Client {
String host = "10.188.36.66";
int port = 5000;
EventLoopGroup workerGroup = new NioEventLoopGroup();
ChannelFuture f;
private Channel ch;
public Client() {
}
public void startClient() throws InterruptedException {
Bootstrap boot = new Bootstrap();
boot.group(workerGroup);
boot.channel(NioSocketChannel.class);
boot.option(ChannelOption.SO_KEEPALIVE, true);
boot.handler(new ChannelInitializer<SocketChannel>() {
#Override
public void initChannel(SocketChannel ch) throws Exception {
// Inbound
ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(65535, 0, 2, 0, 0));
ch.pipeline().addLast(new SomeDataDecoder());
// Outbound
ch.pipeline().addLast(new LengthFieldPrepender(2));
ch.pipeline().addLast(new SomeDataEncoder());
// Handler
ch.pipeline().addLast(new SomeDataHandler());
}
});
// Start the client
f = boot.connect(host, port).sync();
f.addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) throws Exception {
System.out.println("connected to server");
ch = f.channel();
}
});
}
public void stopClient() {
workerGroup.shutdownGracefully();
}
private void writeMessage(String input) {
SomeData data = new SomeData("client", "localhost", 3333);
ChannelFuture fut = ch.writeAndFlush(data);
fut.addListener(new ChannelFutureListener() {
#Override
public void operationComplete(ChannelFuture future) throws Exception {
System.out.println("send message");
}
});
}
public static void main(String[] args) throws InterruptedException {
Client client = new Client();
client.startClient();
System.out.println("running.\n\n");
final Scanner scanner = new Scanner(System.in);
while (true) {
final String input = scanner.nextLine();
if ("q".equals(input.trim())) {
break;
} else {
client.writeMessage(input);
}
}
scanner.close();
client.stopClient(); //call this at some point to shutdown the client
}
}
and the handler:
public class SomeDataHandler extends SimpleChannelInboundHandler<SomeData> {
private ChannelHandlerContext ctx;
#Override
public void channelActive(ChannelHandlerContext ctx) {
System.out.println("connected");
this.ctx = ctx;
}
#Override
protected void channelRead0(ChannelHandlerContext ctx, SomeData msg) throws Exception {
System.out.println("got message: " + msg);
}
#Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
System.out.println("caught exception: " + cause.getMessage());
ctx.close();
}
}
When I send a message via the console on the server side, I get the output:
running.
duplex channel active
duplex read
idle: ALL_IDLE
idle: ALL_IDLE
send message ok
So it looks as though the message is sent but nothing is received on the client side.
When I do it from the client side I get (on the server console):
in decoder, numBytes in message = 31
duplex channelRead
received: SomeData [name=client, ip=localhost, port=3333]
which is what I expect.
So where's the problem? Is it something to do with using a ChannelDuplexHandler on the server side and a SimpleChannelInboundHandler on the client side? Is there something I need to call to kick the message down the pipeline?
UPDATE
I've added a check for future.isSuccess() in the server sendMessage method and I get
send error: java.lang.UnsupportedOperationException on the console.
(Posted on behalf of the OP).
For anyone who's interested, the problem was that I was trying to send the message on the server channel and not the normal channel. This post pointed me in the right direction.
I was using the following code to send messages from the Client to the Server:
Client class:
public class Client {
String host = "localhost";
int port = 14930;
private final ClientHandler clientHandler = new ClientHandler();
public Client(String host, int port) {
this.host = host;
this.port = port;
}
public void run() throws Exception {
try {
workerGroup = new NioEventLoopGroup();
Bootstrap b = new Bootstrap();
b.group(workerGroup);
b.channel(NioSocketChannel.class);
b.option(ChannelOption.SO_KEEPALIVE, true);
b.handler(new ChannelInitializer<SocketChannel>() {
#Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
pipeline.addLast(clientHandler);
}
}
);
ChannelFuture f = b.connect(host, port).sync();
f.channel().closeFuture().sync();
}
finally {
workerGroup.shutdownGracefully();
}
}
public void writeMessage(String msg) {
clientHandler.sendMessage(msg);
}
}
Handler class:
public class ClientHandler extends SimpleChannelInboundHandler<String> {
ChannelHandlerContext ctx;
public void sendMessage(String msgToSend) {
ctx.writeAndFlush(Unpooled.copiedBuffer(msgToSend, CharsetUtil.UTF_8));
}
#Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
this.ctx = ctx;
}
#Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
#Override
protected void channelRead0(ChannelHandlerContext arg0, String msg) throws Exception {
}
void channelInactive(ChannelHandlerContext ctx) throws Exception {
}
}
I am creating the Client like this:
Client client;
client = new Client(ipAddress, serverPort);
client.run();
client.writeMessage("random_text");
The Server:
public final class ChatServer {
public ChatServer(int PORT) throws Exception {
bossGroup = new NioEventLoopGroup();
workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChatServerInitializer());
b.bind(PORT).sync().channel().closeFuture().sync();
}
finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
ServerHandler:
public class ChatServerHandler extends SimpleChannelInboundHandler<String> {
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
Channel incoming = ctx.channel();
channels.add(ctx.channel());
}
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
Channel incoming = ctx.channel();
channels.remove(ctx.channel());
}
#Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
#Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
println(msg);
}
}
Method to send Message from Server to Client:
public void sendMessage(String message) {
if (message != "" && message != null) {
for (Channel c : channels) {
c.writeAndFlush(message + "\r\n");
}
}
}
The problem is that the Server does not get any Messages. I tried some debug steps, the Server adds the Client to its channels, and channelActive gets executed successfully on the Client side.
Instead of polling the variable the whole time you should try to transfer the communication to the handler (either your ChatClientInitializer or an extra handler that you add last). Give this handler a method that you call when a new message was entered. The handler then writes to the channel.
Maybe this example helps:
public class Client implements Runnable {
String host = "localhost";
int port = 9128;
private final ClientHandler clientHandler = new ClientHandler();
private boolean isRunning = false;
private ExecutorService executor = null;
public static void main(String[] args) {
Client client = new Client();
client.startClient();
client.writeMessage("random_text");
//client.stopClient(); //call this at some point to shutdown the client
}
public synchronized void startClient() {
if (!isRunning) {
executor = Executors.newFixedThreadPool(1);
executor.execute(this);
isRunning = true;
}
}
public synchronized boolean stopClient() {
boolean bReturn = true;
if (isRunning) {
if (executor != null) {
executor.shutdown();
try {
executor.shutdownNow();
if (executor.awaitTermination(calcTime(10, 0.66667), TimeUnit.SECONDS)) {
if (!executor.awaitTermination(calcTime(10, 0.33334), TimeUnit.SECONDS)) {
bReturn = false;
}
}
} catch (InterruptedException ie) {
executor.shutdownNow();
Thread.currentThread().interrupt();
} finally {
executor = null;
}
}
isRunning = false;
}
return bReturn;
}
private long calcTime(int nTime, double dValue) {
return (long) ((double) nTime * dValue);
}
#Override
public void run() {
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(workerGroup);
b.channel(NioSocketChannel.class);
b.option(ChannelOption.SO_KEEPALIVE, true);
b.handler(new ChannelInitializer<SocketChannel>() {
#Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
pipeline.addLast(clientHandler);
}
});
ChannelFuture f = b.connect(host, port).sync();
f.channel().closeFuture().sync();
} catch (InterruptedException ex) {
// do nothing
} finally {
workerGroup.shutdownGracefully();
}
}
public void writeMessage(String msg) {
clientHandler.sendMessage(msg);
}
}
A very basic ClientHandler:
public class ClientHandler extends SimpleChannelInboundHandler<String> {
ChannelHandlerContext ctx;
public void sendMessage(String msgToSend) {
if (ctx != null) {
ChannelFuture cf = ctx.write(Unpooled.copiedBuffer(msgToSend, CharsetUtil.UTF_8));
ctx.flush();
if (!cf.isSuccess()) {
System.out.println("Send failed: " + cf.cause());
}
} else {
//ctx not initialized yet. you were too fast. do something here
}
}
#Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
this.ctx = ctx;
}
#Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
#Override
protected void channelRead0(ChannelHandlerContext arg0, String msg) throws Exception {
}
#Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
}
}
I'm trying to modify the securechat example of netty to send bytes (byte array) instead of a string.But I'm unable to send any bytes to the server. what am I doing wrong? It works perfectly if an ObjectDecoder/Encoder is used, but I need raw bytes to be send through the wire.
If instead if a byte[], a ByteBuffer would also suffice, as long as The traffic consists of only those bytes within the buffer.
Can anyone please help?
Client.java
public class Client {
private final String host;
private final int port;
public Client(String host, int port) {
this.host = host;
this.port = port;
}
public void run() throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.handler(new ClientInitializer());
Channel ch = b.connect(host, port).sync().channel();
ChannelFuture lastWriteFuture = null;
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
for (;;) {
String line = in.readLine();
if (line == null) {
break;
}
lastWriteFuture = ch.write(new byte[]{1,2,3,4,5,6,7,8});
}
if (lastWriteFuture != null) {
lastWriteFuture.sync();
}
} finally {
group.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
new Client("localhost",6666).run();
}
}
ClientHandler
public class ClientHandler extends SimpleChannelInboundHandler<Byte[]> {
private static final Logger logger = Logger.getLogger(
ClientHandler.class.getName());
#Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
logger.log(
Level.WARNING,
"Unexpected exception from downstream.", cause);
ctx.close();
}
#Override
protected void channelRead0(ChannelHandlerContext chc, Byte[] i) throws Exception {
System.out.println(i[3]);
}
}
ClientInitializer
public class ClientInitializer extends ChannelInitializer<SocketChannel> {
#Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("frameDecoder",
new LengthFieldBasedFrameDecoder(1048576, 0, 4, 0, 4));
pipeline.addLast("bytesDecoder",
new ByteArrayDecoder());
pipeline.addLast("frameEncoder", new LengthFieldPrepender(4));
pipeline.addLast("bytesEncoder", new ByteArrayEncoder());
pipeline.addLast("handler", new ClientHandler());
}
}
Server
public class Server {
private final int port;
public Server(int port) {
this.port = port;
}
public void run() throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ServerInitializer());
b.bind(port).sync().channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
int port;
if (args.length > 0) {
port = Integer.parseInt(args[0]);
} else {
port = 6666;
}
new Server(port).run();
}
}
ServerHandler
public class ServerHandler extends SimpleChannelInboundHandler<Byte[]> {
private static final Logger logger = Logger.getLogger(
ServerHandler.class.getName());
static final ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
#Override
public void channelActive(final ChannelHandlerContext ctx) throws Exception {
channels.add(ctx.channel());
}
#Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
logger.log(
Level.WARNING,
"Unexpected exception from downstream.", cause);
ctx.close();
}
#Override
protected void channelRead0(ChannelHandlerContext ctx, Byte[] i) throws Exception {
for (Channel c: channels) {
if (c != ctx.channel()) {
c.writeAndFlush(i);
}
}
}
}
ServerInitializer
public class ServerInitializer extends ChannelInitializer<SocketChannel> {
#Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("frameDecoder",
new LengthFieldBasedFrameDecoder(1048576, 0, 4, 0, 4));
pipeline.addLast("bytesDecoder",
new ByteArrayDecoder());
pipeline.addLast("frameEncoder", new LengthFieldPrepender(4));
pipeline.addLast("bytesEncoder", new ByteArrayEncoder());
pipeline.addLast("handler", new ServerHandler());
}
}
You forgot to call channel.flush() in your client after channel.write(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }). Because it was never flushed, the server will never receive it. Alternatively, you can use writeAndFlush() which is a shortcut of write(...) and flush().
Just found this example need change to byte[], otherwise it will not be called.
public class ServerHandler extends SimpleChannelInboundHandler < byte[]> {
}
I want to write a netty based client. It should have method public String send(String msg); which should return response from the server or some future - doesen't matter. Also it should be multithreaded. Like this:
public class Client {
public static void main(String[] args) throws InterruptedException {
Client client = new Client();
}
private Channel channel;
public Client() throws InterruptedException {
EventLoopGroup loopGroup = new NioEventLoopGroup();
Bootstrap b = new Bootstrap();
b.group(loopGroup).channel(NioSocketChannel.class).handler(new ChannelInitializer<SocketChannel>() {
#Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringDecoder()).
addLast(new StringEncoder()).
addLast(new ClientHandler());
}
});
channel = b.connect("localhost", 9091).sync().channel();
}
public String sendMessage(String msg) {
channel.writeAndFlush(msg);
return ??????????;
}
}
And I don't get how can I retrieve response from server after I invoke writeAndFlush(); What should I do?
Also I use Netty 4.0.18.Final
Returning a Future<String> for the method is simple, we are going to implement the following method signature:
public Futute<String> sendMessage(String msg) {
The is relatively easy to do when you are known with the async programming structures. To solve the design problem, we are going to do the following steps:
When a message is written, add a Promise<String> to a ArrayBlockingQueue<Promise>
This will serve as a list of what messages have recently been send, and allows us to change our Future<String> objects return result.
When a message arrives back into the handler, resolve it against the head of the Queue
This allows us to get the correct future to change.
Update the state of the Promise<String>
We call promise.setSuccess() to finally set the state on the object, this will propagate back to the future object.
Example code
public class ClientHandler extends SimpleChannelInboundHandler<String> {
private ChannelHandlerContext ctx;
private BlockingQueue<Promise<String>> messageList = new ArrayBlockingQueue<>(16);
#Override
public void channelActive(ChannelHandlerContext ctx) {
super.channelActive(ctx);
this.ctx = ctx;
}
#Override
public void channelInactive(ChannelHandlerContext ctx) {
super.channelInactive(ctx);
synchronized(this){
Promise<String> prom;
while((prom = messageList.poll()) != null)
prom.setFailure(new IOException("Connection lost"));
messageList = null;
}
}
public Future<String> sendMessage(String message) {
if(ctx == null)
throw new IllegalStateException();
return sendMessage(message, ctx.executor().newPromise());
}
public Future<String> sendMessage(String message, Promise<String> prom) {
synchronized(this){
if(messageList == null) {
// Connection closed
prom.setFailure(new IllegalStateException());
} else if(messageList.offer(prom)) {
// Connection open and message accepted
ctx.writeAndFlush(message).addListener();
} else {
// Connection open and message rejected
prom.setFailure(new BufferOverflowException());
}
return prom;
}
}
#Override
protected void messageReceived(ChannelHandlerContext ctx, String msg) {
synchronized(this){
if(messageList != null) {
messageList.poll().setSuccess(msg);
}
}
}
}
Documentation breakdown
private ChannelHandlerContext ctx;
Used to store our reference to the ChannelHandlerContext, we use this so we can create promises
private BlockingQueue<Promise<String>> messageList = new ArrayBlockingQueue<>();
We keep the past messages in this list so we can change the result of the future
public void channelActive(ChannelHandlerContext ctx)
Called by netty when the connection becomes active. Init our variables here.
public void channelInactive(ChannelHandlerContext ctx)
Called by netty when the connection becomes inactive, either due to error or normal connection close.
protected void messageReceived(ChannelHandlerContext ctx, String msg)
Called by netty when a new message arrives, here pick out the head of the queue, and then we call setsuccess on it.
Warning advise
When using futures, there is 1 thing you need to lookout for, do not call get() from 1 of the netty threads if the future isn't done yet, failure to follow this simple rule will either result in a deadlock or a BlockingOperationException.
You can find the sample in netty project.
We can save the result into the last handler's custom fields. In the following code, it is handler.getFactorial() that is what we want.
refer to http://www.lookatsrc.com/source/io/netty/example/factorial/FactorialClient.java?a=io.netty:netty-all
FactorialClient.java
public final class FactorialClient {
static final boolean SSL = System.getProperty("ssl") != null;
static final String HOST = System.getProperty("host", "127.0.0.1");
static final int PORT = Integer.parseInt(System.getProperty("port", "8322"));
static final int COUNT = Integer.parseInt(System.getProperty("count", "1000"));
public static void main(String[] args) throws Exception {
// Configure SSL.
final SslContext sslCtx;
if (SSL) {
sslCtx = SslContextBuilder.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE).build();
} else {
sslCtx = null;
}
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.handler(new FactorialClientInitializer(sslCtx));
// Make a new connection.
ChannelFuture f = b.connect(HOST, PORT).sync();
// Get the handler instance to retrieve the answer.
FactorialClientHandler handler =
(FactorialClientHandler) f.channel().pipeline().last();
// Print out the answer.
System.err.format("Factorial of %,d is: %,d", COUNT, handler.getFactorial());
} finally {
group.shutdownGracefully();
}
}
}
public class FactorialClientHandler extends SimpleChannelInboundHandler<BigInteger> {
private ChannelHandlerContext ctx;
private int receivedMessages;
private int next = 1;
final BlockingQueue<BigInteger> answer = new LinkedBlockingQueue<BigInteger>();
public BigInteger getFactorial() {
boolean interrupted = false;
try {
for (;;) {
try {
return answer.take();
} catch (InterruptedException ignore) {
interrupted = true;
}
}
} finally {
if (interrupted) {
Thread.currentThread().interrupt();
}
}
}
#Override
public void channelActive(ChannelHandlerContext ctx) {
this.ctx = ctx;
sendNumbers();
}
#Override
public void channelRead0(ChannelHandlerContext ctx, final BigInteger msg) {
receivedMessages ++;
if (receivedMessages == FactorialClient.COUNT) {
// Offer the answer after closing the connection.
ctx.channel().close().addListener(new ChannelFutureListener() {
#Override
public void operationComplete(ChannelFuture future) {
boolean offered = answer.offer(msg);
assert offered;
}
});
}
}
#Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
private void sendNumbers() {
// Do not send more than 4096 numbers.
ChannelFuture future = null;
for (int i = 0; i < 4096 && next <= FactorialClient.COUNT; i++) {
future = ctx.write(Integer.valueOf(next));
next++;
}
if (next <= FactorialClient.COUNT) {
assert future != null;
future.addListener(numberSender);
}
ctx.flush();
}
private final ChannelFutureListener numberSender = new ChannelFutureListener() {
#Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
sendNumbers();
} else {
future.cause().printStackTrace();
future.channel().close();
}
}
};
}
Calling channel.writeAndFlush(msg); already returns a ChannelFuture. To handle the result of this method call, you could add a listener to the future like this:
future.addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) {
// Perform post-closure operation
// ...
}
});
(this is taken from the Netty documentation see: Netty doc)