28

I want with my app to enter in the url of my server e.g. http://192.168.1.8/ and the port e.g. 1234. When my server receives the TCP Request message, it sends back a file (the server is already implemented).

I think that I don't need something complicated like an AsyncTask, as I do not want to keep the connection. Receiving the answer from the server, my connection must be closed.

Any indication for a way forward or a tip is highly appreciated.

Daniel Nugent
  • 43,104
  • 15
  • 109
  • 137
G.V.
  • 709
  • 2
  • 11
  • 17
  • 4
    3 things: `AsyncTask` is not complicated at all; You **do** need something like `AsyncTask` (otherwise you will get `android.os.NetworkOnMainThreadException`); Look into the library `Retrofit` it easily manages everything you need (everybody uses it) – Bartek Lipinski Jul 02 '16 at 17:36

5 Answers5

54

Here is a simple TCP client that uses Sockets that I got working based on code in this tutorial (the code for the tutorial can also be found in this GitHub repository).

Note that this code is geared to sending strings back and forth between the client and server, usually in JSON format.

Here is the TCP client code:

import android.util.Log;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;

public class TcpClient {

    public static final String TAG = TcpClient.class.getSimpleName();
    public static final String SERVER_IP = "192.168.1.8"; //server IP address
    public static final int SERVER_PORT = 1234;
    // message to send to the server
    private String mServerMessage;
    // sends message received notifications
    private OnMessageReceived mMessageListener = null;
    // while this is true, the server will continue running
    private boolean mRun = false;
    // used to send messages
    private PrintWriter mBufferOut;
    // used to read messages from the server
    private BufferedReader mBufferIn;

    /**
     * Constructor of the class. OnMessagedReceived listens for the messages received from server
     */
    public TcpClient(OnMessageReceived listener) {
        mMessageListener = listener;
    }

    /**
     * Sends the message entered by client to the server
     *
     * @param message text entered by client
     */
    public void sendMessage(final String message) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                if (mBufferOut != null) {
                    Log.d(TAG, "Sending: " + message);
                    mBufferOut.println(message);
                    mBufferOut.flush();
                }
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
    }

    /**
     * Close the connection and release the members
     */
    public void stopClient() {

        mRun = false;

        if (mBufferOut != null) {
            mBufferOut.flush();
            mBufferOut.close();
        }

        mMessageListener = null;
        mBufferIn = null;
        mBufferOut = null;
        mServerMessage = null;
    }

    public void run() {

        mRun = true;

        try {
            //here you must put your computer's IP address.
            InetAddress serverAddr = InetAddress.getByName(SERVER_IP);

            Log.d("TCP Client", "C: Connecting...");

            //create a socket to make the connection with the server
            Socket socket = new Socket(serverAddr, SERVER_PORT);

            try {

                //sends the message to the server
                mBufferOut = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);

                //receives the message which the server sends back
                mBufferIn = new BufferedReader(new InputStreamReader(socket.getInputStream()));


                //in this while the client listens for the messages sent by the server
                while (mRun) {

                    mServerMessage = mBufferIn.readLine();

                    if (mServerMessage != null && mMessageListener != null) {
                        //call the method messageReceived from MyActivity class
                        mMessageListener.messageReceived(mServerMessage);
                    }

                }

                Log.d("RESPONSE FROM SERVER", "S: Received Message: '" + mServerMessage + "'");

            } catch (Exception e) {
                Log.e("TCP", "S: Error", e);
            } finally {
                //the socket must be closed. It is not possible to reconnect to this socket
                // after it is closed, which means a new socket instance has to be created.
                socket.close();
            }

        } catch (Exception e) {
            Log.e("TCP", "C: Error", e);
        }

    }

    //Declare the interface. The method messageReceived(String message) will must be implemented in the Activity
    //class at on AsyncTask doInBackground
    public interface OnMessageReceived {
        public void messageReceived(String message);
    }

}

Then, declare a TcpClient as a member variable in your Activity:

