@Android:這是一份很詳細的Socket使用攻略


1.網絡基礎

1.1 計算機網絡分層

計算機網絡分為五層:物理層、數據鏈路層、網絡層、運輸層、應用層

計算機網絡

其中:

  • 網絡層:負責根據IP找到目的地址的主機
  • 運輸層:通過端口把數據傳到目的主機的目的進程,來實現進程與進程之間的通信

1.2 端口號(PORT)

端口號規定為16位,即允許一個IP主機有2的16次方65535個不同的端口。其中:

  • 0~1023:分配給系統的端口號 

    我們不可以亂用

  • 1024~49151:登記端口號,主要是讓第三方應用使用

    但是必須在IANA(互聯網數字分配機構)按照規定手續登記,

  • 49152~65535:短暫端口號,是留給客戶進程選擇暫時使用,一個進程使用完就可以供其他進程使用。

在Socket使用時,可以用1024~65535的端口號

1.3 C/S結構

  • 定義:即客戶端/服務器結構,是軟件系統體系結構
  • 作用:充分利用兩端硬件環境的優勢,將任務合理分配到Client端和Server端來實現,降低了系統的通訊開銷。 

    Socket正是使用這種結構建立連接的,一個套接字接客戶端,一個套接字接服務器。


如圖: Socket架構 
可以看出,Socket的使用可以基於TCP或者UDP協議。 

1.4 TCP協議

  • 定義:Transmission Control Protocol,即傳輸控制協議,是一種傳輸層通信協議 

    基於TCP的應用層協議有FTP、Telnet、SMTP、HTTP、POP3與DNS。

  • 特點:面向連接、面向字節流、全雙工通信、可靠

    • 面向連接:指的是要使用TCP傳輸數據,必須先建立TCP連接,傳輸完成后釋放連接,就像打電話一樣必須先撥號建立一條連接,打完后掛機釋放連接。

    • 全雙工通信:即一旦建立了TCP連接,通信雙方可以在任何時候都能發送數據。

    • 可靠的:指的是通過TCP連接傳送的數據,無差錯,不丟失,不重復,並且按序到達。

    • 面向字節流:流,指的是流入到進程或從進程流出的字符序列。簡單來說,雖然有時候要傳輸的數據流太大,TCP報文長度有限制,不能一次傳輸完,要把它分為好幾個數據塊,但是由於可靠性保證,接收方可以按順序接收數據塊然后重新組成分塊之前的數據流,所以TCP看起來就像直接互相傳輸字節流一樣,面向字節流。

  • TCP建立連接 
    必須進行三次握手:若A要與B進行連接,則必須 

    • 第一次握手:建立連接。客戶端發送連接請求報文段,將SYN位置為1,Sequence Number為x;然后,客戶端進入SYN_SEND狀態,等待服務器的確認。即A發送信息給B
    • 第二次握手:服務器收到客戶端的SYN報文段,需要對這個SYN報文段進行確認。即B收到連接信息后向A返回確認信息
    • 第三次握手:客戶端收到服務器的(SYN+ACK)報文段,並向服務器發送ACK報文段。即A收到確認信息后再次向B返回確認連接信息 

    此時,A告訴自己上層連接建立;B收到連接信息后告訴上層連接建立。

TCP三次握手

這樣就完成TCP三次握手 = 一條TCP連接建立完成 = 可以開始發送數據

  1. 三次握手期間任何一次未收到對面回復都要重發。
  2. 最后一個確認報文段發送完畢以后,客戶端和服務器端都進入ESTABLISHED狀態。

為什么TCP建立連接需要三次握手?

答:防止服務器端因為接收了早已失效的連接請求報文從而一直等待客戶端請求,從而浪費資源

  • “已失效的連接請求報文段”的產生在這樣一種情況下:Client發出的第一個連接請求報文段並沒有丟失,而是在某個網絡結點長時間的滯留了,以致延誤到連接釋放以后的某個時間才到達server。
  • 這是一個早已失效的報文段。但Server收到此失效的連接請求報文段后,就誤認為是Client再次發出的一個新的連接請求。
  • 於是就向Client發出確認報文段,同意建立連接。
  • 假設不采用“三次握手”:只要Server發出確認,新的連接就建立了。
  • 由於現在Client並沒有發出建立連接的請求,因此不會向Server發送數據。
  • 但Server卻以為新的運輸連接已經建立,並一直等待Client發來數據。>- 這樣,Server的資源就白白浪費掉了。

