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
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
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)
Related
I'm working on a simple multiplayer game on android. I have NetworkManager class which opens connection to the server and provides methods like send and receive.
public class NetworkManager {
private Socket socket;
public NetworkManager() throws IOException
{
socket = new Socket(HOST, PORT);
...
}
public void send(String msg) throws IOException { ... }
}
In one activity I connect to the server by creating new instance of NetworkManager. Then, I have to use that instance in several other activities. Is it possible to pass reference to it when starting new activity?
No, and you should not do it anyway. Use a service to do the background work: https://developer.android.com/guide/components/services.html - or at least an AsyncTask, but for your use case a service should be a better option.
You can only pass primitive or Parcelable types from one Activity to another.
My server aperiodically receives join requests from new clients. Upon receiving a new join request, the server runs a service that can be finished real quick. I implement the service as a Java class (called JC) implementing the Runnable interface. I have parameters within the JC class.
At the caller side, I like to have only one instance (or static) of the JC. My question is how to trigger the run() method in the JC every time. Please show me some code. Thanks.
Hope following edits make sense, which is my current implementation.
In the Server that wants to trigger thread executing:
public class Server {
private static RealService mm = new RealService();
private static void update(){
new Thread(mm).start();
}
}
In the Service class:
public class RealService implements Runnable{
#Override
public void run() {
// TODO Auto-generated method stub
// Do something
}
}
You're question is not really clear here, but I would suggest reading about TimerTask in Java
You could use a socket to listen for incoming requests, the server can spawn a new thread each time there is a request. Once the thread completes, you should intimate the client.
You could read about how a concurrent server works.
I have a synchronization issue regarding a bind request and a upstream handler that receives a channelBound event. I need to attach an object to the channel before the handler can ever receive the channelBound event due to the fact that the handler needs to use the object to handle the callback. Example below.
Handler example:
public class MyClientHandler extends SimpleChannelUpstreamHandler {
#Override
public void channelBound(ChannelHandlerContext ctx, ChannelStateEvent e) {
/* Problem: This can occur while the channel attachment is still null. */
MyStatefulObject obj = e.getChannel().getAttachment();
/* Do important things with attachment. */
}
}
Main example:
ClientBootstrap bootstrap = ... //Assume this has been configured correctly.
ChannelFuture f = bootstrap.bind(new InetSocketAddress("192.168.0.15", 0));
/* It is possible the boundEvent has already been fired upstream
* by the IO thread when I get here.
*/
f.getChannel().setAttachment(new MyStatefulObject());
Possible Soultions
I've come up with a couple of solutions to get around this but they both kind of "smell" which is why I'm here asking if anyone has a clean way of doing this.
Solution 1: Spin or block in the channelBound callback until the attachment is not null. I don't like this solution because it ties up an I/O worker.
Solution 2: Make MyClientHandler in to a bi-directional handler and get the attachment using a ThreadLocal in a bindRequested downstream callback. I don't like this because it relies on a Netty implementation detail that the requesting thread is used to fire the bindRequested event.
I find solution 1 to be more tolerable than solution 2. So if that is what I need to do I will.
Is there an easy way to get a channel reference without requesting a bind or connect first?
Yes it is possible that, boundEvent can get the handler before you set the attachment to the channel.
If the attachment is very specific to every channel your opening, then you can register a channel future listener on bind future and set the attachment on operationComplete()
by setting up everything without using BootStraps. Following is a modified version of EchoClient Example, It works fine.
// Configure the client.
final NioClientSocketChannelFactory clientSocketChannelFactory = new NioClientSocketChannelFactory(
Executors.newCachedThreadPool());
// Set up the pipeline factory.
final ChannelPipelineFactory channelPipelineFactory = new ChannelPipelineFactory() {
public ChannelPipeline getPipeline() throws Exception {
return Channels.pipeline(
new MyClientHandler());
}
};
ChannelPipeline pipeline = channelPipelineFactory.getPipeline();
final Channel channel = clientSocketChannelFactory.newChannel(pipeline);
channel.getConfig().setPipelineFactory(channelPipelineFactory);
channel.getConfig().setOption("tcpNoDelay", true);
channel.getConfig().setOption("receiveBufferSize", 1048576);
channel.getConfig().setOption("sendBufferSize", 1048576);
ChannelFuture boundFuture = Channels.future(channel);
boundFuture.addListener(new ChannelFutureListener() {
#Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
future.getChannel().setAttachment(new Object());// set the channel attachment
}
}
});
channel.getPipeline().sendDownstream(new DownstreamChannelStateEvent(channel, boundFuture, ChannelState.BOUND, new InetSocketAddress(host, 0)));
ChannelFuture connectFuture = Channels.future(channel);
channel.getPipeline().sendDownstream(new DownstreamChannelStateEvent(channel, connectFuture, ChannelState.CONNECTED, new InetSocketAddress(host, port)));
channel.getCloseFuture().awaitUninterruptibly();
clientSocketChannelFactory.releaseExternalResources();// do not forget to do this
Make your ChannelPipelineFactory implementation accept a constructor parameter and specify the attachment there. Place a handler in front of all other handlers and make the first handler's channelOpen() method sets the attachment, and then remove the first handler from the pipeline, because it's not needed anymore.
I'm mostly there with Netty but one concept is still alluding me, and I can't find anything in the tutorials and so on. Firstly I do understand that Netty is asynchronous, but there must be a way for a client to call the server and be able to get a response beyond the handler. Let me explain more.
I have a client as illustrated below. And please note that I understand it's bootstrapped and a new connection is established on each call, that's just there to make the example smaller and more succinct. Please ignore that fact.
Client.java
// ServerResponse is a result from the server, in this case
// a list of users of the system (ignore that each time it's all bootstrapped).
public User[] callServerForInformationFromGUIWidget()
{
ClientBootstrap bootstrap = new ClientBootstrap(...);
bootstrap.setPipelineFactory(...);
ChannelFuture future = bootstrap.connect(new InetSocketAddress(host, port));
Channel channel = future.awaitUninterruptibly().getChannel();
// Where request is a POJO sent to the server,
// with a request such as get me a list of users
RequestPojo request = new RequestPojo(requestUserListCommand);
ChannelFuture lastWriteFuture = channel.write(request);
if(lastWriteFuture != null)
lastWriteFuture.awaitUninterruptibly();
}
Now I understand how to get the data on the server, and fire back the result. The only thing is how do I handle it on the client side? Yes the clientHandler class can do something like the following:
ClientHandler.java
#Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
{
User[] users = (User[])e.getMessage();
}
The problem is how does the client code actually get that result? All the examples are similar to a chat service, where the event fires off something else on the client that's not waiting on a response. Even the http client example I found lacking this. The documentation overall is really good, but it's lacking on how to do callbacks. Anyways, in this case I need the client to get the response from the server, and based on the results it will do what it needs.
In other words, how do I write the client to do something like this:
IdealClient.java
// ServerResponse is a result from the server, in this case
// a list of users of the system.
public User[] callServerForInformationFromGUIWidget()
{
...
RequestPojo request = new RequestPojo(requestUserListCommand);
ChannelFuture lastWriteFuture = channel.write(request);
if(lastWriteFuture != null)
lastWriteFuture.awaitUninterruptibly();
User[] users = resultFromCallToServer();
performSomeAction(users);
}
Because the handler doesn't know who is looking for the answer, or who asked the question. And if it's done in the handler, than how?
Back to my comments about the examples, the http client (and handler) examples just dump the result to System.out. If you had a GUI how would you pass the result from your request up to the GUI? I never saw any examples for this.
Jestan is correct. In my case I have a client that need to process price tick data. I use Antlr for the parsing. I fire my events in my parser, but in my case my protocol is String based. Below is an example without Antlr, I pass the String message in your case it could be the users.
//----------------- Event --------------
public class DataChangeEvent {
private String message;
public DataChangeEvent(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
//----------------- Listener --------------
public interface DataChangeListenter {
public void dataChangeEvent(DataChangeEvent event);
}
//----------------- Event Handler that fires the dataChange events --------------
// This class needs to be static since you need to register all your classes that want to be notified of data change events
public class DataChangedHandler {
private static List<DataChangeListenter> listeners = new ArrayList<DataChangeListenter>();
public static void registerDataChangeListener(DataChangeListenter listener) {
listeners.add(listener);
}
public static void fireDataChange(DataChangeEvent dataChangeEvent) {
for(DataChangeListenter listenter : listeners) {
listenter.dataChangeEvent(dataChangeEvent);
}
}
}
//----------------- Example class that implements the listener and registers itself for events --------------
public class ProcessMessage implements DataChangeListenter {
public ProcessMessage() {
DataChangedHandler.registerDataChangeListener(this);
}
public void dataChangeEvent(DataChangeEvent event) {
//Depending on your protocal, I use Antlr to parse my message
System.out.println(event.getMessage());
}
}
//---------------- Netty Handler -----------
public class TelnetClientHandler extends SimpleChannelHandler {
private static final Logger logger = Logger.getLogger(TelnetClientHandler.class.getName());
#Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
String message = (String) e.getMessage();
DataChangedHandler.fireDataChange(message);
}
}
You have to handle it in the Handler with messageReceived(). I'm not sure what your issue is exactly. My guess is you have a response to a request that changes depending on what request was made? Maybe a concrete description of something you are doing of a response that has to know what request it came from. One thing you might be able to do is to pass a long living object the handler that knows the outstanding request, and it can match up the response when it receives it. The pipeline factory method can pass a reference to a manager type object to the Handler.
This was pretty much what I was trying to say. Your Handler is created in the PipelineFactory which is easy to pass parameters to the Handler from there:
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
public ChannelPipeline getPipeline() throws Exception {
ChannelPipeline pipeline = Channels.pipeline();
pipeline.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.nulDelimiter()));
pipeline.addLast("decoder", new XMLDecoder() );
pipeline.addLast("encoder", new XMLEncoder() );
// notice here I'm passing two objects to the Handler so it can
// call the UI.
pipeline.addLast("handler", new MyHandler(param1, param2));
return pipeline;
}
});
When you create your pipeline you'll add your Handler upon a new connection. Simply pass one or more objects that allows it to communicate back to the UI or a controller.
using the Controller.java, I' implementing the run() in NetworkDiscovery.java which queries all the machine in the subnet . The active machines reply with their status. This happens at regular intervals.
public class Controller {
NetworkDiscovery n;
public static int discoveryInterval=2000;
PM pmList;
List pmlist=(List) new PM();
public static void main(String[] args) throws UnknownHostException{
Timer t1=new Timer();
t1.scheduleAtFixedRate(new NetworkDiscovery(), 2000, discoveryInterval);
}
public class NetworkDiscovery extends TimerTask{
InetAddress controllerIP;
int controllerPort;
NetworkDiscovery() throws UnknownHostException {
controllerIP=InetAddress.getLocalHost();
controllerPort=4455;
}
#Override
public void run() {
try {
byte[] recvBuf = new byte[5000];
DatagramPacket packet = new DatagramPacket(recvBuf, recvBuf.length);
DatagramSocket dSock = new DatagramSocket(4445);
dSock.receive(packet);
//implementation related code follows
**dSock.close();**
}
}
On the client's side a similar Datagram socket is opened and objects are received/sent.
The problem is that on the COntroller's side, I'm executing NetworkDiscovery's run() after a specific time interval and during the second execution it says -
java.net.BindException: Address already in use
Since I'm closing the Controller's socket by close(), why does it still show that this address is already being in use? How can I make sure that during the next iteration, the controller starts over fresh call of networkDiscovery?
Perhaps the second task starts before the first was completly executed? Have you tried to insert debug messages and see if the first task was finished?