Android夸進(jìn)程通信機(jī)制七:使用 Socket進(jìn)行進(jìn)程間通信


Android夸進(jìn)程通信機(jī)制系列:
Android夸進(jìn)程通信機(jī)制一:多進(jìn)程簡(jiǎn)介
Android夸進(jìn)程通信機(jī)制二:Parcel 與 Parcelable
Android夸進(jìn)程通信機(jī)制三:Messenger與Message
Android夸進(jìn)程通信機(jī)制四:使用 Bundle進(jìn)行進(jìn)程間通信
Android夸進(jìn)程通信機(jī)制五:使用文件共享進(jìn)行進(jìn)程間通信
Android夸進(jìn)程通信機(jī)制六:使用ContentProvider進(jìn)行進(jìn)程間通信
Android夸進(jìn)程通信機(jī)制七:使用 Socket進(jìn)行進(jìn)程間通信
Android夸進(jìn)程通信機(jī)制八:使用 AIDL進(jìn)行進(jìn)程間通信
Android夸進(jìn)程通信機(jī)制九:AIDL深入了解
...


一、前言

我們知道,在Android中,實(shí)現(xiàn)進(jìn)程間的通信,歸納起來有共享文件、binder和Socket,其中Binder包含多種方式,如Messenger、bundle、contentProvider、AIDL。除了Binder,Android還支持Socket,通過Socket也可以實(shí)現(xiàn)任意兩個(gè)終端之間的通信,當(dāng)然一個(gè)設(shè)備上的兩個(gè)進(jìn)程之間通過Socket通信自然也是可以的。

前幾節(jié),我們一起學(xué)習(xí)了共享文件方式和binder方式,這一節(jié),我們來學(xué)習(xí)一下使用另一種方式進(jìn)行進(jìn)程間的通信-Socket。

二、什么是Socket?

Socket的英文原義是“孔”或“插座”。作為BSD UNIX的進(jìn)程通信機(jī)制,取后一種意思。通常也稱作"套接字",用于描述IP地址和端口,是一個(gè)通信鏈的句柄,可以用來實(shí)現(xiàn)不同虛擬機(jī)或不同計(jì)算機(jī)之間的通信。
網(wǎng)絡(luò)上的兩個(gè)程序通過一個(gè)雙向的通信連接實(shí)現(xiàn)數(shù)據(jù)的交換,這個(gè)連接的一端稱為一個(gè)socket。

Socket 也稱為“套接字”,是網(wǎng)絡(luò)通信中的概念,它分為流式套接字和用戶數(shù)據(jù)報(bào)套接字兩種,分別對(duì)應(yīng)網(wǎng)絡(luò)的傳輸控制層中的 TCP 和 UDP 協(xié)議,TCP 協(xié)議是面向連接的協(xié)議,提供穩(wěn)定的雙向功能,TCP 鏈接的建立經(jīng)過“三次握手”才能完成,為了提供穩(wěn)定的數(shù)據(jù)傳輸功能,其本身提供了超時(shí)重傳機(jī)制,因此具有很高的穩(wěn)定性;而 UDP 是無連接的,提供不穩(wěn)定的單向連接功能,當(dāng)然 UDP 也可以實(shí)現(xiàn)雙向通信功能。在性能上,UDP 具有更好的效率,其缺點(diǎn)是不能保證數(shù)據(jù)一定能正確傳輸。尤其是在網(wǎng)絡(luò)擁堵的情況下。接下來我們演示一個(gè)跨進(jìn)程聊天程序,兩個(gè)進(jìn)程可以通過 Socket 來實(shí)現(xiàn)信息的傳輸,Socket 本身可以支持傳輸任意字節(jié)流的。

三、使用Socket進(jìn)行IPC

1、注意點(diǎn)

使用 Socket 來進(jìn)行通信,有兩點(diǎn)需要意點(diǎn)

    1. 需要聲明權(quán)限:
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
  • 2.不鞥在主線程中訪問網(wǎng)絡(luò)

如果鞥在主線程中訪問網(wǎng)絡(luò),會(huì)導(dǎo)致無法再 Android 4.0 及其以上的設(shè)備中運(yùn)行,會(huì)拋出如下異常:

