Android IPC机制(五)Socket

前言

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

Socket通讯模型

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

Socket使用

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

  1. 创建服务器
    创建一个服务器的单例管理类,用来管理服务器的创建启动,接收客户端消息,发送消息至客户端以及断开连接等。具体代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
public class SocketServerManager {
private static String TAG = "SocketDemo";
private static SocketServerManager socketServerManager;
private ServerSocket serverSocket;
private List<Socket> mClientList = new ArrayList<Socket>();
private ExecutorService mExecutors = null; // 创建线程池对象

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() {
Log.d(TAG, "startServerSync: ");
try {
serverSocket = new ServerSocket(9002);
serverSocket.getInetAddress().getHostAddress();
mExecutors = Executors.newCachedThreadPool(); // 创建线程池
Socket client = null;
// 用死循环等待多个客户端连接
while (true) {
client = serverSocket.accept();
Log.d(TAG, "server get socket");
mClientList.add(client);
// 启动一个线程,用以守候从客户端发来的消息
mExecutors.execute(new SocketHandle(client));
}
} catch (IOException 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;
this.sendMessage(message);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}

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

private void sendMessage(String message) {
Log.d(TAG, "server sendMessage: ");
try {
bufferedWriter.write(message);
bufferedWriter.newLine();
bufferedWriter.flush();
} catch (IOException e) {
e.printStackTrace();
}
}

}
}
  1. 创建客户端

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
public class SocketClientManager {
private static String TAG = "SocketClientManager";
private static final int SOCKET_MSG = 10;
private static SocketClientManager socketClientManager;
private int port = 9002;
private Socket socket;
private ExecutorService executorService;
private BufferedWriter bufferedWriter;
private BufferedReader bufferedReader;

private SocketClientManager(){
executorService = Executors.newCachedThreadPool();
}

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

public void connectSocket(Context context, final Handler handler){
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
Log.d(TAG, "connect start");
try {
socket = new Socket(DeviceInfoUtils.getIpAddress(), port);
Log.d(TAG, "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 msg = new Message();
msg.obj = getLine;
Log.d(TAG, "run: getLine" + getLine);
msg.what = SOCKET_MSG;
handler.sendMessage(msg);
}
}
}
}
}

} 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() {
Log.d(TAG, "send msg" + msg);
try {
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();
}
}
}
  1. 由于Socket使用网络协议通讯,所以首先要在Manifest中添加网络权限:
1
<uses-permission android:name="android.permission.INTERNET"/>

既然是模拟进程间通讯,那么创建Service需要在不同的进程,如下:

1
2
3
4
5
6
<service
android:name=".SocketServerService"
android:process=":remote"
android:enabled="true"
android:exported="true">
</service>

在手机上创建Socket的时候需要知道手机的IP地址,可以通过以下方法获取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static String getIpAddress(){
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;
}

源码:https://github.com/guojingyinan/SocketDemo