This is code from my android client which communicates over wifi to a small server program (not coded in java). This is my first time playing around with sockets, so I'm sure there are lots of little gotchas to worry about. For this project I am limited to Java 7.
The protocol for sending a message is
- 4 Bytes for an integer message size M
- M Bytes for the message payload
The 4 Byte size is trimmed from the payload when reviving and added to the payload when sending.
I decided that the user might want to be able to add and drop connections, so I made a class to handle the socket, input stream and output stream. I didn't create every function of the three classes, I just picked a functions I needed.
public final class SocketIO {
private Socket socket;
private InputStream input;
private OutputStream output;
public SocketIO(){
socket = null;
input = null;
output = null;
}
public void connect(final String ip, final int port) throws IOException {
close();
socket = new Socket(ip, port);
input = socket.getInputStream();
output = socket.getOutputStream();
}
public boolean isConnected() {
if (socket == null){
return false;
}
else if (socket.isConnected()){
return true;
}
else {
socket = null;
return false;
}
}
public void close() throws IOException {
if (socket != null) {
socket.close();
}
if (input != null) {
input.close();
}
if (output != null) {
output.close();
}
}
public void read(byte[] bytes) throws IOException {
if (!isConnected()) {
throw new IOException("Socket not connected");
}
input.read(bytes);
}
public int read(byte[] bytes, int i, int remaining) throws IOException {
if (!isConnected()) {
throw new IOException("Socket not connected");
}
return input.read(bytes, i, remaining);
}
public void write(byte[] bytes) throws IOException {
if (!isConnected()) {
throw new IOException("Socket not connected");
}
output.write(bytes);
}
public void flush() throws IOException {
if (!isConnected()) {
throw new IOException("Socket not connected");
}
output.flush();
}
}
I created two Runnable
classes to read and write to the SocketIO
class. They each require a Queue<byte[]>
to accept incoming or send outgoing message payloads. For this I have been using a ConcurrentLinkedQueue
.
public final class ByteWriter implements Runnable {
private final Queue<byte[]> queue;
final private SocketIO io;
public ByteWriter(final SocketIO serverIo, final Queue<byte[]> source) {
io = serverIo;
queue = source;
}
@Override
public final void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
if (io.isConnected()){
while (queue.size() > 0) {
final byte[] sendBytes = queue.remove();
io.write(ByteBuffer.allocate(4).putInt(sendBytes.length).array());
io.write(sendBytes);
io.flush();
}
}
Thread.sleep(100);
}
} catch (final IOException | InterruptedException e) {
e.printStackTrace();
}
}
}
And here is the Reader:
public final class ByteReader implements Runnable {
private final Queue<byte[]> queue;
final private SocketIO io;
public ByteReader(final SocketIO serverIo, final Queue<byte[]> destination) {
io = serverIo;
queue = destination;
}
@Override
public final void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
if (!io.isConnected()) {
Thread.sleep(100);
continue;
}
final byte[] receiveSizeBytes = new byte[4];
io.read(receiveSizeBytes);
final int receiveSize = ByteBuffer.wrap(receiveSizeBytes).getInt();
final byte[] receiveBytes = new byte[receiveSize];
int bytesRemaining = receiveSize;
int offset = 0;
while (bytesRemaining > 0) {
final int readCount = io.read(receiveBytes, offset, bytesRemaining);
offset += readCount;
bytesRemaining -= readCount;
Thread.sleep(10);
}
queue.add(receiveBytes);
Thread.sleep(100);
}
} catch (final IOException | InterruptedException e) {
e.printStackTrace();
}
}
}
The class which manages these new objects:
public final class ServerConnection implements Runnable {
private final String ip;
private final int port;
private final SocketIO io;
private final Thread writer;
private final Thread reader;
public ServerConnection(
final String ipAddress,
final int portNumber,
final Queue<byte[]> incoming,
final Queue<byte[]> outgoing) {
ip = ipAddress;
port = portNumber;
io = new SocketIO();
writer = new Thread(new ByteWriter(io, outgoing));
reader = new Thread(new ByteReader(io, incoming));
}
@Override
public void run() {
writer.start();
reader.start();
try {
io.connect(ip, port);
while (!Thread.currentThread().isInterrupted()) {
if (!io.isConnected()) {
break;
}
Thread.sleep(100);
}
} catch (final IOException | InterruptedException e) {
e.printStackTrace();
}
try {
io.close();
writer.interrupt();
reader.interrupt();
writer.join();
reader.join();
} catch (final IOException | InterruptedException e) {
e.printStackTrace();
}
}
}
Here is an example of establishing the connection in my program:
public class ControlPanel extends Activity { private final Queue<byte[]> incoming = new ConcurrentLinkedQueue<>(); private final Queue<byte[]> outgoing = new ConcurrentLinkedQueue<>(); private Thread connection = new Thread(); ....... public void connectServer(final View view){ connection.interrupt(); try { connection.join(); } catch (InterruptedException e) { e.printStackTrace(); } final EditText editText = (EditText) findViewById(R.id.editText); connection = new Thread(new ServerConnection(editText.getText().toString(), 1337, incoming, outgoing)); connection.start(); }