Android夸进程通信机制七:使用 Socket进行进程间通信


Android夸进程通信机制系列:
Android夸进程通信机制一:多进程简介
Android夸进程通信机制二:Parcel 与 Parcelable
Android夸进程通信机制三:Messenger与Message
Android夸进程通信机制四:使用 Bundle进行进程间通信
Android夸进程通信机制五:使用文件共享进行进程间通信
Android夸进程通信机制六:使用ContentProvider进行进程间通信
Android夸进程通信机制七:使用 Socket进行进程间通信
Android夸进程通信机制八:使用 AIDL进行进程间通信
Android夸进程通信机制九:AIDL深入了解
...


一、前言

我们知道,在Android中,实现进程间的通信,归纳起来有共享文件、binder和Socket,其中Binder包含多种方式,如Messenger、bundle、contentProvider、AIDL。除了Binder,Android还支持Socket,通过Socket也可以实现任意两个终端之间的通信,当然一个设备上的两个进程之间通过Socket通信自然也是可以的。

前几节,我们一起学习了共享文件方式和binder方式,这一节,我们来学习一下使用另一种方式进行进程间的通信-Socket。

二、什么是Socket?

Socket的英文原义是“孔”或“插座”。作为BSD UNIX的进程通信机制,取后一种意思。通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。
网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。

Socket 也称为“套接字”,是网络通信中的概念,它分为流式套接字和用户数据报套接字两种,分别对应网络的传输控制层中的 TCP 和 UDP 协议,TCP 协议是面向连接的协议,提供稳定的双向功能,TCP 链接的建立经过“三次握手”才能完成,为了提供稳定的数据传输功能,其本身提供了超时重传机制,因此具有很高的稳定性;而 UDP 是无连接的,提供不稳定的单向连接功能,当然 UDP 也可以实现双向通信功能。在性能上,UDP 具有更好的效率,其缺点是不能保证数据一定能正确传输。尤其是在网络拥堵的情况下。接下来我们演示一个跨进程聊天程序,两个进程可以通过 Socket 来实现信息的传输,Socket 本身可以支持传输任意字节流的。

三、使用Socket进行IPC

1、注意点

使用 Socket 来进行通信,有两点需要意点

    1. 需要声明权限:
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
  • 2.不鞥在主线程中访问网络

如果鞥在主线程中访问网络,会导致无法再 Android 4.0 及其以上的设备中运行,会抛出如下异常:

android.os.NetwworkOnMainThreadException。

2. 举例实现

我们通过一个简单的聊天程序,达到使用Socket方式进行进程间通信的效果。

目标

下面开始我们的聊天程序,比较简单,首先在远程 Socket 建立一个 TCP 服务,然后在主界面中链接 TCP 服务,连接上了以后,就可以给服务端发消息。

对于我们发送的每一条文本信息,服务端都会随机的回应我们一句话。为了更好的展示 Socket 的工作机制,在服务端我们需要处理下,使其能够和多个客户端同时建立链接并相应。

服务端

先看一下服务端设计,当 Service 启动时,会在线程中建立 TCP 服务,这里监听的是 8080 端口,然后就可以等待客户端的连接请求。当客户端连接时,就会生成一个新的 Socket,通过每次新创建的 Socket 就可以分别和不同的客户端通信了。服务端每收到一次客户端的信息就会包装一下收到的信息再回复给客户端。当客户端连接断开时,服务端也会相应的关闭对应的 Socket 并结束通话线程,服务端代码如下:

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

import ...

