Has anyone tried Servlets 3.1 non blocking technique on tomcat?
The request from the browser seems to be waiting forever but when I run the server in debug mode, the call returns but still I don't see "Data read.." and "Data written.." in the logs.
Servlet:
#WebServlet(urlPatterns = "/asyncn", asyncSupported = true)
public class AsyncN extends HttpServlet {
private static final long serialVersionUID = 1L;
#Override
protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {
println("Before starting job");
final AsyncContext actx = request.startAsync();
actx.setTimeout(Long.MAX_VALUE);
actx.start(new HeavyTask(actx));
println("After starting job");
}
class HeavyTask implements Runnable {
AsyncContext actx;
HeavyTask(AsyncContext actx) {
this.actx = actx;
}
#Override
public void run() {
try {
Thread.currentThread().setName("Job-Thread-" + actx.getRequest().getParameter("job"));
// set up ReadListener to read data for processing
ServletInputStream input = actx.getRequest().getInputStream();
ReadListener readListener = new ReadListenerImpl(input, actx);
input.setReadListener(readListener);
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void println(String output) {
System.out.println("[" + Thread.currentThread().getName() + "]" + output);
}
}
Listeners:
public class ReadListenerImpl implements ReadListener {
private ServletInputStream input = null;
private AsyncContext actx = null;
// store the processed data to be sent back to client later
private Queue<String> queue = new LinkedBlockingQueue<>();
ReadListenerImpl(ServletInputStream input, AsyncContext actx) {
this.input = input;
this.actx = actx;
}
#Override
public void onDataAvailable() throws IOException {
println("Data is now available, starting to read");
StringBuilder sb = new StringBuilder();
int len = -1;
byte b[] = new byte[8];
// We need to check input#isReady before reading data.
// The ReadListener will be invoked again when
// the input#isReady is changed from false to true
while (input.isReady() && (len = input.read(b)) != -1) {
String data = new String(b, 0, len);
sb.append(data);
}
println("Data read: "+sb.toString());
queue.add(sb.toString());
}
#Override
public void onAllDataRead() throws IOException {
println("All Data read, now invoking write listener");
// now all data are read, set up a WriteListener to write
ServletOutputStream output = actx.getResponse().getOutputStream();
WriteListener writeListener = new WriteListenerImpl(output, queue, actx);
output.setWriteListener(writeListener);
}
#Override
public void onError(Throwable throwable) {
println("onError");
actx.complete();
throwable.printStackTrace();
}
public static void println(String output) {
System.out.println("[" + Thread.currentThread().getName() + "]" + output);
}
}
public class WriteListenerImpl implements WriteListener {
private ServletOutputStream output = null;
private Queue<String> queue = null;
private AsyncContext actx = null;
WriteListenerImpl(ServletOutputStream output, Queue<String> queue, AsyncContext actx) {
this.output = output;
this.queue = queue;
this.actx = actx;
}
#Override
public void onWritePossible() throws IOException {
println("Ready to write, writing data");
// write while there is data and is ready to write
while (queue.peek() != null && output.isReady()) {
String data = queue.poll();
//do some processing here with the data
try {
data = data.toUpperCase();
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
println("Data written: "+data);
output.print(data);
}
// complete the async process when there is no more data to write
if (queue.peek() == null) {
actx.complete();
}
}
#Override
public void onError(Throwable throwable) {
println("onError");
actx.complete();
throwable.printStackTrace();
}
public static void println(String output) {
System.out.println("[" + Thread.currentThread().getName() + "]" + output);
}
}
Sysout logs:
[http-nio-8080-exec-4]Before starting job
[http-nio-8080-exec-4]After starting job
Sysout logs (when I run the server in debug mode):
[http-nio-8080-exec-6]Before starting job
[http-nio-8080-exec-6]After starting job
[http-nio-8080-exec-6]All Data read, now invoking write listener
[http-nio-8080-exec-6]Ready to write, writing data
Creating the new thread is unnecessary, set the readListener from the service method and everything will work asynchronously.
Couple of comments on your code. In the readListener you have:
while (input.isReady() && (len = input.read(b)) != -1)
would suggest instead using this to stick fully with the asynchronous api:
while (input.isReady() && !input.isFinished())
Also for your write listener you have:
while (queue.peek() != null && output.isReady())
you should reverse the conditionals to:
while (output.isReady() && queue.peek() != null)
this protects against calling ac.complete() early if the very last write goes asynchronous.
Related
This is the SocketServer code that generates a server thread
public class ProcessorCorresponder {
protected final static Logger logger = LogManager.getLogger( ProcessorCorresponder.class );
private static int port = Integer.parseInt(PropertiesLoader.getProperty("appserver.port") == null ? "666" : PropertiesLoader.getProperty("appserver.port"));
private static int maxConnections = Integer.parseInt(PropertiesLoader.getProperty("appserver.maxconnections") == null ? "666" : PropertiesLoader.getProperty("appserver.maxconnections"));
public static void main(String[] args) {
logger.info("Starting server .. "
+ "[port->" + port
+ ",databaseName->" + databaseName + "]");
try (ServerSocket listener = new ServerSocket();) {
listener.setReuseAddress(true);
listener.bind(new InetSocketAddress(port));
Socket server;
int i = 0;
while((i++ < maxConnections) || (maxConnections == 0)) {
server = listener.accept();
logger.debug(
"New Thread listening on " + server.getLocalAddress().toString() + ":" + server.getLocalPort()
+ ", initiated from IP => " + server.getInetAddress().toString() + ":" + server.getPort()
);
MySocketServer socSrv = new MySocketServer (server);
Thread t = new Thread( socSrv );
t.start();
}
} catch (Exception ex) {
logger.error("Error in ProcessorInterface", ex);
}
}
}
Server code: This is a thread to handle one connection, there is a program that monitors a serversocket and spins off request threads as needed.
public class MySocketServer implements Runnable {
protected final static Logger logger = LogManager.getLogger(MySocketServer.class);
private final Socket server;
// because we are using threads, we must make this volatile, or the class will
// never exit.
private volatile boolean shouldContinue = true;
private StringBuffer buffHeartbeatMessage = new StringBuffer().append((char) 0).append((char) 0).append((char) 0)
.append((char) 0).append((char) 0).append((char) 0);
private Heartbeat heartbeat = new Heartbeat(/* 60 */3000, buffHeartbeatMessage.toString());
public MySocketServer(Socket server) {
this.server = server;
}
#Override
public void run() {
try (BufferedReader in = new BufferedReader(new InputStreamReader(this.server.getInputStream()));
BufferedOutputStream out = new HeartbeatBufferedOutputStream(this.server.getOutputStream(),
heartbeat)) {
final StreamListener listener = new StreamListener(in);
listener.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent event) {
if (event.getID() == ActionEvent.ACTION_PERFORMED) {
if (event.getActionCommand().equals(StreamListener.ERROR)) {
logger.error("Problem listening to stream.");
listener.setShouldContinue(false);
stopRunning();
} else {
String messageIn = event.getActionCommand();
if (messageIn == null) { // End of Stream;
stopRunning();
} else { // hey, we can do what we were meant for
logger.debug("Request received from client");
// doing stuff here
...
// done doing stuff
logger.debug("Sending Client Response");
try {
sendResponse(opResponse, out);
} catch (Exception ex) {
logger.error("Error sending response to OP.", ex);
}
}
}
}
}
});
listener.start();
while (shouldContinue) {
// loop here until shouldContinue = false;
// this should be set to false in the ActionListener above
}
heartbeat.setShouldStop(true);
return;
} catch (Exception ex) {
logger.error("Error in ESPSocketServer", ex);
return;
}
}
private void stopRunning() {
shouldContinue = false;
}
private void sendResponse(ClientResponse opResponse, BufferedOutputStream out) throws Exception {
logger.debug("Before write");
out.write(opResponse.getResponse().getBytes());
logger.debug("After write. Before flush");
out.flush();
logger.debug("After flush");
// this log message is in my logs, so I know the message was sent
}
}
My StreamListener class.
public class StreamListener extends Thread {
protected final static Logger logger = LogManager.getLogger(StreamListener.class);
public final static String ERROR = "ERROR";
private BufferedReader reader = null;
private List<ActionListener> actionListeners = new ArrayList<>();
private boolean shouldContinue = true;
public StreamListener(BufferedReader reader) {
this.reader = reader;
}
#Override
public void run() {
while (shouldContinue) {
String message;
try {
// client blocks here and never receives message
message = reader.readLine();
ActionEvent event = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, message);
fireActionPerformed(event);
} catch (IOException e) {
e.printStackTrace();
ActionEvent event = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, ERROR);
fireActionPerformed(event);
}
}
}
public void setShouldContinue(boolean shouldContinue) {
this.shouldContinue = shouldContinue;
}
public boolean getShouldContinue() {
return shouldContinue;
}
public boolean addActionListener(ActionListener listener) {
return actionListeners.add(listener);
}
public boolean removeActionListener(ActionListener listener) {
return actionListeners.remove(listener);
}
private void fireActionPerformed(ActionEvent event) {
for (ActionListener listener : actionListeners) {
listener.actionPerformed(event);
}
}
}
My Heartbeat class
public class Heartbeat extends Thread {
private BufferedOutputStream bos = null;
private int beatDelayMS = 0;
private String message = null;
private boolean shouldStop = false;
public Heartbeat(int beatDelayMS, String message) {
this.beatDelayMS = beatDelayMS;
this.message = message;
setDaemon(true);
}
#Override
public void run() {
if (bos == null) { return; }
while(!shouldStop) {
try {
sleep(beatDelayMS);
try {
bos.write(message.getBytes());
bos.flush();
} catch (IOException ex) {
// fall thru
}
} catch (InterruptedException ex) {
if (shouldStop) {
return;
}
}
}
}
public void setBufferedOutputStream(BufferedOutputStream bos) {
this.bos = bos;
}
public BufferedOutputStream getBufferedOutputStream() {
return bos;
}
public void setShouldStop(boolean shouldStop) {
this.shouldStop = shouldStop;
}
public boolean getShouldStop() {
return shouldStop;
}
}
My HeartbeatBufferedOutputStream
public class HeartbeatBufferedOutputStream extends BufferedOutputStream {
private Heartbeat heartbeat = null;
public HeartbeatBufferedOutputStream(OutputStream out, Heartbeat heartbeat) {
super(out);
this.heartbeat = heartbeat;
this.heartbeat.setBufferedOutputStream(this);
heartbeat.start();
}
#Override
public synchronized void flush() throws IOException {
super.flush();
heartbeat.interrupt();
}
}
And finally here is the "Client" class
public class Mockup extends Thread {
protected final static Logger logger = LogManager.getLogger(Mockup.class);
// because we are using threads, we must make this volatile, or the class will
// never exit.
private volatile boolean shouldContinue = true;
public static void main(String[] args) {
new Mockup().start();
}
#Override
public void run() {
try (Socket socket = new Socket("localhost", 16100);
BufferedOutputStream out = new BufferedOutputStream(socket.getOutputStream());
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));) {
final StreamListener listener = new StreamListener(in);
listener.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent event) {
if (event.getID() == ActionEvent.ACTION_PERFORMED) {
if (event.getActionCommand().equals(StreamListener.ERROR)) {
logger.error("Problem listening to stream.");
listener.setShouldContinue(false);
stopRunning();
} else {
String messageIn = event.getActionCommand();
if (messageIn == null) { // End of Stream;
stopRunning();
} else { // hey, we can do what we were meant for
// convert the messageIn to an OrderPower request, this parses the information
logger.info("Received message from server. [" + messageIn + "].");
}
}
}
}
});
listener.start();
StringBuffer buff = new StringBuffer("Some message to send to server");
logger.info("Sending message to server [" + buff.toString() + "]");
out.write(buff.toString().getBytes());
out.flush();
boolean started = false;
while (shouldContinue) {
if (!started) {
logger.debug("In loop");
started = true;
}
// loop here until shouldContinue = false;
// this should be set to false in the ActionListener above
}
logger.info("Exiting Mockup");
return;
} catch (Exception ex) {
logger.error("Error running MockupRunner", ex);
}
}
private void stopRunning() {
shouldContinue = false;
}
}
I have confirmed from logging messages that the Server sends a message to the BufferedOutputStream, and is flushed, but the Client logs indicate that it is blocked on the reader.readLine() and never gets the message.
You are reading lines but you are never writing lines. Add a line terminator to what you send.
In a task which is run by a thread pool, I want to write a bunch of strings to remote, and there is a flag to indicate if the task has been cancelled or not.
I'm using the following code to make sure I can stop as soon as possible:
public void sendToRemote(Iterator<String> data, OutputStream remote) {
try {
System.out.println("##### staring sending")
while(!this.cancelled && data.hasNext()) {
remote.write(data.next())
}
System.out.println("##### finished sending")
System.out.println("")
} catch(Throwable e) {
e.printStackTrace();
} finally {
remote.close();
}
}
I found sometimes, if I give a very large data(or infinite iterator) to this method, even if I have set the this.cancelled to true later, it can't finish in time. The code seems blocked, and after a long time(1 minute or so), there will be an error like:
java.net.SocketTimeoutException: write blocked too long
So I guess it might be the remote.write method can block itself if there is too much data to send, but the remote doesn't consume it in time. Although I set this.cancelled to true, but the method has been blocked in the remote.write(data.next()) line for a long time, so it doesn't have chance to check the value of this.cancelled and skip the loop. Instead, at last, it throws an SocketTimeoutException after a long time.
Is my understanding correct? If it is, how can I avoid the blocking if there is too much data to send?
Try simply closing the remote OutputStream. Your thread will end with an exception.
Thread#1 busy performing sendToRemote();
Thread#2 decided enough is enough and closed the remote. (Assuming
the OutPutStream object was not thread-local, as in a global
reference somewhere)
Thread#1 died with an exception :)
EDIT I found this on the internet
Enabling linger and setting the timeout to a certain number of seconds
will cause a subsequent call to Socket.Close to block until either all data
in the send buffer has been sent or the timeout has elapsed.
The proper solution is probably to use NIO in some manner. I already commented on how Hadoop did it here using nio underneath.
But the simpler solution is in Dexter's answer. I also came across an answer from EJP who suggest to use a BufferedOutputStream to control when data goes out. So I combined the two to arrive at the TimedOutputStream shown below. It does not give complete control on output buffering to remote (much of it is done by the OS), but combining an appropriate buffer size and write timeout provides at least some control (see the second program for testing the TimedOutputStream).
I have not completely tested the TimedOutputStream, so do your own due diligence.
Edit: updated write-method for better correlation between buffer-size and write timeout, also tweaked test program. Added comments about non-safe async close of socket outputstream.
import java.io.*;
import java.util.concurrent.*;
/**
* A {#link BufferedOutputStream} that sets time-out tasks on write operations
* (typically when the buffer is flushed). If a write timeout occurs, the underlying outputstream is closed
* (which may not be appropriate when sockets are used, see also comments on {#link TimedOutputStream#interruptWriteOut}).
* A {#link ScheduledThreadPoolExecutor} is required to schedule the time-out tasks.
* This {#code ScheduledThreadPoolExecutor} should have {#link ScheduledThreadPoolExecutor#setRemoveOnCancelPolicy(boolean)}
* set to {#code true} to prevent a huge task queue.
* If no {#code ScheduledThreadPoolExecutor} is provided in the constructor,
* the executor is created and shutdown with the {#link #close()} method.
* #author vanOekel
*
*/
public class TimedOutputStream extends FilterOutputStream {
protected int timeoutMs = 50_000;
protected final boolean closeExecutor;
protected final ScheduledExecutorService executor;
protected ScheduledFuture<?> timeoutTask;
protected volatile boolean writeTimedout;
protected volatile IOException writeTimeoutCloseException;
/* *** new methods not in BufferedOutputStream *** */
/**
* Default timeout is 50 seconds.
*/
public void setTimeoutMs(int timeoutMs) {
this.timeoutMs = timeoutMs;
}
public int getTimeoutMs() {
return timeoutMs;
}
public boolean isWriteTimeout() {
return writeTimedout;
}
/**
* If a write timeout occurs and closing the underlying output-stream caused an exception,
* then this method will return a non-null IOException.
*/
public IOException getWriteTimeoutCloseException() {
return writeTimeoutCloseException;
}
public ScheduledExecutorService getScheduledExecutor() {
return executor;
}
/**
* See {#link BufferedOutputStream#close()}.
*/
#Override
public void close() throws IOException {
try {
super.close(); // calls flush via FilterOutputStream.
} finally {
if (closeExecutor) {
executor.shutdownNow();
}
}
}
/* ** Mostly a copy of java.io.BufferedOutputStream and updated with time-out options. *** */
protected byte buf[];
protected int count;
public TimedOutputStream(OutputStream out) {
this(out, null);
}
public TimedOutputStream(OutputStream out, ScheduledExecutorService executor) {
this(out, 8192, executor);
}
public TimedOutputStream(OutputStream out, int size) {
this(out, size, null);
}
public TimedOutputStream(OutputStream out, int size, ScheduledExecutorService executor) {
super(out);
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
if (executor == null) {
this.executor = Executors.newScheduledThreadPool(1);
ScheduledThreadPoolExecutor stp = (ScheduledThreadPoolExecutor) this.executor;
stp.setRemoveOnCancelPolicy(true);
closeExecutor = true;
} else {
this.executor = executor;
closeExecutor = false;
}
buf = new byte[size];
}
/**
* Flushbuffer is called by all the write-methods and "flush()".
*/
protected void flushBuffer(boolean flushOut) throws IOException {
if (count > 0 || flushOut) {
timeoutTask = executor.schedule(new TimeoutTask(this), getTimeoutMs(), TimeUnit.MILLISECONDS);
try {
// long start = System.currentTimeMillis(); int len = count;
if (count > 0) {
out.write(buf, 0, count);
count = 0;
}
if (flushOut) {
out.flush(); // in case out is also buffered, this will do the actual write.
}
// System.out.println(Thread.currentThread().getName() + " Write [" + len + "] " + (flushOut ? "and flush " : "") + "time: " + (System.currentTimeMillis() - start));
} finally {
timeoutTask.cancel(false);
}
}
}
protected class TimeoutTask implements Runnable {
protected final TimedOutputStream tout;
public TimeoutTask(TimedOutputStream tout) {
this.tout = tout;
}
#Override public void run() {
tout.interruptWriteOut();
}
}
/**
* Closes the outputstream after a write timeout.
* If sockets are used, calling {#link java.net.Socket#shutdownOutput()} is probably safer
* since the behavior of an async close of the outputstream is undefined.
*/
protected void interruptWriteOut() {
try {
writeTimedout = true;
out.close();
} catch (IOException e) {
writeTimeoutCloseException = e;
}
}
/**
* See {#link BufferedOutputStream#write(int b)}
*/
#Override
public void write(int b) throws IOException {
if (count >= buf.length) {
flushBuffer(false);
}
buf[count++] = (byte)b;
}
/**
* Like {#link BufferedOutputStream#write(byte[], int, int)}
* but with one big difference: the full buffer is always written
* to the underlying outputstream. Large byte-arrays are chopped
* into buffer-size pieces and writtten out piece by piece.
* <br>This provides a closer relation to the write timeout
* and the maximum (buffer) size of the write-operation to wait on.
*/
#Override
public void write(byte b[], int off, int len) throws IOException {
if (count >= buf.length) {
flushBuffer(false);
}
if (len <= buf.length - count) {
System.arraycopy(b, off, buf, count, len);
count += len;
} else {
final int fill = buf.length - count;
System.arraycopy(b, off, buf, count, fill);
count += fill;
flushBuffer(false);
final int remaining = len - fill;
int start = off + fill;
for (int i = 0; i < remaining / buf.length; i++) {
System.arraycopy(b, start, buf, count, buf.length);
count = buf.length;
flushBuffer(false);
start += buf.length;
}
count = remaining % buf.length;
System.arraycopy(b, start, buf, 0, count);
}
}
/**
* See {#link BufferedOutputStream#flush()}
* <br>If a write timeout occurred (i.e. {#link #isWriteTimeout()} returns {#code true}),
* then this method does nothing.
*/
#Override
public void flush() throws IOException {
// Protect against flushing before closing after a write-timeout.
// If that happens, then "out" is already closed in interruptWriteOut.
if (!isWriteTimeout()) {
flushBuffer(true);
}
}
}
And the test program:
import java.io.*;
import java.net.*;
import java.util.concurrent.*;
public class TestTimedSocketOut implements Runnable, Closeable {
public static void main(String[] args) {
TestTimedSocketOut m = new TestTimedSocketOut();
try {
m.run();
} finally {
m.close();
}
}
final int clients = 3; // 2 is minimum, client 1 is expected to fail.
final int timeOut = 1000;
final int bufSize = 4096;
final long maxWait = 5000L;
// need a large array to write, else the OS just buffers everything and makes it work
byte[] largeMsg = new byte[28_602];
final ThreadPoolExecutor tp = (ThreadPoolExecutor) Executors.newCachedThreadPool();
final ScheduledThreadPoolExecutor stp = (ScheduledThreadPoolExecutor) Executors.newScheduledThreadPool(1);
final ConcurrentLinkedQueue<Closeable> closeables = new ConcurrentLinkedQueue<Closeable>();
final CountDownLatch[] serversReady = new CountDownLatch[clients];
final CountDownLatch clientsDone = new CountDownLatch(clients);
final CountDownLatch serversDone = new CountDownLatch(clients);
ServerSocket ss;
int port;
#Override public void run() {
stp.setRemoveOnCancelPolicy(true);
try {
ss = new ServerSocket();
ss.bind(null);
port = ss.getLocalPort();
tp.execute(new SocketAccept());
for (int i = 0; i < clients; i++) {
serversReady[i] = new CountDownLatch(1);
ClientSideSocket css = new ClientSideSocket(i);
closeables.add(css);
tp.execute(css);
// need sleep to ensure client 0 connects first.
Thread.sleep(50L);
}
if (!clientsDone.await(maxWait, TimeUnit.MILLISECONDS)) {
println("CLIENTS DID NOT FINISH");
} else {
if (!serversDone.await(maxWait, TimeUnit.MILLISECONDS)) {
println("SERVERS DID NOT FINISH");
} else {
println("Finished");
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
#Override public void close() {
try { if (ss != null) ss.close(); } catch (Exception ignored) {}
Closeable c = null;
while ((c = closeables.poll()) != null) {
try { c.close(); } catch (Exception ignored) {}
}
tp.shutdownNow();
println("Scheduled tasks executed: " + stp.getTaskCount() + ", max. threads: " + stp.getLargestPoolSize());
stp.shutdownNow();
}
class SocketAccept implements Runnable {
#Override public void run() {
try {
for (int i = 0; i < clients; i++) {
SeverSideSocket sss = new SeverSideSocket(ss.accept(), i);
closeables.add(sss);
tp.execute(sss);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
class SeverSideSocket implements Runnable, Closeable {
Socket s;
int number, cnumber;
boolean completed;
public SeverSideSocket(Socket s, int number) {
this.s = s;
this.number = number;
cnumber = -1;
}
#Override public void run() {
String t = "nothing";
try {
DataInputStream in = new DataInputStream(s.getInputStream());
DataOutputStream out = new DataOutputStream(s.getOutputStream());
serversReady[number].countDown();
Thread.sleep(timeOut);
t = in.readUTF();
in.readFully(new byte[largeMsg.length], 0, largeMsg.length);
t += in.readUTF();
out.writeByte(1);
out.flush();
cnumber = in.readInt();
completed = true;
} catch (Exception e) {
println("server side " + number + " stopped after " + e);
// e.printStackTrace();
} finally {
println("server side " + number + " received: " + t);
if (completed && cnumber != number) {
println("server side " + number + " expected client number " + number + " but got " + cnumber);
}
close();
serversDone.countDown();
}
}
#Override public void close() {
TestTimedSocketOut.close(s);
s = null;
}
}
class ClientSideSocket implements Runnable, Closeable {
Socket s;
int number;
public ClientSideSocket(int number) {
this.number = number;
}
#SuppressWarnings("resource")
#Override public void run() {
Byte b = -1;
TimedOutputStream tout = null;
try {
s = new Socket();
s.connect(new InetSocketAddress(port));
DataInputStream in = new DataInputStream(s.getInputStream());
tout = new TimedOutputStream(s.getOutputStream(), bufSize, stp);
if (number == 1) {
// expect fail
tout.setTimeoutMs(timeOut / 2);
} else {
// expect all OK
tout.setTimeoutMs(timeOut * 2);
}
DataOutputStream out = new DataOutputStream(tout);
if (!serversReady[number].await(maxWait, TimeUnit.MILLISECONDS)) {
throw new RuntimeException("Server side for client side " + number + " not ready.");
}
out.writeUTF("client side " + number + " starting transfer");
out.write(largeMsg);
out.writeUTF(" - client side " + number + " completed transfer");
out.flush();
b = in.readByte();
out.writeInt(number);
out.flush();
} catch (Exception e) {
println("client side " + number + " stopped after " + e);
// e.printStackTrace();
} finally {
println("client side " + number + " result: " + b);
if (tout != null) {
if (tout.isWriteTimeout()) {
println("client side " + number + " had write timeout, close exception: " + tout.getWriteTimeoutCloseException());
} else {
println("client side " + number + " had no write timeout");
}
}
close();
clientsDone.countDown();
}
}
#Override public void close() {
TestTimedSocketOut.close(s);
s = null;
}
}
private static void close(Socket s) {
try { if (s != null) s.close(); } catch (Exception ignored) {}
}
private static final long START_TIME = System.currentTimeMillis();
private static void println(String msg) {
System.out.println((System.currentTimeMillis() - START_TIME) + "\t " + msg);
}
}
I got a very strange behaviour by reading bytes from the input stream of a socket.
In my project clients does requests to a service. For each request a new connection will be established.
At first the bytes are send that tells the service what kind of request will follow.
Then the request itself is send.
The service receives the bytes and proceeds the request. This does work for at least 95% of all request. For the remaining 5% there is a strange behaviour that i can not figure out.
The bytes are not all the bytes that got sended. But the most strange matter on this topic is that the missing bytes are not at the start or at the end of the stream. They are spread through the entire stream.
Sadly i can not provide the full code here cause it is work related. But i can provide the test code that shows the issue itself.
To figure out what is going on i wrote 2 classes. One derives from java.net.Socket and the other one from java.net.ServerSocket.
Here the code:
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
public class DebugSocket extends Socket
{
private class InputStreamWrapper extends InputStream
{
private int
availables,
closes,
marksupporteds,
resets;
private List<Integer>
marks = new ArrayList<Integer>(),
reads = new ArrayList<Integer>();
private List<Long>
skips = new ArrayList<Long>();
#Override
public int available() throws IOException
{
availables++;
return DebugSocket.this.origininput.available();
}
#Override
public void close() throws IOException
{
closes++;
DebugSocket.this.origininput.close();
}
#Override
public synchronized void mark(int readlimit)
{
marks.add(readlimit);
DebugSocket.this.origininput.mark(readlimit);
}
#Override
public boolean markSupported()
{
marksupporteds++;
return DebugSocket.this.origininput.markSupported();
}
#Override
public synchronized void reset() throws IOException
{
resets++;
DebugSocket.this.origininput.reset();
}
#Override
public int read() throws IOException
{
int read = DebugSocket.this.origininput.read();
reads.add(read);
if ( read != -1 )
{
DebugSocket.this.inputdebugbuffer.write(read);
}
return read;
}
#Override
public int read(byte[] b) throws IOException
{
int read = DebugSocket.this.origininput.read(b);
DebugSocket.this.inputdebugbuffer.write(b, 0, read);
return read;
}
#Override
public int read(byte[] b, int off, int len) throws IOException
{
int read = DebugSocket.this.origininput.read(b, off, len);
DebugSocket.this.inputdebugbuffer.write(b, off, read);
return read;
}
#Override
public long skip(long n) throws IOException
{
long skipped = DebugSocket.this.origininput.skip(n);
skips.add(skipped);
return skipped;
}
}
private class OutputStreamWrapper extends OutputStream
{
private int
flushes,
closes;
#Override
public void close() throws IOException
{
closes++;
DebugSocket.this.originoutput.close();
}
#Override
public void flush() throws IOException
{
flushes++;
DebugSocket.this.originoutput.flush();
}
#Override
public void write(int b) throws IOException
{
DebugSocket.this.outputdebugbuffer.write(b);
DebugSocket.this.originoutput.write(b);
DebugSocket.this.originoutput.flush();
}
#Override
public void write(byte[] b) throws IOException
{
DebugSocket.this.outputdebugbuffer.write(b);
DebugSocket.this.originoutput.write(b);
DebugSocket.this.originoutput.flush();
}
#Override
public void write(byte[] b, int off, int len) throws IOException
{
DebugSocket.this.outputdebugbuffer.write(b, off, len);
DebugSocket.this.originoutput.write(b, off, len);
DebugSocket.this.originoutput.flush();
}
}
private static final Object
staticsynch = new Object();
private static long
idcounter = 0;
private final long
id;
private final ByteArrayOutputStream
inputdebugbuffer,
outputdebugbuffer;
private final InputStream
inputwrapper;
private final OutputStream
outputwrapper;
private InputStream
origininput;
private OutputStream
originoutput;
public InputStream getInputStream() throws IOException
{
if ( origininput == null )
{
synchronized ( inputdebugbuffer )
{
if ( origininput == null )
{
origininput = super.getInputStream();
}
}
}
return inputwrapper;
}
public OutputStream getOutputStream() throws IOException
{
if ( originoutput == null )
{
synchronized ( outputdebugbuffer )
{
if ( originoutput == null )
{
originoutput = super.getOutputStream();
}
}
}
return outputwrapper;
}
public DebugSocket()
{
id = getNextId();
inputwrapper = new InputStreamWrapper();
outputwrapper = new OutputStreamWrapper();
inputdebugbuffer = new ByteArrayOutputStream();
outputdebugbuffer = new ByteArrayOutputStream();
}
private static long getNextId()
{
synchronized ( staticsynch )
{
return ++idcounter;
}
}
}
import java.io.IOException;
import java.net.ServerSocket;
public class DebugServerSocket extends ServerSocket
{
public DebugServerSocket() throws IOException
{
super();
}
public DebugSocket accept() throws IOException
{
DebugSocket s = new DebugSocket();
implAccept(s);
return s;
}
}
The class DebugSocket takes notification of each interaction with the InputStream as well as OutputStream
Now when the issue occurs i always can see that bytes are missing.
Here an example:
The client send 1758 bytes. I got the 23 top bytes from the member outputdebugbuffer in the DebugSocket.
Bytes: 0,0,0,0,0,0,0,2,0,0,6,-46,31,-117,8,0,0,0,0,0,0,0,-83
The server received 227 Bytes. For debug issues i always do read the input stream till i get a -1, so that all bytes got proceeded. Now the 16 leading bytes on serverside that i got from the member inputdebugbuffer in the DebugSocket.
Bytes: 0,0,0,6,-46,31,-117,8,0,0,0,0,0,0,0,-83
As shown there are 7 bytes missing. the first 8 bytes are a long value this one i changed to a byte value for debugging. So i figured that the first byte is always correct.
If it were a failure in the code no request would be proceeded but as i said before this happens only to 5% of all connections at best.
Got anyone an idea whats going on here?
I also used the DataInputStream and DataOutputStream to send the data. I always flush after each write operation as you can see in the OutputStreamWrapper of the DebugSocket.
Do i miss something here?
If some other code is required i will try to post it.
P.S. The service is multi threaded and processes 100 request parallel. Also the clients are multi threaded and do 20 requests parallel. As said each request uses its one connection and closes this one right after the request got proceeded.
I hope someone got an idea on this matter.
Edit:
There is no main method to show that does anything like asked in the comments but here the the code blocks of the client and the server that are used.
Client: (Run parallel in 20 threads)
public void sendRequest(long _requesttype, byte[] _bytes)
{
Socket socket = null;
DataInputStream input = null;
DataOutputStream output = null;
InputStream sinput = null;
OutputStream soutput = null;
try
{
socket = new DebugSocket();
socket.connect(serveraddress);
sinput = socket.getInputStream();
soutput = socket.getOutputStream();
input = new DataInputStream(sinput);
output = new DataOutputStream(soutput);
output.writeLong(_requesttype);
output.flush();
soutput.flush();
output.write(_bytes);
output.flush();
soutput.flush();
// wait for notification byte that service had received all data.
input.readByte();
}
catch (IOException ex)
{
LogHelper.log(ex);
}
catch (Error err)
{
throw err;
}
finally
{
output.flush();
soutput.flush();
input.close();
output.close();
finishSocket(socket);
}
}
Server: (Run in a thread for each request. Up to 100 threads)
public void proceedRequest(DebugSocket _socket)
{
DataInputStream input = null;
DataOutputStream output = null;
InputStream sinput = null;
OutputStream soutput = null;
try
{
sinput = _socket.getInputStream();
soutput = _socket.getOutputStream();
input = new DataInputStream(sinput);
output = new DataOutputStream(soutput);
RequestHelper.proceed(input.readLong(), input, output);
// send notification byte to the client.
output.writeByte(1);
output.flush();
soutput.flush();
}
catch (IOException ex)
{
LogHelper.log(ex);
}
catch (Error err)
{
throw err;
}
finally
{
output.flush();
soutput.flush();
input.close();
output.close();
}
}
In the server code the readLong() already fails cause of the missing bytes.
Ok im done with all possible ways to locate the cause. From my experience with socket programming and parallel processing i can say that there is no bug in the code itself. Sniffers as well tell me that. Something on my machine is messing with the transmission.
I deactivated all i could think of (firewall/antivir/malware scanner) but no effect.
Got someone an idea what else could mess with tcp packages?
Edit:
Ok i got it. AVG 2014 is messing. Jetzt deactivating the components did not work. In Options->Settings there is a menu point were you can deactivate the AVG-Protection.
Got someone knowledge on this topic?
My guess is there is a bug somewhere else in the code. I copied the DebugSocket class from the question and created a MCVE (see below). It works fine, I was unable to reproduce the "server cannot read long-value" problem. Try modifying the code below to include more of the your own code until you can reproduce the problem, that should give you an idea where to look for the underlying cause.
import java.io.*;
import java.net.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
public class TestDebugSocket implements Runnable, Closeable {
public static void main(String[] args) {
TestDebugSocket m = new TestDebugSocket();
try {
m.run();
} catch (Exception e) {
e.printStackTrace();
} finally {
m.close();
}
}
final int clients = 20;
final boolean useDebugSocket = true;
final byte[] someBytes = new byte[1758];
final ThreadPoolExecutor tp = (ThreadPoolExecutor) Executors.newCachedThreadPool();
final AtomicLong clientId = new AtomicLong();
final ConcurrentLinkedQueue<Closeable> closeables = new ConcurrentLinkedQueue<Closeable>();
final long maxWait = 5_000L;
final CountDownLatch serversReady = new CountDownLatch(clients);
final CountDownLatch clientsDone = new CountDownLatch(clients);
ServerSocket ss;
int port;
#Override public void run() {
try {
ss = useDebugSocket ? new DebugServerSocket() : new ServerSocket();
ss.bind(null);
port = ss.getLocalPort();
tp.execute(new SocketAccept());
for (int i = 0; i < clients; i++) {
ClientSideSocket css = new ClientSideSocket();
closeables.add(css);
tp.execute(css);
}
if (!clientsDone.await(maxWait, TimeUnit.MILLISECONDS)) {
System.out.println("CLIENTS DID NOT FINISH");
} else {
System.out.println("Finished");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
close();
}
}
#Override public void close() {
try { if (ss != null) ss.close(); } catch (Exception ignored) {}
Closeable c = null;
while ((c = closeables.poll()) != null) {
try { c.close(); } catch (Exception ignored) {}
}
tp.shutdownNow();
}
class DebugServerSocket extends ServerSocket {
public DebugServerSocket() throws IOException {
super();
}
#Override public DebugSocket accept() throws IOException {
DebugSocket s = new DebugSocket();
implAccept(s);
return s;
}
}
class SocketAccept implements Runnable {
#Override public void run() {
try {
for (int i = 0; i < clients; i++) {
SeverSideSocket sss = new SeverSideSocket(ss.accept());
closeables.add(sss);
tp.execute(sss);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
class SeverSideSocket implements Runnable, Closeable {
Socket s;
public SeverSideSocket(Socket s) {
this.s = s;
}
#Override public void run() {
Long l = -1L;
byte[] received = new byte[someBytes.length];
try {
DataInputStream in = new DataInputStream(s.getInputStream());
DataOutputStream out = new DataOutputStream(s.getOutputStream());
serversReady.countDown();
if (!serversReady.await(maxWait, TimeUnit.MILLISECONDS)) {
System.out.println("CLIENTS DID NOT CONNECT ON TIME TO SERVER");
}
l = in.readLong();
in.readFully(received);
out.writeByte(1);
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
// write to console at end to prevent synchronized socket I/O
System.out.println("received long: " + l);
close();
}
}
#Override public void close() {
TestDebugSocket.close(s);
s = null;
}
}
class ClientSideSocket implements Runnable, Closeable {
Socket s;
#SuppressWarnings("resource")
#Override public void run() {
Long l = -1L;
Byte b = -1;
try {
s = useDebugSocket ? new DebugSocket() : new Socket();
s.connect(new InetSocketAddress(port));
DataInputStream in = new DataInputStream(s.getInputStream());
DataOutputStream out = new DataOutputStream(s.getOutputStream());
l = clientId.incrementAndGet();
out.writeLong(l);
out.write(someBytes);
out.flush();
b = in.readByte();
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("long send: " + l + ", result: " + b);
close();
clientsDone.countDown();
}
}
#Override public void close() {
TestDebugSocket.close(s);
s = null;
}
}
static void close(Socket s) {
try { if (s != null) s.close(); } catch (Exception ignored) {}
}
}
private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
// Check for closing frame
if (frame instanceof CloseWebSocketFrame) {
handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());
return;
}
if (frame instanceof PingWebSocketFrame) {
ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
return;
}
if (!(frame instanceof TextWebSocketFrame)) {
throw new UnsupportedOperationException(String.format("%s frame types not supported", frame.getClass()
.getName()));
}
// Send the uppercase string back.
String request = ((TextWebSocketFrame) frame).text();
if (logger.isLoggable(Level.FINE)) {
logger.fine(String.format("%s received %s", ctx.channel(), request));
}
Message msg = new Message(ctx.channel(), request);
ReadQueueHandler.getInstance().addMessageToProcess(msg);
}
public class ReadQueueHandler implements Runnable {
private static int POOL_SIZE = 3;
private static ReadQueueHandler instance;
private final BlockingQueue<Message> messageQueue;
private final ExecutorService threadPool;
private final int threadPoolSize;
private final boolean isActive;
private ReadQueueHandler() {
this.threadPoolSize = POOL_SIZE;
this.threadPool = Executors.newFixedThreadPool(threadPoolSize);
this.messageQueue = new LinkedBlockingQueue<Message>();
isActive = true;
initThreadPool();
}
private void initThreadPool() {
for (int i = 0; i < this.threadPoolSize; i++) {
this.threadPool.execute(this);
}
}
/**
* Add message to read queue
*
* #param message
* - adding message
*/
public void addMessageToProcess(Message message) {
if (message != null) {
this.messageQueue.add(message);
}
}
#Override
public void run() {
while (isActive) {
Message message = null;
try {
message = this.messageQueue.take();
} catch (InterruptedException e) {
System.out.println("Exceptio " + e);
/*
* TODO Add logging
*/
}
if (message != null) {
Channel channel = message.getChannel();
channel.write(new TextWebSocketFrame("Message handled "));
}
}
}
public static ReadQueueHandler getInstance() {
if (instance == null) {
instance = new ReadQueueHandler();
}
return instance;
}
}
If i execute Channel.write("something") instead of adding data to queue, then all work fine and client get data. But if Channel.write("") execute from another thread, than data is not got. What can be reason? Channel write can not be execute from another thread?
For me it seems like you forgot to call flush() after the write is done to guaranteer it's flushed to the socket. For example you could fix this by use:
channel.writeAndFlush(new TextWebSocketFrame("Message handled "));
I am new to Netty and trying to writing a server client project using it. I am able to send the request from client to the server successfully and am able to process it too using my listeners. But the issue am having is when I try to write the response back to the channel for the client processing on the server side using channel.write(), I am unable to get it back at client.
This is my first time on stackoverflow, please forgive if I make some mistake while asking the question or for indentation issues while posting the code.
This is my server :
public class SocketIOServer {
private Logger log = Logger.getLogger(getClass());
private ServerBootstrap bootstrap;
private Channel serverChannel;
private int port;
private boolean running;
public SocketIOServer(int port) {
this.port = port;
this.running = false;
}
public boolean isRunning() {
return this.running;
}
public void start() {
bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory(
Executors.newCachedThreadPool(),
Executors.newCachedThreadPool()));
try {
EncryptionManager encryptionManager = EncryptionManager.getInstance();
} catch (Exception ex) {
java.util.logging.Logger.getLogger(SocketIOServer.class.getName()).log(Level.SEVERE, null, ex);
}
// Set up the event pipeline factory.
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
#Override
public ChannelPipeline getPipeline() throws Exception {
ChannelPipeline pipeline = pipeline();
pipeline.addLast("decode", new FrameDecoder(){
#Override
protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buf) throws Exception {
// Make sure if the length field was received.
if (buf.readableBytes() < 4) {
// The length field was not received yet - return null.
// This method will be invoked again when more packets are
// received and appended to the buffer.
return null;
}
// The length field is in the buffer.
// Mark the current buffer position before reading the length field
// because the whole frame might not be in the buffer yet.
// We will reset the buffer position to the marked position if
// there's not enough bytes in the buffer.
buf.markReaderIndex();
// Read the length field.
int length = buf.readInt();
// Make sure if there's enough bytes in the buffer.
if (buf.readableBytes() < length) {
// The whole bytes were not received yet - return null.
// This method will be invoked again when more packets are
// received and appended to the buffer.
// Reset to the marked position to read the length field again
// next time.
buf.resetReaderIndex();
return null;
}
// There's enough bytes in the buffer. Read it.
ChannelBuffer frame = buf.readBytes(length);
// Successfully decoded a frame. Return the decoded frame.
return frame;
}
});
pipeline.addLast("handler", new GameServerHandler());
return pipeline;
}
});
bootstrap.setOption("backlog", 1024);
// Bind and start to accept incoming connections.
this.serverChannel = bootstrap.bind(new InetSocketAddress(port));
this.running = true;
log.info("Server Started at port [" + port + "]");
System.out.println("Server Started at port [" + port + "]");
}
public static void main(String[] args) {
SocketIOServer server = new SocketIOServer(8888);
server.start();
}
Server Handler class :
public class GameServerHandler extends SimpleChannelUpstreamHandler {
#Override
public void handleUpstream(
ChannelHandlerContext ctx, ChannelEvent e) throws Exception {
if (e instanceof ChannelStateEvent) {
logger.info(e.toString());
}
super.handleUpstream(ctx, e);
}
#Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception{
System.out.println("Following data recieved at the server");
ConnectionManager connectionManager = ConnectionManager.getInstance();
List<Object> paramsList = new ArrayList<Object>();
NetworkClient client = connectionManager.getNetworkClient(e.getChannel().getId());
ChannelBuffer ebuf = (ChannelBuffer)e.getMessage();
if( client == null ) {
client = new NetworkClient(e.getChannel());
connectionManager.addNetworkClient(e.getChannel().getId(), client);
}
byte [] encryptedData = ebuf.array();
System.out.println("encrypted data size : "+ encryptedData.length);
System.out.println("encrypted data : "+ encryptedData);
byte [] decrpytedData = null;
try {
decrpytedData = EncryptionManager.getInstance().decrypt(encryptedData, EncryptionManager.getInstance().getPrivateKey());
}catch (Throwable ee){
ee.printStackTrace();
}
ChannelBuffer buf = ChannelBuffers.buffer(decrpytedData.length);
buf.writeBytes(decrpytedData);
while(buf.readable()) {
long gameTableId = buf.readLong();
GameTable gameTable = gameTableController.getTablePeer(gameTableId);
if(gameTable == null) {
GameTable newGameTable = new GameTable();
newGameTable.setTableId(gameTableId);
newGameTable.registerListeners();
gameTableController.storeTablePeer(gameTableId, newGameTable);
}
int eventHash = buf.readInt();
String eventName = getEventNameFromEventHash(eventHash);
int paramCount = buf.readInt();
if(paramCount > 0) {
for(int count=0;count<paramCount;count++) {
populateParamList(buf, paramsList);
}
if(!NetworkMessenger.broadcastToAllFromNetwork(eventName, client, paramsList.toArray(new Object[paramsList.size()]))) {
logger.debug( "Unhandled Data:" + eventName);
System.out.println("Unhandled Data:" + eventName);
}
logger.debug( "Data processed successfully for " + eventName + " game table id : " + gameTableId);
System.out.println("Data processed successfully for " + eventName + " game table id : " + gameTableId);
}
break;
}
}
#Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {
e.getCause().printStackTrace();
logger.log(
Level.WARN,
"Unexpected exception from downstream.",
e.getCause());
Channel ch = e.getChannel();
ch.close();
}
#Override
public void channelConnected(
ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
ChannelFuture cf = e.getFuture();
cf.addListener(new Greeter());
}
#Override
public void channelDisconnected(
ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
// Unregister the channel from the global channel list
// so the channel does not receive messages anymore.
channels.remove(e.getChannel());
}
private static final class Greeter implements ChannelFutureListener {
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
// Once session is secured, send a greeting.
String welcomeMsg = "Welcome";
ChannelBuffer cb = ChannelBuffers.dynamicBuffer();
cb.writeBytes(welcomeMsg.getBytes());
future.getChannel().write(cb);
// Register the channel to the global channel list
// so the channel received the messages from others.
channels.add(future.getChannel());
} else {
future.getChannel().close();
}
}
}
}
This is my client class :
public class Clientbot {
public static void main(String[] args) throws IOException {
String host = "localhost";
int port = 8888;
final ChannelBuffer buf = ChannelBuffers.dynamicBuffer();
// Configure the client.
ChannelFactory factory =
new NioClientSocketChannelFactory(
Executors.newCachedThreadPool(),
Executors.newCachedThreadPool());
ClientBootstrap bootstrap = new ClientBootstrap(factory);
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
public ChannelPipeline getPipeline() {
ChannelPipeline pipeline = Channels.pipeline();
pipeline.addLast("decode", new FrameDecoder(){
#Override
protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buf) throws Exception {
// Make sure if the length field was received.
if (buf.readableBytes() < 4) {
return null;
}
buf.markReaderIndex();
// Read the length field.
int length = buf.readInt();
// Make sure if there's enough bytes in the buffer.
if (buf.readableBytes() < length) {
buf.resetReaderIndex();
return null;
}
// There's enough bytes in the buffer. Read it.
ChannelBuffer frame = buf.readBytes(length);
// Successfully decoded a frame. Return the decoded frame.
return frame;
}
});
pipeline.addLast("handler", new ClientHandler());
return pipeline;
}
});
bootstrap.setOption("tcpNoDelay", true);
bootstrap.setOption("keepAlive", true);
ChannelFuture future = bootstrap.connect(new InetSocketAddress(host, port));
// Wait until the connection attempt succeeds or fails.
Channel channel = future.awaitUninterruptibly().getChannel();
if (!future.isSuccess()) {
future.getCause().printStackTrace();
bootstrap.releaseExternalResources();
return;
}
ChannelFuture lastWriteFuture = null;
try {
lastWriteFuture = writeSecureSampleData(channel, buf);
} catch (Exception ex) {
ex.printStackTrace();
}
// Wait until all messages are flushed before closing the channel.
if (lastWriteFuture != null) {
lastWriteFuture.awaitUninterruptibly();
}
// Close the connection. Make sure the close operation ends because
// all I/O operations are asynchronous in Netty.
channel.close().awaitUninterruptibly();
// Shut down all thread pools to exit.
bootstrap.releaseExternalResources();
}
private static ChannelFuture writeSecureSampleData(Channel channel, ChannelBuffer buffer) throws Exception {
long gameId = 1234;
ChannelBuffer buf = ChannelBuffers.buffer(256);
buf.writeLong(gameId);
writeParamsForLogin(buf);
byte [] data = buf.array();
byte [] encryptedData = EncryptionManager.getInstance().encrypt(data, EncryptionManager.getInstance().getPublicKey());
int size = encryptedData.length;
buffer.writeInt(size);
buffer.writeBytes(encryptedData);
ChannelFuture writeFuture = channel.write(buffer);
return writeFuture;
}
private static void writeParamsForLogin(ChannelBuffer buf) {
int eventHash = getEventHash("Login");
buf.writeInt(eventHash);
int paramCount = 3 ; // in case of PlayerToken 2 parameters to be send : player id + player token
buf.writeInt(paramCount);
// version , E , N
String version = "1.0";
buf.writeInt(dataType_String);
buf.writeInt(version.length());
buf.writeBytes(version.getBytes());
String E = "61";
buf.writeInt(dataType_ByteArray);
buf.writeInt(E.length());
buf.writeBytes(E.getBytes());
String N = "53";
buf.writeInt(dataType_ByteArray);
buf.writeInt(N.length());
buf.writeBytes(N.getBytes());
}
}
and the client handler class :
public class ClientHandler extends SimpleChannelUpstreamHandler {
#Override
public void messageReceived(
ChannelHandlerContext ctx, MessageEvent e) {
System.err.println(e.getMessage());
System.out.println("Message Recieved");
ChannelBuffer buf = (ChannelBuffer)e.getMessage();
while (buf.readable()) {
System.out.println((char) buf.readByte());
System.out.flush();
}
}
#Override
public void exceptionCaught(
ChannelHandlerContext ctx, ExceptionEvent e) {
logger.log(
Level.WARNING,
"Unexpected exception from downstream.",
e.getCause());
e.getCause().printStackTrace();
e.getChannel().close();
}
}
On receiving the request data on the server, I am processing it and writing back the response using the following method :
private static boolean sendMessage(NetworkClient targetObject, String eventName, Object[] params) {
logger.debug("Sending message for the event : " + eventName);
if(targetObject == null) {
sendMessageToAll(eventName, params);
} else {
if (targetObject.getClientChannel() == null) {
logger.error("Target not defined.");
return false;
}
Channel clientChannel = targetObject.getClientChannel();
ChannelBuffer buf = ChannelBuffers.dynamicBuffer();
long hash = getHashString(eventName);
buf.writeInt(512);
buf.writeLong(hash);
if(params != null) {
buf.writeInt(params.length);
for(Object param : params) {
int type = getTypeOfObject(param);
buf.writeInt(type);
writeParamToBuffer(buf, type, param);
}
}
ChannelFuture cf = null;
try {
cf = clientChannel.write(buf);
if(cf.isSuccess()){
System.out.println("Written to client successfully");
}
} catch (Exception e) {
logger.error("Error in broadcasting for event : " + eventName, e);
} finally {
}
}
return false;
}
This code is still a work in progress, so you might find lot of "not required" stuff in there. I just wanted to show the logic that I am trying to use and want to know why it is not working.
I have taken help from examples at http://docs.jboss.org/netty/3.2/xref/org/jboss/netty/example/ for writing this.
Thanks in advance for any help.
Very complete code.
An easy way to write text to a websockets channel is to do this:
ChannelFuture writeFuture = channel.write(new TextWebSocketFrame("My Text here!");
You don't need to deal with the ChannelBuffers directly. That may resolve your problem.