10

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

Jose Ramon
  • 5,572
  • 25
  • 76
  • 152

2 Answers2

2

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().

Seva
  • 2,388
  • 10
  • 9
  • You can't simply re-use instance of ChannelHandler, unless you specify annotation @io.netty.channel.ChannelHandler.Sharable, see https://netty.io/4.0/api/io/netty/channel/ChannelHandler.Sharable.html – rkosegi Oct 25 '17 at 07:05
  • Also am not sure about the following lines in NettyClient f.channel().closeFuture().sync(); and workerGroup.shutdownGracefully();do they stop the running of the client socket? – Jose Ramon Oct 25 '17 at 09:13
  • Your test client disconnects immediately which is why group size stays one - by the time second client connects, first one already disconnected. I have downloaded your sample from github, replaced ProcessingHandler with a copy from your question and changed channels to static. When testing with telnet, sever reports size increasing with each connection. – Seva Oct 25 '17 at 10:42
  • On closing sockets. I'm not sure what you are trying to do. ctx.close() in your code already closes client socket. workerGroup.shutdownGracefully() should close all channels in the group. But it doesn't make much sense in a client code - you only have one connection (and it obviously won't close other clients connections as they don't belong to your app instance). – Seva Oct 25 '17 at 11:05
  • In fact, in your current code, ctx.close() closes client's socket (or rather initiates closing), then f.channel().closeFuture().sync() is notified once closing procedure is completed and only then workerGroup.shutdownGracefully() is called (by which time the only socket in your app has already been closed, and so, there is already nothing left to close). – Seva Oct 25 '17 at 11:09
  • So i comment out the finally statement workerGroup.shutdownGracefully(); in order the client to be active. Then i changed the ChannelGroup channels to be static and however still receiving only one channel per time. – Jose Ramon Oct 25 '17 at 11:11
  • workerGroup.shutdownGracefully() doesn't matter. If you want to keep it open, comment out ctx.close() in ClientHandler. Or at least wait for few messages to be received before closing it. As a quick test - connect to your server with telnet. Then run your test client (while keeping telnet session open). You'll see group size reported as 2 when test client connects. – Seva Oct 25 '17 at 11:15
  • workerGroup.shutdownGracefully(); closes the connection and the running service immediately. Even if I comment out the ctx.close(). Still am receiving 1 in the channel size.Btw what do you mean to connect my server with telnet? I test the code both using the same pc (for different clients) and also different pc in the same network. – Jose Ramon Oct 25 '17 at 11:23
  • There must be more errors then. The count is 1, because you never have more then 1 client connected in parallel at any time. For telnet, just run `telnet localhost 8080` from command line. It won't be able to talk properly to your server, but it will connect and you'll see connections counted in channels. – Seva Oct 25 '17 at 11:36
  • Oke using telnet the size was increased to two. Therefore there is an issue with the connection in NettyClient. How can I check them? Btw workerGroup.shutdownGracefully(); needs to commented out since it always close the main thread. – Jose Ramon Oct 25 '17 at 11:46
  • I think i found the reason, its the future.addListener(ChannelFutureListener.CLOSE); in the processingHandler (for the server). What is happening thought if i commented that out? – Jose Ramon Oct 25 '17 at 14:46
  • future.addListener(ChannelFutureListener.CLOSE) basically closes the socket gracefully. https://stackoverflow.com/questions/17725853/difference-between-calling-close-directly-and-using-channelfuturelistener-clos – Seva Oct 25 '17 at 19:42
  • As I said, you don't need workerGroup.shutdownGracefully(); in client code, but it doesn't really matter. f.channel().closeFuture().sync() only exits _after_ client socket is closed. So, workerGroup.shutdownGracefully() is also called after _after_ client socket is closed. It might speed up cleanup, but otherwise it is no operation - by the time of this call your test client is already exiting for other reasons. Just tested it. With future.addListener(ChannelFutureListener.CLOSE) and ctx.close() removed clients stay connected forever even with shutdownGracefully() call in client's code. – Seva Oct 25 '17 at 19:56
  • Is it a problem for me if the the client is forever connected? What actually trying to do is to establish communication between two clients and play a javafx game (multiplayer scenario). Thus, I want firstly to receive a message from the server that two clients are connected, then to exchange information between the clients and in the end to close the connection. So i want to close the connection in the end of the game. – Jose Ramon Oct 26 '17 at 09:39
  • And btw I add the code from NettyClient in a method called callClient which return the size of the channelGroup and it is working properly. However, i want to call this function from the main inside a while loop which will block the functionality of the code and when it returns number of channels equal to two to continue the operation of the main. How can i chec asynchronously what is the size of the channel. – Jose Ramon Oct 26 '17 at 09:43
  • Sorry, but answering these sort of questions will take way to long to fit into a stackoverflow answer. I'd suggest finding a good book on Netty instead. I've read "Netty in Action" and it was good. It's probably a bit outdated by now. But all the ideas and principles are still same. – Seva Oct 28 '17 at 01:10
  • I just want to see how can i update the promise function from the main in order to receive the last server message. When I am connecting a second client the first one still receiving from the server that the connected clients are one (while the second client receives that the connected clients are two). – Jose Ramon Oct 30 '17 at 09:32
2

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)
rkosegi
  • 14,165
  • 5
  • 50
  • 83
  • Also am not sure about the following lines in NettyClient f.channel().closeFuture().sync(); and workerGroup.shutdownGracefully();do they stop the running of the client socket? – Jose Ramon Oct 25 '17 at 09:13
  • @JoseRamon : what exactly you are trying to achieve? simply close client connection? – rkosegi Oct 25 '17 at 09:16
  • Actually i want to run my server and when in the server i see that i have two clients active to send from the server a true message. I tried to follow firstly your second solution to declare ChannelGroup as a private static final but still the channels size is constantly one. – Jose Ramon Oct 25 '17 at 09:18
  • @JoseRamon : not possible under normal condition to channel size be 1 if `add` was called twice. Can you confirm that (eg by debugger?) – rkosegi Oct 25 '17 at 09:29
  • I am running once the NettyServer and twice the NettyClient. Yes I add a breakpoint in channelActive in ProcessingHandler where I add every channel to the channel group and the size of the ChannelGroup is always one. – Jose Ramon Oct 25 '17 at 09:34
  • @JoseRamon : check hashCode value of `channels` if it's same as static field hashCode. – rkosegi Oct 25 '17 at 09:38
  • The channels have a int method hashCode but in which static field you are reffereing, the channelGroup? – Jose Ramon Oct 25 '17 at 09:51
  • yes, just check if you are adding to same instance of ChannelGroup twice and that this instance is same as static field. – rkosegi Oct 25 '17 at 09:52
  • No, its not the same, its ......554 and ......255. However the counter is still 1. – Jose Ramon Oct 25 '17 at 09:58
  • So you are not using static field then. – rkosegi Oct 25 '17 at 10:01
  • I just want to see how can i update the promise function from the main in order to receive the last server message. When I am connecting a second client the first one still receiving from the server that the connected clients are one (while the second client receives that the connected clients are two). – Jose Ramon Oct 30 '17 at 09:32