Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Garbage data transmitted in WiFi TCP connection from desktop to Android

Tags:

java

c

android

I've been looking around, and haven't been able to find a solution to this particular issue. Forgive me if this is a newbie mistake, I'm fresh out of school so I'm reading as many books as I can to get caught up on mobile device programming.

The Goal: Transmit data from a PC based socket server to an Android based client wirelessly (802.11 b/g), which will then process said data for output to the user.

The Problem: A large amount of erroneous garbage data is being received in the input stream buffers on the Android phone.

The Procedure: I've written and/or modified three different pieces of code. First is the server side program running on my laptop. The original source code can be found here: beej.us/guide/bgnet/examples/server.c (thanks to Beej for his source code!). I've modified it to remove warnings/errors, and added my own continuous data input loop for testing purposes. Here is the modified code:

    /* A simple server in the internet domain using TCP
   The port number is passed as an argument */
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>

void error(char *msg)
{
    perror(msg);
    exit(1);
}

int main(int argc, char *argv[])
{
     int sockfd, newsockfd, portno;  //, clilen;

     //Modified this fromt he original author's code, as
     //the function is looking for clilen to be of type
     //socklen_t, not int
     socklen_t clilen;

     char buffer[256];
     struct sockaddr_in serv_addr, cli_addr;
     int n;

     if (argc < 2) 
     {
         fprintf(stderr,"ERROR, no port provided\n");
         exit(1);
     }

     sockfd = socket(AF_INET, SOCK_STREAM, 0);
     if (sockfd < 0) 
        error("ERROR opening socket");

     bzero((char *) &serv_addr, sizeof(serv_addr));
     portno = atoi(argv[1]);
     serv_addr.sin_family = AF_INET;
     serv_addr.sin_addr.s_addr = INADDR_ANY;
     serv_addr.sin_port = htons(portno);

     if (bind(sockfd, (struct sockaddr *) &serv_addr,
              sizeof(serv_addr)) < 0) 
              error("ERROR on binding");

     //Added this for some clarity 
     printf("Starting to listen on the socket now..\n");
     listen(sockfd,5);


     clilen = sizeof(cli_addr);

     newsockfd = accept(sockfd, 
                 (struct sockaddr *) &cli_addr, 
                 &clilen);

     //Let me know a socket connection has been established
     printf("Socket established!\n");


     if (newsockfd < 0) 
          error("ERROR on accept");

     bzero(buffer,256);

     //Don't want this socket to block and read, only to write 
     //Modified from the original author
     //n = read(newsockfd,buffer,255);

     //if (n < 0) error("ERROR reading from socket");
     //printf("Here is the message: %s\n",buffer);

     //n = write(newsockfd,"Hello, socket!",18);

     const int SIZE_OF_STRING = 30;
     char string[SIZE_OF_STRING];
     int i = 0;

     for (i = 0; i < SIZE_OF_STRING; ++i)
     {
         string[i] = '\0';
     }

     //Ask input from the user until the word "quit" is seen
     //then close the socket 
     while (!strstr(string, "quit"))
     {
        printf("Please enter something to send to the phone.\n");
        scanf("%s", string);
        strcat(string, "\n");
        n = write(newsockfd, string, sizeof(string));
        if (n < 0) error("ERROR writing to socket");
     }

     printf("\n\nexiting..\n");

     close(newsockfd);
     close(sockfd);

     return 0; 
}

And here is the Android code:

public class SmartSawLineDrawSocketThread extends Thread 
{

    private Handler smartSawMainThreadCommunicationHandle_;
    private Socket smartSawSocketHandle_;
    private InputStream smartSawSocketInputStreamHandle_;
    private BufferedReader smartSawSocketInputBuffer_;
    private InputStreamReader smartSawSocketInputStreamReader_;
    private boolean threadRunning_ = false;
    private boolean isConnected_;
    private char buffer[] = new char[50];



    //Grab the thread's communication handle for use with sending messages to the UI
    //Thread
    public SmartSawLineDrawSocketThread(Handler handle)
    {
        smartSawMainThreadCommunicationHandle_ = handle;
        threadRunning_ = true;
        isConnected_ = false;
    }

    //Attempt a connection to the host
    public int SmartSawLineDrawSocketThreadConnect(String hostIP, String hostPort)
    {
        int rval = 0;
        Log.i("info", "hostIP = " + hostIP);
        Log.i("info", "hostPort = " + Integer.parseInt(hostPort.trim()));
        try 
        {
            smartSawSocketHandle_ = new Socket(hostIP.trim(), Integer.parseInt(hostPort.trim()));
            if (rval == 0)
            {
                smartSawSocketInputBuffer_ = new BufferedReader(new InputStreamReader(smartSawSocketHandle_.getInputStream()));
                smartSawSocketInputStreamReader_ = new InputStreamReader(smartSawSocketHandle_.getInputStream());
                if (smartSawSocketInputBuffer_ != null)
                {
                    isConnected_ = true;
                }
                else
                {
                    Log.e("error", "Input buffer pointer was null!");
                }
            }
        } 
        catch (UnknownHostException e) 
        {
            rval = 1;
            Log.i("info", "unknown host message e when connecting:" + e);
            e.printStackTrace();
            isConnected_ = false;
        }
        catch (IOException e) 
        {
            rval = 2;
            Log.i("info", "unknown IOException e when connecting:" + e);
            e.printStackTrace();
            isConnected_ = false;
        }
        catch (SecurityException e)
        {
            rval = 3;
            Log.i("info", "Need to set a security setting somewhere");
        }


        Log.i("info", "Rval returned with " + rval);    

        return rval;
    }

