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 来进行通信,有两点需要意点
-
- 需要声明权限:
- 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: [email protected]
* 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: [email protected]
* 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地址。