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.
- Google Cloud Messaging: los mensajes no se reciben a veces hasta que se cambia el estado de la red
- HttpUrlConnection de Android: Publicar Multipart
- Cómo probar descargar y subir velocidad wifi / 3g?
- ¿Puede Wi-Fi o la CPU ir a dormir cuando la pantalla está encendida?
- Android comprobar la conexión a Internet
Captura de pantalla:
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 } }
- Android desconocidoHostException Facebook SDK
- Receptor de transmisión para comprobar la conexión a Internet en la aplicación Android
- Comparación de las bibliotecas de redes Android: OkHTTP, Retrofit y Volley
- Rehacer la solicitud anterior cuando el primero falla con 401 No autorizado
- ¿Cómo obtengo IP_ADDRESS en formato IPV4?
- Enviando solicitud de formulario múltiple con varios parámetros?
- Retrofit 2.x: Cabecera de registro para la solicitud y la respuesta
- Android 2.2 MediaPlayer alternativa para HTTPS y transmisión en directo
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:
- Hilo de fondo establece la conexión de socket y luego se va a dormir (a través de wait ()).
- Al pulsar el botón,
SendDataToNetwork
agrega datos a la cola y despierta el hilo de fondo (a través denotify()
). - 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. - Debe tener el método
finish()
que establece un flag definish
(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ó.
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
hostgoogle.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.