采用“三次握手”的辦法可以防止上述現象發生:

  • Client不會向Server的確認發出確認
  • Server由於收不到確認,就知道Client並沒有要求建立連接
  • 所以Server不會等待Client發送數據,資源就沒有被浪費

  • TCP釋放連接 
    TCP釋放連接需要四次揮手過程,現在假設A主動釋放連接:(數據傳輸結束后,通信的雙方都可釋放連接)

    • 第一次揮手:A發送釋放信息到B;(發出去之后,A->B發送數據這條路徑就斷了)
    • 第二次揮手:B收到A的釋放信息之后,回復確認釋放的信息:我同意你的釋放連接請求

    • 第三次揮手:B發送“請求釋放連接“信息給A

    • 第四次揮手:A收到B發送的信息后向B發送確認釋放信息:我同意你的釋放連接請求

      B收到確認信息后就會正式關閉連接; 
      A等待2MSL后依然沒有收到回復,則證明B端已正常關閉,於是A關閉連接

TCp四次握手

為什么TCP釋放連接需要四次揮手?

為了保證雙方都能通知對方“需要釋放連接”,即在釋放連接后都無法接收或發送消息給對方

  • 需要明確的是:TCP是全雙工模式,這意味着是雙向都可以發送、接收的
  • 釋放連接的定義是:雙方都無法接收或發送消息給對方,是雙向的
  • 當主機1發出“釋放連接請求”(FIN報文段)時,只是表示主機1已經沒有數據要發送 / 數據已經全部發送完畢; 
     但是,這個時候主機1還是可以接受來自主機2的數據。
  • 當主機2返回“確認釋放連接”信息(ACK報文段)時,表示它已經知道主機1沒有數據發送了 
    但此時主機2還是可以發送數據給主機1
  • 當主機2也發送了FIN報文段時,即告訴主機1我也沒有數據要發送了 
    此時,主機1和2已經無法進行通信:主機1無法發送數據給主機2,主機2也無法發送數據給主機1,此時,TCP的連接才算釋放
1.5 UDP協議
  • 定義:User Datagram Protocol,即用戶數據報協議,是一種傳輸層通信協議。

    基於UDP的應用層協議有TFTP、SNMP與DNS。

  • 特點:無連接的、不可靠的、面向報文、沒有擁塞控制

    • 無連接的:和TCP要建立連接不同,UDP傳輸數據不需要建立連接,就像寫信,在信封寫上收信人名稱、地址就可以交給郵局發送了,至於能不能送到,就要看郵局的送信能力和送信過程的困難程度了。

    • 不可靠的:因為UDP發出去的數據包發出去就不管了,不管它會不會到達,所以很可能會出現丟包現象,使傳輸的數據出錯。

    • 面向報文:數據報文,就相當於一個數據包,應用層交給UDP多大的數據包,UDP就照樣發送,不會像TCP那樣拆分。

    • 沒有擁塞控制:擁塞,是指到達通信子網中某一部分的分組數量過多,使得該部分網絡來不及處理,以致引起這部分乃至整個網絡性能下降的現象,嚴重時甚至會導致網絡通信業務陷入停頓,即出現死鎖現象,就像交通堵塞一樣。TCP建立連接后如果發送的數據因為信道質量的原因不能到達目的地,它會不斷重發,有可能導致越來越塞,所以需要一個復雜的原理來控制擁塞。而UDP就沒有這個煩惱,發出去就不管了。
  • 應用場景 
    很多的實時應用(如IP電話、實時視頻會議、某些多人同時在線游戲等)要求源主機以很定的速率發送數據,並且允許在網絡發生擁塞時候丟失一些數據,但是要求不能有太大的延時,UDP就剛好適合這種要求。所以說,只有不適合的技術,沒有真正沒用的技術。

1.6 HTTP協議

詳情請看我寫的另外一篇文章你需要了解的HTTP知識都在這里了!


2. Socket定義

  • 即套接字,是一個對 TCP / IP協議進行封裝 的編程調用接口(API)

    1. 即通過Socket,我們才能在Andorid平台上通過 TCP/IP協議進行開發
    2. Socket不是一種協議,而是一個編程調用接口(API),屬於傳輸層(主要解決數據如何在網絡中傳輸)
  • 成對出現,一對套接字:

Socket ={(IP地址1:PORT端口號),(IP地址2:PORT端口號)}
  • 1

3. 原理

Socket的使用類型主要有兩種:

  • 流套接字(streamsocket) :基於 TCP協議,采用 流的方式 提供可靠的字節流服務
  • 數據報套接字(datagramsocket):基於 UDP協議,采用 數據報文 提供數據打包發送的服務