    //Disconnect from the server
    public void SmartSawLineDrawSocketThreadDisconnect()
    {
        try 
        {
            smartSawSocketHandle_.close();
            isConnected_ = false;
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    //Once the thread has started running, make sure we're connected, and start
    //trying to listen for messages
    @Override 
    public void run()
    {
        Log.i("info", "Made it into the run() loop of the thread.");
        while (threadRunning_)
        {
            if (isConnected_ == true)
            {
                try
                {
                    if (smartSawSocketInputStreamReader_.ready() == true)
                    //(smartSawSocketInputBuffer_.ready() == true)
                    {
                        int numread = 0;
                        numread = smartSawSocketInputStreamReader_.read(buffer);

                        Log.i("info", "amount of characters read in: " + numread);

                        Message mainThreadMessage_ = Message.obtain();
                        Bundle mainThreadDataBundle_ = new Bundle();
                        mainThreadDataBundle_.putString("Zero", new String(buffer)); //smartSawSocketInputBuffer_.readLine());
                        mainThreadMessage_.setData(mainThreadDataBundle_);
                        mainThreadMessage_.setTarget(smartSawMainThreadCommunicationHandle_);
                        mainThreadMessage_.sendToTarget();
                        Log.i("info", "Received a string! Sent this to main thread: " + mainThreadDataBundle_.getString("Zero"));


                    }
                }
                catch (IOException e)
                {
                    Log.i("info","IO Exception in thread main loop, e was: " + e);
                }
            }


        }

    } 
}

Homework and Tests: I can successfully transmit one string, but everything after that is unintelligible garbage in the PC-Android connection. Wanting to do my homework first, I wanted to eliminate the server side code. I took Beej's client side code (beej.us/guide/bgnet/examples/client.c) and modified it to just be a good listener. I was able to transmit several strings to the client on a PC-PC based TCP connection. I redirected the client's output to a file, and opened it under a hex editor. Lo and behold, there was no erroneous data to be found. I tested this with a laptop connected to an 802.11b/g router being the server, and with a hard wired desktop being the client. I've eliminated hardware issues, and test code issues on the server side. It MUST be somewhere in how I'm implementing the Android client side code. I also tried the automated BufferedReader class to do my input, as well as manually handling the input with an InputStreamReader class. Both receive the same garbage output after the first string. This leads me to believe it's somewhere in the socket's input stream, but how would I fix that? Does anybody have any suggestions?

Here is the code for the PC based client side test:

/*
** client.c -- a stream socket client demo
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>

#include <arpa/inet.h>

#define PORT "27015" // the port client will be connecting to 

#define MAXDATASIZE 100 // max number of bytes we can get at once 

// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa)
{
    if (sa->sa_family == AF_INET) {
        return &(((struct sockaddr_in*)sa)->sin_addr);
    }

    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

int main(int argc, char *argv[])
{
    int sockfd, numbytes;  
    char buf[MAXDATASIZE];
    struct addrinfo hints, *servinfo, *p;
    int rv;
    char s[INET6_ADDRSTRLEN];

    if (argc != 2) {
        fprintf(stderr,"usage: client hostname\n");
        exit(1);
    }

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    if ((rv = getaddrinfo(argv[1], PORT, &hints, &servinfo)) != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
        return 1;
    }

    // loop through all the results and connect to the first we can
    for(p = servinfo; p != NULL; p = p->ai_next) {
        if ((sockfd = socket(p->ai_family, p->ai_socktype,
                p->ai_protocol)) == -1) {
            perror("client: socket");
            continue;
        }

        if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
            close(sockfd);
            perror("client: connect");
            continue;
        }

        break;
    }

    if (p == NULL) {
        fprintf(stderr, "client: failed to connect\n");
        return 2;
    }

    inet_ntop(p->ai_family, get_in_addr((struct sockaddr *)p->ai_addr),
            s, sizeof s);
    printf("client: connecting to %s\n", s);

    freeaddrinfo(servinfo); // all done with this structure

    //Modified from the original to spit out all strings transmitted from the server, then close the socket
    //when finished.
    while (!strstr(buf, "close"))
    {
        numbytes = recv(sockfd, buf, MAXDATASIZE-1, 0);
        if (numbytes)
        {
            printf("Received: \n");
            printf("%s", buf);
            printf("\n");
        }
    }

    if ((numbytes = recv(sockfd, buf, MAXDATASIZE-1, 0)) == -1) {
        perror("recv");
        exit(1);
    }

    buf[numbytes] = '\0';

    printf("client: received '%s'\n",buf);

    close(sockfd);

    return 0;
} 

Thanks for the help! Moscro

like image 302
moscro Avatar asked Nov 14 '22 05:11

moscro


1 Answers

Client:

You should create the string using new String(buffer, 0, numread) since not all of the buffer may have been filled, so existing characters not overwritten by the read() will appear in the string.

You should also check for numread == -1, since this indicates the connection being closed.

Server:

sizeof(some_array) returns the size (in bytes) of the entire array, which means your server is sending 30 bytes each time. Most of those will be null (\0) which explains why the C client appears to work, since printf assumes that the first null byte indicates the end of the string. Java doesn't, however, which is probably why the extra bytes appear in the log message as garbage.

One solution might be:

  • modify the server to send the exact number of characters in the message including the terminating \n, which would be strlen(string) + 1
  • then in the client, ditch the second InputStreamReader and just use BufferedReader's readLine() method, which reads up to the next newline character
like image 109
SimonJ Avatar answered Dec 18 '22 12:12

SimonJ