android进程间通讯(3)--使用socket

android进程间通讯(3)–使用Socket

前言:本文记录android进程间通讯的另一种通讯方式–Socket。Socket也称“套接字”,是网络通讯中的概念,它分为流式套接字和用户数据报套接字两种,分别对应网络传输层的TCP和UDP协议。TCP协议是面向连接的协议,提供稳定的双向通讯功能,TCP连接的建立需要经过“三次握手”,具有很高的稳定性。UDP是无连接的,提供不稳定的单向通讯功能,当然UDP也可以实现双向通讯,UDP效率更高,但是不能保证数据一定能正确传输,存在丢包的问题。

一.Socket通讯模型

由于Socket可以使用TCP和UDP两种协议实现通讯,所以其基本通讯模型如图(图凑合看吧):


image

因为Socket是C/S结构,所以应用进程A和应用进程B一个作为服务器一个作为客户端,通过Socket实现双向通讯。比如进程A作为服务器,进程B作为客户端,Socket通过IP地址和端口发送数据经过网络解析,最终传输到客户端进程B。

二.Socket使用

下面将通过具体的项目来演示使用Socket来进行进程间通讯。使用Socket通讯,不仅可以实现同一台设备上的不同进程通讯,同时能实现不同设备间的进程通讯,因为这是基于网络的通讯。由于TCP协议比较常用,下面使用TCP协议通过Socket建立连接实现进程间通讯。

1.创建服务器

创建一个服务器的单例管理类,用来管理服务器的创建启动,接收客户端消息,发送消息至客户端以及断开连接等。具体代码如下:

