TCP Android client and server code

The server code below which I blogged about during it progress is now working and ready for me to move on to other little projects.

When the server receives a header of “GSIF” (Get Server Image File) it also reads the requested width and height of the image. It will load the image in, scale it to the requested size, compress it as a PNG and then send the data to the client.

EDIT: This would have been much better if I’d have used DataInputStream and DataOutputStream instead of reading bytes.

android-tcp-client-server-app

Whilst the server is running its output is like this:

WLGfx Home Server - Starting up
Shutdown hook attached
TCP Server thread started...
police pulled me over.jpg
icy stare from wife.jpg
quiche.jpg
shopping carts.jpg
liverpool shoe shop burgled.jpg
camo-truck.jpg
monkey cpr.jpg
cure for shits.jpg
irish speed sign.jpg
hold in a fart.jpg
satisfaction erection.jpg
6 to 8 beers a day.jpg
beer and scrambled eggs.jpg
toilet paper.jpg
ass signals batman.jpg
moby stairlift.jpg
it might rain today.jpg
hulk jerking you off.jpg
prick with a fork.jpg
sunday lunches.jpg
dog saves baby.jpg
evolution started this way.jpg
less make up.jpg
ham football.jpg
poor blind dog.jpg
turning the heat off.jpg
drink with me.jpg
happy hour about to end.jpg
rock bottom.jpg
astronaught carrot.jpg
seeing eye dogs.jpg
david fell decorating.jpg
two piece bathing suit.jpg
key cleaner.jpg
balls-stuck-in-door.jpg
jehovas never returned.jpg
ride me all day.jpg
TCP Incoming socket handler
Read 4 bytes from socket
Got header: GSIF
Read 2 bytes from socket
Read 2 bytes from socket
Requested dimensions: 896,431
TCP Incoming socket handler
Read 4 bytes from socket
Got header: GSIF
Read 2 bytes from socket
Read 2 bytes from socket
Requested dimensions: 896,431
TCP Incoming socket handler
Read 4 bytes from socket

The TCPServer class code:

package com.wlgfx.server.home;

import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import javax.imageio.ImageIO;

public class TCPServer implements Runnable {
    
	private Thread thread = null;
	boolean stopServer;
	Random random;
	List image_files = null;

	public TCPServer() {
		start();
	}
	
	public void start() {
		if (thread == null) {
			stopServer = false;
			thread = new Thread(this);
			thread.start();
		}
	}
	