android.os.NetwworkOnMainThreadException。

2. 舉例實(shí)現(xiàn)

我們通過一個(gè)簡(jiǎn)單的聊天程序,達(dá)到使用Socket方式進(jìn)行進(jìn)程間通信的效果。

目標(biāo)

下面開始我們的聊天程序,比較簡(jiǎn)單,首先在遠(yuǎn)程 Socket 建立一個(gè) TCP 服務(wù),然后在主界面中鏈接 TCP 服務(wù),連接上了以后,就可以給服務(wù)端發(fā)消息。

對(duì)于我們發(fā)送的每一條文本信息,服務(wù)端都會(huì)隨機(jī)的回應(yīng)我們一句話。為了更好的展示 Socket 的工作機(jī)制,在服務(wù)端我們需要處理下,使其能夠和多個(gè)客戶端同時(shí)建立鏈接并相應(yīng)。

服務(wù)端

先看一下服務(wù)端設(shè)計(jì),當(dāng) Service 啟動(dòng)時(shí),會(huì)在線程中建立 TCP 服務(wù),這里監(jiān)聽的是 8080 端口,然后就可以等待客戶端的連接請(qǐng)求。當(dāng)客戶端連接時(shí),就會(huì)生成一個(gè)新的 Socket,通過每次新創(chuàng)建的 Socket 就可以分別和不同的客戶端通信了。服務(wù)端每收到一次客戶端的信息就會(huì)包裝一下收到的信息再回復(fù)給客戶端。當(dāng)客戶端連接斷開時(shí),服務(wù)端也會(huì)相應(yīng)的關(guān)閉對(duì)應(yīng)的 Socket 并結(jié)束通話線程,服務(wù)端代碼如下:

/**
 * Copyright (C), 2015-2019, 雨紛紛工作室
 * FileName: IPCSocketService
 * Author: yufenfen
 * Email: ybyj1314@126.com
 * Date: 2016/4/5 10:11 AM
 * Description: IPC之使用Socket方式的服務(wù)端
 */
package yb.demo.myProcesses.useSocket;

import ...

/**
 * @ClassName: IPCSocketService
 * @Description: IPC之使用Socket方式的服務(wù)端
 * @Author: yufenfen
 * @Date: 2016/4/5 10:11 AM
 */

public class IPCSocketService extends Service {

