Ejemplo: socket de red bidireccional de Android utilizando AsyncTask

La mayoría de los ejemplos de socket de red que encontré para Android eran un solo direccional. Necesitaba una solución para un flujo de datos bidireccional. Finalmente aprendí de la AsyncTask. Este ejemplo muestra cómo obtener datos de un socket y enviar los datos de nuevo a él. Debido a la naturaleza de bloqueo de un socket que está recibiendo datos, ese bloqueo debe ejecutarse en un subproceso distinto del subproceso de interfaz de usuario.

Por ejemplo, este código se conecta a un servidor web. Al presionar 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á hasta que un lado se desconecte.

Captura de pantalla:

Captura de pantalla de 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 } } 

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

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

Dado que existe la posibilidad de que el usuario puede pulsar el botón varias veces, mientras que los datos antiguos siguen siendo enviados, debe tener cola sincronizada dentro de NetworkTask. Entonces:

  1. Hilo de fondo establece la conexión de socket y luego se va a dormir (a través de wait ()).
  2. Al pulsar el botón, SendDataToNetwork agrega datos a la cola y despierta el hilo de fondo (a través de notify() ).
  3. Cuando se enciende el subproceso de fondo, comprueba primero el indicador de finish . Si se establece, cierra las conexiones y sale. Si no, lee datos de la cola, lo envía a la red y vuelve a dormir.
  4. Debe tener el método finish() que establece un flag de finish (variable atómica, como boolean) y despierta el hilo de fondo. Esta es una manera de salir con gracia del subproceso de fondo.

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

La tarea SendDataToNetwork ejecuta en el subproceso ui principal, lo que significa que se bloqueará un Honeycomb o una aplicación superior debido a la excepción NetworkOnMainThreadException Fatal. SendDataToNetwork es lo que parece mi SendDataToNetwork 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; } 

Ejemplo más interactivo

Similar a los OP, pero puede controlar el host, el puerto y el mensaje + hay una notificación de error emergente si la conexión falló.

Introduzca aquí la descripción de la imagen

Uso 1:

  • Conseguir Android y un escritorio Linux en una LAN
  • Encuentra la IP del escritorio con ifconfig
  • Ejecute netcat -l 12345 en un terminal
  • En Android, rellena el IP del escritorio
  • Haga clic en el servidor de contacto
  • En el terminal, escriba la respuesta y pulse Ctrl + D
  • Aparece en la output: sección

Uso 2:

  • google.com host google.com
  • Puerto 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 que la respuesta espere otras solicitudes y la aplicación se bloqueará hasta que se agoten. Tales servidores esperan que analiza el encabezado Content-Width y cierre usted mismo.

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

Código

Añadir a AndroidManifest.xml :

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

Y la actividad principal 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); } } } 

En GitHub con la construcción cliché .

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

Probado en Android 5.1.1, Sony Xperia 3 D6643.

  • Retrofit 2 no puede cargar un archivo con dos parámetros adicionales de cadena separados
  • Ejecución de WebView en segundo plano
  • CONNECTIVITY_CHANGE obsoleta en el objetivo de Android N
  • ConectividadManager getActiveNetworkInfo excepción de puntero nulo
  • Android Volley Library: ¿Cómo enviar una imagen al servidor?
  • ¿Por qué URLConnection tiempo de espera después de 6 + minutos en lugar de 5 segundos?
  • ¿Cómo personalizar la conversión JSON del cuerpo de la respuesta en Retrofit?
  • Oyente de red Android
  • Cómo determinar si el tipo de red es 2G, 3G o 4G
  • ¿Cómo obtener el nombre del operador que está conectado a Internet en el teléfono dual sim android?
  • Eventos enviados por el servidor en Android
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.