2011-02-27 9 views
35

La mayoría de los ejemplos de socket de red que encontré para Android eran solo direccionales. Necesitaba una solución para un flujo de datos bidireccional. Finalmente aprendí sobre AsyncTask. Este ejemplo muestra cómo obtener datos de un socket y enviar datos a él. Debido a la naturaleza de bloqueo de un socket que está recibiendo datos, ese bloqueo debe ejecutarse en un hilo que no sea el de la interfaz de usuario.Ejemplo: conector de red bidireccional de Android con AsyncTask

Por ejemplo, este código se conecta a un servidor web. Presionando el botón "Start AsyncTask" se abrirá el socket. Una vez que el socket está abierto, el servidor web espera una solicitud. Al presionar el botón "Enviar mensaje" se enviará una solicitud al servidor. Cualquier respuesta del servidor se mostrará en TextView. En el caso de http, un servidor web se desconectará del cliente una vez que se hayan enviado todos los datos. Para otros flujos de datos TCP, la conexión permanecerá activa hasta que se desconecte un lado.

Captura de pantalla:

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

¿Podría proporcionar la parte del servidor, ubicada en un dispositivo Android? – dothedos

Respuesta

3

Su SendDataToNetwork no se ejecuta en el mismo hilo que doInBackground(). Existe la posibilidad de que SendDataToNetwork comience a enviar datos antes de que el socket esté listo.

Para evitar todo esto, simplemente use SendDataToNetwork para guardar los datos y enviar la señal al hilo de fondo de que los datos están listos para ser enviados.

Dado que existe la posibilidad de que el usuario pueda presionar el botón varias veces, mientras se siguen enviando los datos anteriores, debe haber sincronizado la Cola dentro de NetworkTask. Luego:

  1. El hilo de fondo configura la conexión del zócalo y luego se pone en reposo (a través de wait()).
  2. Al presionar un botón, SendDataToNetwork agrega datos a la cola y se activa el hilo de fondo (a través de notify()).
  3. Cuando el hilo de fondo se despierta, primero comprueba el indicador finish. Si se establece, cierra las conexiones y sale. Si no, lee los datos de la cola, los envía a la red y vuelve a dormir.
  4. Debe tener el método finish() que establece un indicador finish (variable atómica, como booleano) y activa el hilo de fondo. Esta es una forma de salir graciosamente del hilo de fondo.

Tome un vistazo a cómo se realiza la sincronización de hilos: http://www.jchq.net/tutorial/07_03Tut.htm

+0

Pero si el hilo de fondo va a dormir en un comando wait(), ¿cómo va a recoger los datos entrantes del socket? –

+0

Sí, tienes razón. Luego, use su solución y bloquee el envío de datos hasta que el socket esté listo (= botón de deshabilitar) o cree un hilo separado para enviar. –

+1

¿Qué hay de malo con el uso de "try {" y "if (nsocket.isConnected()) {"dentro de SendDataToNetwork? –

3

Los SendDataToNetwork tarea se ejecuta en el hilo principal de interfaz de usuario, lo que significa que se colgará un nido de abeja o superior debido a la aplicación NetworkOnMainThreadException excepción fatal.Esto es lo que mis SendDataToNetwork se ve como para evitar este problema:

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; 
} 
1

Más ejemplo interactiva

Al igual que en el PO de, pero se puede controlar host, el puerto y el mensaje + hay una notificación de error emergente si el la conexión falló.

enter image description here

Sintaxis 1:

  • consiguen Android y un escritorio Linux en una red LAN
  • encontrar el IP del escritorio con ifconfig
  • plazo netcat -l 12345 en un terminal
  • en Android, complete la dirección IP del escritorio
  • haga clic en el servidor de contactos
  • en el terminal, escriba la respuesta, y pulsa Ctrl + D
  • como aparece en la sección output:

Sintaxis 2: puerto

  • nombre de host google.com
  • 80
  • Mensaje: "GET/HTTP/1.1\r\nHost: google.com\r\n\r\n"

Tenga en cuenta que algunos servidores HTTP no se cerrarán después de la respuesta esperando más solicitudes, y la aplicación se bloqueará hasta que se agote el tiempo de espera. Dichos servidores esperan que analice el encabezado Content-Width y se cierre.

Si la conexión falla, se muestra un mensaje de alerta al usuario en un cuadro de diálogo.

Código

Añadir a AndroidManifest.xml:

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

Y la principal actividad es:

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); 
     } 
    } 
} 

On GitHub with build boilerplate.

También hemos publicado un ejemplo de servidor en Android: https://stackoverflow.com/a/35745834/895245

Probado en Android 5.1.1, Sony Xperia 3 D6643.

Cuestiones relacionadas