2

This is a continuation of this question because it my orginal question was answered, but it did not solve the bug.

Question:

  • How do I fix the code hanging on this line inStream.readline()

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:

  • It is reading and writing better, but still not working properly

What is happening now:

  • If outMessage is initialized with a value, upon connection with the server, the socket:
    1. writes and flushes the value (server receives & responds)
    2. updates value of outMessage (to null or to "x" depending on how i have it hard-coded)
    3. reads and shows the response message from the server
    4. re-enters for the next loop
    5. IF i set outMessage to null, it skips over that if statements correctly then hangs; otherwise, if i set outMessage to a string (lets say "x"), it goes through the whole if statement, then hangs.
      • The code it hangs on is either of the inStream.readline() calls (I currently have one commented out).

Additional info: - once connected, I can type in the "send" box, submit (updates the outMessage value), then disconnect. Upon re-connecting, it will read the value and do the sequence again until it get stuck on that same line.

Changes since the referenced question: - Made outMessage and connectionStatus both 'volatile' - added end-of-line delimiters in neccesary places.

Code:

        public void run() { 
            while (connectionStatus != TCP_SOCKET_STATUS_CONNECTED) {
                try {
                    Thread.sleep(500);  
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            while (connectionStatus == TCP_SOCKET_STATUS_CONNECTED) {
                try {   
                    if (outMessage != null){                                            
                        OutStream.writeBytes(outMessage + "\n");                    
                        OutStream.flush();                                          
                        sendMessageToAllUI(0, MAINACTIVITY_SET_TEXT_STATE, "appendText" , "OUT TO SERVER: " + outMessage);
                        outMessage = "x";                                           
                    }                                                           
                    Thread.sleep(100);
 //             if (InStream.readLine().length() > 0) {                             
                        String modifiedSentence = InStream.readLine();              
                        sendMessageToAllUI(0, MAINACTIVITY_SET_TEXT_STATE, "appendText" , "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() {
        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) {
            connectionFailed();
        } catch (SocketTimeoutException e) {
            connectionFailed();
        } catch (IOException e) {
            // Close the socket
            try {
                tempSocketClient.close();
            } catch (IOException e2) {
            }
            connectionFailed();
            return;
        }
    } 

Server:

public static void main(String[] args) throws IOException {
    String clientSentence;
    String capitalizedSentence;
    try {
        ServerSocket welcomeSocket = new ServerSocket(8888);
        SERVERIP = getLocalIpAddress();
        System.out.println("Connected and waiting for client input!\n Listening on IP: " + SERVERIP +"\n\n");
        Socket connectionSocket = welcomeSocket.accept();
        BufferedReader inFromClient = new BufferedReader(new InputStreamReader(connectionSocket.getInputStream()));
        DataOutputStream outToClient = new DataOutputStream(connectionSocket.getOutputStream());
        while(true)
        {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            clientSentence = inFromClient.readLine();
            System.out.println("clientSentance == " + clientSentence);
            String ip = connectionSocket.getInetAddress().toString().substring(1);
            if(clientSentence != null)
            {
                System.out.println("In from client ("+ip+")("+ System.currentTimeMillis() +"): "+clientSentence);
                capitalizedSentence = clientSentence.toUpperCase() + '\n';
                outToClient.writeBytes(capitalizedSentence + '\n');
                System.out.println("Out to client ("+ip+"): "+capitalizedSentence);
            }
        }
    } catch (IOException e) {
        //if server is already running, it will not open new port but instead re-print the open ports information
         SERVERIP = getLocalIpAddress();
         System.out.println("Connected and waiting for client input!\n");
         System.out.println("Listening on IP: " + SERVERIP +"\n\n");

    }
}

Thanks in advance!

Edits:

  • added the server code after updating
  • I tried messing around with setting the SoTimout for the socket but took that back out
Community
  • 1
  • 1
Nissi
  • 205
  • 3
  • 13
  • Why is this a problem though? It sounds like this is precisely the behavior that you would expect of your code. What are you expecting it to do in this case? (Also, get rid of this `socketClient.setTcpNoDelay(true);`. It doesn't solve anything.) – David Schwartz Aug 08 '12 at 21:26
  • it is doing send > receive > next loop correctly, but then it does send > ? and nothing shows up on my server + it waits indefinitely for the servers response = The socket is currently useless after the first pass. It wouldn't make sense to me that the outStream isn't sending it (since it did just fine the first time). Maybe I am asking the wrong question? I am working on figuring out how to monitor the TCP connection from the server side to see all the packets... but I haven't managed to do that just yet – Nissi Aug 08 '12 at 21:34
  • Your code is designed to wait indefinitely for the server's response. The socket is useless because your code has chosen to wait forever for a response even when the server is not required to send a response. – David Schwartz Aug 08 '12 at 21:43
  • Correct, so what I am looking for is how to make that not the case. I want this to check if something is there to send, if it is, send it. Then, check if something is there to read in, if there is, read it and send it off to the handler of my other thread. I want it to constantly check one, then the other, then do it again. I could add a timeout counter for the read attempt, but I was thinking someone else could probably suggest a different if statement or something so it would check, then immediately proceed to reading what is there or simply moving on and checking it again on the next loop. – Nissi Aug 08 '12 at 22:01
  • Only perform your `readLine` and `sendMessageToAllUI` if there was data available. Wrap those two statements with `if (InStream.available > 0) { /* readLine and sendToUI */ }` – tcarvin Aug 09 '12 at 11:48

2 Answers2

2

Your server is specifically designed to receive exactly one line from a client and send exactly one line back. Look at the code:

    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");
        }

Notice that inside the loop it accepts a new connection, reads exactly one line, and then writes exactly one line. It doesn't close the connection. It doesn't sanely end the conversation. It just stops reading.

A client that worked with this server would have to connect, send exactly one line, read exactly one line back, and then the client would have to close the connection. Your client doesn't do that. Why? Because you had no idea that's what you had to do. Why? Because you had no design ... no plan.

So that's your specific issue. But please, let me urge you to take a huge step back and totally change your approach. Before you write a single line of code, please actually design and specify a protocol at the byte level. The protocol should say what data is sent, how messages are delimited, who sends when, who closes the connection, and so on.

Otherwise, it's impossible to debug your code. Looking at the server code above, is it correct? Well, who knows. Because it's unclear what it's supposed to do. When you wrote the client, you assumed the server behaved one way. Was that assumption valid? Is the server broken? Who knows, because there's no specification of what the server is supposed to do.

David Schwartz
  • 179,497
  • 17
  • 214
  • 278
  • I noted in the post I linked to from my previous question that this is a java server i found online. So was the original socket interaction, which did close the socket after every communication. I re-wrote my client-side, but in my Noob-ness I did not realize this server code would cause issue here. To fix this, should I move everything above clientSentence = inFromClient.readLine(); outside the While loop? as for the lack of design... Yes, my design has had to be re-worked a few times as I learned this, but when I started building this, I had never even heard of TCP. So please give pointers – Nissi Aug 08 '12 at 22:19
1

You need to check if there is data available:

if (InStream.available > 0) {                                                      
   String modifiedSentence = InStream.readLine();
   sendMessageToAllUI(0, MAINACTIVITY_SET_TEXT_STATE, "appendText" , "IN FROM SERVER: " + modifiedSentence); 
}

But to be honest, even that is not ideal because you have no gurantee that the eond-of-line will have been received. If the server sends a few bytes but never sends the end-of-line then you will still be blocking forever. Production socket code should never rely on readLine but instead read into a buffer and check that buffer for end-of-line (or whatever criteria your protocol needs).


Didn't read closely enough, I thought InStream was an InputStream instance. InputStream has available. InputStreamReader has ready (which in turn calls InputStream.available. As long as you keep a refernce to either of these then you can see if data is available to be read.

tcarvin
  • 10,715
  • 3
  • 31
  • 52
  • 1
    If the protocol specification guarantees that the server will send a complete line, then there's nothing wrong with using `readLine`, assuming you don't have to tolerate broken servers. His problem is, fundamentally, that he has no protocol specification, so he has no idea what he can and can't rely on. – David Schwartz Aug 08 '12 at 21:50
  • I disagree. TCP/IP can fragment anywhere and if a line is long enough (over a few thousand bytes) you are guranteed it will fragment and and then he could easily lose the end of it in a network disconnect situation. – tcarvin Aug 08 '12 at 21:53
  • You are 100% right on the fact he needs to define hos protocol though! – tcarvin Aug 08 '12 at 21:53
  • 1
    The same thing could happen with any blocking read. You'd have that same problem if you tried to read a single byte -- you might lose the network connection and block forever. That has nothing to do with the end-of-line issue or segmentation. – David Schwartz Aug 09 '12 at 00:39
  • If the `available` property returns a byte count, as in my sample, then you will *never* block on a susbsequent `read`. That is not true of `readLine` because readLine is only satisfied by the presense of certain characters in the stream. – tcarvin Aug 09 '12 at 11:22
  • 1
    Sure, but what do you do if `available` returns zero? (What did you expect the OP to do? Just loop back and spin burning 100% CPU?) – David Schwartz Aug 09 '12 at 11:28
  • His question was "How do I fix the code hanging on this line inStream.readline()", and the answer is to wrap it in a check for data `available`. In his code, if there is no code he sleeps (so 100% CPU burn not an issue), checks if he has a pending outbound message to send (and sends if he does) and then checks if he has an inbound message (and read if he does). Rinse, wash, repeat. Typical, though simplistic, network thread loop. – tcarvin Aug 09 '12 at 11:32
  • Oh, I see. You were answering the question he asked rather than resolving his underlying issue. To do it that way, he'd need to make his code *much* more complex than it is (because he won't know if the available data contains a full line), and it would still be pretty awful because it would be polling the connection. – David Schwartz Aug 09 '12 at 11:35
  • That was my "it's not ideal" statement. Stackoverflow is not the right venue to teach proper network coding, there is too much to cover in a single answer. I was just trying to get his started down a path. If he makes the change I suggested then his code will function in most cases. I wouldn't put it in production, but asan academic exercise or proof-of-concept it would be fine. – tcarvin Aug 09 '12 at 11:40
  • It is currently a proof-of-concept. Also, I re-worked some of the server code to reflect the issues pointed out in one of the other answers. I will update that in my answer shortly. – Nissi Aug 09 '12 at 16:58
  • Also, I really appreciate both the help for getting it working quickly and noting that this is "not ideal." Two responses to that: @tcarvin or David, could you post a link to an example, tutorial, or at least an article that you deem "ideal" for this case? I am new, and actually built most of this based off of tutorials that apparently were sub-par. Guidance to "good material" would be greatly appreciated. Also, as for getting it working quickly... I am using BufferedReader and when I do inStream. available does not come up as an option. Did I do something wrong for that? TYVM! – Nissi Aug 09 '12 at 17:04
  • @DavidSchwartz the above comment is directed towards you as well, but it wouldn't let me include two names. – Nissi Aug 09 '12 at 17:05
  • Thank you, I will report back as soon as I can give it a try! – Nissi Aug 12 '12 at 18:30
  • Thanks for the update, it works! now the only odd behavior is that every time I send, it sends a message out, receives the response, and receives a second response that is null.. so if I send message, i get back MESSAGE and (nothing). If I figure that out, i'll post it. Thanks for the help guys! – Nissi Aug 14 '12 at 13:18