具體原理圖如下:

原理圖

4. Socket 與 Http 對比

  • Socket屬於傳輸層,因為 TCP / IP協議屬於傳輸層,解決的是數據如何在網絡中傳輸的問題
  • HTTP協議 屬於 應用層,解決的是如何包裝數據

由於二者不屬於同一層面,所以本來是沒有可比性的。但隨着發展,默認的Http里封裝了下面幾層的使用,所以才會出現Socket & HTTP協議的對比:(主要是工作方式的不同):

  • Http:采用 請求—響應 方式。

    1. 即建立網絡連接后,當 客戶端 向 服務器 發送請求后,服務器端才能向客戶端返回數據。
    2. 可理解為:是客戶端有需要才進行通信
  • Socket:采用 服務器主動發送數據 的方式

    1. 即建立網絡連接后,服務器可主動發送消息給客戶端,而不需要由客戶端向服務器發送請求
    2. 可理解為:是服務器端有需要才進行通信

5. 使用步驟

  • Socket可基於TCP或者UDP協議,但TCP更加常用
  • 所以下面的使用步驟 & 實例的Socket將基於TCP協議
    // 步驟1:創建客戶端 & 服務器的連接
    // 創建Socket對象 & 指定服務端的IP及端口號
     
    Socket socket = new Socket("192.168.1.32", 1989);
    // 判斷客戶端和服務器是否連接成功
    socket.isConnected());
     
    // 步驟2:客戶端 & 服務器 通信
    // 通信包括:客戶端 接收服務器的數據 & 發送數據 到 服務器
    <-- 操作1:接收服務器的數據 -->
    // 步驟1:創建輸入流對象InputStream
    InputStream is = socket.getInputStream()
     
    // 步驟2:創建輸入流讀取器對象 並傳入輸入流對象
     
    // 該對象作用:獲取服務器返回的數據
     
    InputStreamReader isr = new InputStreamReader(is);
     
    BufferedReader br = new BufferedReader(isr);
     
    // 步驟3:通過輸入流讀取器對象 接收服務器發送過來的數據
     
    br.readLine();
     
    <-- 操作2:發送數據 到 服務器 -->
     
    // 步驟1:從Socket 獲得輸出流對象OutputStream
     
    // 該對象作用:發送數據
     
    OutputStream outputStream = socket.getOutputStream();
     
    // 步驟2:寫入需要發送的數據到輸出流對象中
     
    outputStream.write(("Carson_Ho"+"\n").getBytes("utf-8"));
     
    // 特別注意:數據的結尾加上換行符才可讓服務器端的readline()停止阻塞
     
    // 步驟3:發送數據到服務端
     
    outputStream.flush();
     
    // 步驟3:斷開客戶端 & 服務器 連接
     
    os.close();
     
    // 斷開 客戶端發送到服務器 的連接,即關閉輸出流對象OutputStream
     
    br.close();
     
    // 斷開 服務器發送到客戶端 的連接,即關閉輸入流讀取器對象BufferedReader
     
    socket.close();
     
    // 最終關閉整個Socket連接

     

6. 具體實例

  • 實例 Demo 代碼包括:客戶端 & 服務器
  • 本文着重講解客戶端,服務器僅采用最簡單的寫法進行展示

6.1 客戶端 實現

步驟1:加入網絡權限

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

步驟2:主布局界面設置

包括創建Socket連接、客戶端 & 服務器通信的按鈕

  1.  
    <Button
  2.  
    android:id="@+id/connect"
  3.  
    android:layout_width="match_parent"
  4.  
    android:layout_height="wrap_content"
  5.  
    android:text="connect" />
  6.  
  7. <Button
  8.  
    android:id="@+id/disconnect"
  9.  
    android:layout_width="match_parent"
  10.  
    android:layout_height="wrap_content"
  11.  
    android:text="disconnect" />
  12.  
  13. <TextView
  14.  
    android:id="@+id/receive_message"
  15.  
    android:layout_width="match_parent"
  16.  
    android:layout_height="wrap_content" />
  17.  
  18. <Button
  19.  
    android:id="@+id/Receive"
  20.  
    android:layout_width="match_parent"
  21.  
    android:layout_height="wrap_content"
  22.  
    android:text="Receive from message" />
  23.  
  24. <EditText
  25.  
    android:id="@+id/edit"
  26.  
    android:layout_width="match_parent"
  27.  
    android:layout_height="wrap_content" />
  28.  
     
  29.  
    <Button
  30.  
    android:id="@+id/send"
  31.  
    android:layout_width="match_parent"
  32.  
    android:layout_height="wrap_content"
  33.  
    android:text="send"/>

