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
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.
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.
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.
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
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()
.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With