I have a try/catch block but the exception is never caught

I’m in the process of writing a very basic Android client to communicate with a server over a Tcp socket.

While I can send a message to the server successfully, I cannot receive any data.

The server code works with a C++ client I’ve written, so if a client connects (which the android client does) everything else should be ok.

This is the client code

class RemoteComms(addrIn: String, portIn: Int) {
    private val addr = addrIn
    private val port = portIn
    private var clientSocket = Socket()
    var outputStream: OutputStream = OutputStream.nullOutputStream()
    var inputStream: InputStream = InputStream.nullInputStream()
    private var isConnected = false
    private val Version = 1


    @OptIn(ExperimentalCoroutinesApi::class)
    fun connectRemote() = runBlocking {
        launch(newSingleThreadContext("MyOwnThread")) {
            //launch(newSingleThreadContext("MyOwnThread")) {
            try {
                clientSocket = Socket(addr, port)
                isConnected = true
                outputStream = clientSocket.getOutputStream()
                inputStream = clientSocket.getInputStream()
                initMsgReuest()
            } catch (e: Exception) {
                isConnected = false
                println("Error: ${e.message}")
            }
        }
    }

    fun isConnected():Boolean{
        return isConnected
    }

    fun initMsgReuest(){
        sendMsg(
            message {
                version = Version
                type = MsgType.MSG_TYPE_CONNECT
                requestConnect {
                    sendCurrentSong = true
                }
            }
        )
        readMsg()
        println("Testing the readMsg returns")
    }

    fun sendMsg(msg: Message) {
        try { 
            outputStream.write(msg.toByteArray())
        }
        catch (e: Exception) {
            println("Error: ${e.message}")
        }
    }

    fun readMsg(){
        val buffer = ByteArray(1024) // Adjust as needed
        try {
            val bytesRead = inputStream.read(buffer)
            val receivedData = String(buffer, 0, bytesRead)
            println("Received: $receivedData")
        }
        catch(e: Exception) {
            println("Error: ${e.message}")
        }


    }


    fun CloseSocket(){
        clientSocket.close()
    }
}

I set a breakpoint in the debugger and once I step over
bytesRead = inputStream.read(buffer)
the debugger just says “The application is running”

What I don’t understand is that I have a try/catch block but the exception is never caught

Maybe the issue is that there is nothing to read?

Thanks

I have changed

val bytesRead = inputStream.read(buffer)

to

buffer = inputStream.readAllBytes()

which made no difference.

As I played around, I stopped the server after I stepped over the 'bytesRead" and the client debugger continued on the next line!
I have captured the output (see attached). This shows that buffer contains data, but not sure why this doesnt work when the server sends the message and stays up.

I believe the issue is that InputStream doesn’t know how much data you want to send.
So when you call inputStream.read or inputStream.readAllBytes it will block until it can read something.

In order to have a proper communication between the client and the server you will need to specify the package size you wish to send, and then on you will know how many bytes you need to read on the other side.

Still reading your code, but one thing that caught my attention is your private var isConnected = false. You can get rid of your fun isConnected(): Boolean, and instead change your variable to

var isConnected = false
    private set

The other thing I can see as an issue is that you create a new single thread context every time you call connectRemote, and you never clean up the context. I’m pretty sure you have to explicitly close the context when you’re done with it, or it hangs around forever. I’m assuming that right now, while you’re debugging, you only call connectRemote once, and then shut down your application, but in the future, this could lead to a leak, if you call connectRemote multiple times. Especially since you don’t check isConnected before trying to connect.

1 Like

It seems it is waiting for the incoming data. How do you know the message has been sent successfully? Did you check if the server actually sent anything?

Please try flushing the outputstream after writing to it as maybe you didn’t really send anything to the server and both sides are waiting for each other.

Is there a reason connectRemot() isn’t a suspend function? Then you could get rid of the runBlocking, which shouldn’t be used in real code.

And you definitely need to check the docs for newSingleThreadContext, there is cleanup required.

I have sent the data already. The issue I have is reading the inputStream
I’m learning Kotlin as I program this, but doesnt readAllBytes read all remaining bytes from the input stream?

Thanks

