Android IPC机制(五)用Socket实现跨进程聊天程序

本文首发于微信公众号「后厂技术官」

1.Socket简介

Socket也称作“套接字“,是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用以实现进程在网络中通信。它分为流式套接字和数据包套接字,分别对应网络传输控制层的TCP和UDP协议。TCP协议是一种面向连接的、可靠的、基于字节流的传输层通信协议。它使用三次握手协议建立连接,并且提供了超时重传机制,具有很高的稳定性。UDP协议则是是一种无连接的协议,且不对传送数据包进行可靠性保证,适合于一次传输少量数据,UDP传输的可靠性由应用层负责。在网络质量令人十分不满意的环境下,UDP协议数据包丢失会比较严重。但是由于UDP的特性:它不属于连接型协议,因而具有资源消耗小,处理速度快的优点,所以通常音频、视频和普通数据在传送时使用UDP较多。
VnQOhQ.jpg

从上图我们也可以看出,不同的用户进程通过Socket来进行通信,所以Socket也是一种IPC方式,接下来我们用TCP服务来实现一个简单的聊天程序。

2.实现聊天程序服务端

配置

首先我们来实现服务端,当然要使用Socket我们需要在AndroidManifest.xml声明如下的权限:

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

我们需要实现一个远程的Service来当作聊天程序的服务端,AndroidManifest.xml文件中配置service:

<service
   android:name=".SocketServerService"
   android:process=":remote" />

实现Service

接下来我们在Service启动时,在线程中建立TCP服务,我们监听的是8688端口,等待客户端连接,当客户端连接时就会生成Socket。通过每次创建的Socket就可以和不同的客户端通信了。当客户端断开连接时,服务端也会关闭Socket并结束结束通话线程。服务端首先会向客户端发送一条消息:“您好,我是服务端”,并接收客户端发来的消息,将收到的消息进行加工再返回给客户端。

package com.example.liuwangshu.moonsocket;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.text.TextUtils;
import android.util.Log;
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;
public class SocketServerService extends Service {
    private boolean isServiceDestroyed = false;
    @Override
    public void onCreate() {
        new Thread(new TcpServer()).start();
        super.onCreate();
    }
    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }
    private class TcpServer implements Runnable {
        @Override
        public void run() {
            ServerSocket serverSocket;
            try {
                //监听8688端口
                serverSocket = new ServerSocket(8688);
            } catch (IOException e) {

                return;
            }
            while (!isServiceDestroyed) {
                try {
                    // 接受客户端请求,并且阻塞直到接收到消息
                    final Socket client = serverSocket.accept();
                    new Thread() {
                        @Override
                        public void run() {
                            try {
                                responseClient(client);
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }.start();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    private void responseClient(Socket client) throws IOException {
        // 用于接收客户端消息
        BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
        // 用于向客户端发送消息
        PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())), true);
        out.println("您好,我是服务端");
        while (!isServiceDestroyed) {
            String str = in.readLine();
            Log.i("moon", "收到客户端发来的信息" + str);
            if (TextUtils.isEmpty(str)) {
                //客户端断开了连接
                Log.i("moon", "客户端断开连接");
                break;
            }
            String message = "收到了客户端的信息为:" + str;
            // 从客户端收到的消息加工再发送给客户端
            out.println(message);
        }
        out.close();
        in.close();
        client.close();
    }
    @Override
    public void onDestroy() {
        isServiceDestroyed = true;
        super.onDestroy();
    }
}

3.实现聊天程序客户端

客户端Activity会在onCreate方法中启动服务端,并开启线程连接服务端Socket。为了确保能连接成功,采用了超时重连的策略,每次连接失败时都会重新建立连接。连接成功后,客户端会收到服务端发送的消息:“您好,我是服务端”,我们也可以在EditText输入字符并发送到服务端。

package com.example.liuwangshu.moonsocket;
import android.content.Intent;
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.Button;
import android.widget.EditText;
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.Socket;
public class SocketClientActivity extends AppCompatActivity {
    private Button bt_send;
    private EditText et_receive;
    private Socket mClientSocket;
    private PrintWriter mPrintWriter;
    private TextView tv_message;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_socket);
        initView();
        Intent service = new Intent(this, SocketServerService.class);
        startService(service);
        new Thread() {
            @Override
            public void run() {
                connectSocketServer();
            }
        }.start();

    }
    private void initView() {
        et_receive= (EditText) findViewById(R.id.et_receive);
        bt_send= (Button) findViewById(R.id.bt_send);
        tv_message= (TextView) this.findViewById(R.id.tv_message);
        bt_send.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                final String msg = et_receive.getText().toString();
                //向服务器发送信息
                if(!TextUtils.isEmpty(msg)&&null!=mPrintWriter) {
                    mPrintWriter.println(msg);
                    tv_message.setText(tv_message.getText() + "\n" + "客户端:" + msg);
                    et_receive.setText("");
                }
            }
        });
    }
    private void connectSocketServer() {
        Socket socket = null;
        while (socket == null) {
            try {
                //选择和服务器相同的端口8688
                socket = new Socket("localhost", 8688);
                mClientSocket = socket;
                mPrintWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);
            } catch (IOException e) {
                SystemClock.sleep(1000);
        }
        }
        try {
            // 接收服务器端的消息
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            while (!isFinishing()) {
                final String msg = br.readLine();
                if (msg != null) {
                    runOnUiThread(new Runnable() {
                                      @Override
                                      public void run() {
                                          tv_message.setText(tv_message.getText() + "\n" + "服务端:" + msg);
                                      }
                                  }
                    );
                }
            }
            mPrintWriter.close();
            br.close();
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

布局很简单(activity_socket.xml):

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/tv_message"
        android:layout_width="match_parent"
        android:layout_height="400dp" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:layout_alignParentBottom="true"
        android:orientation="horizontal">  
        <EditText
            android:id="@+id/et_receive"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="2"
            />     
        <Button
            android:id="@+id/bt_send"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:text="向服务器发消息" />
    </LinearLayout>
</RelativeLayout>

4.运行聊天程序

运行程序,我们可以看到客户端和服务端是两个进程:
VnQqAS.png

客户端首先会收到服务端的信息:”您好,我是服务端”,接下来我们向服务端发送“我想要怒放的生命”。这时候服务端收到了这条信息并返回给客户端加工后的这条信息:
VnQLtg.png

github源码下载

分享到 评论

分享前沿技术、技术资讯、行业秘闻,技术管理,助力10万+程序员成长进阶。

---