37 votes

Exemple : socket réseau bi-directionnel Android utilisant AsyncTask

La plupart des exemples de prises réseau que j'ai trouvés pour Android étaient unidirectionnels. J'avais besoin d'une solution pour un flux de données bidirectionnel. J'ai finalement appris l'existence de l'AsyncTask. Cet exemple montre comment récupérer des données d'un socket et les lui renvoyer. En raison de la nature bloquante d'un socket qui reçoit des données, ce blocage doit être exécuté dans un thread autre que celui de l'interface utilisateur.

A titre d'exemple, ce code se connecte à un serveur web. En appuyant sur le bouton "Start AsyncTask", le socket est ouvert. Une fois le socket ouvert, le serveur web attend une requête. En appuyant sur le bouton "Send Message", une demande est envoyée au serveur. Toute réponse du serveur sera affichée dans le TextView. Dans le cas de http, le serveur web se déconnecte du client une fois que toutes les données ont été envoyées. Pour les autres flux de données TCP, la connexion reste active jusqu'à ce qu'un des deux côtés se déconnecte.

Capture d'écran :

Screenshot of Application

AndroidManifest.xml :

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.exampleasynctask"
      android:versionCode="1"
      android:versionName="1.0">
    <uses-sdk android:minSdkVersion="8" />
    <uses-permission android:name="android.permission.INTERNET" />
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".MainActivity"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

res \layout\main.xml :

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<Button android:id="@+id/btnStart" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Start AsyncTask"></Button>
<Button android:id="@+id/btnSend" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Send Message"></Button>
<TextView android:id="@+id/textStatus" android:textSize="24sp" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Status Goes Here" />
</LinearLayout>

src \com.exampleasynctask\MainActivity.java :

