android进程间通讯(3)–使用Socket
前言:本文记录android进程间通讯的另一种通讯方式–Socket。Socket也称“套接字”,是网络通讯中的概念,它分为流式套接字和用户数据报套接字两种,分别对应网络传输层的TCP和UDP协议。TCP协议是面向连接的协议,提供稳定的双向通讯功能,TCP连接的建立需要经过“三次握手”,具有很高的稳定性。UDP是无连接的,提供不稳定的单向通讯功能,当然UDP也可以实现双向通讯,UDP效率更高,但是不能保证数据一定能正确传输,存在丢包的问题。
一.Socket通讯模型
由于Socket可以使用TCP和UDP两种协议实现通讯,所以其基本通讯模型如图(图凑合看吧):
因为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中的布局界面大致如下:
各个按钮的点击事件如下代码所示:
@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