步驟3:創建Socket連接、客戶端 & 服務器通信

具體請看注釋

MainActivity.java

  1.  
    package scut.carson_ho.socket_carson;
  2.  
  3. import android.os.Bundle;
  4.  
    import android.os.Handler;
  5.  
    import android.os.Message;
  6.  
    import android.support.v7.app.AppCompatActivity;
  7.  
    import android.view.View;
  8.  
    import android.widget.Button;
  9.  
    import android.widget.EditText;
  10.  
    import android.widget.TextView;
  11.  
     
  12.  
    import java.io.BufferedReader;
  13.  
    import java.io.IOException;
  14.  
    import java.io.InputStream;
  15.  
    import java.io.InputStreamReader;
  16.  
    import java.io.OutputStream;
  17.  
    import java.net.Socket;
  18.  
    import java.util.concurrent.ExecutorService;
  19.  
    import java.util.concurrent.Executors;
  20.  
     
  21.  
    public class MainActivity extends AppCompatActivity {
  22.  
     
  23.  
    /**
  24.  
    * 主 變量
  25.  
    */
  26.  
     
  27.  
    // 主線程Handler
  28.  
    // 用於將從服務器獲取的消息顯示出來
  29.  
    private Handler mMainHandler;
  30.  
     
  31.  
    // Socket變量
  32.  
    private Socket socket;
  33.  
     
  34.  
    // 線程池
  35.  
    // 為了方便展示,此處直接采用線程池進行線程管理,而沒有一個個開線程
  36.  
    private ExecutorService mThreadPool;
  37. /**
  38.  
    * 接收服務器消息 變量
  39.  
    */
  40.  
    // 輸入流對象
  41.  
    InputStream is;
  42. // 輸入流讀取器對象
  43.  
    InputStreamReader isr ;
  44.  
    BufferedReader br ;
  45.  
     
  46.  
    // 接收服務器發送過來的消息
  47.  
    String response;
  48.  
     
  49.  
     
  50.  
    /**
  51.  
    * 發送消息到服務器 變量
  52.  
    */
  53.  
    // 輸出流對象
  54.  
    OutputStream outputStream;
  55.  
     
  56.  
    /**
  57.  
    * 按鈕 變量
  58.  
    */
  59.  
     
  60.  
    // 連接 斷開連接 發送數據到服務器 的按鈕變量
  61.  
    private Button btnConnect, btnDisconnect, btnSend;
  62.  
     
  63.  
    // 顯示接收服務器消息 按鈕
  64.  
    private TextView Receive,receive_message;
  65.  
     
  66.  
    // 輸入需要發送的消息 輸入框
  67.  
    private EditText mEdit;
  68.  
     
  69.  
    @Override
  70.  
    protected void onCreate(Bundle savedInstanceState) {
  71.  
    super.onCreate(savedInstanceState);
  72.  
    setContentView(R.layout.activity_main);
  73.  
     
  74.  
    /**
  75.  
    * 初始化操作
  76.  
    */
  77.  
     
  78.  
    // 初始化所有按鈕
  79.  
    btnConnect = (Button) findViewById(R.id.connect);
  80.  
    btnDisconnect = (Button) findViewById(R.id.disconnect);
  81.  
    btnSend = (Button) findViewById(R.id.send);
  82.  
    mEdit = (EditText) findViewById(R.id.edit);
  83.  
    receive_message = (TextView) findViewById(R.id.receive_message);
  84.  
    Receive = (Button) findViewById(R.id.Receive);
  85.  
     
  86.  
    // 初始化線程池
  87.  
    mThreadPool = Executors.newCachedThreadPool();
  88.  
     
  89.  
     
  90.  
    // 實例化主線程,用於更新接收過來的消息
  91.  
    mMainHandler = new Handler() {
  92.  
    @Override
  93.  
    public void handleMessage(Message msg) {
  94.  
    switch (msg.what) {
  95.  
    case 0:
  96.  
    receive_message.setText(response);
  97.  
    break;
  98.  
    }
  99.  
    }
  100.  
    };
  101.  
     
  102.  
     
  103.  
    /**
  104.  
    * 創建客戶端 & 服務器的連接
  105.  
    */
  106.  
    btnConnect.setOnClickListener( new View.OnClickListener() {
  107.  
    @Override
  108.  
    public void onClick(View v) {
  109.  
     
  110.  
    // 利用線程池直接開啟一個線程 & 執行該線程
  111.  
    mThreadPool.execute( new Runnable() {
  112.  
    @Override
  113.  
    public void run() {
  114.  
     
  115.  
    try {
  116.  
     
  117.  
    // 創建Socket對象 & 指定服務端的IP 及 端口號
  118.  
    socket = new Socket("192.168.1.172", 8989);
  119.  
     
  120.  
    // 判斷客戶端和服務器是否連接成功
  121.  
    System.out.println(socket.isConnected());
  122.  
     
  123.  
    } catch (IOException e) {
  124.  
    e.printStackTrace();
  125.  
    }
  126.  
     
  127.  
    }
  128.  
    });
  129.  
     
  130.  
    }
  131.  
    });
  132.  
     
  133.  
    /**
  134.  
    * 接收 服務器消息
  135.  
    */
  136.  
    Receive.setOnClickListener( new View.OnClickListener() {
  137.  
    @Override
  138.  
    public void onClick(View v) {
  139.  
     
  140.  
    // 利用線程池直接開啟一個線程 & 執行該線程
  141.  
    mThreadPool.execute( new Runnable() {
  142.  
    @Override
  143.  
    public void run() {
  144.  
     
  145.  
    try {
  146.  
    // 步驟1:創建輸入流對象InputStream
  147.  
    is = socket.getInputStream();
  148.  
     
  149.  
    // 步驟2:創建輸入流讀取器對象 並傳入輸入流對象
  150.  
    // 該對象作用:獲取服務器返回的數據
  151.  
    isr = new InputStreamReader(is);
  152.  
    br = new BufferedReader(isr);
  153.  
     
  154.  
    // 步驟3:通過輸入流讀取器對象 接收服務器發送過來的數據
  155.  
    response = br.readLine();
  156.  
     
  157.  
    // 步驟4:通知主線程,將接收的消息顯示到界面
  158.  
    Message msg = Message.obtain();
  159.  
    msg.what = 0;
  160.  
    mMainHandler.sendMessage(msg);
  161.  
     
  162.  
    } catch (IOException e) {
  163.  
    e.printStackTrace();
  164.  
    }
  165.  
     
  166.  
    }
  167.  
    });
  168.  
     
  169.  
    }
  170.  
    });
  171.  
     
  172.  
     
  173.  
    /**
  174.  
    * 發送消息 給 服務器
  175.  
    */
  176.  
    btnSend.setOnClickListener( new View.OnClickListener() {
  177.  
    @Override
  178.  
    public void onClick(View v) {
  179.  
     
  180.  
    // 利用線程池直接開啟一個線程 & 執行該線程
  181.  
    mThreadPool.execute( new Runnable() {
  182.  
    @Override
  183.  
    public void run() {
  184.  
     
  185.  
    try {
  186.  
    // 步驟1:從Socket 獲得輸出流對象OutputStream
  187.  
    // 該對象作用:發送數據
  188.  
    outputStream = socket.getOutputStream();
  189.  
     
  190.  
    // 步驟2:寫入需要發送的數據到輸出流對象中
  191.  
    outputStream.write((mEdit.getText().toString()+ "\n").getBytes("utf-8"));
  192.  
    // 特別注意:數據的結尾加上換行符才可讓服務器端的readline()停止阻塞
  193.  
     
  194.  
    // 步驟3:發送數據到服務端
  195.  
    outputStream.flush();
  196.  
     
  197.  
    } catch (IOException e) {
  198.  
    e.printStackTrace();
  199.  
    }
  200.  
     
  201.  
    }
  202.  
    });
  203.  
     
  204.  
    }
  205.  
    });
  206.  
     
  207.  
     
  208.  
    /**
  209.  
    * 斷開客戶端 & 服務器的連接
  210.  
    */
  211.  
    btnDisconnect.setOnClickListener( new View.OnClickListener() {
  212.  
    @Override
  213.  
    public void onClick(View v) {
  214.  
     
  215.  
    try {
  216.  
    // 斷開 客戶端發送到服務器 的連接,即關閉輸出流對象OutputStream
  217.  
    outputStream.close();
  218.  
     
  219.  
    // 斷開 服務器發送到客戶端 的連接,即關閉輸入流讀取器對象BufferedReader
  220.  
    br.close();
  221.  
     
  222.  
    // 最終關閉整個Socket連接
  223.  
    socket.close();
  224.  
     
  225.  
    // 判斷客戶端和服務器是否已經斷開連接
  226.  
    System.out.println(socket.isConnected());
  227.  
     
  228.  
    } catch (IOException e) {
  229.  
    e.printStackTrace();
  230.  
    }
  231.  
     
  232.  
    }
  233.  
    });
  234.  
     
  235.  
     
  236.  
    }
  237.  
    }

