Android的IPC機制(七)—— Socket的原理簡析與使用

綜述

  在前面的幾篇文章中,我們介紹了許多在Android中有關進程間通信的方式,但都是在一個設備上進行的進程間通信,而這時候我們兩個應用在不同的設備上的時候,在這個時候我們就不能通過前方介紹的那些方法來解決了。但是我們通過網絡進行通信來處理這個問題。今天就來介紹一下Android中網絡通信的其中一種方式——Socket。Socket翻譯爲中文爲套接字,而現在套接字也成爲了操作系統中的一部分。下面我們就來看一下如何使用這個套接字的。

TCP/IP介紹

  這裏有一點需要說明一下,在Internet所使用的各種協議中,最重要的和最著名的就是TCP和IP兩個協議。而我們現在經常提到的TCP/IP並不一定單指TCP和IP這兩個具體的協議,而往往表示Internet所使用的整個TCP/IP協議族。

TCP/IP的體系結構

  TCP/IP協議可以爲各式各樣的應用提供服務,同時TCP/IP協議也允許IP協議在各式各樣的網絡構成的互聯網上運行。TCP/IP協議是一個四層的體系結構,它包含應用層、傳輸層、網絡層、網絡接口層。在傳輸層中主要使用的有兩種協議:TCP(Transmission Control Protocol,傳輸控制協議)和UDP(User Datagram Protocol,用戶數據報協議)。下面是TCP/IP體系結構示意圖。
這裏寫圖片描述

傳輸控制協議TCP

  TCP是TCP/IP體系中非常複雜的一個協議,在這裏我們簡單看一下TCP協議的特點,對於TCP協議的詳細內容,可以查看一些計算機網絡的相關書籍。
1. TCP是面向連接傳輸協議,也就是說在我們的應用程序在使用TCP協議之前,必須先建立起TCP連接。在傳送數據完畢後,必須釋放已經建立的TCP連接。就像我們打電話一樣,打電話之前首先需要撥號進行建立連接,等通話結束後再掛斷釋放連接。
2. 每一條TCP連接只能有兩個端點,每一條TCP連接只能是點對點的。
3. TCP提供了一個可靠交付的服務,也就是說通過TCP連接傳送的數據,無差錯,不丟失,不重複,並且按序到達。
4. TCP提供全雙工通信,它允許通信雙方的應用進程在任何時候都能夠發送數據。
5. TCP通信中是面向字節流的,其中的“流”指的是流入到進程或從進程流出的字節序列。

用戶數據報協議UDP

  用戶數據報協議UDP只是在IP的數據服務上增加了很少的一點功能(複用、分用的功能以及差錯檢測的功能)。在這裏簡單說一下UDP的特點。
1. UDP是無連接,也就是在發送數據之前是不需要建立連接的,也就減少了開銷和發送數據之間的延時。
2. UDP它只能是盡最大努力地交付,也就是不能夠保證可靠交付。
3. UDP它是面向報文的。發送方的UDP對應用程序交下來的報文,再添加首部後就向下交付給IP層。
4. UDP它沒有擁塞控制,也就是說在網絡出現擁塞的情況下不會使源主機的發送速率降低。
5. UDP支持一對一,一對多,多對一和多對多的交互通信。

Socket在TCP/IP中的作用

  Socket是工作於TCP/IP協議中應用層和傳輸層之間的一種抽象(不屬於應用層也不屬於傳輸層)。在Android系統中,它可以分爲流套接字(streamsocket)和數據報套接字(datagramsocket)。而Socket中的流套接字將TCP協議作爲其端對端協議,提供了一個可信賴的字節流服務;數據報套接字使用UDP協議,提供數據打包發送服務。
  在網絡編程的時候,我們經常把Socket作爲應用進程和傳輸層協議之間的接口。在下面圖中表示了這樣一個概念。在圖中我們假定了運輸層使用的是TCP協議(如果使用的是UDP協議,情況也是類似的,只是UDP是無連接的通信的兩端依然可以用兩個套接字來標誌)。並且現在套接字已經成爲操作系統內核的一部分。
這裏寫圖片描述
  不過有一點我們要注意,在套接字以上的進程是受應用程序控制的,而在套接字以下的的傳輸層協議軟件則是由計算機操作系統控制。因此,只要我們的應用程序使用TCP/IP協議進行通信,它就必須通過Socket與操作系統交互並請求服務。從這裏可以看出來,我們對Socket以上的應用進程具有完全的控制,但對Socket以下的傳輸層卻只有很少的控制。例如,我們可以選擇傳輸層協議(TCP或UDP)和一些傳輸層的參數(如最大緩存空間和最大報文長度)。

