Netty Client to Server message
Asked Answered
B

2

11

This is actually my first post on here and I have been trying to figure this out for some time but I am finally calling in the flag and going to try to get some help on this topic.

So I have a Client and a Server that was modeled after the echo client/server and the secure chat client/server. I am not interested in the SSL part of the chat and using the echo just to ensure that I am getting responses to and from the client/server. I will add all the pertinent code at the bottom of this post. The problem I am getting at the moment is that I can send a message from the server to the client upon the client connecting but I am not able to send a message from the client to the Server upon the server sending the client the initial message. The message being sent from the server is:

Welcome to the server!

The message from the client is

test

I should know that I got the message from the client cause it should echo back

[You] test

I do know that the Server sees the Client and it gives me status updates but I cannot send a message to the server for some reason. Now for a question on top of this... By some chance I am currently using a StringDecoder and StringEncoder as the Decoder and Encoder... If you are making a game (which is what I am doing) and you will have things like logins, player movements, world updates, etc... is sending Strings the best way to do this? I know I see a lot with byte streams and in my programming class I went through we touched on manipulating byte streams but I am still not 100% comfortable with them yet. If byte streams are the better/best way to do this then can someone please explain in some detail how it works to manipulate a byte stream to be able to handle different items.

As stated before this is the start of everything in the client:

public class Client {

public Client() {
    // Initialize the window
    GameWindow.init();
    // Initialize the server connection
    ClientHandler.init();
}

public static void main(String[] args) throws Exception {

    // Set a default server address if one isn't specified in the arguments
    if (args.length < 2 || args.length > 3) {
        System.err.println("Usage: " + Client.class.getSimpleName() + " <host> <port> [<first message size>]");
        System.err.println("Using default values.");
    } else {
        // Parse arguments
        Settings.host = args[0];
        Settings.port = Integer.parseInt(args[1]);
    }

    // start client
    new Client();
}

ClientHandler:

package simple.client.net;

import java.net.InetSocketAddress;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
import org.jboss.netty.channel.WriteCompletionEvent;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;

import simple.client.Settings;

public class ClientHandler extends SimpleChannelUpstreamHandler {

private static final Logger logger = Logger.getLogger(ClientHandler.class.getName());

public static Channel channel;

public ClientHandler() {
}

public static void init() {
    // Configure the client.
    ClientBootstrap bootstrap = new ClientBootstrap(new NioClientSocketChannelFactory(Executors.newCachedThreadPool(), Executors.newCachedThreadPool()));

    // Set up the pipeline factory.
    bootstrap.setPipelineFactory(new ClientPipelineFactory());

    // Start the connection attempt.
    ChannelFuture future = bootstrap.connect(new InetSocketAddress(Settings.host, Settings.port));

    // Wait until the connection is closed or the connection attempt fails.
    channel = future.awaitUninterruptibly().getChannel();

    // This is where the test write is <<------
    ChannelFuture test = channel.write("test");

    if (!future.isSuccess()) {
        future.getCause().printStackTrace();
        bootstrap.releaseExternalResources();
        return;
    }
}

@Override
public void channelBound(ChannelHandlerContext ctx, ChannelStateEvent e) {
    System.out.println("Bound: " + e.getChannel().isBound());
}

@Override
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) {
    System.out.println("Connected: " + e.getChannel().isConnected());
    System.out.println("Connected: " + e.getChannel().getRemoteAddress());
}

@Override
public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) {
    System.out.println("Closed: " + e.getChannel());
}

@Override
public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) {
    System.out.println("Disconnected: " + e.getChannel());
}

@Override
public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) {
    System.out.println("Open: " + e.getChannel().isOpen());
}

@Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {
    System.out.println("Error: " + e.getCause());
}

@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
    System.out.println("Message: " + e.getMessage());
}
}

And finally the ClientPipeline:

package simple.client.net;

import static org.jboss.netty.channel.Channels.*;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.handler.codec.frame.DelimiterBasedFrameDecoder;
import org.jboss.netty.handler.codec.frame.Delimiters;
import org.jboss.netty.handler.codec.string.StringDecoder;
import org.jboss.netty.handler.codec.string.StringEncoder;

public class ClientPipelineFactory implements ChannelPipelineFactory {

public ChannelPipeline getPipeline() throws Exception {
    ChannelPipeline pipeline = pipeline();

    pipeline.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
    pipeline.addLast("decoder", new StringDecoder());
    pipeline.addLast("encoder", new StringEncoder());
    pipeline.addLast("handler", new ClientHandler());

    return pipeline;
}

}

Server Side:

package simple.server;

public class Server {
public static void main(String[] args) throws Exception {
    ServerChannelHandler.init();
}
}

ServerChannelHandler:

package simple.server;

import java.net.InetSocketAddress;
import java.util.concurrent.Executors;
import java.util.logging.Logger;

import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;
import org.jboss.netty.channel.group.ChannelGroup;
import org.jboss.netty.channel.group.DefaultChannelGroup;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;

public class ServerChannelHandler extends SimpleChannelHandler {

private static final Logger logger = Logger.getLogger(ServerChannelHandler.class.getName());

private static ChannelGroup channels;
private static ServerBootstrap bootstrap;

public ServerChannelHandler() {
}

/**
 * Initialize the Server Channel Handler
 */
public static void init() {
    // create a channels group to add incoming channels to
    channels = new DefaultChannelGroup();

    // create the server bootstrap (fancy word for pre-made server setup)
    bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory(
            Executors.newCachedThreadPool(), Executors.newCachedThreadPool()));