6.2 服務器 實現

  • 因本文主要講解客戶端,所以服務器僅僅是為了配合客戶端展示;
  • 為了簡化服務器使用,此處采用Mina框架
  1. 服務器代碼請在eclipse平台運行
  2. 按照我的步驟一步步實現就可以無腦運行了

步驟1:導入Mina

請直接移步到百度網盤:下載鏈接(密碼: q73e)

示意圖

步驟2:創建服務器線程 
TestHandler.java

  1.  
    package mina;
  2.  
    // 導入包
  3.  
     
  4.  
    public class TestHandler extends IoHandlerAdapter {
  5.  
     
  6.  
    @Override
  7.  
    public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
  8.  
    System.out.println( "exceptionCaught: " + cause);
  9.  
    }
  10.  
     
  11.  
    @Override
  12.  
    public void messageReceived(IoSession session, Object message) throws Exception {
  13.  
    System.out.println( "recieve : " + (String) message);
  14.  
    session.write( "hello I am server");
  15.  
    }
  16.  
     
  17.  
    @Override
  18.  
    public void messageSent(IoSession session, Object message) throws Exception {
  19.  
     
  20.  
    }
  21.  
     
  22.  
    @Override
  23.  
    public void sessionClosed(IoSession session) throws Exception {
  24.  
    System.out.println( "sessionClosed");
  25.  
    }
  26.  
     
  27.  
    @Override
  28.  
    public void sessionOpened(IoSession session) throws Exception {
  29.  
    System.out.println( "sessionOpen");
  30.  
    }
  31.  
     
  32.  
    @Override
  33.  
    public void sessionIdle(IoSession session, IdleStatus status) throws Exception {
  34.  
    }
  35.  
     
  36.  
    }

