亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

全部開發者教程

Android 入門教程

菜單類控件
菜單:Menu
并發編程
多線程

Socket 網絡接口

大家在學習計算機網絡的時候一定學習過 TCP/IP 協議以及最經典的 OSI 七層結構,簡單的回憶一下這 7 層結構:
從下到上依次是:

  • 物理層
  • 數據鏈路層
  • 互聯層
  • 網絡層
  • 會話層
  • 表示層
  • 應用層

osi

TCP/IP 協議對這 7 層了做一點精簡,變為了 4 層結構:

tcp/ip

我們現在的網路通信模型基本上都是按照這個層級來分發的,當然也包括了 Android 中的網絡模型,簡單回顧一下基礎之后,開始學習今天的網絡接口——Socket。

1. TCP 與 UDP

在模型之下,又衍生出兩種經典的傳輸層協議——TCP 和 UDP,我們分別看看這兩個協議。

1.1 TCP 協議

TCP 協議是傳輸控制協議是一個面向連接的協議,所謂的面向連接表示的是通信雙方在傳輸數據之前,需要搭建一個專用的通信線路,并且在結束的時候需要將其關閉。在有了這條專用線路作保障之后,就能準確無誤的將數據傳遞給對方,所以 TCP 是一種可靠的通信方式,它能夠準確知道對方是否成功接收了消息。

1.2 UDP 協議

UDP 又叫用戶數據包協議,相對于 TCP,它是一種面向無連接的協議,也就是通信雙方在交換數據之前無需建立一條專用通道,當然在通信結束前也無需釋放通道。這樣一來,通信的效率非常高,但缺點是我們無法確定發出去的消息對方是否能夠準確收到,所以它是一個輕量不可靠的通信方式。

以上的定義描述了二者主要的差異,更多細致的內容可以參考其他資料。

2. Socket 的基礎概念

Socket 翻譯成中文是“套接字”,它是應用層和傳輸層中間的一個抽象中間件,它封裝了底層的 TCP / IP 協議族,并向上暴露 API 給應用層,從而向上屏蔽底層協議細節。 所以 Socket 可以作為底層網絡門面來讓我們不在拘泥于復雜的底層傳輸協議,而將更多的重心放在自己的功能開發上。 下圖是 Socket 的一個整體工作原理:

socket

每個 Socket 對象都對應著一個 IP 地址和一個端口號,用來標識互聯網上的唯一目的地址,然后就可以通過 TCP 或者 UDP 將數據發送給對方。

3. Socket 的工作流程

首先看看 Socket 的工作流程圖:

socketworkflow

3.1 Server 監聽端口

首先由服務端初始化 Socket 接口,然后綁定并監聽自己的端口號,此時服務端會阻塞式等待客戶端連接。

3.2 Client 連接端口

客戶端可以在需要發送消息的時候初始化 Socket 接口,設置服務端的 IP 地址和端口號就可以連接到服務器,接著在連接成功之后,雙方就完成了連接的建立。

3.3 數據傳遞和連接斷開

在連接建立好之后,客戶端或者服務端雙方就可以開始發送數據了,在數據傳輸完畢之后,雙方任一方都可以申請斷開連接,此后通道關閉,數據傳輸完成。

4. Socket 的基本用法

  1. 首先創建一個 ServerSocket 對象
public ServerSocket(int port) throws IOException

創建 Socket 服務只需要傳入一個端口號即可。

  1. 阻塞監聽端口
public Socket accept() throws IOException

通過調用accept()方法,server 便會阻塞式的監聽第 1 步設置的端口號,等待客戶端連接。

  1. 客戶度創建 Socket 對象
public Socket(String host, int port)  throws UnknownHostException, IOException

和前面說的一樣,創建 Socket 需要兩個必要的參數:

  • **host:**服務端的網絡地址
  • **port:**服務端開放的對應端口號
  1. 獲取輸入輸出流
socket.getInputStream();
socket.getOutputStream();

服務端和客戶端通過這兩個方法分別拿到輸入輸出流,從而向流里面寫消息或者從流里面讀數據完成數據的發送和接收。

  1. 斷開連接
    在數據傳輸完畢之后,通過close()方法斷開連接,完成本次通信。