    // set the server pipeline factory
    bootstrap.setPipelineFactory(new ServerPipelineFactory());

    // server settings
    bootstrap.setOption("keepAlive", true);

    // bind the server to the port
    bootstrap.bind(new InetSocketAddress(Settings.PORT_ID));
}

@Override
public void channelBound(ChannelHandlerContext ctx, ChannelStateEvent e) {
    System.out.println("Bound: " + e.getChannel());
}

@Override
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) {
    System.out.println("Connected: " + e.getChannel());
    channels.add(e.getChannel());
    e.getChannel().write("Welcome to the test server!\n\r");
}

@Override
public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) {
    System.out.println("Closed: " + e.getChannel());
}

@Override
public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) {
    System.out.println("Disconnected: " + e.getChannel());
}

@Override
public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) {
    System.out.println("Open: " + e.getChannel());
}

@Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {
    System.out.println("Error: " + e.getCause());
}

@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
    System.out.println("Message: " + e.getMessage());
    for (Channel c : channels) {
        if (e.getMessage().equals("shutdown")) {
            shutdown();
        }
        if (c != e.getChannel()) {
            c.write("[" + e.getChannel().getRemoteAddress() + "] " + e.getMessage() + "\n\r");
        } else {
            c.write("[You] " + e.getMessage() + "\n\r");
        }
    }
}

/**
 * Shuts down the server safely
 */
public static final void shutdown() {
    channels.close();
    bootstrap.releaseExternalResources();
    System.exit(0);
}
}

ServerPipelineFactory:

package simple.server;

import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.handler.codec.frame.DelimiterBasedFrameDecoder;
import org.jboss.netty.handler.codec.frame.Delimiters;
import org.jboss.netty.handler.codec.string.StringDecoder;
import org.jboss.netty.handler.codec.string.StringEncoder;

import simple.server.decoder.Decoder;
import simple.server.encoder.Encoder;

public class ServerPipelineFactory implements ChannelPipelineFactory {
@Override
public ChannelPipeline getPipeline() throws Exception {
    ChannelPipeline pipeline = Channels.pipeline();

    pipeline.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
    pipeline.addLast("decoder", new StringDecoder());
    pipeline.addLast("encoder", new StringEncoder());
    pipeline.addLast("handler", new ServerChannelHandler());

    return pipeline;
}
}

Once again everyone I appreciate any help you can give me on understanding this.

Blumenfeld answered 17/12, 2012 at 22:6 Comment(2)
Sure sorry didn't know if anyone would've wanted it but I should also add that I can Telnet localhost 45000 to my server and it does as expected there..... I am editing the original post to add in the server code.Blumenfeld
Anyone able to help me with this problem?Blumenfeld
I
5

You forgot to append \r\n to "test". It should be: channel.write("test\r\n").`

As you see from the pipeline, the decoding part is composed of two handlers. The first one splits and merges the received data into a single line of string and strips the line ending from it. The second one converts the single line of string into java.lang.String.

On the encoding side, there's only one handler, which converts a java.lang.String into ByteBuf, and that's all it does. Perhaps it is better introduce a handler called LineEncoder, LineDecoder, and LineCodec that does the usually expected job: https://github.com/netty/netty/issues/1811

Iver answered 18/12, 2012 at 6:27 Comment(3)
Thats a really silly answer to my issue but ironically it worked.... Wow I didn't know that it's picky about that information.Blumenfeld
I couldn't have guessed this. Strange behaviour, is there a reason for it?Phrasing
Updated the answer to answer the questions in the comments.Iver
M
1

DO new String("test"). That would be more generic. Answer to latter part of your post- Create a Class Object with all your information like Logins, Player movements etc. in the Object and pass it. Make sure your class implements Serializable. Passing it as String is a bad way to do it since I assume it will become sort of hardcoded.Client code will look like this:

ChannelPipeline p = ch.pipeline();
                        p.addLast(
                                new ObjectEncoder(),                            
                                new ObjectDecoder(ClassResolvers.cacheDisabled(getClass().getClassLoader())),
                                 new ClientHandler());              
                     }                  
                 });

                // Start the connection attempt.
            ChannelFuture f=  b.connect(host, port);
            channel=f.awaitUninterruptibly().channel();
            TestObj obj= new TestObj();
            channel.writeAndFlush(obj);

Server code will look like this:

 ChannelPipeline p = ch.pipeline();

                p.addLast(
                       new ObjectEncoder(),                         
                       new ObjectDecoder(ClassResolvers.cacheDisabled(getClass().getClassLoader())),
                        new DiscardServerHandler());
            }

Server Handler will be:

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    System.out.println("channelRead"+((TestObj)msg).getCurrency());
}
Mcmaster answered 31/5, 2015 at 1:0 Comment(5)
This does not provide an answer to the question. To critique or request clarification from an author, leave a comment below their post - you can always comment on your own posts, and once you have sufficient reputation you will be able to comment on any post.Alboran
I am using this and it works. By Using an ObjectEncoder and ObjectDecoder suits more to his Usecase rather than String Decoder. That was part of his question. Although there is an accepted answer to his original problem, I have suggested an altogether different approach.Mcmaster
If you are using this and it works, can you post a code snippet to illustrate? The current format of your answer doesn't really help.Alboran
Highlighting the part of question i was answering to: "if you are making a game (which is what I am doing) and you will have things like logins, player movements, world updates, etc... is sending Strings the best way to do this? "Mcmaster
does my code has some flaw or am i misunderstanding the question?Mcmaster

© 2022 - 2024 — McMap. All rights reserved.