public class MainActivity extends Activity {

    TcpClient mTcpClient;

    //............

Then, use an AsyncTask for connecting to your server and receiving responses on the UI thread (Note that messages received from the server are handled in the onProgressUpdate() method override in the AsyncTask):

public class ConnectTask extends AsyncTask<String, String, TcpClient> {

    @Override
    protected TcpClient doInBackground(String... message) {

        //we create a TCPClient object
        mTcpClient = new TcpClient(new TcpClient.OnMessageReceived() {
            @Override
            //here the messageReceived method is implemented
            public void messageReceived(String message) {
                //this method calls the onProgressUpdate
                publishProgress(message);
            }
        });
        mTcpClient.run();

        return null;
    }

    @Override
    protected void onProgressUpdate(String... values) {
        super.onProgressUpdate(values);
        //response received from server
        Log.d("test", "response " + values[0]);
        //process server response here....

}

To start the connection to your server, execute the AsyncTask:

new ConnectTask().execute("");

Then, sending a message to the server:

//sends the message to the server
if (mTcpClient != null) {
    mTcpClient.sendMessage("testing");
}

You can close the connection to the server at any time:

if (mTcpClient != null) {
    mTcpClient.stopClient();
}
Daniel Nugent
  • 43,104
  • 15
  • 109
  • 137
  • Thank you for your help! But I do not see where I set that i want to enter in '/zyz' my server sends different data when i enter in /zyz or /xxa or /xxb etc. – G.V. Jul 03 '16 at 08:25
  • Another problem is that i have already implemented a client like this but the message TCP request does not reach the server. On the other hand ordering the same address from a browser in my phone, the TCP Request message that is created by the browser reaches its destination. – G.V. Jul 03 '16 at 14:39
  • 1
    @G.v it's starting to sound like you're actually using http and using the query string in a GET request, and not tcp.... – Daniel Nugent Jul 03 '16 at 15:05
  • @Daniel_Nugent Hi again, as a a first step i want to use the simplest method in order to take my data from the server, so I try to use the URI part of the http in order to the request to be understandable by the server. In a next step I'll try to implement the TCP with messages. – G.V. Jul 03 '16 at 15:41
  • @G.v. If you already have your server working, and it expects a GET request over http, then just do an http request in the app code. – Daniel Nugent Jul 03 '16 at 15:50
  • Hi @DanielNugent, I want to try and use this code. is there a place to put a connected() callback in the TcpClient? I want the activity to be notified when the client is connected - so I know when to start sending messages. – Ofek Agmon Oct 23 '16 at 09:43
  • 13
    Don't forget to add `` to your `AndroidManifest.xml` – Ziad Akiki Mar 19 '17 at 09:59
  • @DanielNugent hi Daniel, i'm using your TCP Client but i have an issue, if i try to open the connection and send the message it doesn't happen but i have to put a delay between opening the connection and sending the message using a handler or thread.sleep – John K Jul 27 '17 at 07:00
  • @DanielNugent thank for your answer. I am currently trying to implement your solution, however I am experiencing an error. When I try to send the message with TcpClient, as such: `mBufferOut.println(message);` NetworkOnMainThread exception is thrown. Do you have any suggestions? Thank you. – Paolo Feb 09 '18 at 17:10
  • @pkpkpk Are you sure that you've started the AsyncTask? i.e. `new ConnectTask().execute("");` – Daniel Nugent Feb 09 '18 at 23:16
  • @DanielNugent Yes I have called this at the beginning. The connection to the server is made however, calling `sendMessage(message);` flags the error, as I described. I have recently fixed the error after following the changes described here: [link](https://github.com/badaix/snapcast/issues/97). Here is also the link to the file change: [link](https://github.com/badaix/snapcast/pull/99/files). Although this error is fixed, I am still interested in why the error was being flagged. Do you have anymore advice? – Paolo Feb 13 '18 at 16:46
  • 1
    @pkpkpk My guess is that newer OS versions are more strict about this, the original code is very old, and worked on older OS versions. I just changed the answer with the fix you linked to. – Daniel Nugent Feb 13 '18 at 18:40
  • This code seem not working in OS 6+, i put log in sending method but it seem socket can not connect – kemdo Apr 04 '18 at 11:04
  • @kemdo Are you sure that your server is up and running and fully functional? – Daniel Nugent Apr 04 '18 at 17:28
  • It's running normally if i send data right after connect to socket, when the loop running i can send any data – kemdo Apr 05 '18 at 02:28
  • please suggest :- this code will work for android Version 7+ or NOUGAT and later versions – Yogesh Borhade Nov 01 '18 at 11:49
  • @YogeshBorhade It worked for me the last time I tested it, I believe it was on 7.1.1. I did update the answer in February in order to make it work on newer OS versions. It should work now, let me know if it doesn't. – Daniel Nugent Nov 01 '18 at 18:16
  • Not working for me, null inputStrem , but I'm able to get data from TCP Client App. – Vishva Vijay Jan 14 '20 at 10:45
  • 1
    -1 "AsyncTasks should ideally be used for short operations (a few seconds at the most.)" https://developer.android.com/reference/android/os/AsyncTask.html – Bora M. Alper Jan 30 '20 at 21:18
  • @BoraM.Alper I agree with you 100%, but this code was ported from a tutorial, and I just spent minimal time to make it work as a quick example! Maybe someday I'll update it to use a Thread if I have some spare time! – Daniel Nugent Jan 31 '20 at 18:05
  • 1
    It's my 2nd day trying to learn android. Being familiar with tcp/ip in C in the past, I find the snippet above very illustrative in terms of the arrangement and the abstractions involved related to low-level and high-level interactions. Thanks. – daparic Mar 03 '20 at 08:51
  • @DanielNugent hi I created server side with WindowsForm C# and i created a Client from Android emulateor i want to communicate them in localhost but when server send back message cannot receive from client. here my question can you check? https://stackoverflow.com/questions/64315559/kotlin-bufferreader-is-not-getting-server-response – saulyasar Oct 13 '20 at 06:30
  • @saulyasar You probably just need to use the IP address `10.0.2.2`, see here: https://stackoverflow.com/questions/5528850/how-do-you-connect-localhost-in-the-android-emulator – Daniel Nugent Oct 15 '20 at 16:14
  • 1
    @DanielNugent thank you for your answer I have tried your code what do you think as we know as AsyncTask depreciated in java what is alternative here – Edgar Oct 31 '20 at 10:33
  • Same issue here regarding depreciation. The example should be updated :) – OscarVanL Nov 03 '20 at 10:30
  • 1
    @OscarVanL I will update this when I can, just need to find the time to get an updated example working! – Daniel Nugent Nov 03 '20 at 16:06
12

Thanks for code. In my case I was having trouble receiving data, as I'm not using a line-based protocol. I wrote an alternate implementation that works with my specific server setup:

