3

I have been trying various implementations to make this work, and have searched StackOverflow and Android Developers for a solution, but I am not too experienced in programming and cannot get this block to code to work properly.

My Intent:

  1. This is in a thread that will loop through checking if there is an outMessage, if there is, it will send the message.
  2. Next it will check it if there is anything in the in-stream, if there is, it will send it to the handler in my main activity.
  3. Lastly, it will sleep for 1 second, then check again.
  4. This should allow me to read/write multiple times without needing to close and open the socket.

Problem:

  • The outstream does not get flushed until I close the socket. flush() seems to have no effect.

My request:

  • Please post the changes required to get this code working properly as described above (any annotations to explain WHY would be greatly appreciated. Links to other similar questions/answers would be great to help me learn, but i have been looking at those for a couple weeks and just cant get it to work, so please make sure you also include the changed this code needs in order to work as described above. Thanks in advance.

Other:

  • I am wondering if my instream &/or outstream need to look for end-of-line characters?
  • Would something like TCP_NODELAY be used here?
  • Any extra info that can be given will be very appreciated. I want to learn this stuff well, but I currently cannot get anything to work.

Code:

 public void run() {                        
        while (connectionStatus == TCP_SOCKET_STATUS_CONNECTED) {
            try {   
                if (outMessage != null){
                   OutStream.writeBytes(outMessage);
                   OutStream.flush();           
                   outMessage = ("OUT TO SERVER: " + outMessage);           
                // socketClient.close();     
                   sendMessageToAllUI(0, MAINACTIVITY_SET_TEXT_STATE, "appendText" , outMessage);
                   outMessage = null;               
                } 
                if (InStream != null) {                     
                    String modifiedSentence = InStream.readLine();      
                    sendMessageToAllUI(0, MAINACTIVITY_SET_TEXT_STATE, "appendText" , "\n" + "IN FROM SERVER: " + modifiedSentence);
            }
            Thread.sleep(1000);
        } catch (IOException e) {               
            connectionLost();
            break;
        } catch (InterruptedException e) {
                e.printStackTrace();
            }
    }                           
}

The thread that makes the socket:

public void run() {
        if(I) Log.i(LOGTAG, "Attempt Connection with IP: " + serverIP + " ...");
        setName("AttemptConnectionThread");
        connectionStatus = TCP_SOCKET_STATUS_CONNECTING;
        try {
            SocketAddress sockaddr = new InetSocketAddress(serverIP, port);
            tempSocketClient = new Socket(); // Create an unbound socket

            // This method will block no more than timeoutMs. If the timeout occurs, SocketTimeoutException is thrown.
            tempSocketClient.connect(sockaddr, timeoutMs);
            OutStream = new DataOutputStream(tempSocketClient.getOutputStream());
            InStream = new BufferedReader(new InputStreamReader(tempSocketClient.getInputStream()));
            socketClient = tempSocketClient;
            socketClient.setTcpNoDelay(true);
            connected(); 
        } catch (UnknownHostException e) {
            if(I) Log.i(LOGTAG,"     ...UnknownException e: e.getMessage() shows: " + e.getMessage());
            connectionFailed();
        } catch (SocketTimeoutException e) {
            if(I) Log.i(LOGTAG,"     ...SocketTimoutException e: e.getMessage() shows: " + e.getMessage());
            connectionFailed();
        } catch (IOException e) {
            if(I) Log.i(LOGTAG,"     ...caught on run()");
            // Close the socket
            try {
                tempSocketClient.close();
            } catch (IOException e2) {
                Log.e(LOGTAG, "unable to close() socket during connection failure", e2);
            }
            if(I) Log.i(LOGTAG,"     ...IOException e: e.getMessage() shows: " + e.getMessage());
            connectionFailed();
            return;
        }
    } 

The java server I found online and am using until I port this over to the real server:

public class Server {

private static String SERVERIP;

/**
 * @param args
 * @throws IOException
 */

public static void main(String[] args) throws IOException {
    String clientSentence;
    String capitalizedSentence;

    try {
        ServerSocket welcomeSocket = new ServerSocket(8888);
        getIp();
        System.out.println("Connected and waiting for client input!\n");

        while (true) {
            Socket connectionSocket = welcomeSocket.accept();
            BufferedReader inFromClient = new BufferedReader(
                    new InputStreamReader(connectionSocket.getInputStream()));
            DataOutputStream outToClient = new DataOutputStream(
                    connectionSocket.getOutputStream());

            clientSentence = inFromClient.readLine();
            String ip = connectionSocket.getInetAddress().toString()
                    .substring(1);
            System.out.println("In from client (" + ip + "): "
                    + clientSentence);
            if (clientSentence != null) {
                capitalizedSentence = clientSentence.toUpperCase() + '\n';
                System.out.println("Out to client (" + ip + "): "
                        + capitalizedSentence);
                outToClient.writeBytes(capitalizedSentence + "\n");
            }

        }
    } catch (IOException e) {
        // if server is already running, it will not open new port but
        // instead re-print the open ports information
        getIp();
        System.out
                .println("Server connected and waiting for client input!\n");

    }
}

private static void getIp() {
    InetAddress ipAddr;
    try {
        ipAddr = InetAddress.getLocalHost();
        System.out.println("Current IP address : "
                + ipAddr.getHostAddress());

    } catch (UnknownHostException e) {
        e.printStackTrace();
    }
}
}
Nissi
  • 205
  • 3
  • 13
  • How big are the messages? Are they generated in a different thread? No locking? – Nikolai Fetissov Aug 07 '12 at 14:25
  • First, The messages will be small strings mostly. Things like "turn around" or "walk straight." There might be multi-line ones as well but still short. Second, if you meant blocking, I don't believe so but I am a bit confused by how it works / how to check. I am pretty sure it only blocks when creating the socket. Should this block anywhere?; if you did indeed mean "locking" I do not know what that is in this context. – Nissi Aug 07 '12 at 14:44
  • I mean **locking** as in avoiding race conditions between threads. My question is where `outMessage` is created? – Nikolai Fetissov Aug 07 '12 at 15:04
  • mSocket.setTcpNoDelay(on); is how I interpret the [this](http://developer.android.com/reference/java/net/SocketOptions.html#TCP_NODELAY), but eclipse wants me to initialize "on" do I set do boolean on = true; ? and should I do these when I create the socket since the state should be persistent? – Nissi Aug 07 '12 at 15:04
  • outMessage is a static member in my service. It is currently a string, but I will eventually change it to a queue once the socket is working properly. This method is in a nested class within the service. So I can keep the socket open between multiple activities which can bind to the service – Nissi Aug 07 '12 at 15:07
  • Disabling Nagle is probably the first order of business here. What I was driving at is if you create your messages on a different thread you need to at least mark `outMessages` as `volatile`, so memory updates propagate from thread to thread. – Nikolai Fetissov Aug 07 '12 at 15:16
  • to make it volatile, I looked into [java.util.concurrent.atomic](http://developer.android.com/reference/java/util/concurrent/atomic/package-summary.html) and have a limited understanding of it. Do I initialize the variable as atomic? and [this_site](http://www.javamex.com/tutorials/synchronization_volatile.shtml) suggested some issues with missing/losing tags or references. Is there any special way to interact with the volatile variable to update or reference it? Also, the updating of this variable has gone well for me so far. Have I just been fortunate? – Nissi Aug 07 '12 at 15:34
  • Basically, `volatile` forces Java not to cache the value, but always read it from memory on access. Don't be too concerned with atomic stuff until you get a good general understanding of concurrency with locks/monitors. – Nikolai Fetissov Aug 07 '12 at 17:14
  • So is there a way to mark as volatile without using the previously mentioned concurrent.atomic? If yes, you you show me the syntax or a link please? – Nissi Aug 07 '12 at 17:52
  • It'd be just `static volatile String outMessage;`, see http://docs.oracle.com/javase/specs/jls/se5.0/html/classes.html#8.3.1.4, and http://javarevisited.blogspot.com/2011/06/volatile-keyword-java-example-tutorial.html. – Nikolai Fetissov Aug 07 '12 at 18:07
  • Thanks! Should I do that with my in/out streams, and Socket as well since they are also stored in the service but accessed by the in/out thread? – Nissi Aug 07 '12 at 18:15
  • Only if you constantly overwrite them from different threads. – Nikolai Fetissov Aug 07 '12 at 18:38
  • This has sparked another question for me, which I posted [here](http://stackoverflow.com/questions/11852525/is-the-volatile-mark-necessary-for-a-queue-with-one-thread-adding-and-another-re). Thanks for your help as I try to get this socket working. – Nissi Aug 07 '12 at 19:13
  • By the way, do not disable Nagle. It won't fix anything. It only affects performance and typically it makes performance worse. It's never needed in "strict alternation" protocols where one side always replies to a query. – David Schwartz Aug 08 '12 at 21:52
  • What if I plan to build a server that will respond to some messages, but just read others? – Nissi Aug 08 '12 at 22:21

2 Answers2

3

I suspect you are reading lines at the consumer, but you aren't writing lines, so the consumer blocks until it gets EOS and delivers one big line. Add line terminators when sending as necessary.

user207421
  • 305,947
  • 44
  • 307
  • 483
  • This fixed the specific issue I posted about, but it led me straight into another one. My thread hangs on inputStream.readLine(), I posted a new question about that issue [here](http://stackoverflow.com/questions/11873366/android-tcp-app-hanging-on-instream-readline) in case you or anyone else way want to take a look. Thanks for the help. – Nissi Aug 08 '12 at 21:24
  • I know that this answer is 3-4 years old, but if you see my comment by any chance, could you please explain in detail what you meant when said "but you aren't writing lines"? Looks like I met the similar problem and this can be the cause – Sergey Maslov Nov 01 '15 at 19:57
1

Since these are short messages (< MSS), I guess the stack may be implementing Nagle's algorithm. Is the server doing some kind of delayed ACKs? If possible you should capture a trace and see if there are pending acknowledgements from the other side.

In either case, TCP_NODELAY should help.

jman
  • 11,334
  • 5
  • 39
  • 61
  • 1. What does ACK stand for? I googled it but couldn't find a definition 2. I will give the TCP_NODELAY that you and Nikolai suggested a shot and mark answered if it does indeed answer the question. Thanks for the help so far! – Nissi Aug 07 '12 at 16:22
  • 1
    ACK is tcp acknowledgement. Instead of sending an ACK for each segment, the server may be waiting to bunch them. If the client implements Nagle's algo, it'd be waiting for an ACK before it sends segments with < MSS. In a TCP trace, you'll have to look for an ACK number in the TCP header and co-relate it to the SEQ number to see if this is happening. Or you can post a snippet of the capture here. – jman Aug 07 '12 at 16:28
  • Ok, I updated the question to show the run method in my thread that creates the socket as well as the Java server. You will see that I had socketClient.setTcpNoDelay(true); in there already, but it wasn't fixing it. The documentation has that it should be socketClient.setTcpNoDelay(on); but then I need to initialize on. I am a bit perplexed as to what I should initialize "on" as to make socketClient.setTcpNoDelay(on); work? – Nissi Aug 07 '12 at 17:54
  • in regards to the TCP trace, do I do that from android (client) or computer (server) side? and where/how do I implement that? would it just be socketClient. ? – Nissi Aug 07 '12 at 18:04
  • 1
    You can take a trace with tcpdump on the server side. – jman Aug 07 '12 at 19:49
  • I made a new question for this tangent. It is [here](http://stackoverflow.com/questions/11853018/how-to-get-android-tcp-trace) – Nissi Aug 07 '12 at 19:50
  • @Nissi setTcpNoDelay(true) is correct, but I doubt that this is the real problem. – user207421 Aug 08 '12 at 03:00
  • @EJP yeah, I have it in there still, but the first problem was not having a '\n' where I needed it (I accidentally put it in the log statement rather than the actual code I needed it in). Dumb mistake and it made me mentally check that off... – Nissi Aug 08 '12 at 22:05