1

I have a TCP client and a TCP server class in my app. The client sends small strings such as "1|2|" or "1|11|"

Client class

public class TcpClient {


private static final int MAX_DATA_RETRY = 1;
private static final int PING_TIMEOUT = 100;

private ClientThread thread;
private boolean mRun = true;
private PrintWriter mBufferOut;

private String mIPAdress;


private ArrayList<BufferDataItem> messageBuffer = new ArrayList<BufferDataItem>();
private Socket mSocket;

public TcpClient()
{
    thread = new ClientThread();
    thread.start();
}

private class ClientThread extends Thread {

    @Override
    public void run() {

        while(mRun)
        {
            if(messageBuffer.size() <= 0)
                continue;

            BufferDataItem currMessage = messageBuffer.get(0);
            currMessage.retryCount++;
            if(currMessage.retryCount > MAX_DATA_RETRY)
            {
                messageBuffer.remove(0);
                continue;
            }

            try {
                //here you must put your computer's IP address.
                InetAddress serverAddr = InetAddress.getByName(currMessage.ip);



                //Log.e("TCP Client", "C: Connecting...");

                try {
                    if(!serverAddr.isReachable(PING_TIMEOUT))
                    {
                        //only attempt to connect to devices that are reachable
                        messageBuffer.remove(0);
                        continue;
                    }


                    //create a socket to make the connection with the server
                    mSocket = new Socket(serverAddr, TcpManager.SERVER_PORT);

                    //Log.i("TCP Debug", "inside try catch");
                    //sends the message to the server

                    mBufferOut = new PrintWriter(new BufferedWriter(new OutputStreamWriter(mSocket.getOutputStream())), true);

                    String message = currMessage.message;
                    if (mBufferOut != null && !mBufferOut.checkError()) {
                        Log.d("TCP SEND", "PUTTING IN BUFFER!  " + message);
                        mBufferOut.println(message);
                        listener.messageSent(message, currMessage.ip);
                        messageBuffer.remove(0);
                    }
                    mBufferOut.flush();

                }
                catch (ConnectException e) {
                    //Connection refused by found device!
                    //Log.e("TCP", "C: ConnectException   ip = "+currMessage.ip, e);
                    listener.hostUnreachable(currMessage.ip);
                    continue;
                }
                catch (Exception e) {
                    Log.e("TCP", "S: Error", e);
                    listener.messageSendError(e);
                }
                finally {
                    if(mSocket != null)
                        mSocket.close();
                }

            }
            catch (Exception e) {
                Log.e("TCP", "C: Error", e);
                listener.messageSendError(e);
                continue;
            }
        }
    }
}



/**
 * Sends the message entered by client to the server
 *
 * @param message text entered by client
 */
public void sendMessage(String message) {

    BufferDataItem data = new BufferDataItem();
    data.message = message;
    data.ip = mIPAdress;
    messageBuffer.add(data);
}

public void sendMessage(String message, String ip) {
    mIPAdress = ip;
    BufferDataItem data = new BufferDataItem();
    data.message = message;
    data.ip = mIPAdress;
    messageBuffer.add(data);
}


/**
 * Close the connection and release the members
 */
public void stopClient() {
    Log.i("Debug", "stopClient");

    mRun = false;

    if (mBufferOut != null) {
        mBufferOut.flush();
        mBufferOut.close();
    }
    mBufferOut = null;
}

private class BufferDataItem
{
    public String message = "";
    public int retryCount = 0;
    public String ip = "";
}

private OnMessageSent listener = null;

public interface OnMessageSent {
    public void messageSent(String message, String ip);

    public void hostUnreachable(String ip);

    public void messageSendError(Exception e);

}

public void setMessageSentListener(OnMessageSent listener)
{
    this.listener = listener;
}

public void removeMessageSentListener()
{
    this.listener = null;
}

}

Server Class

public class TcpServer {

private ServerThread thread;
private boolean mRun = true;
private boolean mEnd = false;


public TcpServer()
{
    thread = new ServerThread();
    thread.start();
}

private class ServerThread extends Thread {

    @Override
    public void run() {

        try {
            Boolean end = false;
            ServerSocket ss = new ServerSocket(TcpManager.SERVER_PORT);
            while (mRun) {
                //Server is waiting for client here, if needed
                Socket s = ss.accept();
                BufferedReader input = new BufferedReader(new InputStreamReader(s.getInputStream()));
                //PrintWriter output = new PrintWriter(s.getOutputStream(), true); //Autoflush
                String st = input.readLine();
                String remoteIP = s.getRemoteSocketAddress().toString();
                int index = remoteIP.indexOf(":");
                remoteIP = remoteIP.substring(1,index);

                Log.d("TCP READ", "TCP READ: " + st);
                if(st != null)
                    listener.messageReceived(st, remoteIP);
                //output.println("Good bye and thanks for all the fish :)");

                if(mEnd)
                {
                    s.close();
                    mRun = false;
                }
            }

            ss.close();


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

//Declare the interface. The method messageReceived(String message) will must be implemented in the MyActivity
//class at on asynckTask doInBackground
public interface OnMessageReceived {
    public void messageReceived(String message, String ip);
}

private OnMessageReceived listener = null;

public void SetMessageReceivedListener(OnMessageReceived listener)
{
    this.listener = listener;
}

public void RemoveMessageReceivedListener()
{
    this.listener = null;
}

}

This works fine for the first few times it runs and then but then the Client sends "1|11|" and the the server sets st as null during the readLine. String st = input.readLine();

Does anyone have any suggestions?

Dan
  • 15
  • 3
  • ArrayList is not a thread safe container. Client code modifies the `messageBuffer` from different threads without any synchronization. – Zaboj Campula Sep 27 '17 at 05:33
  • Moreover the `mEnd` is never set to true in the server code. So the server never closes client sockets and associated streams -> resource leak. – Zaboj Campula Sep 27 '17 at 06:35
  • thanks for that Zaboj. I must admit multi threading does my head in. Can you suggest a thread safe alternative to ArrayList? With the server i thought that the socket would have to stay open so that it can continue to receive ongoing data. Is that not the case? – Dan Sep 27 '17 at 10:37
  • The client creates a new tcp connection for each message. The server creates a new socket for each tcp connection. These sockets must be closed. Perhaps it is not the primary error but server will have lack of resources after a long run. – Zaboj Campula Sep 27 '17 at 12:07
  • Thanks for that. What about a thread safe alternative to ArrayList? – Dan Sep 27 '17 at 22:16
  • This worked. Can you please write as an answer so I can accept the answer? – Dan Sep 28 '17 at 01:05

1 Answers1

0

I see two possible reason why the server does not receive valid data after some time.

  1. Server does not close the socket s because the mEnd is never set to true. The client open a new TCP connection for each message. The server creates a socket for the connection but it never close the socket and the server side of the connection remains open. It is a resource leak and it may cause the problem.

  2. The client use the ArrayList<BufferDataItem> messageBuffer. ArrayList is not a thread safe collection and messageBuffer is used from more than one thread. It is safe to use synchronizedList here. See the How do I make my ArrayList Thread-Safe? Another approach to problem in Java? or Concurrent threads adding to ArrayList at same time - what happens?

Zaboj Campula
  • 3,155
  • 3
  • 24
  • 42