5. Socket 網絡通信示例

本節在電腦上通過 Java 搭建一個 Socket Server,然后手機作為 Client 來連接 Server。這個需要保證手機和電腦在同一個 Wifi 網段下。

5.1 搭建 Server

大家在學習 Android 之前,應該都有學過純 Java 程序,可以通過javac命令來將 java 代碼編譯成字節碼,然后通過java命令運行,當然也可以在 Android Studio 里面直接運行帶main()方法的 Java 程序。
首先在工程里新建一個 Java Library,注意不是 Android Library。

libsocket

然后創建一個帶main()函數的類,在里面完成 Socket 的創建和初始化:

package com.emercy.libsocket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Enumeration;

public class SocketServer {
    public static void main(String[] args) throws IOException {
        // 1. Create ServerSocket
        ServerSocket serverSocket = new ServerSocket(8888);
        // 2. monitoring
        System.out.println("server start listen : " + getIpAddress());

        Socket socket = serverSocket.accept();
        System.out.println("accept");

        // 3. input stream
        InputStream is = socket.getInputStream();
        InputStreamReader reader = new InputStreamReader(is);
        BufferedReader br = new BufferedReader(reader);
        String content;
        StringBuffer sb = new StringBuffer();
        while ((content = br.readLine()) != null) {
            sb.append(content);
        }

        System.out.println("server receiver: " + sb.toString());

        socket.shutdownInput();

        br.close();
        reader.close();
        is.close();

        socket.close();
        serverSocket.close();


        System.out.println("server receiver: ");

    }

    public static String getIpAddress() {
        try {
            Enumeration<NetworkInterface> allNetInterfaces = NetworkInterface.getNetworkInterfaces();
            InetAddress ip = null;
            while (allNetInterfaces.hasMoreElements()) {
                NetworkInterface netInterface = (NetworkInterface) allNetInterfaces.nextElement();
                if (netInterface.isLoopback() || netInterface.isVirtual() || !netInterface.isUp()) {
                    continue;
                } else {
                    Enumeration<InetAddress> addresses = netInterface.getInetAddresses();
                    while (addresses.hasMoreElements()) {
                        ip = addresses.nextElement();
                        if (ip instanceof Inet4Address) {
                            return ip.getHostAddress();
                        }
                    }
                }
            }
        } catch (Exception e) {
            System.err.println("IP地址獲取失敗" + e.toString());
        }
        return "";
    }
}

運行之后,開始等待連接并打印當前設備的 IP 地址,這里要特別注意,有些教程里面會用InetAddress.getLocalHost()這種方式獲取 IP 返回“127.0.0.1”,這個是 local host,在跨設備通信中無法使用。

5.2 Client 連接

接下來編寫 Android 程序,xml 里面只放置一個 Button 用于觸發連接,這里就不列出來了。點擊 button 之后按照上面的步驟來依次創建 Socket,設置 IP 和 port,接著獲取輸入輸出流即可。
**注意:**網絡請求屬于耗時操作, Android 要求網絡請求必須在子線程中執行,所以我們需要在onClick()中 new 一個 thread。


package com.emercy.myapplication;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        //1. Create Client
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Socket socket;
                        try {
                            //1. create socket
                            socket = new Socket("10.64.210.51", 12345);
                            //2. output stream
                            OutputStream os = socket.getOutputStream();
                            //3. Send data
                            os.write("Hello world".getBytes());
                            System.out.println("send message");
                            os.flush();

                            socket.shutdownOutput();

                            os.close();
                            socket.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            }
        });
    }
}

首先運行 Server,接著打開 App,點擊“連接”Button 就可以和 Server 通信了。

6. 小結

本節學習了一個底層的網絡接口——Socket,它內部實現了計算機網絡中最基礎的協議和模型,可以讓我們不再關心那些繁瑣復雜的協議規則,從而輕松的將數據傳輸出去。首先給大家回顧了 IOS 和 TCP / IP 的幾層模型,然后工作在傳輸層的兩個重要通信協議 TCP / UDP,接著按照步驟來分別創建 SocketServer 和 Socket,通過 InputStream 和 OutputStream 進行通信。