19greg96
19greg96

Reputation: 2591

Android socket read not working unless connection is shutdown before timeout

I'm trying to make a socket server on my desktop and connect to it from my android, however, android can't read from the socket unless the server shuts the connection down after sending data.

Desktop server code:

#define SERVER_BUFLEN   512
#define SERVER_PORT     "27033"

SOCKET ListenSocket = INVALID_SOCKET;
SOCKET ClientSocket = INVALID_SOCKET;

int main () {
    setvbuf(stdout, NULL, _IONBF, 0);

    WSADATA wsaData;
    int iResult;

    struct addrinfo *result = NULL;
    struct addrinfo hints;

    int iSendResult;
    char recvbuf[SERVER_BUFLEN];
    int recvbuflen = SERVER_BUFLEN;

    // Initialize Winsock
    iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
    if (iResult != 0) {
        printf("WSAStartup failed with error: %d\n", iResult);
        return 1;
    }

    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;
    hints.ai_flags = AI_PASSIVE;

    // Resolve the server address and port
    iResult = getaddrinfo(NULL, SERVER_PORT, &hints, &result);
    if ( iResult != 0 ) {
        printf("getaddrinfo failed with error: %d\n", iResult);
        WSACleanup();
        return 1;
    }

    // Create a SOCKET for connecting to server
    ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
    if (ListenSocket == INVALID_SOCKET) {
        printf("socket failed with error: %ld\n", WSAGetLastError());
        freeaddrinfo(result);
        WSACleanup();
        return 1;
    }

    // Setup the TCP listening socket
    iResult = bind( ListenSocket, result->ai_addr, (int)result->ai_addrlen);
    if (iResult == SOCKET_ERROR) {
        printf("bind failed with error: %d\n", WSAGetLastError());
        freeaddrinfo(result);
        closesocket(ListenSocket);
        ListenSocket = INVALID_SOCKET;
        WSACleanup();
        return 1;
    }

    freeaddrinfo(result);


    iResult = listen(ListenSocket, SOMAXCONN);
    if (iResult == SOCKET_ERROR) {
        printf("listen failed with error: %d\n", WSAGetLastError());
        closesocket(ListenSocket);
        ListenSocket = INVALID_SOCKET;
        WSACleanup();
        return 1;
    }

    printf ("waiting for new client\n");
    // Accept a client socket
    ClientSocket = accept(ListenSocket, NULL, NULL);
    if (ClientSocket == INVALID_SOCKET) {
        printf("accept failed with error: %d\n", WSAGetLastError());
    }
    printf ("new client connected\n");

    //int flag = 1;
    //setsockopt(ClientSocket, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int));

    do {
        iResult = recv(ClientSocket, recvbuf, recvbuflen, 0);
        if (iResult > 0) {
            printf("Bytes received: %d\n", iResult);

            iSendResult = send( ClientSocket, recvbuf, iResult, 0 );
            if (iSendResult == SOCKET_ERROR) {
                printf("send failed with error: %d\n", WSAGetLastError());
            }
            printf("Bytes sent: %d\n", iSendResult);
            //iResult = shutdown(ClientSocket, SD_SEND);
        } else if (iResult == 0) {
            printf("Connection closing...\n");
        } else  {
            printf("recv failed with error: %d\n", WSAGetLastError());
        }
    } while (iResult > 0);

    // shutdown the connection since we're done
    iResult = shutdown(ClientSocket, SD_SEND);
    if (iResult == SOCKET_ERROR) {
        printf("shutdown failed with error: %d\n", WSAGetLastError());
    }
    closesocket(ClientSocket);
    printf ("client left\n");

    if (ListenSocket != INVALID_SOCKET) {
        closesocket(ListenSocket);
    }
    WSACleanup();
    return 0;
}

Android client code:

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.d(TAG, "onCreate");


        StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
        StrictMode.setThreadPolicy(policy);

        try {
            Socket sock = new Socket();
            //sock.setReceiveBufferSize(10);
            sock.connect(new InetSocketAddress("192.168.0.101", 27033));

            OutputStream os = sock.getOutputStream();
            InputStream is = sock.getInputStream();

            String msg = "Hello World!\r\n\0";
            byte[] arr = msg.getBytes(Charset.forName("UTF-8"));

            os.write(arr);
            os.flush();

            Log.d(TAG, "RECV MSG: " + is.read ());

            sock.close();
        } catch (Exception e) {
            Log.d(TAG, e.getMessage());
        }


    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy");
    }
}

The output from the server is:

waiting for new client
new client connected
Bytes received: 15
Bytes sent: 15
recv failed with error: 10060
client left

The output from the client is:

08-23 18:29:55.997 26472-26472/com.example.greg.systemremote D/MainActivity: onCreate
08-23 18:29:55.997 26472-26472/com.example.greg.systemremote D/libc-netbsd: [getaddrinfo]: hostname=192.168.0.101; servname=(null); netid=0; mark=0
08-23 18:29:55.997 26472-26472/com.example.greg.systemremote D/libc-netbsd: [getaddrinfo]: ai_addrlen=0; ai_canonname=(null); ai_flags=4; ai_family=0
08-23 18:33:54.254 26472-26472/com.example.greg.systemremote D/MainActivity: recvfrom failed: ETIMEDOUT (Connection timed out)

