本文系转载文章,阅读原文可获取源码,文章末尾有原文链接
ps:本文的讲的是使用 Socket 进行进程间通信,demo 是用 Kotlin 语言写的
1、使用 Socket
Socket 的中文名字称为“套接字”,是应用层 与 TCP/IP 协议族通信的中间软件抽象层,表现为一个封装了 TCP / IP 协议族 的编程接口(API);
它分为流式套接字和用户数据报套接字两种,分别对应于网络的传输控制层中的TCP和 UDP协议。
TCP协议是面向连接的协议,提供稳定的双向通信功能,连接的建立需要经过“三次握手”才能完成,为了提供稳定的数据传输功能;TCP 为了保证数据包传输的可靠行,会给每个包一个序号,同时此序号也保证了发送到接收端主机能够按序接收,然后接收端主机对成功接收到的数据包发回一个相应的确认字符,如果发送端主机在合理的往返时延内未收到确认字符,那么对应的数据包就被认为丢失并将被重传;UDP是一种无连接的协议,不保证可靠性,UDP有不提供数据包分组、组装和不能对数据包进行排序的缺点,即无法得知其是否安全完整到达,但在性能上,UDP具有更好的效率。
我们用 Socket 进行 IPC 通信时,它也属于网络操作,很有可能是耗时的,所以在接收或者发送数据的时候尽量要用子线程来操作,因为放在主线程中会影响程序的响应效率,从性能方面也不应该在主线程中访问网络;下面我们来写一个 demo;
(1)服务器端,新建一个 kt 类 MyService(包名com.xe.ipcservice) 并继承 Service:
class MyService: Service() {
private var mIsServiceDestoryed = false
private val TAG = "MyService"
override fun onBind(intent: Intent?): IBinder {
return null!!
}
override fun onDestroy() {
super.onDestroy()
mIsServiceDestoryed = true
}
override fun onCreate() {
super.onCreate()
Thread(TcpServer()).start()
}
private fun recevi(client: Socket) {
var inB: BufferedReader? = null
var out: PrintWriter? = null
try {
inB = BufferedReader(InputStreamReader(client.getInputStream()))
out = PrintWriter(BufferedWriter(OutputStreamWriter(client.getOutputStream())), true)
var msg: String = ""
while (!mIsServiceDestoryed) {
Thread.sleep(50)
msg = inB!!.readLine()
if (msg != null) {
var s: String = "服务器端收到消息,正准备发送回去------" + msg
Log.d(TAG, s)
out.println(s)
} else {
Log.d(TAG, "msg == null")
}
}
} catch (e: IOException) {
e.printStackTrace()
} catch (e: InterruptedException) {
e.printStackTrace()
} finally {
if (out != null) {
out.close()
}
if (inB != null) {
try {
inB.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
}
}
internal inner class TcpServer : Runnable {
override fun run() {
var serverSocket: ServerSocket? = null
try {
serverSocket = ServerSocket(8083)
} catch (e: IOException) {
e.printStackTrace()
}
while (!mIsServiceDestoryed) {
try {
val client = serverSocket!!.accept()
recevi(client)
} catch (e: IOException) {
e.printStackTrace()
}
}
}
}
}
这里我们的服务器端用的是8083端口号,一开始的时候开启一个子线程,创建一个 ServerSocket 对象,并等待客户端的连接,当客户端连接成功后,通过 ServerSocket 对象获取到输入流 BufferedReader 和输出流 PrintWriter;通过 BufferedReader 接收到客户端发送过来的数据经过修饰内容之后再用 PrintWriter 发送给客户端。
(2)客户端,创建一个 kt 类型的 Activity,它的名字为 ClientActivity(包名com.xe.ipcdemo5):
class ClientActivity : AppCompatActivity() {
var mBtnConnect: Button? = null
var mTvMessage: TextView? = null
var mBtnSend: Button? = null
var mH: Handler? = null
var mReceiveThread: Thread? = null
var mPrintWriter: PrintWriter? = null
var mClientSocket: Socket? = null
var isThreadActive: Boolean = true
var mDefinedMessages = arrayOf("你好!", "请问你叫什么名字", "今天天气不错", "给你讲个笑话吧", "这个可以多人聊天")
companion object {
var MESSAGE_SOCKET_CONNECTED: Int = 1
var UPDATE_VIEW: Int = 2
var TAG: String = "ClientActivity"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_client)
init();
startMyService()
}
fun startMyService() {
startService(Intent(this, MyService::class.java))
}
fun init() {
mBtnConnect = findViewById(R.id.btn_connect)
mTvMessage = findViewById(R.id.tv_message)
mBtnSend = findViewById(R.id.btn_send);
mH = MyHandler();
mReceiveThread = ReceiveThread();
}
fun onClick(v: View) {
if (v.id == R.id.btn_connect) {
connect(v)
} else if (v.id == R.id.btn_send) {
sendMessage(v)
}
}
fun sendMessage(v: View) {
mBtnSend!!.isEnabled = false
var t: Thread = SendThread()
t.start()
}
inner class SendThread : Thread() {
override fun run() {
super.run()
try {
var index: Int = Random().nextInt(mDefinedMessages.size)
if (mPrintWriter != null) {
mPrintWriter!!.println(mDefinedMessages[index])
} else {
Log.d(TAG,"mPrintWriter == null")
}
} catch (e: Exception) {
} finally {
mH!!.sendEmptyMessage(UPDATE_VIEW)
}
}
}
fun connect(v: View) {
mReceiveThread!!.start()
v.isEnabled = false
}
inner class MyHandler : Handler() {
override fun handleMessage(msg: Message?) {
super.handleMessage(msg)
if (msg!!.what == MESSAGE_SOCKET_CONNECTED) {
var message: String = msg!!.obj as String
var mTvContent: String = mTvMessage!!.text.toString()
mTvContent = mTvContent + "\n" + message
mTvMessage!!.setText(mTvContent)
} else if (msg!!.what == UPDATE_VIEW){
mBtnSend!!.isEnabled = true
}
}
}
inner class ReceiveThread : Thread() {
override fun run() {
super.run()
var socket: Socket? = null
while (socket == null) {
try {
socket = Socket("127.0.0.1", 8083);
mClientSocket = socket;
mPrintWriter = PrintWriter(BufferedWriter(OutputStreamWriter(socket.getOutputStream())), true);
} catch (e: IOException) {
e.printStackTrace();
}
}
var br: BufferedReader? = null
try {
br = BufferedReader(InputStreamReader(socket.getInputStream()));
while (isThreadActive) {
var msg = br!!.readLine()
Thread.sleep(500);
if (msg != null) {
var message: Message = Message.obtain();
message.what = MESSAGE_SOCKET_CONNECTED
message.obj = msg
mH!!.sendMessage(message);
}
}
} catch (e: IOException) {
e.printStackTrace();
} catch (e: InterruptedException) {
e.printStackTrace();
} finally {
if (br != null) {
try {
br.close();
} catch (e: IOException) {
e.printStackTrace();
}
}
}
try {
socket.close();
} catch (e: IOException) {
e.printStackTrace();
}
}
}
override fun onDestroy() {
super.onDestroy()
isThreadActive = false;
mReceiveThread = null
if (mPrintWriter != null) {
mPrintWriter!!.close()
mPrintWriter = null
}
if (mClientSocket != null) {
try {
mClientSocket!!.shutdownInput()
mClientSocket!!.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
}
}
ClientActivity 对应的布局文件 activity_client 如下所示:
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.xe.ipcdemo5.ClientActivity">
首先客户端先开启 MyService 同时也开启了一个进程,通过点击“连接服务器”的按钮开启一个子线程 ReceiveThread,该子线程主要的事情是通过端口号 8083 创建一个 Socket 对象,通过 Socket 对象获取一个输入流 BufferedReader 和一个输出流 PrintWriter,然后通过 BufferedReader 进行等待接收数据,接收到的数据切换到主线程,并用 TextView 进行显示;点击“发送一条消息”的按钮,主要的事情是开启一个子线程用 PrintWriter 将数据发送出去。
(3)对 AndroidManifest.xml 文件进行相应的配置:
package="com.xe.ipcdemo5">
程序一开始运行的界面如下所示:
图片
点击“连接服务器”按钮后,再连续点击“发送一条消息”按钮,界面改变如下所示:
图片
控制台的日志打印如下所示:
图片