步驟3:創建服務器主代碼 
TestHandler.java

  1.  
    package mina;
  2.  
     
  3.  
    import java.io.IOException;
  4.  
    import java.net.InetSocketAddress;
  5.  
     
  6.  
    import org.apache.mina.filter.codec.ProtocolCodecFilter;
  7.  
    import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
  8.  
    import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
  9.  
     
  10.  
    public class TestServer {
  11.  
    public static void main(String[] args) {
  12.  
    NioSocketAcceptor acceptor = null;
  13.  
    try {
  14.  
    acceptor = new NioSocketAcceptor();
  15.  
    acceptor .setHandler(new TestHandler());
  16.  
    acceptor .getFilterChain().addLast("mFilter", new ProtocolCodecFilter(new TextLineCodecFactory()));
  17.  
    acceptor .setReuseAddress(true);
  18.  
    acceptor .bind(new InetSocketAddress(8989));
  19.  
    } catch (Exception e) {
  20.  
    e .printStackTrace();
  21.  
    }
  22.  
    }
  23.  
    }

至此,客戶端 & 服務器的代碼均實現完畢。


6.3 測試結果

  • 點擊 Connect按鈕: 連接成功

示意圖

  • 輸入發送的消息,點擊 Send 按鈕發送

示意圖

  • 服務器接收到客戶端發送的消息

示意圖

  • 點擊 Receive From Message按鈕,客戶端 讀取 服務器返回的消息

示意圖

  • 點擊 DisConnect按鈕,斷開 客戶端 & 服務器的連接

客戶端示意圖

服務器示意圖


6.4 源碼地址

Carson_Ho的Github地址:Socket具體實例


7. 總結

  • 相信大家已經非常了解關於Socket的使用
  • 下面我將繼續對 Android 的網絡編程進行講解,有興趣可以繼續關注Carson_Ho的安卓開發筆記

 

 

 

 

Android之Socket的基於UDP傳輸

 

接收方創建步驟:

1.  創建一個DatagramSocket對象,並指定監聽的端口號

DatagramSocket socket = new  DatagramSocket (4567);

2. 創建一個byte數組用於接收

byte data[] = new byte[1024];

3. 創建一個空的DatagramPackage對象

 DatagramPackage package = new DatagramPackage(data , data.length);

4. 使用receive方法接收發送方所發送的數據,同時這也是一個阻塞的方法

socket.receive(package); 

5. 得到發送過來的數據

new String(package.getData() , package.getOffset() , package.getLength());

 

發送方創建步驟:

1.  創建一個DatagramSocket對象