package com.exampleasynctask;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends Activity {
    Button btnStart, btnSend;
    TextView textStatus;
    NetworkTask networktask;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        btnStart = (Button)findViewById(R.id.btnStart);
        btnSend = (Button)findViewById(R.id.btnSend);
        textStatus = (TextView)findViewById(R.id.textStatus);
        btnStart.setOnClickListener(btnStartListener);
        btnSend.setOnClickListener(btnSendListener);
        networktask = new NetworkTask(); //Create initial instance so SendDataToNetwork doesn't throw an error.
    }

    private OnClickListener btnStartListener = new OnClickListener() {
        public void onClick(View v){
            btnStart.setVisibility(View.INVISIBLE);
            networktask = new NetworkTask(); //New instance of NetworkTask
            networktask.execute();
        }
    };
    private OnClickListener btnSendListener = new OnClickListener() {
        public void onClick(View v){
            textStatus.setText("Sending Message to AsyncTask.");
            networktask.SendDataToNetwork("GET / HTTP/1.1\r\n\r\n");
        }
    };

    public class NetworkTask extends AsyncTask<Void, byte[], Boolean> {
        Socket nsocket; //Network Socket
        InputStream nis; //Network Input Stream
        OutputStream nos; //Network Output Stream

        @Override
        protected void onPreExecute() {
            Log.i("AsyncTask", "onPreExecute");
        }

        @Override
        protected Boolean doInBackground(Void... params) { //This runs on a different thread
            boolean result = false;
            try {
                Log.i("AsyncTask", "doInBackground: Creating socket");
                SocketAddress sockaddr = new InetSocketAddress("192.168.1.1", 80);
                nsocket = new Socket();
                nsocket.connect(sockaddr, 5000); //10 second connection timeout
                if (nsocket.isConnected()) { 
                    nis = nsocket.getInputStream();
                    nos = nsocket.getOutputStream();
                    Log.i("AsyncTask", "doInBackground: Socket created, streams assigned");
                    Log.i("AsyncTask", "doInBackground: Waiting for inital data...");
                    byte[] buffer = new byte[4096];
                    int read = nis.read(buffer, 0, 4096); //This is blocking
                    while(read != -1){
                        byte[] tempdata = new byte[read];
                        System.arraycopy(buffer, 0, tempdata, 0, read);
                        publishProgress(tempdata);
                        Log.i("AsyncTask", "doInBackground: Got some data");
                        read = nis.read(buffer, 0, 4096); //This is blocking
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
                Log.i("AsyncTask", "doInBackground: IOException");
                result = true;
            } catch (Exception e) {
                e.printStackTrace();
                Log.i("AsyncTask", "doInBackground: Exception");
                result = true;
            } finally {
                try {
                    nis.close();
                    nos.close();
                    nsocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                Log.i("AsyncTask", "doInBackground: Finished");
            }
            return result;
        }

        public void SendDataToNetwork(String cmd) { //You run this from the main thread.
            try {
                if (nsocket.isConnected()) {
                    Log.i("AsyncTask", "SendDataToNetwork: Writing received message to socket");
                    nos.write(cmd.getBytes());
                } else {
                    Log.i("AsyncTask", "SendDataToNetwork: Cannot send message. Socket is closed");
                }
            } catch (Exception e) {
                Log.i("AsyncTask", "SendDataToNetwork: Message send failed. Caught an exception");
            }
        }

        @Override
        protected void onProgressUpdate(byte[]... values) {
            if (values.length > 0) {
                Log.i("AsyncTask", "onProgressUpdate: " + values[0].length + " bytes received.");
                textStatus.setText(new String(values[0]));
            }
        }
        @Override
        protected void onCancelled() {
            Log.i("AsyncTask", "Cancelled.");
            btnStart.setVisibility(View.VISIBLE);
        }
        @Override
        protected void onPostExecute(Boolean result) {
            if (result) {
                Log.i("AsyncTask", "onPostExecute: Completed with an Error.");
                textStatus.setText("There was a connection error.");
            } else {
                Log.i("AsyncTask", "onPostExecute: Completed.");
            }
            btnStart.setVisibility(View.VISIBLE);
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        networktask.cancel(true); //In case the task is currently running
    }
}

0 votes

Pourriez-vous fournir la partie côté serveur, située sur un appareil Android ?

4voto

RedLenses Points 388

Le site SendDataToNetwork s'exécute dans le fil d'exécution principal de l'interface utilisateur, ce qui signifie qu'elle fera planter une application Honeycomb ou supérieure en raison des problèmes suivants NetworkOnMainThreadException Exception fatale. Voici ce que mon SendDataToNetwork pour éviter ce problème :

public boolean sendDataToNetwork(final byte[] cmd) { 
    if (_nsocket.isConnected()) {
        Log.i(TAG, "SendDataToNetwork: Writing received message to socket");
        new Thread(new Runnable() {
            public void run() {
                try {
                    _nos.write(cmd);
                } catch (Exception e) {
                    e.printStackTrace();
                    Log.i(TAG, "SendDataToNetwork: Message send failed. Caught an exception");
                }
            }
        }).start();

        return true;
    }

    Log.i(TAG, "SendDataToNetwork: Cannot send message. Socket is closed");
    return false;
}

3voto

Peter Knego Points 57985

Votre SendDataToNetwork ne s'exécute pas sur le même thread que doInBackground() . Il y a une possibilité que SendDataToNetwork commencerait à envoyer des données avant que le socket ne soit prêt.

Pour éviter tout cela, il suffit d'utiliser SendDataToNetwork pour sauvegarder les données et signaler au fil d'arrière-plan que les données sont prêtes à être envoyées.

Puisqu'il est possible que l'utilisateur appuie sur le bouton plusieurs fois, alors que les anciennes données sont toujours envoyées, vous devriez avoir une file d'attente synchronisée dans NetworkTask. Ensuite :

  1. Le thread d'arrière-plan établit la connexion au socket et se met ensuite en veille (via wait()).
  2. Sur pression du bouton, SendDataToNetwork ajoute les données à la file d'attente et réveille le thread d'arrière-plan (via le bouton notify() ).
  3. Lorsque le thread d'arrière-plan se réveille, il vérifie tout d'abord le fichier finish le drapeau. S'il est activé, il ferme les connexions et sort. Sinon, il lit les données de la file d'attente, les envoie au réseau et se remet en veille.
  4. Vous devriez avoir finish() qui définit un finish (variable atomique, comme un booléen) et réveille le thread d'arrière-plan. C'est une façon de quitter gracieusement le thread d'arrière-plan.

Regardez comment se fait la synchronisation des fils : http://www.jchq.net/tutorial/07_03Tut.htm

0 votes

Mais si le thread d'arrière-plan se met en veille sur une commande wait(), comment va-t-il récupérer les données entrantes de la socket ?

0 votes

Oui, vous avez raison. Alors, soit vous utilisez votre solution et bloquez l'envoi de données jusqu'à ce que la socket soit prête (= bouton désactivé), soit vous créez un thread séparé pour l'envoi.

1 votes

Qu'y a-t-il de mal à utiliser "try {" et "if (nsocket.isConnected()) {" dans SendDataToNetwork ?

3voto

Ciro Santilli Points 3341

Exemple plus interactif

Similaire à celui de l'OP, mais vous pouvez contrôler l'hôte, le port et le message + il y a une notification d'erreur popup si la connexion a échoué.

enter image description here

Utilisation 1 :

  • obtenir Android et un bureau Linux sur un réseau local
  • trouver l'IP du bureau avec ifconfig
  • exécuter netcat -l 12345 sur un terminal
  • sur Android, remplissez l'IP de l'ordinateur de bureau.
  • serveur de contact à clic
  • sur le terminal, tapez la réponse, puis appuyez sur Ctrl + D
  • il apparaît sur le output: section

Utilisation 2 :

  • nom d'hôte google.com
  • port 80
  • Message : "GET / HTTP/1.1\r\nHost: google.com\r\n\r\n"

Notez que certains serveurs HTTP ne se ferment pas après la réponse et attendent d'autres demandes, et l'application se bloquera jusqu'à ce qu'ils expirent. Ces serveurs s'attendent à ce que vous analysiez le code Content-Width tête et fermez-vous.

Si la connexion échoue, un message d'alerte est affiché à l'utilisateur dans une boîte de dialogue.

Code

Ajouter à AndroidManifest.xml :

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

Et l'activité principale est :

import android.app.Activity;
import android.app.AlertDialog;
import android.app.IntentService;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class Main extends Activity {
    final static String TAG = "AndroidCheatSocket";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        final LinearLayout linearLayout = new LinearLayout(this);
        linearLayout.setOrientation(LinearLayout.VERTICAL);
        TextView textView;

        final String defaultHostname = "192.168.0.";
        textView = new TextView(this);
        textView.setText("hostname / IP:");
        linearLayout.addView(textView);
        final EditText hostnameEditText = new EditText(this);
        hostnameEditText.setText(defaultHostname);
        hostnameEditText.setSingleLine(true);
        linearLayout.addView(hostnameEditText);

        textView = new TextView(this);
        textView.setText("port:");
        linearLayout.addView(textView);
        final EditText portEditText = new EditText(this);
        portEditText.setText("12345");
        portEditText.setSingleLine(true);
        linearLayout.addView(portEditText);

        textView = new TextView(this);
        textView.setText("data to send:");
        linearLayout.addView(textView);
        final EditText dataEditText = new EditText(this);
        dataEditText.setText(String.format("GET / HTTP/1.1\r\nHost: %s\r\n\r\n", defaultHostname));
        linearLayout.addView(dataEditText);

        final TextView replyTextView = new TextView(this);
        final ScrollView replyTextScrollView = new ScrollView(this);
        replyTextScrollView.addView(replyTextView);

        final Button button = new Button(this);
        button.setText("contact server");
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                button.setEnabled(false);
                new MyAsyncTask(Main.this, replyTextView, button).execute(
                        hostnameEditText.getText().toString(),
                        portEditText.getText().toString(),
                        dataEditText.getText().toString());

            }
        });
        linearLayout.addView(button);

        textView = new TextView(this);
        textView.setText("output:");
        linearLayout.addView(textView);
        linearLayout.addView(replyTextScrollView);

        this.setContentView(linearLayout);
    }

    private class MyAsyncTask extends AsyncTask<String, Void, String> {
        Activity activity;
        Button button;
        TextView textView;
        IOException ioException;
        MyAsyncTask(Activity activity, TextView textView, Button button) {
            super();
            this.activity = activity;
            this.textView = textView;
            this.button = button;
            this.ioException = null;
        }
        @Override
        protected String doInBackground(String... params) {
            StringBuilder sb = new StringBuilder();
            try {
                Socket socket = new Socket(
                        params[0],
                        Integer.parseInt(params[1]));
                OutputStream out = socket.getOutputStream();
                out.write(params[2].getBytes());
                InputStream in = socket.getInputStream();
                byte buf[] = new byte[1024];
                int nbytes;
                while ((nbytes = in.read(buf)) != -1) {
                    sb.append(new String(buf, 0, nbytes));
                }
                socket.close();
            } catch(IOException e) {
                this.ioException = e;
                return "error";
            }
            return sb.toString();
        }
        @Override
        protected void onPostExecute(String result) {
            if (this.ioException != null) {
                new AlertDialog.Builder(this.activity)
                    .setTitle("An error occurrsed")
                    .setMessage(this.ioException.toString())
                    .setIcon(android.R.drawable.ic_dialog_alert)
                    .show();
            } else {
                this.textView.setText(result);
            }
            this.button.setEnabled(true);
        }
    }
}

Sur GitHub avec le modèle de construction .

J'ai également publié un exemple de serveur Android à l'adresse suivante : https://stackoverflow.com/a/35745834/895245

Testé sous Android 5.1.1, Sony Xperia 3 D6643.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X