本文系转载文章,浏览原文可获取源码,文章开端有原文链接
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 如下所示:
<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayout
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"> <Button android:id="@+id/btn_connect" android:layout_width="match_parent" android:text="连贯服务器" android:onClick="onClick" android:layout_height="wrap_content" /> <Button android:id="@+id/btn_send" android:layout_width="match_parent" android:text="发送一条音讯" android:onClick="onClick" android:layout_height="wrap_content" /> <TextView android:id="@+id/tv_message" android:layout_width="match_parent" android:layout_height="wrap_content" />
</LinearLayout>
首先客户端先开启 MyService 同时也开启了一个过程,通过点击“连贯服务器”的按钮开启一个子线程 ReceiveThread,该子线程次要的事件是通过端口号 8083 创立一个 Socket 对象,通过 Socket 对象获取一个输出流 BufferedReader 和一个输入流 PrintWriter,而后通过 BufferedReader 进行期待接收数据,接管到的数据切换到主线程,并用 TextView 进行显示;点击“发送一条音讯”的按钮,次要的事件是开启一个子线程用 PrintWriter 将数据发送进来。
(3)对 AndroidManifest.xml 文件进行相应的配置:
<?xml version=”1.0″ encoding=”utf-8″?>
<manifest xmlns:android=”http://schemas.android.com/apk/res/android”
package="com.xe.ipcdemo5"> <uses-permission android:name="android.permission.INTERNET"></uses-permission> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".ClientActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <service android:name="com.xe.ipcservice.MyService" android:process=":remote"> </service> </application>
</manifest>
程序一开始运行的界面如下所示:
图片
点击“连贯服务器”按钮后,再间断点击“发送一条音讯”按钮,界面扭转如下所示:
图片
控制台的日志打印如下所示:
图片