/**
 * @ClassName: IPCSocketService
 * @Description: IPC之使用Socket方式的服务端
 * @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服务器……………………,端口:" + SERVERSOCKET_PORT,
                            Toast.LENGTH_LONG
                    ).show();
                    break;
                case Socket_Status_succee:
                    Toast.makeText(IPCSocketService.this.getApplicationContext(),
                            "成功连接服务器,端口:" + SERVERSOCKET_PORT,
                            Toast.LENGTH_LONG
                    ).show();
                    break;
                case Socket_Status_fail:
                    String error = msg.getData().getString("error");
                    Toast.makeText(IPCSocketService.this.getApplicationContext(),
                            "连接服务器失败,端口:" + SERVERSOCKET_PORT+ ", " + error,
                            Toast.LENGTH_LONG
                    ).show();
                    break;
                default:
                    break;
            }
        }
    };

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

        Toast.makeText(IPCSocketService.this.getApplicationContext(),
                "建立TCP服务器,端口:" + 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();         //每接受到一个新Socket连接请求,就会新建一个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) {                         //这段代码对应步骤三
            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("成功连接服务器"+"(服务器发送)");

                mHandler.sendEmptyMessage(Socket_Status_succee);

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

        }

        @Override
        public void run() {
            try {
                while (true) {                                   //循环接收、读取 Client 端发送过来的信息
                    if ((receiveMsg = in.readLine())!=null) {
                        System.out.println("receiveMsg:"+receiveMsg);
                        if (receiveMsg.equals("0")) {
                            System.out.println("客户端请求断开连接");
                            printWriter.println("服务端断开连接"+"(服务器发送)");
                            mList.remove(socket);
                            in.close();
                            socket.close();                         //接受 Client 端的断开连接请求,并关闭 Socket 连接
                            break;
                        } else {
                            sendMsg = "我已接收:" + receiveMsg + "(服务器发送)";
                            printWriter.println(sendMsg);           //向 Client 端反馈、发送信息
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

服务端使用线程池实现多客户端连接,server.accept() 表示等待客户端连接,当有客户端连接时新建一个线程去处理。

客户端

接着看一下客户端,客户端 Activity 启动时,会在 onCreate 中开启一个线程去链接服务端的 Socket,至于为什么用线程在前边已经做了介绍。

/**
 * 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());  //在一个新的线程中请求 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对象获取失败,即连接建立失败,会走到这段逻辑

                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();
        }
    }
}

服务端连接成功后,就可以和服务端进行通信了。
接着和发送消息的过程,这个就简单了,这里不再详细说明.

四、结语

上述就是通过 Socket 来进行进程间通信的示例,这里采用 TCP 套接字实现利用Sock在进程间的通信,还可以采用 UDP 套接字,这里就不做详细举例了。

另外,我们知道两个进程如果要进行通讯最基本的一个前提就是能够唯一的标识一个进程,在本地进程通讯中我们可以使用 PID 来唯一标识一个进程,但 PID 只在本地是唯一的,网络中两个进程 PID 冲突几率很大,这时我们就需要通过其他手段来唯一标识网络中的进程了,我们知道 IP 层的 ip 地址可以唯一标示主机,而 TCP 层协议和端口号结合就可以唯一标示主机的一个进程了。

我们示例的IP用的本地主机

public static final String SERVERSOCKET_HOST = "localhost";

你可以替换成其他可见的IP地址。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,417评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,921评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,850评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,945评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,069评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,188评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,239评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,994评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,409评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,735评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,898评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,578评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,205评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,916评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,156评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,722评论 2 363
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,781评论 2 351

推荐阅读更多精彩内容

  • 参考:http://www.2cto.com/net/201611/569006.html TCP HTTP UD...
    F麦子阅读 2,945评论 0 14
  • 网络编程 一.楔子 你现在已经学会了写python代码,假如你写了两个python文件a.py和b.py,分别去运...
    go以恒阅读 2,003评论 0 6
  • 第一章 引言和网络编程基础知识 1.1 分别简述OSI参考模型和TCP/IP模型,并阐述他们之间的对应关系 1.2...
    V0W阅读 5,312评论 0 9
  • 一、网络各个协议:TCP/IP、SOCKET、HTTP等 网络七层由下往上分别为物理层、数据链路层、网络层、传输层...
    杯水救车薪阅读 2,262评论 0 17
  • 第二张又一个错字“没” 祝:女生节快乐。 ps:今天概率论的活动是“有求必应屋”,我当了一次“救世主”,这个女孩子...
    傅五岁阅读 313评论 18 1