	public void stop() {
		stopServer = true;
		while (thread != null) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	private void initialise_image_list() {
		random = new Random();
		image_files = new ArrayList();
		
		File folder = new File(Global.dir_pictures);
		File[] files = folder.listFiles();
		for (File file : files) {
			if (file.isFile()) {
				image_files.add(file.getName());
				System.out.println(file.getName());
			}
		}
	}

	@Override
	public void run() {
		System.out.println("TCP Server thread started...");
		
		initialise_image_list();
		
		ServerSocket serverSocket = null;
		
		try {
			serverSocket = new ServerSocket(Global.tcp_port);
			serverSocket.setSoTimeout(1000);
			
			while (stopServer == false) {
				try {
					Socket socket = serverSocket.accept();
					if (socket != null) new TCPSocketHandler(socket);
				} catch (InterruptedIOException e) {
					//System.out.println("Timed out socket");
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				serverSocket.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
		thread = null;
		
		System.out.println("TCP Thread finished...");
	}
	
	private class TCPSocketHandler implements Runnable {
		
		Thread thread = null;
		Socket socket = null;
		byte[] header = new byte[4];
		
		public TCPSocketHandler(Socket insocket) {
			socket = insocket;
			thread = new Thread(this);
			thread.start();
		}

		@Override
		public void run() {
			System.out.println("TCP Incoming socket handler");
			
			try {
				readSocketBytes(socket, header, 4);
				String head = new String(header, StandardCharsets.UTF_8);
				System.out.println("Got header: " + head);
				
				if (head.equals("GSIF")) { // get server image file
					process_gsif(socket);
				}
			} catch (IOException e) {
				e.printStackTrace();
			} finally {
				try {
					socket.getOutputStream().flush();
					socket.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		
		private void readSocketBytes(Socket socket, byte[] bytes, int len) throws IOException {
			int position = 0;
			
			InputStream in = socket.getInputStream();
			while (position < len) { int count = in.read(bytes, position, len - position); if (count >= 0) position += count;
				else throw new IOException();
			}
			
			System.out.println("Read " + position + " bytes from socket");
		}
		
		private void writeSocketBytes(Socket socket, byte[] bytes) throws IOException {
			OutputStream out = socket.getOutputStream();
			byte[] length_data = new byte[4];
			int size = bytes.length;
			length_data[0] = (byte) ((size >> 24) & 255);
			length_data[1] = (byte) ((size >> 16) & 255);
			length_data[2] = (byte) ((size >>  8) & 255);
			length_data[3] = (byte) ((size) & 255);
			out.write(length_data);
			out.write(bytes);
		}
		
		private int readSocketInt16(Socket socket) throws IOException {
			byte[] data = new byte[2];
			readSocketBytes(socket, data, 2);
			return (data[0] & 255) << 8 | (data[1] & 255);
		}
		
		private void process_gsif(Socket socket) {
			// header: GSIF
			// short: width
			// short: height
			// returns...
			// int32: length
			// byte array: png image data
			
			// get random image, load it in, scale to requested size,
			// compress as png, and send it to client
			
			BufferedImage image = null;
			String image_file = image_files.get(random.nextInt(image_files.size()));
			
			try {
				int width = readSocketInt16(socket);
				int height = readSocketInt16(socket);
				
				System.out.println("Requested dimensions: " + width + "," + height);
				
				image = getScaledImage(ImageIO.read(new File(Global.dir_pictures + image_file)), 
						width, height);
				ByteArrayOutputStream baos = new ByteArrayOutputStream();
				ImageIO.write(image, "png", baos);
				baos.flush();
				byte[] data = baos.toByteArray();
				writeSocketBytes(socket, data);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
		private BufferedImage getScaledImage(BufferedImage image, int width, int height) {
			double scaleX = (double)width / image.getWidth();
			double scaleY = (double)height / image.getHeight();
			AffineTransform scaleTransform = AffineTransform.getScaleInstance(scaleX, scaleY);
			AffineTransformOp biliniearScaleOp = new AffineTransformOp(scaleTransform, 
					AffineTransformOp.TYPE_BILINEAR);
			return biliniearScaleOp.filter(image, new BufferedImage(width, height, 
					image.getType()));
		}
	}
}

The android ASync class to fetch a random image:

package com.uiprojtest.wlgfx.uiprojecttest;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.util.Log;
import android.widget.ImageView;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;

/**
 * Created by wlgfx on 02/09/16.
 */

public class GetImageFromServer extends AsyncTask<Void, Void, Bitmap> {

    private final String tag = "WLGFX-AsyncBitmap";

    private Context context;
    private ImageView imageView;
    private int width;
    private int height;

    GetImageFromServer(Context ctx, ImageView view) {
        context = ctx;
        imageView = view;
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        imageView.setImageBitmap(null);
        width = imageView.getWidth();
        height = imageView.getHeight();
    }

    @Override
    protected void onPostExecute(Bitmap bitmap) {
        super.onPostExecute(bitmap);
        imageView.setImageBitmap(bitmap);

        Log.d(tag, "Bitmap: " +
        "(" + bitmap.getWidth() +
        "," + bitmap.getHeight() + ")" +
        " " + bitmap.getByteCount());
    }

    /* commented out now as it was the original test
    @Override
    protected Bitmap doInBackground(Void... voids) {
        BitmapFactory.Options options = new BitmapFactory.Options();

        options.inJustDecodeBounds = true; // just get image dimensions
        BitmapFactory.decodeResource(context.getResources(),
                R.drawable.bike, options);

        options.inSampleSize = Math.max(options.outWidth / width,
                options.outHeight / height);

        options.inJustDecodeBounds = false; // let's do this now
        options.inPreferredConfig = Bitmap.Config.RGB_565; // reduced memory footprint
        Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(),
                R.drawable.bike, options);

        // could rescale here to exact required size but no need for this

        return bitmap;
    }*/

    @Override
    protected Bitmap doInBackground(Void... voids) {
        String head = "GSIF";
        byte[] header_data = head.getBytes();

        try {
            InetAddress address = InetAddress.getByName(Global.server_host);

            Socket socket = new Socket(address, Global.tcp_port);
            OutputStream out = socket.getOutputStream();
            out.write(header_data);

            byte[] dim = new byte[2];

            dim[0] = (byte)(width >> 8);
            dim[1] = (byte)width;
            out.write(dim);

            dim[0] = (byte)(height >> 8);
            dim[1] = (byte)(height);
            out.write(dim);

            byte[] length = new byte[4];
            readSocketBytes(socket, length);

            int size = (length[0] & 255) << 24 |
                    (length[1] & 255) << 16 |
                    (length[2] & 255)<< 8 |
                    length[3] & 255;

            byte[] image_data = new byte[size];
            readSocketBytes(socket, image_data);

            Bitmap bitmap = BitmapFactory.decodeByteArray(image_data, 0, size);

            socket.close();

            return bitmap;
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return null;
    }

    private void readSocketBytes(Socket socket, byte[] data) throws IOException {
        int length = data.length;
        InputStream in = socket.getInputStream();
        int position = 0;

        while (position < length) { int count = in.read(data, position, length - position); if (count > 0) position += count;
            else throw new IOException();
        }
    }
}