public class SocketServerManager {
private static SocketServerManager socketServerManager;
private ServerSocket serverSocket;
private List<Socket> mClientList = new ArrayList<Socket>();
private ExecutorService mExecutors = null; // 线程池对象
private SocketServerManager(){

}
public static SocketServerManager getInstance(){
    if(socketServerManager==null){
       synchronized (SocketServerManager.class){
           if(socketServerManager==null){
               socketServerManager=new SocketServerManager();
           }
       }
    }
    return socketServerManager;
}

public void startSocketServer(){
    new Thread(new Runnable() {
        @Override
        public void run() {
            startServerSync();
        }
    }).start();
}

private void startServerSync(){
    try {
        Log.i("socket_hdc "," server start");
        serverSocket = new ServerSocket(9002);
        serverSocket.getInetAddress().getHostAddress();
        mExecutors = Executors.newCachedThreadPool(); // 创建线程池
        Socket client = null;
        /*
         * 用死循环等待多个客户端的连接,连接一个就启动一个线程进行管理
         */
        while (true) {
            client = serverSocket.accept();
            Log.i("socket_hdc "," server get socket");
            // 把客户端放入集合中
            mClientList.add(client);
            mExecutors.execute(new SocketHandle(client)); // 启动一个线程,用以守候从客户端发来的消息
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

class SocketHandle implements Runnable {
    private Socket socket;
    private BufferedReader bufferedReader = null;
    private BufferedWriter bufferedWriter = null;
    private String message = "";

    public SocketHandle(Socket socket) {
        this.socket = socket;
        try {
            bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));// 获得输入流对象
            bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));// 创建输出流对象
            // 客户端只要一连到服务器,便发送连接成功的信息
            message = "服务器地址:" + this.socket.getInetAddress();
            this.sendMessage(message);
            message = "当前连接的客户端总数:" + mClientList.size();
            this.sendMessage(message);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    @Override
    public void run() {
        try {
            while (true) {
                if ((message = bufferedReader.readLine()) != null) {
                    if(message.equals("quit")){
                        closeSocket();
                        break;
                    }
                    // 接收客户端发过来的信息message,然后转发给客户端。
                    message = "服务器收到 " + ":" + message;
                    this.sendMessage(message);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void closeSocket() throws IOException {
        mClientList.remove(socket);
        bufferedReader.close();
        bufferedWriter.close();
        message = "主机:" + socket.getInetAddress() + "关闭连接\n目前在线:" + mClientList.size();
        socket.close();
        this.sendMessage(message);
    }

    public void sendMessage(String msg) {
        try {
            Log.i("socket_hdc"," server send msg "+msg);

            bufferedWriter.write(msg);
            bufferedWriter.newLine();
            bufferedWriter.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2.创建客户端

创建一个客户端的单例管理类,用来管理客户的创建连接,接收服务器发送的消息,发送消息至服务器以及断开连接等。具体代码如下:

public class SocketManager {
private static SocketManager socketManager;
private int port=9002;
private Socket socket;
private ExecutorService executorService;
private BufferedReader bufferedReader = null;
private BufferedWriter bufferedWriter = null;
private SocketManager(){
    executorService= Executors.newCachedThreadPool();
}
public static SocketManager getInstance(){
    if(socketManager==null){
        synchronized (SocketManager.class){
            if(socketManager==null){
                socketManager=new SocketManager();
            }
        }
    }
    return socketManager;
}
public void connectSocket(Context context, final Handler handler){
    Thread thread =new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Log.i("socket_hdc","connect start");
                socket =new Socket(DeviceInfoUtil.getIpAddress(),port);

                Log.i("socket_hdc"," socket "+(socket==null));

                bufferedReader =new BufferedReader(new InputStreamReader(socket.getInputStream()));// 创建输入流对象
                bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));// 创建输出流对象
                while (true) {//死循环守护,监控服务器发来的消息
                    if (!socket.isClosed()) {//如果服务器没有关闭
                        if (socket.isConnected()) {//连接正常
                            if (!socket.isInputShutdown()) {//如果输入流没有断开
                                String getLine;
                                if ((getLine = bufferedReader.readLine()) != null) {//读取接收的信息
                                    getLine += "\n";
                                    Message message=new Message();
                                    message.obj=getLine;
                                    Log.i("socket_hdc",getLine);
                                    message.what=MainActivity.SOCKET_MSG;
                                    handler.sendMessage(message);//通知UI更新
                                } else {

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

public void sendMessage(final String msg) {
    if(executorService!=null&&socket!=null){
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    Log.i("socket_hdc"," send msg "+msg);
                    bufferedWriter.write(msg);
                    bufferedWriter.newLine();
                    bufferedWriter.flush();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

public void closeSocket() throws IOException {
    bufferedReader.close();
    bufferedWriter.close();
    if(socket!=null){
        socket.close();
    }
}

3.搭建主界面

在MainActivity中的布局界面大致如下:


image

各个按钮的点击事件如下代码所示:

 @Override
public void onClick(View view) {
    switch (view.getId()){
        case R.id.socket_server_start_btn://--启动服务器,即启动一个Service,创建SocketServer
            Intent intent =new Intent(this,SocketServerService.class);
            startService(intent);
            break;
        case R.id.socket_conn_btn://--创建间客户端Socket,建立连接
            SocketManager.getInstance().connectSocket(this,mHandler);
            break;
        case R.id.socket_disconnect_btn://--关闭连接,通过发送quit消息通知服务器关闭
            SocketManager.getInstance().sendMessage("quit");
            try {
                SocketManager.getInstance().closeSocket();
            } catch (IOException e) {
                e.printStackTrace();
            }
            break;
        case R.id.socket_send_msg_btn://--客户端向服务器发送消息
            String msg =socket_send_msg_et.getText().toString();
            Log.i("socket_hdc"," btn send "+msg);
            SocketManager.getInstance().sendMessage(msg);
            break;
    }
}

4.AndroidManifest文件

由于Socket使用网络协议通讯,所以首先要在Manifest中添加网络权限:

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

既然是模拟进程间通讯,那么创建两个不同的进程,如下:

<activity android:name=".MainActivity"
        android:screenOrientation="portrait">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
    <service android:name=".SocketServerService"
        android:process="hdc.socket.server"/>

其中MainActivity在主进程中,SocketServerService的进程设置问hdc.socket.server。
在主进程中创建客户端Socket,在hdc.socket.server进程中创建服务器。通过客户端向服务器发送消息,服务器接收到消息后,再向客户端发送消息即反馈,客户端接收到服务器反馈的消息后,通过TextView来进行展示。需要注意的是,在手机上创建Socket的时候需要知道手机的IP地址,可以通过以下方法获取。

//--获取ip地址
public static String getIpAddress() {
    if(TextUtils.isEmpty(mIpAddress)){
        try {
            for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) {
                NetworkInterface intf = en.nextElement();
                for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements();) {
                    InetAddress inetAddress = enumIpAddr.nextElement();
                    if (!inetAddress.isLoopbackAddress()) {
                        mIpAddress = inetAddress.getHostAddress().toString();
                    }
                }
            }
        } catch (SocketException ex) {
            ex.printStackTrace();
        }
    }
    return mIpAddress;
}

总结:18年第一天上班,完结android进程间通讯的最后一个Socket通讯。Socket在android进程通讯中的使用不是很多,大多是通过Binder(http://blog.csdn.net/xingkong_hdc/article/details/79273005). 但是SystemService进程与Zygote进程之间是通过Socket的方式进行通讯的。
关于Socket的这篇Demo的完整项目,已分享到github:
https://github.com/xingkonghdc/SocketTest

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

推荐阅读更多精彩内容

  • 计算机网络概述 网络编程的实质就是两个(或多个)设备(例如计算机)之间的数据传输。 按照计算机网络的定义,通过一定...
    蛋炒饭_By阅读 1,175评论 0 10
  • 网络编程 网络编程对于很多的初学者来说,都是很向往的一种编程技能,但是很多的初学者却因为很长一段时间无法进入网络编...
    程序员欧阳阅读 1,969评论 1 37
  • 匆匆那年我们轻轻掀开雨帘瞥见青春的笑脸, 怎奈从此再也不舍剪断岁月精心编织的长辫。 匆匆那年我们拨乱多少时间拨断谁...
    天开了阅读 153评论 0 1
  • 姹紫嫣红奈何天, 满眼春色不知名。
    HAPPY_2019阅读 386评论 0 0
  • 前言 自定义View原理是Android开发者必须了解的基础; 在了解自定义View之前,你需要有一定的知识储备;...
    Carson带你学安卓阅读 75,121评论 47 473