Socket使用案例

  在這裏我們選擇傳輸層協議爲TCP協議,也就是我們將使用流套接字作爲例子進行舉例說明。現在我們現在做一個聊天室功能。在這裏我們創建兩個應用程序,分別運行在兩個不同的設備上。首先看一下效果圖。

演示

    

客戶端代碼

package com.example.ljd.socketclient;

import android.annotation.SuppressLint;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.sql.Date;
import java.text.SimpleDateFormat;

import butterknife.Bind;
import butterknife.ButterKnife;
import butterknife.OnClick;

public class MainActivity extends AppCompatActivity{

    private static final int RECEIVE_NEW_MESSAGE = 1;
    private static final int SOCKET_CONNECT_SUCCESS = 2;
    private static final int SOCKET_CONNECT_FAIL = 3;

    @Bind(R.id.msg_edit_text)
    EditText mMessageEditText;

    @Bind(R.id.show_linear)
    LinearLayout mShowLinear;

    private PrintWriter mPrintWriter;
    private Socket mClientSocket;
    private boolean mIsConnectServer = false;

    @SuppressLint("HandlerLeak")
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {

                case RECEIVE_NEW_MESSAGE:
                    TextView textView = new TextView(MainActivity.this);
                    textView.setText((String)msg.obj);
                    mShowLinear.addView(textView);
                    break;

                case SOCKET_CONNECT_SUCCESS:
                    Toast.makeText(MainActivity.this,"連接服務端成功",Toast.LENGTH_SHORT).show();
                    break;

                case SOCKET_CONNECT_FAIL:
                    Toast.makeText(MainActivity.this,"連接服務端失敗,請重新嘗試",Toast.LENGTH_SHORT).show();
                    break;
                default:
                    break;

            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);

    }

    @Override
    protected void onDestroy() {
        ButterKnife.unbind(this);
        disConnectServer();
        super.onDestroy();
    }

    @OnClick({R.id.send_btn,R.id.connect_btn,R.id.disconnect_btn})
    public void onClickButton(View v) {

        switch (v.getId()){
            case R.id.send_btn:
                sendMessageToServer();
                break;
            case R.id.connect_btn:
                new Thread() {
                    @Override
                    public void run() {
                        connectServer();
                    }
                }.start();
                break;
            case R.id.disconnect_btn:
                Toast.makeText(MainActivity.this,"已經斷開連接",Toast.LENGTH_SHORT).show();
                disConnectServer();
                break;
        }

    }

    private String getTime(long time) {
        return new SimpleDateFormat("(HH:mm:ss)").format(new Date(time));
    }

    private void connectServer() {

        if (mIsConnectServer)
            return;

        int count = 0;
        while (mClientSocket == null) {
            try {
                mClientSocket = new Socket("10.10.14.160", 8088);
                mPrintWriter = new PrintWriter(new BufferedWriter(
                        new OutputStreamWriter(mClientSocket.getOutputStream())), true);
                mIsConnectServer = true;
                mHandler.obtainMessage(SOCKET_CONNECT_SUCCESS).sendToTarget();
            } catch (IOException e) {
                SystemClock.sleep(1000);
                count++;
                if (count == 5){
                    mHandler.obtainMessage(SOCKET_CONNECT_FAIL).sendToTarget();
                    return;
                }
            }
        }

        try {
            // 接收服務器端的消息
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(
                    mClientSocket.getInputStream()));
            while (!MainActivity.this.isFinishing()) {
                String msg = bufferedReader.readLine();
                if (msg != null) {
                    String time = getTime(System.currentTimeMillis());
                    final String showedMsg = "server " + time + ":" + msg;
                    mHandler.obtainMessage(RECEIVE_NEW_MESSAGE, showedMsg)
                            .sendToTarget();
                }
            }
            mPrintWriter.close();
            bufferedReader.close();
            mClientSocket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void disConnectServer(){
        mIsConnectServer = false;
        if (mClientSocket != null) {
            try {
                mClientSocket.shutdownInput();
                mClientSocket.close();
                mClientSocket = null;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void sendMessageToServer(){
        if (!mIsConnectServer){
            Toast.makeText(this,"沒有連接上服務端,請重新連接",Toast.LENGTH_SHORT).show();
            return;
        }
        final String msg = mMessageEditText.getText().toString();
        if (!TextUtils.isEmpty(msg) && mPrintWriter != null) {
            mPrintWriter.println(msg);
            mMessageEditText.setText("");
            String time = getTime(System.currentTimeMillis());
            final String showedMsg = "client " + time + ":" + msg;
            TextView textView = new TextView(this);
            textView.setText(showedMsg);
            mShowLinear.addView(textView);
        }
    }
}

客戶端佈局代碼

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="5dp"
    >

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" >

        <LinearLayout
            android:id="@+id/show_linear"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical" >
        </LinearLayout>
    </ScrollView>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <EditText
            android:id="@+id/msg_edit_text"
            android:layout_width="0dp"
            android:layout_gravity="bottom"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            />

        <Button
            android:id="@+id/send_btn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="發送" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <Button
            android:id="@+id/connect_btn"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="wrap_content"
            android:text="連接"/>

        <Button
            android:id="@+id/disconnect_btn"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="wrap_content"
            android:text="斷開連接"/>
    </LinearLayout>
</LinearLayout>

服務端代碼 

package com.example.ljd.socketserver;

import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.sql.Date;
import java.text.SimpleDateFormat;


import butterknife.Bind;
import butterknife.ButterKnife;
import butterknife.OnClick;

public class MainActivity extends AppCompatActivity{

    @Bind(R.id.show_linear)
    LinearLayout mShowLinear;

    @Bind(R.id.msg_edit_text)
    EditText mMessageEditText;

    private ServerSocket mServerSocket;
    private PrintWriter mPrintWriter;
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == 0){
                TextView textView = new TextView(MainActivity.this);
                textView.setText((String)msg.obj);
                mShowLinear.addView(textView);
            }
            super.handleMessage(msg);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        try {
            mServerSocket = new ServerSocket(8088);
        } catch (IOException e) {
            e.printStackTrace();
        }
        new Thread(new AcceptClient()).start();
    }


    @Override
    public void onDestroy() {
        ButterKnife.unbind(this);
        if (mServerSocket != null){
            try {
                mServerSocket.close();
                mServerSocket = null;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        super.onDestroy();
    }

    @OnClick(R.id.send_btn)
    public void onClickButton() {
        final String msg = mMessageEditText.getText().toString();
        if (!TextUtils.isEmpty(msg) && mPrintWriter != null) {
            //將消息寫入到流中
            mPrintWriter.println(msg);
            mMessageEditText.setText("");
            String time = getTime(System.currentTimeMillis());
            final String showedMsg = "server " + time + ":" + msg;
            TextView textView = new TextView(this);
            textView.setText(showedMsg);
            mShowLinear.addView(textView);
        }
    }

    private String getTime(long time) {
        return new SimpleDateFormat("(HH:mm:ss)").format(new Date(time));
    }

    class AcceptClient implements Runnable{

        @Override
        public void run() {
            try {
                Socket clientSocket = null;
                while (clientSocket == null){
                    clientSocket = mServerSocket.accept();
                    mPrintWriter = new PrintWriter(new BufferedWriter(
                            new OutputStreamWriter(clientSocket.getOutputStream())), true);
                }
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(
                        clientSocket.getInputStream()));
                while (!MainActivity.this.isFinishing()) {
                    //讀取客戶端發來的消息
                    String msg = bufferedReader.readLine();
                    if (msg != null) {
                        String time = getTime(System.currentTimeMillis());
                        final String showedMsg = "client " + time + ":" + msg;
                        mHandler.obtainMessage(0, showedMsg)
                                .sendToTarget();
                    }
                }
                bufferedReader.close();
                clientSocket.close();
                mPrintWriter.close();

            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

服務端佈局代碼

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="5dp"
    >

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" >

        <LinearLayout
            android:id="@+id/show_linear"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical" >
        </LinearLayout>
    </ScrollView>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <EditText
            android:id="@+id/msg_edit_text"
            android:layout_width="0dp"
            android:layout_gravity="bottom"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            />

        <Button
            android:id="@+id/send_btn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="發送" />
    </LinearLayout>

</LinearLayout>

添加權限

  在客戶端與服務端應用中我們還需要添加一些權限

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

總結

  在網絡通信中我們除了socket我們最常用的還有一種方式那就是http通信,而http連接採用的是”請求—響應方式“,也就是說只有當客戶端發出請求時,服務端才能夠向客戶端返回數據。在這裏我們就不在詳細介紹。對於Android的IPC機制就說到這裏了,當然還有其他方式可以進行跨進程通信,我們可以自行研究了。

源碼下載

發佈了33 篇原創文章 · 獲贊 195 · 訪問量 36萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章