2011-02-27 62 views
35

我在Android上找到的大多數網絡套接字示例都是單向的。我需要雙向數據流的解決方案。我最終了解了AsyncTask。這個例子展示瞭如何從套接字獲取數據並將數據發回給它。由於正在接收數據的套接字的阻塞性質,阻塞需要在UI線程以外的線程中運行。示例:使用AsyncTask的Android雙向網絡套接字

爲了舉例,此代碼連接到Web服務器。按下「Start AsyncTask」按鈕將打開套接字。一旦套接字打開,Web服務器就會等待請求。按下「發送消息」按鈕將向服務器發送請求。來自服務器的任何響應都將顯示在TextView中。在http的情況下,一旦發送完所有數據,Web服務器將與客戶端斷開連接。對於其他TCP數據流,連接將保持直到一方斷開。

截圖:

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 \佈局\ 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

您能否提供位於Android設備上的服務器端部分? – dothedos 2012-05-15 21:27:30

回答

3

您的SendDataToNetworkdoInBackground()不在同一個線程上運行。有可能在套接字準備好之前SendDataToNetwork開始發送數據。

爲了避免這一切只是使用SendDataToNetwork保存數據和信號到後臺線程,數據準備好發送。

由於用戶可能會多次按下按鈕,而舊數據仍在發送時,您應該在NetworkTask中同步隊列。然後:

  1. 後臺線程設置套接字連接,然後進入睡眠狀態(通過wait())。
  2. 開啓按鈕,SendDataToNetwork將數據添加到隊列並喚醒後臺線程(通過notify())。
  3. 當後臺線程喚醒時,它首先檢查finish標誌。如果設置,則關閉連接並退出。如果沒有,則從隊列讀取數據,將其發送到網絡並返回到睡眠狀態。
  4. 你應該有finish()方法,它設置一個finish標誌(原子變量,如布爾值)並喚醒後臺線程。這是正常退出後臺線程的一種方式。

看看線程同步是怎麼做的:http://www.jchq.net/tutorial/07_03Tut.htm

+0

但是,如果後臺線程在wait()命令上進入睡眠狀態,它將如何從套接字中獲取傳入數據? – 2011-02-27 21:36:20

+0

是的,你是對的。然後,使用您的解決方案並阻止發送數據,直到套接字準備就緒(=禁用按鈕)或創建一個單獨的線程進行發送。 – 2011-02-27 21:41:48

+1

使用「try {」和「if(nsocket。isConnected()){「SendDataToNetwork內? – 2011-02-27 22:00:40

3

SendDataToNetwork任務運行主UI線程,這意味着它會崩潰蜂窩或更高的應用由於NetworkOnMainThreadException致命的異常。下面是我的SendDataToNetwork樣子來避免這個問題:

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

更多的互動例如

類似於OP的,但你可以控制主機,端口和消息+有一個彈出錯誤通知,如果連接失敗。

enter image description here

用法1:

  • 獲得Android和一個LAN
  • Linux桌面在桌面找到的IP與ifconfig
  • 運行netcat -l 12345上的終端
  • 上Android,填寫桌面的IP地址
  • 點擊接觸服務器
  • 在終端上,輸入的答覆,並擊中Ctrl + D它出現在output:

用法2

    • 主機名google.com
    • 端口80
    • 內容:"GET/HTTP/1.1\r\nHost: google.com\r\n\r\n"

    請注意,某些HTTP服務器在應答預計到進一步的請求後將不會關閉,並且應用程序將掛起直到它們超時。這些服務器希望你解析Content-Width標題並關閉自己。

    如果連接失敗,則會在對話框中向用戶顯示警報消息。

    代碼

    添加到AndroidManifest.xml

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

    和主要活動是:

    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

    我還張貼在一個Android服務器例如:https://stackoverflow.com/a/35745834/895245

    測試在Android 5.1.1,索尼的Xperia 3 D6643。