I have a working client written in C++ and the communication between the client and server works as expected.
I’m also in deboh mode on the Server, so I know when the message is sent including any errors)

idk if the runBlocking actually matters, since launch is being used with a context. I think that means the launch effectively detaches from the runBlocking, and connectRemote() would return instantly.

If we look at documentation for readAllBytes we can see:

Reads all remaining bytes from the input stream.
This method blocks until all remaining bytes have been read and end of stream is detected,
or an exception is thrown. This method does not close the input stream.

So it seems that readAllBytes will wait for the end of stream is detected which is something that happens when you close the socket.
This would explain why the debug continues once you stop the server.

Yes, but the original code was using InputStream.read(byte[]) and this method waits for any incoming data, even a single byte.

I don’t think it detaches. newSingleThreadContext creates a dispatcher, not a full context with a Job. It shouldn’t be much different than e.g. launch(Dispatchers.IO) {}.

Are you sure you tested the code using inputStream.read(buffer) and that you at all see logs made using println()? By looking at your original code, I would generally expect it to fail, but not at reading, but at converting to a string. Data visible in the image above doesn’t seem like a string.

@broot

Yes, but the original code was using InputStream.read(byte[]) and this method waits for any incoming data, even a single byte.

If we look at the doc for the InputStream.read it says:

Reads some number of bytes from the input stream and stores them into the buffer array b.
The number of bytes actually read is returned as an integer.
This method blocks until input data is available, end of file is detected, or an exception is thrown.
If the length of b is zero, then no bytes are read and 0 is returned;
otherwise, there is an attempt to read at least one byte.
If no byte is available because the stream is at the end of the file,
the value -1 is returned; otherwise, at least one byte is read and stored into b.

So it should block until some data is received. If you are not sending anything from the server, the readMsg method should be blocked until there is some data OR the socket is closed.

Yes, this is how it works. readAllBytes() is not an option here though as it waits for the socket to close.

runBlocking creates a scope, and it blocks the thread until all coroutines created within that scope finish. So the function does not return instantly.

I’m not sure why the OP is creating a new thread context for this, when it looks like Dispatchers.IO would work just as well. The function should probably be:

suspend fun connectRemote() {
    withContext(Dispatchers.IO) {
        try {
            .
            .
            .
        }
    }
}

Thank you all for your prompt replies and suggestions/explanations.

As I indicated at the beginning I’m completly new to Kotlin so I wrote this code after searching for possible approaches to what I need/want do do; simple message exchange over Tcp sockets.

The first thing I found was that to create a socket this needs to be done in a different thread. Again after lenghty searches (there are many potential solutions) I chose Coroutines.

The issue with InputStream.readAllBytes() was that it would read all bytes from that stream (if there are any) for that one message. I assumed that the next time I call that function it would get the next message as the documentation states

Reads all remaining bytes from the input stream. This method blocks until all remaining bytes have been read and end of stream is detected, or an exception is thrown. This method does not close the input stream.

When this stream reaches end of stream, further invocations of this method will return an empty byte array.

Note that this method is intended for simple cases where it is convenient to read all bytes into a byte array. It is not intended for reading input streams with large amounts of data. 

I got the code working as follows

fun readMsg(){
        try {
            val bytesAvailable = inputStream.available()
            if (bytesAvailable > 0){
                val bytesRead = inputStream.read(buffer,0,bytesAvailable)
                println("Received: $(bytesRead}")
            }

        }
        catch(e: Exception) {
            println("Error: ${e.message}")
        }

    }

Thanks again for your help

This is not really a proper way of handling socket communication. And this is not specific to Kotlin, but to socket programming in general. It will work in some cases, but if you start using it for real, you will soon see it fails randomly. If you implemented the server similarly, then I guess it has similar flaws.

TCP is a stream-protocol. It doesn’t have a concept of messages. You send 30 bytes on one side, on the second you may get 2x 15 bytes or even 30 times a single byte. Or you send two messages, but on the receiving side you get both as a single “message”.

If you need to send messages, than either use one of already existing message-based communication protocols or implement your own on top of TCP. Very simple and common way is by using TLV. Making long story short: we need to know upfront how many bytes of data we need to read. And we have to read in a loop until we get as much as we need. We can’t assume that we will read exactly what we need.

1 Like