本文作者:CodingBlock 文章链接:https://cloud.tencent.com/developer/article/1351737
学过计算机网络的人多多少少对Socket都会有所了解,在Android中,我们也可以借助Socket来实现进程间通讯,即使对Socket不熟悉也没关系,本篇文章将会用一个非常简单的例子,来说明通过Socket实现进程间通讯的步骤,为了打消大家对Socket的陌生感,我们先来看看Socket的基本概念和用法。
Socket又称“套接字”,是网络通信中的概念,应用程序通常通过“套接字”向网络发出请求或者应答网络请求。网络上的两个程序通过一个双向的通讯链接实现数据交换,这个链接的一端称为一个Socket,它本身可以支持传输任意的字节流。
Socket分为流式套接字和用户数据报套接字两种,分别对应于网络的传输控制层的TCP和UDP协议。
接着再说下TCP的“三次握手”,它是指TCP建立链接的如下三个步骤:
在java中通过Socket和ServerSocket两个类可以很方便的实现Socket通讯,ServerSocket用于服务器端,Socket是建立网络连接时使用的。在连接成功时,两端都会产生一个Socket实例,操作这个实例,完成所需的会话。接下来创建一个Socket连接的示例,这个示例同时也说明了Socket可以实现进程间通讯。
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
/**
* @author CodingBlock
* @博客地址 http://www.cnblogs.com/codingblock/
*/
public class TCPSocketService extends Service {
private static final String TAG = TCPSocketService.class.getSimpleName();
private boolean mIsServiceDestroyed = false;
@Override
public void onCreate() {
super.onCreate();
new Thread(new TcpServer()).start();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
mIsServiceDestroyed = true;
super.onDestroy();
}
}
/**
* @author CodingBlock
* @博客地址 http://www.cnblogs.com/codingblock/
*/
private class TcpServer implements Runnable {
@Override
public void run() {
ServerSocket serverSocket = null;
try {
// 监听本地端口
serverSocket = new ServerSocket(8088);
} catch (IOException e) {
Log.i(TAG, "run: 8088 failed");
e.printStackTrace();
return;
}
// 接收客户端请求
while (!mIsServiceDestroyed) {
try {
final Socket client = serverSocket.accept();
Log.i(TAG, "run: 接收客户端请求:client = " + client);
// 回应客户端
new Thread(new Runnable() {
@Override
public void run() {
responeClient(client);
}
}).start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* @author CodingBlock
* @博客地址 http://www.cnblogs.com/codingblock/
*/
private void responeClient(Socket client) {
BufferedReader in = null;
PrintWriter out = null;
try {
// 用于接收客户端消息
in = new BufferedReader(new InputStreamReader(client.getInputStream()));
// 用于想客户端发送消息
out = new PrintWriter(new OutputStreamWriter(client.getOutputStream()), true);
// 向客户端发送消息
out.println("欢迎交流!");
while (!mIsServiceDestroyed) {
// 读取客户端发来的消息
String msgFromClient = in.readLine();
Log.i(TAG, "responeClient: msg from client:" + msgFromClient);
if (msgFromClient == null) {
// 当客户端断开连接时realLine()就会返回null,在此时跳出循环。
break;;
}
// 向客户端回应消息
out.println("已经收到你发来的消息:【" + msgFromClient + "】,请放心!");
}
Log.i(TAG, "responeClient: client quit!");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
if (client != null) {
client.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
为了更加直观的让我们感受到Socket确实是可以夸进程通信,我们将客户端的Socket请求放在另外一个APP中实现。(当然,要知道即使是在同一个APP,只要将上面的TCPSocketService在AndroidManifest中设置上process属性也就会变成两个进程效果和两个APP是一样的)
/**
* @author CodingBlock
* @博客地址 http://www.cnblogs.com/codingblock/
*/
// 循环连接服务端Socket
Socket socket = null;
while (socket == null) {
try {
// 指定服务端Socket地址和端口号,初始化Socket
socket = new Socket("localhost", 8088);
mClientSocket = socket;
mPrintWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);
Log.i(TAG, "onCreate: 连接服务端Socket成功!");
} catch (IOException e) {
e.printStackTrace();
SystemClock.sleep(1000); // 如果连接失败了,就等1s重试
Log.i(TAG, "onCreate: 连接服务端Socket失败,正在重试...");
}
}
/**
* @author CodingBlock
* @博客地址 http://www.cnblogs.com/codingblock/
*/
// 连接成功后向服务端发送一条测试消息
mPrintWriter.println("你好,服务端,我是客户端");
/**
* @author CodingBlock
* @博客地址 http://www.cnblogs.com/codingblock/
*/
// 成功后就去循环读取服务端发送过来的消息
try {
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
while (!TCPClientActivity.this.isFinishing()) {
String msgFromServer = in.readLine();
Log.i(TAG, "onCreate: msg from server:" + msgFromServer);
}
// 循环结束,关闭相关流,关闭socket
Log.i(TAG, "onCreate: 客户端退出!");
in.close();
mPrintWriter.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
/**
* @author CodingBlock
* @博客地址 http://www.cnblogs.com/codingblock/
*/
@Override
protected void onDestroy() {
super.onDestroy();
// 退出时关闭socket连接
if (mClientSocket != null) {
try {
mClientSocket.shutdownInput();
mClientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
上面由于需要将代码分段解说,客户端的实现代码有些零碎,下面贴出TCPClientActivity的完整代码以方便参考:
/**
* @author CodingBlock
* @博客地址 http://www.cnblogs.com/codingblock/
*/
public class TCPClientActivity extends AppCompatActivity {
private final static String TAG = TCPClientActivity.class.getSimpleName();
private Socket mClientSocket;
private PrintWriter mPrintWriter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tcp_client);
new Thread(new Runnable() {
@Override
public void run() {
// 循环连接服务端Socket
Socket socket = null;
while (socket == null) {
try {
// 指定服务端Socket地址和端口号,初始化Socket
socket = new Socket("localhost", 8088);
mClientSocket = socket;
mPrintWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);
Log.i(TAG, "onCreate: 连接服务端Socket成功!");
} catch (IOException e) {
e.printStackTrace();
SystemClock.sleep(1000); // 如果连接失败了,就等1s重试
Log.i(TAG, "onCreate: 连接服务端Socket失败,正在重试...");
}
}
// 连接成功后向服务端发送一条测试消息
mPrintWriter.println("你好,服务端,我是客户端");
// 成功后就去循环读取服务端发送过来的消息
try {
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
while (!TCPClientActivity.this.isFinishing()) {
Log.i(TAG, "run: in.readLine()");
String msgFromServer = in.readLine();
Log.i(TAG, "onCreate: msg from server:" + msgFromServer);
}
// 循环结束,关闭相关流,关闭socket
Log.i(TAG, "onCreate: 客户端退出!");
in.close();
mPrintWriter.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
@Override
protected void onDestroy() {
super.onDestroy();
// 退出时关闭socket连接
if (mClientSocket != null) {
try {
mClientSocket.shutdownInput();
mClientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
到此为止,一个完整的Socket通讯代码已经写完了,测试一下:
首先启动服务端,本次示例中服务端在ipc工程中,启动后,log如下:
.../cn.codingblock.ipc I/TCPSocketService: onCreate: 正在启动ServerSocket...
.../cn.codingblock.ipc I/TCPSocketService: run: 8088 started
可以看到,8088端口已经启动了。
再启动客户端,客户端的代码在ipcclient工程中,log如下:
.../cn.codingblock.ipcclient I/TCPClientActivity: onCreate: 连接服务端Socket成功!
.../cn.codingblock.ipcclient I/TCPClientActivity: run: in.readLine()
.../cn.codingblock.ipcclient I/TCPClientActivity: onCreate: msg from server:欢迎交流!
.../cn.codingblock.ipcclient I/TCPClientActivity: run: in.readLine()
.../cn.codingblock.ipcclient I/TCPClientActivity: onCreate: msg from server:已经收到你发来的消息:【你好,服务端,我是客户端】,请放心!
.../cn.codingblock.ipcclient I/TCPClientActivity: run: in.readLine()
.../cn.codingblock.ipcclient D/EGL_emulation: eglMakeCurrent: 0x9b4850c0: ver 2 0 (tinfo 0x9b4831d0)
可以看到,客户端的Socket和服务端的Socket已经可以成功交流了,在代码中,Socket链接成功后我们向服务端发送了一条“你好,服务端,我是客户端”的消息也收到了服务端的回应。
同时通过最后两行log我们也可以看到,当没有收到新消息时程序并没有陷入死循环,而是在readLine()时阻塞了。
回头再看服务端的log:
.../cn.codingblock.ipc I/TCPSocketService: run: 接收客户端请求:client = Socket[address=/127.0.0.1,port=57073,localPort=8088]
.../cn.codingblock.ipc I/TCPSocketService: responeClient: msg from client:你好,服务端,我是客户端
最后,我们将客户端退出,观察服务端的log:
.../cn.codingblock.ipc I/TCPSocketService: responeClient: msg from client:null
.../cn.codingblock.ipc I/TCPSocketService: responeClient: client quit!
通过测试log可知,Socket可以很好进行进程间通讯,我们也可以将上面的示例做的更复杂一下,例如可以为服务端APP和客户端APP都加上聊天窗口,这样就变成了一个简单的聊天软件,是不是很酷,感兴趣的童鞋可以试着实现一下。
通过上面的文章我们可以发现Socket功能确实很强大,支持在网络间(同时也包括进程间)传输任意字节流,并且也支持一对多并发实时通信。但同时我们也发现,Socket在使用起来相对来说比较繁琐,而且不支持RPC也就是说我们无法通过获取某个对象就可以在本地方便的远程调用服务端的方法。Socket的使用场景一般是用于网络数据交换。
最后想说的是,本系列文章为博主对Android知识进行再次梳理,查缺补漏的学习过程,一方面是对自己遗忘的东西加以复习重新掌握,另一方面相信在重新学习的过程中定会有巨大的新收获,如果你也有跟我同样的想法,不妨关注我一起学习,互相探讨,共同进步!
参考文献:
本文作者:CodingBlock 文章链接:https://cloud.tencent.com/developer/article/1351737