Notice that no message is received on the client side.

I tried disabling Nagle with

int flag = 1;
setsockopt(ClientSocket, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int));

But it didn't change anything, so I tried inspecting the network traffic with Wireshark and as you can see from the output, the data is being sent to android: Wireshark output (192.168.0.183 is the android device)


However, if I uncomment the line

iResult = shutdown(ClientSocket, SD_SEND);

and thus close the connection after sending the data, it is received on the android side and the network traffic is as follows: Network traffic with socket shutdown

and the android output is

08-23 18:47:41.984 13164-13164/com.example.greg.systemremote D/MainActivity: onCreate
08-23 18:47:41.985 13164-13164/com.example.greg.systemremote D/libc-netbsd: [getaddrinfo]: hostname=192.168.0.101; servname=(null); netid=0; mark=0
08-23 18:47:41.985 13164-13164/com.example.greg.systemremote D/libc-netbsd: [getaddrinfo]: ai_addrlen=0; ai_canonname=(null); ai_flags=4; ai_family=0
08-23 18:47:42.318 13164-13164/com.example.greg.systemremote D/MainActivity: RECV MSG: 72

(which is nominal)

So, my question is, how can I send data back and fourth on this socket connection, without closing the connection? Also please note that I'm planing on sending binary data, and not text.

Upvotes: 0

Views: 1065

Answers (1)

Am_I_Helpful
Am_I_Helpful

Reputation: 19158

The source of your error at C++ side is : do-while loop.

do {
        iResult = recv(ClientSocket, recvbuf, recvbuflen, 0);
        if (iResult > 0) {
            printf("Bytes received: %d\n", iResult);

            iSendResult = send( ClientSocket, recvbuf, iResult, 0 );
            if (iSendResult == SOCKET_ERROR) {
                printf("send failed with error: %d\n", WSAGetLastError());
            }
            printf("Bytes sent: %d\n", iSendResult);
            //iResult = shutdown(ClientSocket, SD_SEND);
        } else if (iResult == 0) {
            printf("Connection closing...\n");
        } else  {
            printf("recv failed with error: %d\n", WSAGetLastError());
        }
    } while (iResult > 0);

For the first iteration, it gets inside the loop, receives data and sends data successfully to Android-device. Everything goes perfectly. And, then at Android side, you close the connection only after accepting one-response.

try {
            Socket sock = new Socket();
            //sock.setReceiveBufferSize(10);
            sock.connect(new InetSocketAddress("192.168.0.101", 27033));

            OutputStream os = sock.getOutputStream();
            InputStream is = sock.getInputStream();

            String msg = "Hello World!\r\n\0";
            byte[] arr = msg.getBytes(Charset.forName("UTF-8"));

            os.write(arr);
            os.flush();

            Log.d(TAG, "RECV MSG: " + is.read ());

            sock.close();
        } catch (Exception e) {
            Log.d(TAG, e.getMessage());
        }

Since C++ code is in a do-while loop, with iResult>0, so it will go inside the loop, and it will again try to receive data; but, unfortunately the connection has been closed at the Android side.

So, you need to check if the connection is open, then only you can proceed with receiving data. Currently, your coding logic stands poor to receive data.

It'll keep on throwing exception in the current state.

Also, from the error code Windows Sockets Error Code 10060 :

WSAETIMEDOUT 10060

Connection timed out.

A connection attempt failed because the connected party did not properly respond after a period of time, or the established connection failed because the connected host has failed to respond.


EDIT :

The source of error at Android side seems to be your creation of network-connection in the onCreate() method! This is discouraged everywhere(in Android).

You should use the onCreate() method only to create and instantiate the objects that you will be using in your application. There should not be any blocking call, as in your case(sock.connect(IP,port)).

From Android's Developer Guide on Service :

A Service is an application component representing either an application's desire to perform a longer-running operation while not interacting with the user or to supply functionality for other applications to use.

Note that services, like other application objects, run in the main thread of their hosting process. This means that, if your service is going to do any CPU intensive (such as MP3 playback) or blocking (such as networking) operations, it should spawn its own thread in which to do that work(emphasis mine).

Also, from Processes and Threads :

Android might decide to shut down a process at some point, when memory is low and required by other processes that are more immediately serving the user.

When deciding which processes to kill, the Android system weighs their relative importance to the user. For example, it more readily shuts down a process hosting activities that are no longer visible on screen, compared to a process hosting visible activities. The decision whether to terminate a process, therefore, depends on the state of the components running in that process.

Also, there are simply two rules to Android's single thread model:

  • Do not block the UI thread

  • Do not access the Android UI toolkit from outside the UI thread.

Suggested Solution :

Create a separate service to achieve network-connection establishment(check local service sample). You can refer to the link to how to achieve your network connection through a service, not in the activity itself!

Upvotes: 1

Related Questions