    //變量
    ...
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case Socket_Status_ing:
                    Toast.makeText(IPCSocketService.this.getApplicationContext(),
                            "建立TCP服務(wù)器……………………,端口:" + SERVERSOCKET_PORT,
                            Toast.LENGTH_LONG
                    ).show();
                    break;
                case Socket_Status_succee:
                    Toast.makeText(IPCSocketService.this.getApplicationContext(),
                            "成功連接服務(wù)器,端口:" + SERVERSOCKET_PORT,
                            Toast.LENGTH_LONG
                    ).show();
                    break;
                case Socket_Status_fail:
                    String error = msg.getData().getString("error");
                    Toast.makeText(IPCSocketService.this.getApplicationContext(),
                            "連接服務(wù)器失敗,端口:" + SERVERSOCKET_PORT+ ", " + error,
                            Toast.LENGTH_LONG
                    ).show();
                    break;
                default:
                    break;
            }
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();

        Toast.makeText(IPCSocketService.this.getApplicationContext(),
                "建立TCP服務(wù)器,端口:" + SERVERSOCKET_PORT,
                Toast.LENGTH_LONG
        ).show();
      
        //注意在新線程中操作
        new Thread(){
            @Override
            public void run() {
                createSocket();
            }
        }.start();
    }

   ····
    public void createSocket() {
        try {
            mServer = new ServerSocket(SERVERSOCKET_PORT);
            mExecutorService = Executors.newCachedThreadPool();

            Socket client = null;
            while (mIsServiceDestoryed) {
                client = mServer.accept();         //每接受到一個(gè)新Socket連接請(qǐng)求,就會(huì)新建一個(gè)Thread去處理與其之間的通信

                mHandler.sendEmptyMessage(Socket_Status_ing);

                mList.add(client);
                mExecutorService.execute(new Service(client));
            }
        } catch (Exception e) {
            e.printStackTrace();

            Message msg = mHandler.obtainMessage(Socket_Status_fail);
            Bundle data = new Bundle();
            data.putString("error", e.toString());
            mHandler.sendMessage(msg);
        }
    }

    class Service implements Runnable {
        private Socket socket;
        private BufferedReader in = null;
        private PrintWriter printWriter=null;
        private String receiveMsg;
        private String sendMsg;


        public Service(Socket socket) {                         //這段代碼對(duì)應(yīng)步驟三
            this.socket = socket;
            try {
                printWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter( socket.getOutputStream(), "UTF-8")), true);
                in = new BufferedReader(new InputStreamReader(
                        socket.getInputStream(),"UTF-8"));
                printWriter.println("成功連接服務(wù)器"+"(服務(wù)器發(fā)送)");

                mHandler.sendEmptyMessage(Socket_Status_succee);

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

        }

        @Override
        public void run() {
            try {
                while (true) {                                   //循環(huán)接收、讀取 Client 端發(fā)送過來的信息
                    if ((receiveMsg = in.readLine())!=null) {
                        System.out.println("receiveMsg:"+receiveMsg);
                        if (receiveMsg.equals("0")) {
                            System.out.println("客戶端請(qǐng)求斷開連接");
                            printWriter.println("服務(wù)端斷開連接"+"(服務(wù)器發(fā)送)");
                            mList.remove(socket);
                            in.close();
                            socket.close();                         //接受 Client 端的斷開連接請(qǐng)求,并關(guān)閉 Socket 連接
                            break;
                        } else {
                            sendMsg = "我已接收:" + receiveMsg + "(服務(wù)器發(fā)送)";
                            printWriter.println(sendMsg);           //向 Client 端反饋、發(fā)送信息
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

服務(wù)端使用線程池實(shí)現(xiàn)多客戶端連接,server.accept() 表示等待客戶端連接,當(dāng)有客戶端連接時(shí)新建一個(gè)線程去處理。

客戶端

接著看一下客戶端,客戶端 Activity 啟動(dòng)時(shí),會(huì)在 onCreate 中開啟一個(gè)線程去鏈接服務(wù)端的 Socket,至于為什么用線程在前邊已經(jīng)做了介紹。

/**
 * Copyright (C), 2015-2019, 雨紛紛工作室
 * FileName: IPCSocketClientActivity
 * Author: yufenfen
 * Email: ybyj1314@126.com
 * Date: 2016/4/5 5:01 PM
 * Description: IPC之Socket方式,客戶端
 */
package yb.demo.myProcesses.useSocket;

import ...

/**
 * @ClassName: IPCSocketClientActivity
 * @Description: IPC之Socket方式,客戶端
 * @Author: yufenfen
 * @Date: 2016/4/5 5:01 PM
 */

public class IPCSocketClientActivity extends Activity {
    private static final String TAG = "IPCSocketClientActivity";

    private EditText mEditText;
    private TextView mTextView;
    private PrintWriter printWriter;
    private BufferedReader in;
    private ExecutorService mExecutorService = null;
    private String receiveMsg;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.ipcsocketclient_activity_layout);
        mEditText = (EditText) findViewById(R.id.send_msg);
        mTextView = (TextView) findViewById(R.id.socket_msg);
        mExecutorService = Executors.newCachedThreadPool();
    }

    public void connect(View view) {
        mExecutorService.execute(new connectService());  //在一個(gè)新的線程中請(qǐng)求 Socket 連接
    }

    public void send(View view) {
        String sendMsg = mEditText.getText().toString();
        mExecutorService.execute(new sendService(sendMsg));
    }

    public void disconnect(View view) {
        mExecutorService.execute(new sendService("0"));
    }

    private class sendService implements Runnable {
        private String msg;

        sendService(String msg) {
            this.msg = msg;
        }

        @Override
        public void run() {
            printWriter.println(this.msg);
        }
    }

    private class connectService implements Runnable {
        @Override
        public void run() {
            try {
                Socket socket = new Socket(IPCSocketService.SERVERSOCKET_HOST, IPCSocketService.SERVERSOCKET_PORT);      
                socket.setSoTimeout(60000);
                printWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter( 
                        socket.getOutputStream(), "UTF-8")), true);
                in = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
                receiveMsg();
            } catch (Exception e) {
                Log.e(TAG, ("connectService:" + e.getMessage()));   //如果Socket對(duì)象獲取失敗,即連接建立失敗,會(huì)走到這段邏輯

                e.printStackTrace();
            }
        }
    }

    private void receiveMsg() {
        try {
            while (true) {                                    
                if ((receiveMsg = in.readLine()) != null) {
                    Log.d(TAG, "receiveMsg:" + receiveMsg);
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            mTextView.setText(receiveMsg + "\n\n" + mTextView.getText());
                        }
                    });
                }
            }
        } catch (IOException e) {
            Log.e(TAG, "receiveMsg: ");
            e.printStackTrace();
        }
    }
}

