We are using Websockets from the Grizzly project and had expected that the implementation would allow multiple incoming messages over a connection to be processed at the same time. It appears that this is not the case or there is a configuration step that we have missed. To validate this I have created a modified echo test that delays in the onMessage after echoing the text. When a client sends multiple messages over the same connection the server always blocks until onMessage completes before processing a subsequent message. Is this the expected functionality?
The simplified server code is as follows:
package com.grorange.samples.echo;
import java.util.concurrent.atomic.AtomicBoolean;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.grizzly.http.server.NetworkListener;
import org.glassfish.grizzly.websockets.DataFrame;
import org.glassfish.grizzly.websockets.WebSocket;
import org.glassfish.grizzly.websockets.WebSocketAddOn;
import org.glassfish.grizzly.websockets.WebSocketApplication;
import org.glassfish.grizzly.websockets.WebSocketEngine;
public class Echo extends WebSocketApplication {
private final AtomicBoolean inMessage = new AtomicBoolean(false);
#Override
public void onClose(WebSocket socket, DataFrame frame) {
super.onClose(socket, frame);
System.out.println("Disconnected!");
}
#Override
public void onConnect(WebSocket socket) {
System.out.println("Connected!");
}
#Override
public void onMessage(WebSocket socket, String text) {
System.out.println("Server: " + text);
socket.send(text);
if (this.inMessage.compareAndSet(false, true)) {
try {
Thread.sleep(10000);
} catch (Exception e) {}
this.inMessage.set(false);
}
}
#Override
public void onMessage(WebSocket socket, byte[] bytes) {
socket.send(bytes);
if (this.inMessage.compareAndSet(false, true)) {
try {
Thread.sleep(Long.MAX_VALUE);
} catch (Exception e) {}
this.inMessage.set(false);
}
}
public static void main(String[] args) throws Exception {
HttpServer server = HttpServer.createSimpleServer("http://0.0.0.0", 8083);
WebSocketAddOn addOn = new WebSocketAddOn();
addOn.setTimeoutInSeconds(60);
for (NetworkListener listener : server.getListeners()) {
listener.registerAddOn(addOn);
}
WebSocketEngine.getEngine().register("", "/Echo", new Echo());
server.start();
Thread.sleep(Long.MAX_VALUE);
}
}
The simplified client code is:
Yes, it's expected.
The way to go is to pass message processing, inside onMessage, to a different thread.
Related
I'm experimenting with creating a socket application that sends some data from another application to a client. The code to access the data uses an abstract class.
SubscriptionAdapter listener = new SubscriptionAdapter() {
#Override
public void onSubscriptionData(SubscriptionData data) {
for (AnyJson json : data.getMessages()) {
System.out.println(json.toString());
}
}
};
The mechanics of SubscriptionAdapter() were a little weird to figure out. onSubscriptionData gets called over and over and the for loop will return 1 to many records. Here is some sample output:
Yup
Got message: {"type":"lightning","location":{"coords":{"lon":18.509391,"lat":38.7581},"polarity":0}}
Yup
Got message: {"type":"lightning","location":{"coords":{"lon":18.504115,"lat":38.765076},"polarity":0}}
Got message: {"type":"lightning","location":{"coords":{"lon":18.494549,"lat":38.778813},"polarity":0}}
"Yup" is a line of debug code I put in above the for loop to confirm how the class functions.
Below is the sample socket server I'm working with:
import java.net.*;
import java.io.*;
public class Server {
/**
* #param args the command line arguments
*/
public static void main(String[] args) //throws Exception
{
try
{
ServerSocket serverSocket = new ServerSocket(9090);
System.out.println("Waiting for clients...");
Socket socket = serverSocket.accept();
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
out.println("Hello client!");
out.close();
socket.close();
serverSocket.close();
} catch (Exception e)
{
System.out.println(e.toString());
}
}
}
The goal is to get System.out.println(json.toString()); to output to the socket connection. However, I've been running into scope issues.
I can't just put the code for the server inside onSubscriptionData because it will keep trying to create a new socket on a port that is already open.
I can't put the code OUTSIDE of the abstract class because then PrintWriter out = new PrintWriter(socket.getOutputStream(), true); winds up being out of scope.
I've been at it for a few hours and at this point, I could use some suggestions.
Update as requested. Below is the full code:
import com.satori.rtm.*;
import com.satori.rtm.model.*;
public class SubscribeToWeatherChannel {
static final String endpoint = [end point];
static final String appkey = [app key];
static final String channel = "full-weather";
public static void main(String[] args) throws InterruptedException {
final RtmClient client = new RtmClientBuilder(endpoint, appkey)
.setListener(new RtmClientAdapter() {
#Override
public void onEnterConnected(RtmClient client) {
System.out.println("Connected to Satori RTM!");
}
})
.build();
SubscriptionAdapter listener = new SubscriptionAdapter() {
#Override
public void onSubscriptionData(SubscriptionData data) {
for (AnyJson json : data.getMessages()) {
System.out.println(json.toString());
}
}
};
client.createSubscription(channel, SubscriptionMode.SIMPLE, listener);
client.start();
}
}
Credit actually to Kayaman for the solution.
Don't create an anonymous instance of the abstract class. Create a
proper class that extends SubscriptionAdapter, then you can pass it
PrintWriters or any other things you need.
import com.satori.rtm.*;
import com.satori.rtm.model.*;
import java.net.*;
import java.io.*;
public class SpecificSubscriptionAdapter extends SubscriptionAdapter {
public static PrintWriter out;
public SpecificSubscriptionAdapter(PrintWriter out){
this.out = out;
}
#Override
public void onSubscriptionData(SubscriptionData data) {
for (AnyJson json : data.getMessages()) {
out.println(json.toString());
}
}
}
I am trying to use TooTallNate's Java-Websocket to connect to OkCoin. I found this simple code example somewhere, but I can't get it to work. The connection is immediately closed and so the call mWs.send(...) throws a WebsocketNotConnectedException. I can't figure out why; so far I have found a number of similar questions, none of which have an answer.
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import org.json.JSONObject;
import java.net.URI;
import java.net.URISyntaxException;
public class TestApp {
public static void main(String[] args) {
try {
URI uri = new URI("wss://real.okcoin.cn:10440/websocket/okcoinapi");
final WebSocketClient mWs = new WebSocketClient(uri) {
#Override
public void onMessage(String message) {
JSONObject obj = new JSONObject(message);
}
#Override
public void onOpen(ServerHandshake handshake) {
System.out.println("opened connection");
}
#Override
public void onClose(int code, String reason, boolean remote) {
System.out.println("closed connection");
}
#Override
public void onError(Exception ex) {
ex.printStackTrace();
}
};
mWs.connect();
JSONObject obj = new JSONObject();
obj.put("event", "addChannel");
obj.put("channel", "ok_btccny_ticker");
mWs.send(obj.toString());
} catch (URISyntaxException e) {
System.err.println("URI not formatted correctly");
}
}
}
Use mWs.connectBlocking() instead of mWs.connect() with this it will not close automatically.
See
I'm new to Netty and I wrote based on an example I found a Netty http server, that keeps http connections open to send server-sent-events to the browser client.
Problem is that it only accepts up to about ~5 connections and after that blocks new connections. I googled and found most answers said to set SO_LOGBACK to a higher value. Tried different values and while I saw no difference. I even set it to MAX_INTEGER value and still had only 5 connections.
Server code (Using Netty version 4.1.6.Final):
package server;
import static io.netty.buffer.Unpooled.copiedBuffer;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.HttpVersion;
public class NettyHttpServer {
private ChannelFuture channel;
private final EventLoopGroup masterGroup;
public NettyHttpServer() {
masterGroup = new NioEventLoopGroup(100);
}
public void start() {
try {
final ServerBootstrap bootstrap = new ServerBootstrap().group(masterGroup)
.channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer < SocketChannel > () {
#Override
public void initChannel(final SocketChannel ch) throws Exception {
ch.pipeline().addLast("codec", new HttpServerCodec());
ch.pipeline().addLast("aggregator", new HttpObjectAggregator(512 * 1024));
ch.pipeline().addLast("request", new ChannelInboundHandlerAdapter() {
#Override
public void channelRead(final ChannelHandlerContext ctx, final Object msg)
throws Exception {
System.out.println(msg);
registerToPubSub(ctx, msg);
}
#Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
#Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.writeAndFlush(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
HttpResponseStatus.INTERNAL_SERVER_ERROR,
copiedBuffer(cause.getMessage().getBytes())));
}
});
}
}).option(ChannelOption.SO_BACKLOG, Integer.MAX_VALUE)
.childOption(ChannelOption.SO_KEEPALIVE, true);
channel = bootstrap.bind(8081).sync();
// channels.add(bootstrap.bind(8080).sync());
} catch (final InterruptedException e) {}
}
public void shutdown() {
masterGroup.shutdownGracefully();
try {
channel.channel().closeFuture().sync();
} catch (InterruptedException e) {}
}
private void registerToPubSub(final ChannelHandlerContext ctx, Object msg) {
new Thread() {
#Override
public void run() {
while (true) {
final String responseMessage = "data:abcdef\n\n";
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
copiedBuffer(responseMessage.getBytes()));
response.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
response.headers().set(HttpHeaders.Names.CONTENT_TYPE, "text/event-stream");
response.headers().set(HttpHeaders.Names.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
response.headers().set("Cache-Control", "no-cache");
ctx.writeAndFlush(response);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
}.start();
}
public static void main(String[] args) {
new NettyHttpServer().start();
}
}
Client js code (I run it more than 5 times from my browser in different tabs, and the not all of them get:
var source = new EventSource("http://localhost:8081");
source.onmessage = function(event) {
console.log(event.data);
};
source.onerror= function(err){console.log(err); source.close()};
source.onopen = function(event){console.log('open'); console.log(event)}
You need to let the browser know that you are done sending the response, and for that you have three options.
Set a content length
Send it chunked
Close the connection when you are done
You aren't doing any of those. I suspect your browser is still waiting for the full response to each request you send, and is using a new connection for each request in your testing. After 5 requests your browser must be refusing to create new connections.
Another thing I noticed is that you are creating a new thread for each request in your server, and never letting it die. That will cause problems down the line as you try to scale. If you really want that code to run in a different thread then I suggest looking at overloaded methods for adding handlers to the pipeline; those should let you specify a thread pool to run them in.
I have a kryonet client/server that work find.. well mostly. The client remains idle and eventually disconnects after awhile but thats not the issue i'm trying to solve currently. Currently, the server and client can establish a connection and send data back and forth(Before the client times out) as long as the client and server are on the same computer. If you try to connect to a different computer on the LAN the connection times out and fails.
So here's my question(s):
What would be a possible cause for the connection issue?
What is the proper way to keep a client alive? ( secondary goal but if you know it, that'd be great)
*I'm using LibGDX and Kryonet for this. As far as I know, they shouldn't have any conflicts.
Server:
package com.me.mygdxgame;
import java.io.IOException;
import java.util.ArrayList;
import com.badlogic.gdx.math.Vector2;
import com.esotericsoftware.kryonet.Connection;
import com.esotericsoftware.kryonet.Listener;
import com.esotericsoftware.kryonet.Server;
import com.me.mygdxgame.Network.Obstacles;
public class GameServer {
Server server;
public GameServer () throws IOException {
server = new Server() {
protected Connection newConnection () {
return new PlayerConnection();
}
};
Network.register(server);
//Sends Stuff to Client
server.addListener(new Listener() {
public void received (Connection c, Object object) {
PlayerConnection connection = (PlayerConnection)c;
if (object instanceof Obstacles) {
if (connection.name != null) return;
ArrayList<Vector2> obs = ((Obstacles)object).obstacles;
if (obs == null) return;
System.out.println("Obstacles recieved.");
for(int i = 0; i < obs.size(); i++)
System.out.println("Obstacle " + i + "- x: " + obs.get(i).x );
return;
}
}
});
server.bind(Network.port);
server.start();
}
public void sendAll () { //Send out data
Obstacles ob = new Obstacles();
ob.obstacles = new ArrayList<Vector2>();
for(int i =0; i < Map.obstacles.size(); i++){
ob.obstacles.add(new Vector2(Map.obstacles.get(i).x,Map.obstacles.get(i).y));
}
server.sendToAllTCP(ob);
}
static class PlayerConnection extends Connection {
public String name;
}
}
Client:
package com.me.mygdxgame;
import java.awt.EventQueue;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import com.badlogic.gdx.ApplicationListener;
import com.esotericsoftware.kryonet.Client;
import com.esotericsoftware.kryonet.Connection;
import com.esotericsoftware.kryonet.Listener;
import com.me.mygdxgame.Network.Obstacles;
public class GameClient implements ApplicationListener{
Client client;
String name;
String RefreshHost;
boolean Connected = false;
ArrayList<String> hosts = new ArrayList<String>();
public static String host;
public GameClient (String host) {
client = new Client();
client.start();
this.host = host;
Network.register(client);
client.addListener(new Listener() {
public void connected (Connection connection) {
System.out.println("connected");
Connected = true;
}
public void received (Connection connection, Object object) {
if (object instanceof Obstacles) {
Obstacles obs = (Obstacles)object;
System.out.println("Obstacle recieved on client - " + obs.obstacles.size());
client.sendTCP(obs);
System.out.println("Obstacles sent back.");
return;
}else {
System.out.println("invalid packet");
}
}
public void disconnected (Connection connection) {
EventQueue.invokeLater(new Runnable() {
public void run () {
System.out.println("closed");
Connected = false;
client.close();
}
});
}
});
new Thread("Connect") {
public void run () {
try {
client.connect(5000, GameClient.host, Network.port);
System.out.println("Connected!");
client.setKeepAliveTCP(NORM_PRIORITY);
while(Connected) {
//System.out.println(client.isIdle());
}
client.run();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}.start();
}
#Override
public void create() {
// TODO Auto-generated method stub
}
#Override
public void resize(int width, int height) {
// TODO Auto-generated method stub
}
#Override
public void render() {
// TODO Auto-generated method stub
}
#Override
public void pause() {
// TODO Auto-generated method stub
}
#Override
public void resume() {
// TODO Auto-generated method stub
}
#Override
public void dispose() {
// TODO Auto-generated method stub
}
}
I suggest you set the host BEFORE you start the client
public GameClient (String host) {
client = new Client();
this.host = host;
client.start();
I am not familiar with kryonet Client, but it makes sense to do it that way.
Generally make sure that your client is trying to connect to the host that you have server running on...
One possible cause for such connection issue is a firewall blocking your Network.port
Another one, sorry but I have to ask: Is the server-app running in the other machine?
I ask because I dont'see a main function in your server code
public static void main(String[] args) throws IOException {
Log.set(Log.LEVEL_DEBUG);
new GameServer();
}
I use to get running my server-app with this terminal command
java -jar myserverfile.jar
How do you get it running in the "remote" machine?
By the way, I am using libgdx and kryonet for my game and so far I haven't get issues using them together.
In my case I have the server in a AWS instance listening for game-client testing from my computer.
I am trying to implement a TCP Server in Java using nio.
Its simply using the Selector's select method to get the ready keys. And then processing those keys if they are acceptable, readable and so. Server is working just fine till im using a single thread. But when im trying to use more threads to process the keys, the server's response gets slowed and eventually stops responding, say after 4-5 requests.
This is all what im doing:(Pseudo)
Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
while (keyIterator.hasNext()) {
SelectionKey readyKey = keyIterator.next();
if (readyKey.isAcceptable()) {
//A new connection attempt, registering socket channel with selector
} else {
Worker.add( readyKey );
}
Worker is the thread class that performs Input/Output from the channel.
This is the code of my Worker class:
private static List<SelectionKey> keyPool = Collections.synchronizedList(new LinkedList());
public static void add(SelectionKey key) {
synchronized (keyPool) {
keyPool.add(key);
keyPool.notifyAll();
}
}
public void run() {
while ( true ) {
SelectionKey myKey = null;
synchronized (keyPool) {
try {
while (keyPool.isEmpty()) {
keyPool.wait();
}
} catch (InterruptedException ex) {
}
myKey = keyPool.remove(0);
keyPool.notifyAll();
}
if (myKey != null && myKey.isValid() ) {
if (myKey.isReadable()) {
//Performing reading
} else if (myKey.isWritable()) {
//performing writing
myKey.cancel();
}
}
}
My basic idea is to add the key to the keyPool from which various threads can get a key, one at a time.
My BaseServer class itself is running as a thread. It is creating 10 Worker threads before the event loop to begin. I also tried to increase the priority of BaseServer thread, so that it gets more chance to accept the acceptable keys. Still, to it stops responding after approx 8 requests. Please help, were I am going wrong. Thanks in advance. :)
Third, you aren't removing anything from the selected-key set. You must do that every time around the loop, e.g. by calling keyIterator.remove() after you call next().
You need to read the NIO Tutorials.
First of all, you should not really be using wait() and notify() calls anymore since there exist good Standard Java (1.5+) wrapper classes in java.util.concurrent, such as BlockingQueue.
Second, it's suggested to do IO in the selecting thread itself, not in the worker threads. The worker threads should just queue up reads/and writes to the selector thread(s).
This page explains it pretty good and even provides working code samples of a simple TCP/IP server: http://rox-xmlrpc.sourceforge.net/niotut/
Sorry, I don't yet have time to look at your specific example.
Try using xsocket library. It saved me a lot of time reading on forums.
Download: http://xsocket.org/
Tutorial: http://xsocket.sourceforge.net/core/tutorial/V2/TutorialCore.htm
Server Code:
import org.xsocket.connection.*;
/**
*
* #author wsserver
*/
public class XServer {
protected static IServer server;
public static void main(String[] args) {
try {
server = new Server(9905, new XServerHandler());
server.start();
} catch (Exception ex) {
System.out.println(ex.getMessage());
}
}
protected static void shutdownServer(){
try{
server.close();
}catch(Exception ex){
System.out.println(ex.getMessage());
}
}
}
Server Handler:
import java.io.IOException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.util.*;
import org.xsocket.*;
import org.xsocket.connection.*;
public class XServerHandler implements IConnectHandler, IDisconnectHandler, IDataHandler {
private Set<ConnectedClients> sessions = Collections.synchronizedSet(new HashSet<ConnectedClients>());
Charset charset = Charset.forName("ISO-8859-1");
CharsetEncoder encoder = charset.newEncoder();
CharsetDecoder decoder = charset.newDecoder();
ByteBuffer buffer = ByteBuffer.allocate(1024);
#Override
public boolean onConnect(INonBlockingConnection inbc) throws IOException, BufferUnderflowException, MaxReadSizeExceededException {
try {
synchronized (sessions) {
sessions.add(new ConnectedClients(inbc, inbc.getRemoteAddress()));
}
System.out.println("onConnect"+" IP:"+inbc.getRemoteAddress().getHostAddress()+" Port:"+inbc.getRemotePort());
} catch (Exception ex) {
System.out.println("onConnect: " + ex.getMessage());
}
return true;
}
#Override
public boolean onDisconnect(INonBlockingConnection inbc) throws IOException {
try {
synchronized (sessions) {
sessions.remove(inbc);
}
System.out.println("onDisconnect");
} catch (Exception ex) {
System.out.println("onDisconnect: " + ex.getMessage());
}
return true;
}
#Override
public boolean onData(INonBlockingConnection inbc) throws IOException, BufferUnderflowException, ClosedChannelException, MaxReadSizeExceededException {
inbc.read(buffer);
buffer.flip();
String request = decoder.decode(buffer).toString();
System.out.println("request:"+request);
buffer.clear();
return true;
}
}
Connected Clients:
import java.net.InetAddress;
import org.xsocket.connection.INonBlockingConnection;
/**
*
* #author wsserver
*/
public class ConnectedClients {
private INonBlockingConnection inbc;
private InetAddress address;
//CONSTRUCTOR
public ConnectedClients(INonBlockingConnection inbc, InetAddress address) {
this.inbc = inbc;
this.address = address;
}
//GETERS AND SETTERS
public INonBlockingConnection getInbc() {
return inbc;
}
public void setInbc(INonBlockingConnection inbc) {
this.inbc = inbc;
}
public InetAddress getAddress() {
return address;
}
public void setAddress(InetAddress address) {
this.address = address;
}
}
Client Code:
import java.net.InetAddress;
import org.xsocket.connection.INonBlockingConnection;
import org.xsocket.connection.NonBlockingConnection;
/**
*
* #author wsserver
*/
public class XClient {
protected static INonBlockingConnection inbc;
public static void main(String[] args) {
try {
inbc = new NonBlockingConnection(InetAddress.getByName("localhost"), 9905, new XClientHandler());
while(true){
}
} catch (Exception ex) {
System.out.println(ex.getMessage());
}
}
}
Client Handler:
import java.io.IOException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import org.xsocket.MaxReadSizeExceededException;
import org.xsocket.connection.IConnectExceptionHandler;
import org.xsocket.connection.IConnectHandler;
import org.xsocket.connection.IDataHandler;
import org.xsocket.connection.IDisconnectHandler;
import org.xsocket.connection.INonBlockingConnection;
/**
*
* #author wsserver
*/
public class XClientHandler implements IConnectHandler, IDataHandler,IDisconnectHandler, IConnectExceptionHandler {
Charset charset = Charset.forName("ISO-8859-1");
CharsetEncoder encoder = charset.newEncoder();
CharsetDecoder decoder = charset.newDecoder();
ByteBuffer buffer = ByteBuffer.allocate(1024);
#Override
public boolean onConnect(INonBlockingConnection nbc) throws IOException {
System.out.println("Connected to server");
nbc.write("hello server\r\n");
return true;
}
#Override
public boolean onConnectException(INonBlockingConnection nbc, IOException ioe) throws IOException {
System.out.println("On connect exception:"+ioe.getMessage());
return true;
}
#Override
public boolean onDisconnect(INonBlockingConnection nbc) throws IOException {
System.out.println("Dissconected from server");
return true;
}
#Override
public boolean onData(INonBlockingConnection inbc) throws IOException, BufferUnderflowException, ClosedChannelException, MaxReadSizeExceededException {
inbc.read(buffer);
buffer.flip();
String request = decoder.decode(buffer).toString();
System.out.println(request);
buffer.clear();
return true;
}
}
I spent a lot of time reading on forums about this, i hope i can help u with my code.