DatagramSocket socket = new  DatagramSocket (4567);

2.  創建一個 InetAddress , 相當於是地址

InetAddress serverAddress = InetAddress.getByName("想要發送到的那個IP地址"); 

3.  這是隨意發送一個數據

String str = "hello";

4.  轉為byte類型

byte data[] = str.getBytes();

  5.  創建一個DatagramPacket 對象,並指定要講這個數據包發送到網絡當中的哪個地址,以及端口號

DatagramPacket  package = new DatagramPacket (data , data.length , serverAddress , 4567);

6.  調用DatagramSocket對象的send方法 發送數據

 socket . send(package);

 http://www.cnblogs.com/lee0oo0/archive/2012/04/04/2431907.html
 

一、有的手機不能直接接收UDP包,可能是手機廠商在定制Rom的時候把這個功能給關掉了。

1、可先在oncreate()方法里面實例化一個WifiManager.MulticastLock 對象lock;具體如下:

WifiManager manager = (WifiManager) this
                .getSystemService(Context.WIFI_SERVICE);
WifiManager.MulticastLock lock= manager.createMulticastLock("test wifi");

2、在調用廣播發送、接收報文之前先調用lock.acquire()方法;

3、用完之后及時調用lock.release()釋放資源,否決多次調用lock.acquire()方法,程序可能會崩,詳情請見

Caused by: java.lang.UnsupportedOperationException: Exceeded maximum number of wifi locks

注;記得在配置文件里面添加如下權限:

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

經過這樣處理后,多數手機都能正常發送接收到廣播報文。

本小點轉載自Android手機接收不到UDP報文

二、在UDP通信中,android端發送UDP廣播包沒有問題。至於接收的話,有時候不能接收到包。

在UDP通信中,Android端發送UDP廣播包沒有問題。至於接收的話,有時候不能接收到包。但是如果UDP包中指定了目標主機的地址的話,那么android端就能正常接收。

下面上一段代碼,大家可用這段代碼進行測試

1、在一個Service里面,我們創建一個線程

復制代碼
public void onCreate() {//用於創建線程
        WifiManager manager = (WifiManager) this
                .getSystemService(Context.WIFI_SERVICE);
        udphelper = new UdpHelper(manager);
        
        //傳遞WifiManager對象,以便在UDPHelper類里面使用MulticastLock
        udphelper.addObserver(MsgReceiveService.this);
        tReceived = new Thread(udphelper);
        tReceived.start();
        super.onCreate();
    }
復制代碼

2、弄一個UDP幫助類,這個類主要用於發送和接收數據

復制代碼
package com.example.com.ihome.bang.util;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Observable;
import com.example.com.ihome.bang.tool.SendThread;
import android.net.wifi.WifiManager;
import android.util.Log;

/**
 * 
 * UdpHelper幫助類
 * 
 * @author 陳喆榕
 * 
 */
