Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Communication in Netty Nio java

I want to create a communication system with two clients and a server in Netty nio. More specifically, firstly, I want when two clients are connected with the server to send a message from the server and after that to be able to exchnage data between the two clients. I am using the code provided from this example. My modifications in the code can be found here: link

It seems that the channelRead in the serverHandler works when the first client is connceted so it always return 1 but when a second client is connected does not change to 2. How can I check properly from the server when both clients are connected to the server? How can I read this value dynamically from my main function of the Client? Then which is the best way to let both clients communicate?

EDIT1: Apparently it seems that the client service is running and close directly so every time that I am running a new NettyClient is connected but the connection is closed after that. So the counter is always chnages from zero to one. As I was advised in the below comments I tested it using telnet in the same port and the counter seems to increasing normally, however, with the NettyClient service no.

EDIT2: It seems that the issue I got was from future.addListener(ChannelFutureListener.CLOSE); which was in channelRead in the ProcessingHandler class. When I commented it that out it seems that the code works. However, am not sure what are the consequences of commented that out. Moreover, I want from my main function of the client to check when the return message is specific two. How, could I create a method that waits for a specific message from server and meanwhile it blocks the main functionality.

 static EventLoopGroup workerGroup = new NioEventLoopGroup();
 static Promise<Object> promise = workerGroup.next().newPromise(); 
 public static void callClient() throws Exception {
    String host = "localhost";
    int port = 8080;
    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 {
                ch.pipeline().addLast(new RequestDataEncoder(), new ResponseDataDecoder(), new ClientHandler(promise));
            }
        });
        ChannelFuture f = b.connect(host, port).sync();
    } finally {
        //workerGroup.shutdownGracefully();
    }
}

I want inside the main function to call the method and return the result and when it is 2 to continue with the main functionality. However, I cannot call callClient inside the while since it will run multiple times the same client.

   callBack();
    while (true) {
        Object msg = promise.get();
        System.out.println("Case1: the connected clients is not two");
        int ret = Integer.parseInt(msg.toString());
        if (ret == 2){
            break;
        }
    }
   System.out.println("Case2: the connected clients is two");
   // proceed with the main functionality

How can I update the promise variable for the first client. When I run two clients, for the first client I always received the message :

Case1: the connected clients is not two

seems that the promise is not updated normally, while for the second client I always received the:

Case2: the connected clients is two

like image 858
Jose Ramon Avatar asked Oct 20 '17 15:10

Jose Ramon


People also ask

What is Netty's Nio?

Core Concepts Netty is a non-blocking framework. This leads to high throughput compared to blocking IO. Understanding non-blocking IO is crucial to understanding Netty's core components and their relationships. 2.1. Channel Channel is the base of Java NIO.

What is Netty and how does it work?

The main purpose of Netty is building high-performance protocol servers based on NIO (or possibly NIO.2) with separation and loose coupling of the network and business logic components. It might implement a widely known protocol, such as HTTP, or your own specific protocol. 2. Core Concepts Netty is a non-blocking framework.

What is Nio channel in Java?

Channel is the base of Java NIO. It represents an open connection which is capable of IO operations such as reading and writing. 2.2. Future Every IO operation on a Channel in Netty is non-blocking. This means that every operation is returned immediately after the call.

What is messagetobyteencoder in Netty?

For encoding outgoing data, Netty has extensions of the ChannelOutboundHandler called encoders. MessageToByteEncoder is the base for most encoder implementations. We can convert the message from byte sequence to Java object and vice versa with encoders and decoders. 3. Example Server Application


2 Answers

If my memory is correct, ChannelHandlerContext is one per channel and it can have multiple ChannelHandlers in it's pipeline. Your channels variable is an instance variable of your handler class. And you create a new ProcessingHandler instance for each connection. Thus each will have one and only one connection in channels variable once initialized - the one it was created for.

See new ProcessingHandler() in initChannel function in the server code (NettyServer.java).

You can either make channels variable static so that it is shared between ProcessingHandler instances. Or you can create a single ProcessingHandler instance elsewhere (e.g. as a local variable in the run() function) and then pass that instance to addLast call instead of new ProcessingHandler().

like image 139
Seva Avatar answered Sep 20 '22 20:09

Seva


Why the size of ChannelGroup channels is always one. Even if I connect more clients?

Because child ChannelInitializer is called for every new Channel (client). There you are creating new instance of ProcessingHandler, so every channel see its own instance of ChannelGroup.

Solution 1 - Channel Attribute

Use Attribute and associate it with Channel.

Create attribute somewhere (let's say inside Constants class):

public static final AttributeKey<ChannelGroup> CH_GRP_ATTR = 
       AttributeKey.valueOf(SomeClass.class.getName());

Now, create ChannelGroup which will be used by all instances of ProcessingHandler:

final ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

Update your child ChannelInitializer in NettyServer :

@Override
public void initChannel(SocketChannel ch) throws Exception {
    ch.pipeline().addLast(
        new RequestDecoder(), 
        new ResponseDataEncoder(), 
        new ProcessingHandler());

    ch.attr(Constants.CH_GRP_ATTR).set(channels);
}

Now you can access instance of ChannelGroup inside your handlers like this:

@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
    final ChannelGroup channels = ctx.channel().attr(Constants.CH_GRP_ATTR).get();
    channels.add(ctx.channel());

This will work, because every time new client connects, ChannelInitializer will be called with same reference to ChannelGroup.

Solution 2 - static field

If you declare ChannelGroup as static, all class instances will see same ChannelGroup instance:

private static final ChannelGroup channels =
     new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

Solution 3 - propagate shared instance

Introduce parameter into constructor of ProcessingHandler:

private final ChannelGroup channels;
public ProcessingHandler(ChannelGroup chg) {
    this.channels = chg;
}

Now, inside your NettyServer class create instance of ChannelGroup and propagate it to ProcessingHandler constructor:

final ChannelGroup channels = new 
      DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

@Override
public void initChannel(SocketChannel ch) throws Exception {
    ch.pipeline().addLast(
        new RequestDecoder(), 
        new ResponseDataEncoder(), 
        new ProcessingHandler(channels)); // <- here
}

Personally, I would choose first solution, because

  • It clearly associate ChannelGroup with Channel context
  • You can access same ChannelGroup in other handlers
  • You can have multiple instances of server (running on different port, within same JVM)
like image 21
rkosegi Avatar answered Sep 19 '22 20:09

rkosegi