  1. In TcpClient.java file, "run()" command you should replace with code fragment below

    public void run() {

    mRun = true;
    
    try {
        InetAddress serverAddr = InetAddress.getByName(SERVER_IP);
        Log.e("TCP Client", "C: Connecting...");
        Socket socket = new Socket(serverAddr, SERVER_PORT);
        try {
            mBufferOut = new PrintWriter(socket.getOutputStream());
            Log.e("TCP Client", "C: Sent.");
            mBufferIn = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            int charsRead = 0; char[] buffer = new char[1024]; //choose your buffer size if you need other than 1024
    
            while (mRun) {
                charsRead = mBufferIn.read(buffer);
                mServerMessage = new String(buffer).substring(0, charsRead);
                if (mServerMessage != null && mMessageListener != null) {
                    mMessageListener.messageReceived(mServerMessage);}
                mServerMessage = null;
            }
            Log.e("RESPONSE FROM SERVER", "S: Received Message: '" + mServerMessage + "'"); 
    

//rest of code is OK, see original

  1. doInBackgroud in your MainActivity.java posts received message to onProgressUpdate, you can display it in other object e.g. TextView

    @Override
    protected void onProgressUpdate(String... values) {
            super.onProgressUpdate(values);
            Log.d("test", "response " + values[0]);
            response.setText(response.getText() + "/n" +values[0]);
    } 
    

//"response" is TextView object declared in your function

public class MainActivity extends AppCompatActivity {
TextView response; //...so on

and function

protected void onCreate(Bundle savedInstanceState) { response = (TextView) findViewById(R.id.textView); //..so on

Daniel Nugent
  • 43,104
  • 15
  • 109
  • 137
RAIMA
  • 121
  • 1
  • 4
  • 1
    What code are you referring/thanking for? Is this more of a comment to a different answer? Maybe one which has been deleted? Would you like to change this into a standalone answer? – Yunnosch Jun 18 '17 at 18:01
  • Code at the top webpage, thread named "Really simple TCP client".. Or I see something different? – RAIMA Jun 18 '17 at 20:22
  • 1
    At the top I see a question, without code. In the middle I see an answer, with code. If you refer to that answer, read the rest of my comment above. I.e. would you like to make a standalone answer? There could potentially be more answers, with differently good code. It would be unclear what you refer to. You can write an answer which builds on the currently only answer, but you should refer more clearly. The point is, your answer currently might seem like a "answer-which-should-be-comment". But since you got an upvote meanwhile (congratulations), it is probably just me. Have fun. – Yunnosch Jun 18 '17 at 21:10
  • Posting to this webpage first time, so may be I am not much familiar with the rules (sorry for that). But when you spend half day at make things working, it is very clear what you need despite of what comment refer to..:). But thanks for remark, I'll try be more precise next time. (by the way stand alone answer would need to copy original code posted by author, not just corrected fragment, so I am not sure If it is good idea). Regards. – RAIMA Jun 19 '17 at 16:20
  • If you would mostly copy other answers code, then you can write an answer which builds on that answer (not copying it, you are right), but refer more clearly, e.g. by saying "answer by Daniel Nugent". Some time passed now. It does not look like what I feared (many more answers) will happen now. So you are fine. Still no downvote either, so you are good to practically ignore me. If you find (for future particiapation) something useful in my comment, then thanks for the compliment. Getting your first answer upvoted and not downvoted is an achievement. Looking forward to read more from you. – Yunnosch Jun 19 '17 at 16:27
  • i can send data but cant receive data. – Moh_beh Apr 13 '19 at 14:30
  • Perhaps `SO` needs its own dedicate pastebin or gist so that we can absolutely refer to snippets without ambiguity. Or a way for snippets to be tagged so that it can be referred to later. – daparic Mar 03 '20 at 09:01
3

first of all give internet permission to your app in android manifest

<uses-permission android:name="android.permission.INTERNET"/>

you can use AsyncTask to send your request and receive your file back so easily

    public void send_request() {
    send_request sr = new send_request();
    sr.execute();
}

class send_request extends AsyncTask<Void, Void, String> {

    @Override
    protected String doInBackground(Void... voids) {
        try {             /*note : ip address must be in Quotation mark*/
                          /*note : port doesn't need to be inside the Quotation mark*/
            Socket s = new Socket(/*ip address :*/"192.168.1.8",/*port :*/ 1234);
            PrintWriter printWriter = new PrintWriter(s.getOutputStream());
            printWriter.write("your message");
            printWriter.flush();
            printWriter.close();
            s.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public void onPostExecute(final String Data) {
        /*Data is what you receive from your server*/
        Log.e("my_Data","Data is : " + Data);
    }
}
Younes
  • 462
  • 7
  • 15
  • Not Working for me. – Vishva Vijay Jan 14 '20 at 10:46
  • 2
    As indicated in documentations, the `android.os.AsyncTask` is to be avoided and deprecated. For reasons it causes **missed callbacks** and other issues. Therefore, no additions can claim to have a more durable construct derived on top of that snippet. – daparic Mar 03 '20 at 09:11
1

Try this code in TcpClient :

 public void run() {
        mRun = true;
        try {
            InetAddress serverAddr = InetAddress.getByName(SERVER_IP);
            Log.e("TCP Client", "C: Connecting...");
            Socket socket = new Socket(serverAddr, SERVER_PORT);
            try {
                mBufferOut = new PrintWriter(socket.getOutputStream());
                Log.e("TCP Client", "C: Sent.");
                mBufferIn = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                int charsRead = 0; char[] buffer = new char[2024]; //choose your buffer size if you need other than 1024
                while (mRun) {
                    charsRead = mBufferIn.read(buffer);
                    mServerMessage = new String(buffer).substring(0, charsRead);
                    if (mServerMessage != null && mMessageListener != null) {
                        Log.e("in if---------->>", " Received : '" + mServerMessage + "'");
                    }
                    mServerMessage = null;
                }
                Log.e("-------------- >>", " Received : '" + mServerMessage + "'");
            } catch (Exception e) {
                Log.e("TCP", "S: Error", e);
            } finally {
                //the socket must be closed. It is not possible to reconnect to this socket
                // after it is closed, which means a new socket instance has to be created.
                socket.close();
                Log.e("-------------- >>", "Close socket " );
            }

        } catch (Exception e) {
            Log.e("TCP", "C: Error", e);
        }

    }

It work correctly. This line in other code above to cause wrong.

mMessageListener.messageReceived(mServerMessage);

remove this line and your App work well. you can monitor your Log in android studio too.

You can use this code for your server in Golang.This is my server:

package main

import (
    "bufio"
    "flag"
    "fmt"
    "net"
    "strconv"
)
var addr = flag.String("addr", "", "The address to listen to; default is \"\" (all interfaces).")
var port = flag.Int("port", 37533, "The port to listen on; default is 37533.")
func main() {
    flag.Parse()
    fmt.Println("Starting server...")
    src := *addr + ":" + strconv.Itoa(*port)
    listener, _ := net.Listen("tcp", src)
    fmt.Printf("Listening on %s.\n", src)

    defer listener.Close()

    for {
        conn, err := listener.Accept()
        if err != nil {
            fmt.Printf("Some connection error: %s\n", err)
        }
        go handleConnection(conn)
    }
}

func handleConnection(conn net.Conn) {
    remoteAddr := conn.RemoteAddr().String()
  LocalAddr :=conn.LocalAddr().String()

  fmt.Println("Client LocalAddr  " + LocalAddr)
    fmt.Println("Client connected from " + remoteAddr)
    scanner := bufio.NewScanner(conn)
    for {
        ok := scanner.Scan()

        if !ok {
            break
        }

        handleMessage(scanner.Text(), conn)
    }
    fmt.Println("Client at " + remoteAddr + " disconnected.")
}

func handleMessage(message string, conn net.Conn) {
    fmt.Println("> " + message)
    if len(message) > 0 {
    conn.Write([]byte("This is from Golang.\n"))
    fmt.Println("----------> we send it....")
    }
}
Moh_beh
  • 251
  • 1
  • 3
  • 14
  • Thanks for code amendment as attempt to build more reliable code. Just fyi, the server code does not need any programming as it is a one-liner if you build on top of the ancients: `socat -xd TCP-LISTEN:37533,reuseaddr,fork stdout` – daparic Mar 03 '20 at 09:17
1

What is the final deployment environment? Your code points to a local IP. Things might be changed when you deploy it to a real server environment? Be aware of NAT problems if you launch this code in a hosted server. Then you will not be able to get packets. Consider using a more complicated protocol (e.g., Socket.io can help)

Rami Khawaly
  • 126
  • 3
  • 14