public class UdpHelper  implements Runnable {
    public    Boolean IsThreadDisable = false;//指示監聽線程是否終止
    private static WifiManager.MulticastLock lock;
    InetAddress mInetAddress;
    public UdpHelper(WifiManager manager) {
         this.lock= manager.createMulticastLock("UDPwifi"); 
    }
    public void StartListen()  {
        // UDP服務器監聽的端口
        Integer port = 8903;
        // 接收的字節大小,客戶端發送的數據不能超過這個大小
        byte[] message = new byte[100];
        try {
            // 建立Socket連接
            DatagramSocket datagramSocket = new DatagramSocket(port);
            datagramSocket.setBroadcast(true);
            DatagramPacket datagramPacket = new DatagramPacket(message,
                    message.length);
            try {
                while (!IsThreadDisable) {
                    // 准備接收數據
                    Log.d("UDP Demo", "准備接受");
                     this.lock.acquire();
                     
                    datagramSocket.receive(datagramPacket);
                    String strMsg=new String(datagramPacket.getData()).trim();
                    Log.d("UDP Demo", datagramPacket.getAddress()
                            .getHostAddress().toString()
                            + ":" +strMsg );this.lock.release();
                }
            } catch (IOException e) {//IOException
                e.printStackTrace();
            }
        } catch (SocketException e) {
            e.printStackTrace();
        }

    }
    public static void send(String message) {
        message = (message == null ? "Hello IdeasAndroid!" : message);
        int server_port = 8904;
        Log.d("UDP Demo", "UDP發送數據:"+message);
        DatagramSocket s = null;
        try {
            s = new DatagramSocket();
        } catch (SocketException e) {
            e.printStackTrace();
        }
        InetAddress local = null;
        try {
            local = InetAddress.getByName("255.255.255.255");
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        int msg_length = message.length();
        byte[] messageByte = message.getBytes();
        DatagramPacket p = new DatagramPacket(messageByte, msg_length, local,
                server_port);
        try {

            s.send(p);
            s.close();
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
            StartListen();
    }
}
復制代碼

最后, 添加個人的實例 :

復制代碼
package com.example.android.helloactivity;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import com.example.andriod.udp.UDPClient;
import com.example.andriod.udp.UDPServer;

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

public class MainAct extends Activity {
    EditText msg_et = null;
    Button send_bt = null;
    TextView info_tv = null;
    private static final String TAG ="MainAct";
    private UDPClient client;
    private String sendInfo;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.udp_test);
        msg_et = (EditText) findViewById(R.id.edit_msg);
        send_bt = (Button) findViewById(R.id.send_bt);
        info_tv = (TextView) findViewById(R.id.receive_msg);
        info_tv.setText("source");
        // 開啟服務器
        ExecutorService exec = Executors.newCachedThreadPool();
        UDPServer server = new UDPServer();
        exec.execute(server);
        // 發送消息
        send_bt.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {

                myThread1 thread = new myThread1("22");
                new Thread(thread).start();

            }
        });
    }

    final Handler mHander = new Handler() {

        public void handleMessage(Message msg) {
            // TODO Auto-generated method stub
            //super.handleMessage(msg);
            info_tv.setText(sendInfo);
            
            Log.d(TAG, "client.send()=");
        }
    };

    class myThread1 implements Runnable {

        private String threadName;

        public myThread1(String name) {
            this.threadName = name;
        }

        public void run() {
            Log.d(TAG, "MyThread  execu"+msg_et.getText().toString());
            client = new UDPClient(msg_et.getText().toString());
            sendInfo=client.send();
            
            Message msg = mHander.obtainMessage();
            msg.arg1=1;
            mHander.sendMessage(msg);
            Log.d(TAG, "client.send()=");
        }
    }
}
復制代碼
復制代碼
package com.example.andriod.udp;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

import android.util.Log;

public class UDPServer implements Runnable {
    private static final int PORT = 6000;
    private byte[] msg = new byte[2048];
    private boolean life = true;

    public UDPServer() {
    }

    public boolean isLife() {
        return life;
    }

    public void setLife(boolean life) {
        this.life = life;
    }

    @Override
    public void run() {
        DatagramSocket dSocket = null;
        DatagramPacket dPacket = new DatagramPacket(msg, msg.length);
        try {
            dSocket = new DatagramSocket(PORT);
            while (life) {
                try {
                    dSocket.receive(dPacket);
                    Log.d("tian msg sever received",
                            new String(dPacket.getData(), dPacket.getOffset(),
                                    dPacket.getLength())
                                    + "dPacket.getLength()="
                                    + dPacket.getLength());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        } catch (SocketException e) {
            e.printStackTrace();
        }
    }
}
復制代碼
復制代碼
package com.example.andriod.udp;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;

import android.util.Log;

public class UDPClient {
    private static final int SERVER_PORT = 6000;
    private DatagramSocket dSocket = null;
    private String msg;

    public UDPClient(String msg) {
        super();
        this.msg = msg;
    }

    public String send() {
        StringBuilder sb = new StringBuilder();
        InetAddress local = null;
        try {
            local = InetAddress.getByName("localhost"); // 本機測試
            sb.append("已找到服務器,連接中...").append("/n");
        } catch (UnknownHostException e) {
            sb.append("未找到服務器.").append("/n");
            e.printStackTrace();
        }
        try {
            dSocket = new DatagramSocket(); // 注意此處要先在配置文件里設置權限,否則會拋權限不足的異常
            sb.append("正在連接服務器...").append("/n");
        } catch (SocketException e) {
            e.printStackTrace();
            sb.append("服務器連接失敗.").append("/n");
        }
        int msg_len = msg == null ? 0 : msg.length();
        DatagramPacket dPacket = new DatagramPacket(msg.getBytes(), msg_len,
                local, SERVER_PORT);
        try {
            dSocket.send(dPacket);
            Log.d("tian", "msg=="+msg+"dpackage="+dPacket.getData()+"dPacket.leng="+dPacket.getLength());
            sb.append("消息發送成功!").append("/n");
        } catch (IOException e) {
            e.printStackTrace();
            sb.append("消息發送失敗.").append("/n");
        }
        dSocket.close();
        return sb.toString();
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM