I've written minimal example, but it's still too long, so let me know I should post the link to Pastebin instead.
Server:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.StandardSocketOptions;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
public class Srv {
static final int PORT = 9001;
static final String HOST = "127.0.0.1";
public static void runInstance() {
try (AsynchronousServerSocketChannel asynchronousServerSocketChannel =
AsynchronousServerSocketChannel.open()) {
if (asynchronousServerSocketChannel.isOpen()) {
asynchronousServerSocketChannel.setOption(StandardSocketOptions.SO_RCVBUF, 1024);
asynchronousServerSocketChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true);
asynchronousServerSocketChannel.bind(new InetSocketAddress(HOST, PORT));
System.out.println(String.format("Launched master on %s:%d", HOST, PORT));
asynchronousServerSocketChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {
#Override
public void completed(AsynchronousSocketChannel result, Void attachment) {
asynchronousServerSocketChannel.accept(null, this);
try {
result.read(ByteBuffer.allocate(1024));
System.out.println("Conn from:" + result.getRemoteAddress());
} catch (Exception exn) {
exn.printStackTrace();
} finally {
try {
result.close();
} catch (IOException exn) {
exn.printStackTrace();
}
}
}
#Override
public void failed(Throwable exn, Void attachment) {
asynchronousServerSocketChannel.accept(null, this);
throw new UnsupportedOperationException("can't accept");
}
});
System.in.read();
} else {
System.out.println("The asynchronous server-socket channel cannot be opened");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Client:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.StandardSocketOptions;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import static java.lang.Thread.sleep;
public class Wrk {
static final int PORT = 9001;
static final String HOST = "127.0.0.1";
public static void runInstance() throws InterruptedException {
sleep(1000); //HERE
try(AsynchronousSocketChannel asynchronousSocketChannel = AsynchronousSocketChannel.open()) {
if (asynchronousSocketChannel.isOpen()) {
asynchronousSocketChannel.setOption(StandardSocketOptions.SO_RCVBUF, 1024);
asynchronousSocketChannel.setOption(StandardSocketOptions.SO_SNDBUF, 1024);
asynchronousSocketChannel.setOption(StandardSocketOptions.SO_KEEPALIVE, true);
asynchronousSocketChannel.connect(new InetSocketAddress(HOST, PORT), null,
new CompletionHandler<Void, Void>() {
#Override
public void completed(Void result, Void attachment) {
try {
System.out.println("Connected to: " + HOST + PORT);
asynchronousSocketChannel.read(ByteBuffer.allocate(1024)).get();
} catch (Exception exn) {
exn.printStackTrace();
} finally {
try {
asynchronousSocketChannel.close();
} catch (IOException exn) {
exn.printStackTrace();
}
}
}
#Override
public void failed(Throwable throwable, Void o) {
System.out.println("Connection cannot be established");
}
});
sleep(1000); //AND HERE
} else {
System.out.println("The asynchronous socket channel cannot be opened");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Boilerplate to run:
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread srv = new Thread(new Runnable() {
#Override
public void run() {
Srv.runInstance();
}
});
srv.start();
Thread wrk1 = new Thread(new Runnable() {
#Override
public void run() {
try {
Wrk.runInstance();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
wrk1.start();
}
}
So, if I have the code like this, it gives me the following output:
Launched master on 127.0.0.1:9001
Connected to: 127.0.0.1 9001
Conn from:/127.0.0.1:50415
But if I remove those sleep()s in Srv.runInstance(), I get:
Connection cannot be established
Launched master on 127.0.0.1:9001
Conn from:/127.0.0.1:50438
So, does that mean that client connects to server, but server refuses? I don't clearly understand what happens here and documentation is rather poor, so I don't know where to look for solutions.
The only sleep I see in the posted code is in Wrk.runInstance. However, the order of your output makes it pretty clear what is going on. Your working is trying to connect to the server before it has fully initialized. As you can see in your output, the "Connection message" is before the "Launched master".
The server takes a bit of time to start, so without the sleep, your client is trying to connect to something which is not yet there.
Related
I'm trying to teach myself Java programming. Currently I am working on some client-server code. My objective is to increment the client count before the thread is launched, and decrement the count when the thread completes. Right now, the thread just sleeps for a few seconds. You can see the full directions here:
http://archive.oreilly.com/oreillyschool/courses/java5/Homework/Projects/serverEssentials_proj.project.html
Here is the code I currently have:
RepositoryServer.java
package server;
import java.io.*;
import java.net.*;
public class RepositoryServer {
ServerSocket serverSocket = null;
int state = 0;
public void bind() throws IOException {
serverSocket = new ServerSocket(9172);
state=1;
}
public void process() throws IOException {
while (state ==1) {
Socket client = serverSocket.accept();
new RepositoryThread(client).start();
}
shutdown();
}
void shutdown() throws IOException {
if (serverSocket != null) {
serverSocket.close();
serverSocket=null;
state=0;
}
}
}
RepositoryClient.java
package client;
import java.io.*;
import java.net.*;
public class RepositoryClient {
public static void main(String[] args) throws Exception {
Socket server = new Socket("localhost", 9172);
PrintWriter toServer = new PrintWriter (server.getOutputStream(),true);
BufferedReader fromServer = new BufferedReader (new InputStreamReader(server.getInputStream()));
for (int num=0; num<3; num++) {
toServer.println("SIZE");
if (!toServer.checkError()) {
int response = Integer.valueOf(fromServer.readLine());
String value = fromServer.readLine();
if (response==0) {
System.out.println(num + ": Number of Images: "+ value);
} else if (response==-1) {
System.err.println(value);
} else {
System.err.println("Received unknown response: " +response);
}
}
}
server.close();
}
}
RepositoryThread.java
package server;
import java.io.*;
import java.net.*;
public class RepositoryThread extends Thread {
Socket client;
BufferedReader fromClient;
PrintWriter toClient;
RepositoryThread (Socket s) throws IOException {
fromClient = new BufferedReader(new InputStreamReader(s.getInputStream()));
toClient=new PrintWriter(s.getOutputStream(), true);
client = s;
}
public void run() {
try {
while (true) {
String request = fromClient.readLine();
if (request == null) {
break;
}
if (request.equals("SIZE")) {
output("0");
} else {
//internal server error. Try to continue and keep processing
outputError("Unable to process request: "+ request);
continue;
}
}
} catch (IOException ioe) {
System.err.println("Thread processing terminated: " + ioe.getMessage());
}
try {
fromClient.close();
toClient.close();
client.close();
} catch (IOException ioe) {
System.err.println("Unable to close connection: " +ioe.getMessage());
}
}
void output(String result) {
toClient.println(0);
toClient.println(result);
}
void outputError(String error) {
toClient.println(-1);
toClient.println(error);
}
}
ServerLauncher.java
package server;
public class ServerLauncher {
public static RepositoryServer create() throws Exception {
RepositoryServer server = new RepositoryServer();
server.bind();
return server;
}
public static void main(String[] args) throws Exception {
RepositoryServer server = create();
System.out.println("Server awaiting client connections");
server.process();
System.out.println("Server shutting down.");
}
}
TestServer.java
package server;
import java.io.*;
import client.*;
import junit.framework.TestCase;
public class TestServer extends TestCase {
static int clientCount =0;
public void testMultipleClients() throws Exception {
RepositoryServer server = launchServer();
System.out.println(clientCount);
launchClient();
System.out.println(clientCount);
launchClient();
System.out.println(clientCount);
launchClient();
System.out.println(clientCount);
//wait until everything done.
//isClientCountZero();
Thread.sleep(10000);
server.shutdown();
assertEquals(0, server.state);
}
public static void launchClient() {
//incrementClientCount();
new Thread() {
public void run() {
try {
RepositoryClient.main(new String[]{});
} catch (Exception e) {
System.err.println("Unable to launch test client.");
}
}
}.start();
//decrementClientCount();
}
public static RepositoryServer launchServer() throws Exception {
final RepositoryServer server = ServerLauncher.create();
assertEquals(1, server.state);
new Thread() {
public void run() {
try {
server.process();
} catch (IOException ioe) {
System.err.println("Server completed");
}
}
}.start();
Thread.sleep(2000);
return server;
}
public static synchronized boolean isClientCountZero(){
if (clientCount==0) {
return true;
} else {
return false;
}
}
public static synchronized void decrementClientCount(){
clientCount--;
}
public static synchronized void incrementClientCount(){
clientCount++;
}
}
The code in question is in TestServer.java. When the Thread.sleep() methods are active, the server outputs text as expected. However, when the Thread.sleep() methods are commented out and replaced with the increment, decrement, and clientCount methods, it no longer outputs any text, just 'Server Completed'. What is my wrong with my solution to the posted link?
Thank you
I use Kryo-net for send and receive messages. On the server side I open new thread and sets the server, the problem is that the thread is ended at the end of the code so there isnt really listener for requests.
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryonet.Connection;
import com.esotericsoftware.kryonet.Listener;
import com.esotericsoftware.kryonet.Server;
public class MessagingServer implements Runnable{
private Server server;
public void stop(){
this.server.stop();
}
public MessagingServer(){
this.server = new Server();
}
#Override
public void run() {
try{
if(server!=null){ stop(); }
this.server.start();
this.server.bind(54555, 54777);
Kryo kryo = this.server.getKryo();
kryo.register(NewRequiredJobRequest.class);
kryo.register(NewRequiredJobResponse.class);
server.addListener(new Listener() {
#Override
public void received (Connection connection, Object object) {
if (object instanceof HelloRequest) {
HelloRequest request = (HelloRequest)object;
System.out.println(request.text);
HelloResponse response = new HelloResponse();
response.text = "Thanks!";
connection.sendTCP(response);
}
}
});
} catch (Exception e) {
System.out.println("kryo server exception"));
}
// once the code reach here the thread is ended..
}
}
Server object probably has some sort of listen or accept method that must run in a loop.
KryoNet Client#start and Server#start starts a daemon thread. If you have no other non-daemon threads in your app:
new Thread(client).start();
new Thread(server).start();
I've never use kryo, but I think this will help.
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryonet.Connection;
import com.esotericsoftware.kryonet.Listener;
import com.esotericsoftware.kryonet.Server;
public class MessagingServer implements Runnable{
private Server server;
public void stop(){
this.server.stop();
}
public MessagingServer(){
this.server = new Server();
}
#Override
public void run() {
try{
if(server!=null){ stop(); }
this.server.start();
this.server.bind(54555, 54777);
while(true) {
Kryo kryo = this.server.getKryo();
kryo.register(NewRequiredJobRequest.class);
kryo.register(NewRequiredJobResponse.class);
server.addListener(new Listener() {
#Override
public void received (Connection connection, Object object) {
if (object instanceof HelloRequest) {
HelloRequest request = (HelloRequest)object;
System.out.println(request.text);
HelloResponse response = new HelloResponse();
response.text = "Thanks!";
connection.sendTCP(response);
}
}
});
}
} catch (Exception e) {
System.out.println("kryo server exception"));
}
}
}
I'm trying to implement WebSockets with a Javascript-based client and a Java-based server. I think I've done all the correct steps, but for an unknown reason, I can't establish the connection with both.
When the server socket receives a connection, it handles to form a websocket-accept response, and it sends back to the client, but the connection in the client socket instantly close, weird that there's no handshake problem.
Does anyone have an idea what might be the problem?
Here's my server code implemented in java:
package server;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import server.message.Message;
import server.message.SpeakMessage;
public class Server implements ConnectionListener {
private static final int PORT = 1509;
private MessageDispatcher dispatcher = new MessageDispatcher();
private List<ConnectionManager> clients = new ArrayList<>();
public void listen() {
try (ServerSocket server = new ServerSocket(PORT)) {
System.out.printf("Listening on port %d...%n", PORT);
while (true) {
System.out.println("Waiting for connection...");
Socket client = server.accept();
System.out.println("Incoming connection - Attempting to establish connection...");
ConnectionManager manager = new ConnectionManager(client, dispatcher, this);
manager.start();
}
} catch (IOException e) {
System.out.println("Unable to start server");
e.printStackTrace();
}
System.exit(0);
}
public void execute() {
try {
while (true) {
if (dispatcher.isEmpty()) {
Thread.sleep(100);
continue;
}
Message msg = dispatcher.read();
if (msg instanceof SpeakMessage)
broadcast(MessageEncoder.spoke(((SpeakMessage) msg).getText()));
}
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
public static void main(String[] args) {
final Server server = new Server();
new Thread(new Runnable() {
#Override
public void run() {
server.listen();
}
}).start();
server.execute();
}
public synchronized void broadcast(byte[] message) {
for (ConnectionManager client : clients) {
client.send(message);
}
}
#Override
public synchronized void clientConnected(ConnectionManager who) {
clients.add(who);
System.out.println("Connected client " + clients.size());
}
#Override
public synchronized void clientDisconnected(ConnectionManager who) {
clients.remove(who);
}
}
Heres subclass ConnectionManager of server:
package server;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.Socket;
import java.security.MessageDigest;
import java.util.Properties;
import server.message.HandshakeMessage;
import server.message.Message;
public class ConnectionManager {
private static final int CLIENT_VERSION = 1;
private Socket socket;
private MessageDecoder decoder = new MessageDecoder();
private MessageDispatcher dispatcher;
private ConnectionListener listener;
public ConnectionManager(Socket connection, MessageDispatcher dispatcher, ConnectionListener listener) {
socket = connection;
this.dispatcher = dispatcher;
this.listener = listener;
}
public void start() {
Thread t = new Thread(new ChannelReader());
t.setName("Client thread");
t.setDaemon(true);
t.start();
}
public void send(byte[] data) {
if (socket == null)
return;
try {
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
dos.write(data);
dos.flush();
} catch (IOException e) {
disconnect("Client closed the connection");
}
}
private class ChannelReader implements Runnable {
private boolean accepted = false;
private String ret = null;
#Override
public void run() {
try {
DataInputStream in = new DataInputStream(socket.getInputStream());
while (socket != null && socket.isConnected()) {
int len = in.readShort();
if (len < 0) {
disconnect("Invalid message length.");
}
String s;
readLine(in);
Properties props = new Properties();
while((s=readLine(in)) != null && !s.equals("")) {
String[] q = s.split(": ");
props.put(q[0], q[1]);
}
if(props.get("Upgrade").equals("websocket") && props.get("Sec-WebSocket-Version").equals("13")) { // check if is websocket 8
String key = (String) props.get("Sec-WebSocket-Key");
String r = key + "" + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; // magic key
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.reset();
md.update(r.getBytes());
byte[] sha1hash = md.digest();
String returnBase = base64(sha1hash);
ret = "HTTP/1.1 101 Switching Protocols\r\n";
ret+="Upgrade: websocket\r\n";
ret+="Connection: Upgrade\r\n";
ret+="Sec-WebSocket-Accept: "+returnBase;
} else {
disconnect("Client got wrong version of websocket");
}
Message msg = decoder.decode((String) props.get("Sec-WebSocket-Protocol"));
if (!accepted) {
doHandshake(msg);
} else if (dispatcher != null) {
dispatcher.dispatch(msg);
}
}
} catch (Exception e) {
disconnect(e.getMessage());
e.printStackTrace();
}
}
private void doHandshake(Message msg) {
if (!(msg instanceof HandshakeMessage)) {
disconnect("Missing handshake message");
return;
}
HandshakeMessage handshake = (HandshakeMessage) msg;
if (handshake.getVersion() != CLIENT_VERSION) {
disconnect("Client failed in handshake.");
return;
}
send(ret.getBytes());
accepted = true;
listener.clientConnected(ConnectionManager.this);
}
private String base64(byte[] input) throws ClassNotFoundException,
SecurityException, NoSuchMethodException, IllegalArgumentException,
IllegalAccessException, InvocationTargetException, InstantiationException {
Class<?> c = Class.forName("sun.misc.BASE64Encoder");
java.lang.reflect.Method m = c.getMethod("encode", new Class<?>[]{byte[].class});
String s = (String) m.invoke(c.newInstance(), input);
return s;
}
private String readLine(InputStream in) {
try{
String line = "";
int pread;
int read = 0;
while(true) {
pread = read;
read = in.read();
if(read!=13&&read!=10)
line += (char) read;
if(pread==13&&read==10) break;
}
return line;
}catch(IOException ex){
}
return null;
}
}
public synchronized void disconnect(String message) {
System.err.println(message);
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
}
}
socket = null;
listener.clientDisconnected(ConnectionManager.this);
}
}
And the MessageDispatcher:
package server;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingDeque;
import server.message.Message;
public class MessageDispatcher {
Queue<Message> messageQueue = new LinkedBlockingDeque<>();
public void dispatch(Message message) {
messageQueue.offer(message);
}
public Message read() {
return messageQueue.poll();
}
public boolean isEmpty() {
return messageQueue.isEmpty();
}
}
And heres my client code implemented in javascript:
var canvas, // Canvas DOM element
ctx, // Canvas rendering context
socket; // Socket connection
function init() {
// Initialise the canvas
canvas = document.getElementById("gameCanvas");
ctx = canvas.getContext("2d");
// Maximise the canvas
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// Initialise socket connection
if (window.WebSocket) {
socket = new WebSocket("ws://localhost:1509/", ["1", "YURI"]);
socket.onopen = onSocketConnected();
socket.onclose = onSocketDisconnect();
socket.onmessage = onSocketMessage();
socket.onerror = onSocketError();
} else {
alert("The browser does not support websocket.");
}
};
// Socket message
function onSocketMessage(message) {
console.log('Message: ' + message.data);
};
// Socket error
function onSocketError(error) {
console.log('Error: ' + error.data);
};
// Socket connected
function onSocketConnected() {
console.log("Connected to socket server");
};
// Socket disconnected
function onSocketDisconnect() {
console.log("Disconnected from socket server");
};
I think, it is because you are using the Socket Package on the Java Server Side and the WebSocket API on the Client Side. Your idea is really good but the wrong technology.
Keep the WebSocket on the Client Side (Javascript) becaue you don't have lots of other possibilities, but try JWebSocket on the Server side (Java). In Fact WebSocket is using TCP/IP but its own communication protocol over TCP/IP. The Java Socket Package is purely TCP/IP. Re-write your server with JWebSocket, all details about JWebSocket can be found at:
http://jwebsocket.org/.
I hope my answer will help you.
you must specify end of return packet with "\r\n\r\n"
ret = "HTTP/1.1 101 Switching Protocols\r\n";
ret+="Upgrade: websocket\r\n";
ret+="Connection: Upgrade\r\n";
ret+="Sec-WebSocket-Accept: "+returnBase + "\r\n\r\n";
and for create accept key i use
public class WSKeyGenerator {
private final static String MAGIC_KEY =
"258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
public static String getKey(String strWebSocketKey) throws
NoSuchAlgorithmException {
strWebSocketKey += MAGIC_KEY;
MessageDigest shaMD = MessageDigest.getInstance("SHA-1");
shaMD.reset();
shaMD.update(strWebSocketKey.getBytes());
byte messageDigest[] = shaMD.digest();
BASE64Encoder b64 = new BASE64Encoder();
return b64.encode(messageDigest);
}
}
I recommend that use the http://websocket.org/echo.html to check the server's websocket functionality
I'm trying to read a nonblocking socket to avoid getting stuck at some point in my program. Does anyone know why when I try to read always return zero? It would be a problem with ByteBuffer? This problem occurs in the read method with lenght is always zero.
package com.viewt.eyebird.communication;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.LinkedList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.viewt.eyebird.PhoneInformation;
import com.viewt.eyebird.TrackingServiceData;
import com.viewt.eyebird.commands.*;
import android.os.Handler;
import android.util.Log;
final public class ServerCommunication {
protected final int socketTimeout;
protected final TrackingServiceData commandData;
protected final Handler handler;
protected final ServerCommunicationChecker clientChecker;
protected final LinkedList<Serialize> queue = new LinkedList<Serialize>();
protected final ByteBuffer socketBuffer = ByteBuffer.allocate(1024);
protected final StringBuilder readBuffer = new StringBuilder();
protected static final Pattern commandPattern = Pattern.compile(">[^<]+<");
protected static final ServerCommand availableCommands[] = { new Panic(),
new ChangeServer(), new GetServer(), new Restart(),
new PasswordCleanup() };
protected InetSocketAddress inetSocketAddress;
protected SocketChannel sChannel;
public ServerCommunication(Handler handler, String host, int port,
int timeAlive, int socketTimeout,
PhoneInformation phoneInformation, TrackingServiceData commandData) {
this.commandData = commandData;
this.handler = handler;
this.socketTimeout = socketTimeout;
try {
connect(host, port);
} catch (CommunicationException e) {
Log.getStackTraceString(e);
}
clientChecker = new ServerCommunicationChecker(handler, this,
timeAlive, new AliveResponse(phoneInformation));
handler.postDelayed(clientChecker, timeAlive);
}
public void connect() throws CommunicationException {
try {
sChannel = SocketChannel.open();
sChannel.configureBlocking(false);
sChannel.socket().setSoTimeout(socketTimeout);
sChannel.connect(inetSocketAddress);
} catch (IOException e) {
throw new CommunicationException(e);
}
}
public boolean isConnectionPending() {
return sChannel.isConnectionPending();
}
public boolean finishConnection() throws CommunicationException {
try {
return sChannel.finishConnect();
} catch (IOException e) {
throw new CommunicationException(e);
}
}
public void connect(String host, int port) throws CommunicationException {
inetSocketAddress = new InetSocketAddress(host, port);
connect();
}
public void send(Serialize serialize) throws CommunicationException {
try {
sChannel.write(ByteBuffer
.wrap(String.valueOf(serialize).getBytes()));
} catch (IOException e) {
throw new CommunicationException(e);
}
}
public void sendOrQueue(Serialize serialize) {
try {
send(serialize);
} catch (Exception e) {
queue(serialize);
}
}
public void queue(Serialize serialize) {
queue.add(serialize);
}
#Override
protected void finalize() throws Throwable {
handler.removeCallbacks(clientChecker);
super.finalize();
}
public void sync() throws CommunicationException {
int queueSize = queue.size();
for (int i = 0; i < queueSize; i++) {
send(queue.getFirst());
queue.removeFirst();
}
}
public void read() throws CommunicationException {
int length, readed = 0;
try {
while ((length = sChannel.read(socketBuffer)) > 0)
for (readed = 0; readed < length; readed++)
readBuffer.append(socketBuffer.get());
} catch (IOException e) {
throw new CommunicationException(e);
} finally {
socketBuffer.flip();
}
Matcher matcher = commandPattern.matcher(readBuffer);
int lastCommand;
if ((lastCommand = readBuffer.lastIndexOf("<")) != -1)
readBuffer.delete(0, lastCommand);
while (matcher.find()) {
for (ServerCommand command : availableCommands) {
try {
command.command(matcher.group(), commandData);
break;
} catch (CommandBadFormatException e) {
continue;
}
}
}
if (length == -1)
throw new CommunicationException("Server closed");
}
}
You are using non blocking channel, which means it will block till data is available. It returns with 0 immediately without blocking if no data is available.
Applications always read from network buffers.
You probably try to read immediately after you send some data and then when you get 0 bytes you stop reading. You didn't even give any time to the network to return data.
Instead, you should read in a loop and if you get no data, sleep a little with Thread.sleep(time) (use about 100-300ms), then retry.
You should stop the loop if you already waited too long: count the sleeps, reset when you get some data. Or stop when all data is read.
I have two threads that I'm dealing with Java NIO for non-blocking sockets. This is what the threads are doing:
Thread 1:
A loop that calls on the select() method of a selector. If any keys are available, they are processed accordingly.
Thread 2:
Occasionally registers a SocketChannel to the selector by calling register().
The problem is, unless the timeout for select() is very small (like around 100ms), the call to register() will block indefinitely. Even though the channel is configured to be nonblocking, and the javadocs state that the Selector object is thread safe (but it's selection keys are not, I know).
So anyone have any ideas on what the issue could be? The application works perfectly if I put everything in one thread. No problems occur then, but I'd really like to have separate threads. Any help is appreciated. I've posted my example code below:
Change the select(1000) to select(100) and it'll work. Leave it as select() or select(1000) and it won't.
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Iterator;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class UDPSocket
{
private DatagramChannel clientChannel;
private String dstHost;
private int dstPort;
private static Selector recvSelector;
private static volatile boolean initialized;
private static ExecutorService eventQueue = Executors.newSingleThreadExecutor();
public static void init()
{
initialized = true;
try
{
recvSelector = Selector.open();
}
catch (IOException e)
{
System.err.println(e);
}
Thread t = new Thread(new Runnable()
{
#Override
public void run()
{
while(initialized)
{
readData();
Thread.yield();
}
}
});
t.start();
}
public static void shutdown()
{
initialized = false;
}
private static void readData()
{
try
{
int numKeys = recvSelector.select(1000);
if (numKeys > 0)
{
Iterator i = recvSelector.selectedKeys().iterator();
while(i.hasNext())
{
SelectionKey key = i.next();
i.remove();
if (key.isValid() && key.isReadable())
{
DatagramChannel channel = (DatagramChannel) key.channel();
// allocate every time we receive so that it's a copy that won't get erased
final ByteBuffer buffer = ByteBuffer.allocate(Short.MAX_VALUE);
channel.receive(buffer);
buffer.flip();
final SocketSubscriber subscriber = (SocketSubscriber) key.attachment();
// let user handle event on a dedicated thread
eventQueue.execute(new Runnable()
{
#Override
public void run()
{
subscriber.onData(buffer);
}
});
}
}
}
}
catch (IOException e)
{
System.err.println(e);
}
}
public UDPSocket(String dstHost, int dstPort)
{
try
{
this.dstHost = dstHost;
this.dstPort = dstPort;
clientChannel = DatagramChannel.open();
clientChannel.configureBlocking(false);
}
catch (IOException e)
{
System.err.println(e);
}
}
public void addListener(SocketSubscriber subscriber)
{
try
{
DatagramChannel serverChannel = DatagramChannel.open();
serverChannel.configureBlocking(false);
DatagramSocket socket = serverChannel.socket();
socket.bind(new InetSocketAddress(dstPort));
SelectionKey key = serverChannel.register(recvSelector, SelectionKey.OP_READ);
key.attach(subscriber);
}
catch (IOException e)
{
System.err.println(e);
}
}
public void send(ByteBuffer buffer)
{
try
{
clientChannel.send(buffer, new InetSocketAddress(dstHost, dstPort));
}
catch (IOException e)
{
System.err.println(e);
}
}
public void close()
{
try
{
clientChannel.close();
}
catch (IOException e)
{
System.err.println(e);
}
}
}
import java.nio.ByteBuffer;
public interface SocketSubscriber
{
public void onData(ByteBuffer data);
}
Example usage:
public class Test implements SocketSubscriber
{
public static void main(String[] args) throws Exception
{
UDPSocket.init();
UDPSocket test = new UDPSocket("localhost", 1234);
test.addListener(new Test());
UDPSocket test2 = new UDPSocket("localhost", 4321);
test2.addListener(new Test());
System.out.println("Listening...");
ByteBuffer buffer = ByteBuffer.allocate(500);
test.send(buffer);
buffer.rewind();
test2.send(buffer);
System.out.println("Data sent...");
Thread.sleep(5000);
UDPSocket.shutdown();
}
#Override
public void onData(ByteBuffer data)
{
System.out.println("Received " + data.limit() + " bytes of data.");
}
}
The Selector has several documented levels of internal synchronization, and you are running into them all. Call wakeup() on the selector before you call register(). Make sure the select() loop works correctly if there are zero selected keys, which is what will happen on wakeup().
I ran into the same issue today (that is "wakeupAndRegister" not being available). I hope my solution might be helpful:
Create a sync object:
Object registeringSync = new Object();
Register a channel by doing:
synchronized (registeringSync) {
selector.wakeup(); // Wakes up a CURRENT or (important) NEXT select
// !!! Might run into a deadlock "between" these lines if not using the lock !!!
// To force it, insert Thread.sleep(1000); here
channel.register(selector, ...);
}
The thread should do the following:
public void run() {
while (initialized) {
if (selector.select() != 0) { // Blocks until "wakeup"
// Iterate through selected keys
}
synchronized (registeringSync) { } // Cannot continue until "register" is complete
}
}