I'm somewhat new to network programming. I have a server that uses Ubuntu, which needs to send data quickly to about 50 clients.
As of now, I have about 50 concurrent connections (of course), and it needs to be scalable up to 500.
I've created a JSON-based protocol to handle the streams and binary. Can these 2 classes be reviewed for any potential problems, bad techniques, etc?
JSONServer
package com.wordpress.waffalware.tcpson;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import org.json.JSONObject;
public class JSONServer {
private int connectedSocketLimit = 10000;
private RequestCallback onRequest;
private ErrorCallback onError;
private ServerSocket server;
private boolean connected;
public final ArrayList<JSONClient> clients = new ArrayList<JSONClient>();
public void setOnRequest(RequestCallback handler) {
onRequest = handler;
}
public void setOnError(ErrorCallback handler) {
onError = handler;
}
public boolean isConnected() {
return connected;
}
public void start(int port) throws IOException {
server = new ServerSocket();
server.setSoTimeout(5000);
server.setReuseAddress(true);
server.setPerformancePreferences(1, 0, 0);
server.bind(new InetSocketAddress(InetAddress.getByAddress(new byte[] {
0, 0, 0, 0 }), port));
Thread accepter = new Thread(new Runnable() {
@Override
public void run() {
while (connected) {
Socket client = null;
try {
client = server.accept();
} catch (SocketTimeoutException e) {
continue;
} catch (IOException e) {
if (onError != null) {
onError.onError(e);
}
}
if (client != null) {
handleClient(new JSONClient(client));
}
}
}
}, "JSONServer-accepter");
connected = true;
accepter.start();
}
public void close() {
connected = false;
if (server != null) {
try {
server.close();
} catch (IOException e) {
e.printStackTrace();
}
server = null;
}
}
@Override
protected void finalize() throws Throwable {
close();
super.finalize();
}
private void handleClient(final JSONClient client) {
Thread clientThread = new Thread(new Runnable() {
@Override
public void run() {
try {
clients.remove(client);
clients.add(client);
client.setSoTimeout(5000);
while (connected) {
JSONPacket request = client.readPacket();
if (request == null) {
break;
}
JSONPacket response;
if (onRequest != null) {
response = onRequest.onRequest(request);
} else {
response = new JSONPacket(new JSONObject());
}
client.writePacket(response);
if (clients.size() > connectedSocketLimit) {
break;
}
}
} catch (SocketTimeoutException e) {
if (onError != null) {
onError.onError(e);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
client.close();
clients.remove(client);
}
}
}, "JSONServer-clientHandler");
clientThread.start();
}
public interface RequestCallback {
public JSONPacket onRequest(JSONPacket request);
}
public interface ErrorCallback {
public void onError(Exception e);
}
}
JSONClient
package com.wordpress.waffalware.tcpson;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
import org.json.JSONObject;
public class JSONClient {
private boolean connected;
private Socket client;
private static final byte[] VERSION1 = new byte[] { 0x17, 0x78 };
public static JSONPacket getResponse(String host, int port,
JSONPacket request) throws IOException {
JSONClient client = new JSONClient();
try {
client.setSoTimeout(5000);
client.connect(new InetSocketAddress(host, port));
client.writePacket(request);
return client.readPacket();
} finally {
client.close();
}
}
public JSONClient() {
client = new Socket();
}
protected JSONClient(Socket baseClient) {
client = baseClient;
connected = baseClient.isConnected() && !baseClient.isClosed();
}
public boolean isConnected() {
return connected;
}
public void connect(SocketAddress endpoint) throws IOException {
client.connect(endpoint);
connected = true;
}
public void close() {
connected = false;
try {
client.close();
} catch (IOException e) {
e.printStackTrace();
}
client = new Socket();
}
/**
* Writes <code>packet</code> to the <code>Socket</code>.
*
* @param packet
* The <code>JSONPacket</code> to write to <code>Socket</code>.
* @throws IOException
* If an IO/Error has occurred.
*/
public void writePacket(JSONPacket packet) throws IOException {
try {
OutputStream str = client.getOutputStream();
byte[] payloadRaw = packet.getPayload().toString().getBytes();
byte[] extraData = packet.getExtraData();
byte[] buffer = ByteBuffer
.allocate(14 + payloadRaw.length + extraData.length)
.put(VERSION1).put(intToByteArray(0))
.put(intToByteArray(payloadRaw.length))
.put(intToByteArray(extraData.length)).put(payloadRaw)
.put(extraData).array();
str.write(buffer);
str.flush();
} catch (IOException e) {
close();
throw e;
}
}
/**
* Reads and creates a <code>JSONPacket</code> from the <code>Socket</code><br>
* <br>
* If <code>null</code> is returned, the other side of the
* <code>Socket</code> should be treated as incompatible, and the
* <code>Socket</code> be closed immediately.
*
* @param str
* The stream to read the <code>JSONPacket</code> from.
* @return A <code>JSONPacket</code> representing the data from the stream,
* or <code>null</code> if there were no bytes to read or the data
* does not follow the format.
* @throws IOException
* If an IO/Error has occurred.
*/
public JSONPacket readPacket() throws IOException {
try {
byte[] tempBytes;
JSONPacket ret = null;
InputStream str = client.getInputStream();
tempBytes = new byte[14];
if (str.read(tempBytes, 0, tempBytes.length) == tempBytes.length) {
if (VERSION1[0] == tempBytes[0] & VERSION1[1] == tempBytes[1]) {
// int reservedValue = byteArrayToInt(tempBytes, 2);
int payloadSize = byteArrayToInt(tempBytes, 6);
int extraDataSize = byteArrayToInt(tempBytes, 10);
if (payloadSize >= 0 & extraDataSize >= 0) {
tempBytes = new byte[payloadSize];
if (str.read(tempBytes, 0, tempBytes.length) == tempBytes.length) {
JSONObject payloadData = new JSONObject(new String(
tempBytes));
tempBytes = new byte[extraDataSize];
if (str.read(tempBytes, 0, tempBytes.length) == tempBytes.length) {
ret = new JSONPacket(payloadData, tempBytes);
}
}
}
}
}
return ret;
} catch (IOException e) {
close();
throw e;
}
}
public void setSoTimeout(int timeout) throws SocketException {
// TODO: Should the connection stop if an exception is thrown?
client.setSoTimeout(timeout);
}
private static int byteArrayToInt(byte[] buffer, int offset) {
return (buffer[offset] & 0xFF) << 24
| (buffer[offset + 1] & 0xFF) << 16
| (buffer[offset + 2] & 0xFF) << 8 | buffer[offset + 3] & 0xFF;
}
private static byte[] intToByteArray(int buffer) {
return new byte[] { (byte) ((buffer >> 24) & 0xFF),
(byte) ((buffer >> 16) & 0xFF), (byte) ((buffer >> 8) & 0xFF),
(byte) (buffer & 0xFF) };
}
}
The JSONPacket
is just a holding object for a byte array, and a JSONObject
.
A long time ago, the accept
function kept throwing an IOException
because there were "too many open files".
I suspected it was that I forgot to call Socket.close()
after I called accept
.
Main Points
- Are there any techniques I should be using?
- Does the code above properly close sockets?
- I have researched about
java.nio
and wondered, should I use that package in my current situation? I know it's meant for non-blocking sockets, and millions of connections, but I only expect to have up to 500 concurrent connections. - Am I using threads correctly?
1 socket --> 1 connection