服務(wù)端連接成功后,就可以和服務(wù)端進(jìn)行通信了。
接著和發(fā)送消息的過程,這個(gè)就簡(jiǎn)單了,這里不再詳細(xì)說明.

四、結(jié)語

上述就是通過 Socket 來進(jìn)行進(jìn)程間通信的示例,這里采用 TCP 套接字實(shí)現(xiàn)利用Sock在進(jìn)程間的通信,還可以采用 UDP 套接字,這里就不做詳細(xì)舉例了。

另外,我們知道兩個(gè)進(jìn)程如果要進(jìn)行通訊最基本的一個(gè)前提就是能夠唯一的標(biāo)識(shí)一個(gè)進(jìn)程,在本地進(jìn)程通訊中我們可以使用 PID 來唯一標(biāo)識(shí)一個(gè)進(jìn)程,但 PID 只在本地是唯一的,網(wǎng)絡(luò)中兩個(gè)進(jìn)程 PID 沖突幾率很大,這時(shí)我們就需要通過其他手段來唯一標(biāo)識(shí)網(wǎng)絡(luò)中的進(jìn)程了,我們知道 IP 層的 ip 地址可以唯一標(biāo)示主機(jī),而 TCP 層協(xié)議和端口號(hào)結(jié)合就可以唯一標(biāo)示主機(jī)的一個(gè)進(jìn)程了。

我們示例的IP用的本地主機(jī)

public static final String SERVERSOCKET_HOST = "localhost";

你可以替換成其他可見的IP地址。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 參考:http://www.2cto.com/net/201611/569006.html TCP HTTP UD...
    F麥子閱讀 3,067評(píng)論 0 14
  • 網(wǎng)絡(luò)編程 一.楔子 你現(xiàn)在已經(jīng)學(xué)會(huì)了寫python代碼,假如你寫了兩個(gè)python文件a.py和b.py,分別去運(yùn)...
    go以恒閱讀 2,238評(píng)論 0 6
  • 第一章 引言和網(wǎng)絡(luò)編程基礎(chǔ)知識(shí) 1.1 分別簡(jiǎn)述OSI參考模型和TCP/IP模型,并闡述他們之間的對(duì)應(yīng)關(guān)系 1.2...
    V0W閱讀 5,611評(píng)論 0 9
  • 一、網(wǎng)絡(luò)各個(gè)協(xié)議:TCP/IP、SOCKET、HTTP等 網(wǎng)絡(luò)七層由下往上分別為物理層、數(shù)據(jù)鏈路層、網(wǎng)絡(luò)層、傳輸層...
    杯水救車薪閱讀 2,358評(píng)論 0 17
  • 第二張又一個(gè)錯(cuò)字“沒” 祝:女生節(jié)快樂。 ps:今天概率論的活動(dòng)是“有求必應(yīng)屋”,我當(dāng)了一次“救世主”,這個(gè)女孩子...
    傅五歲閱讀 381評(píng)論 18 1

友情鏈接更多精彩內(nèi)容