Android IPC機制(五)用Socket實現跨進程聊天程式
1.Socket簡介
Socket也稱作“通訊端“,是在應用程式層和傳輸層之間的一個抽象層,它把TCP/IP層複雜的操作抽象為幾個簡單的介面供應用程式層調用已實現進程在網路中通訊。它分為流式通訊端和資料包通訊端,分別對應網路傳輸控制層的TCP和UDP協議。TCP協議是一種連線導向的、可靠的、基於位元組流的傳輸層通訊協定。它使用三向交握協議建立串連,並且提供了逾時重傳機制,具有很高的穩定性。UDP協議則是是一種不需連線的協議,且不對傳送資料包進行可靠性保證,適合於一次傳輸少量資料,UDP傳輸的可靠性由應用程式層負責。在網路品質令人十分不滿意的環境下,UDP協議資料包丟失會比較嚴重。但是由於UDP的特性:它不屬於串連型協議,因而具有資源消耗小,處理速度快的優點,所以通常音頻、視頻和普通資料在傳送時使用UDP較多。
從我們也可以看出,不同的使用者進程通過Socket來進行通訊,所以Socket也是一種IPC方式,接下來我們用TCP服務來實現一個簡單的聊天程式。
2.實現聊天程式服務端
配置
首先我們來實現服務端,當然要使用Socket我們需要在AndroidManifest.xml聲明如下的許可權:
<code class="hljs xml"> <uses-permission android:name="android.permission.INTERNET"> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission></uses-permission></code>
我們需要實現一個遠端Service來當作聊天程式的服務端,AndroidManifest.xml檔案中配置service:
<code class="hljs xml"> <service android:name=".SocketServerService" android:process=":remote"></service></code>
實現Service
接下來我們在Service啟動時,線上程中建立TCP服務,我們監聽的是8688連接埠,等待用戶端串連,當用戶端串連時就會產生Socket。通過每次建立的Socket就可以和不同的用戶端通訊了。當用戶端中斷連線時,服務端也會關閉Socket並結束結束通話線程。服務端首先會向用戶端發送一條訊息:“您好,我是服務端”,並接收用戶端發來的訊息,將收到的訊息進行加工再返回給用戶端。
package com.example.liuwangshu.moonsocket;import android.app.Service;import android.content.Intent;import android.os.IBinder;import android.text.TextUtils;import android.util.Log;import java.io.BufferedReader;import java.io.BufferedWriter;import java.io.IOException;import java.io.InputStreamReader;import java.io.OutputStreamWriter;import java.io.PrintWriter;import java.net.ServerSocket;import java.net.Socket;public class SocketServerService extends Service { private boolean isServiceDestroyed = false; @Override public void onCreate() { new Thread(new TcpServer()).start(); super.onCreate(); } @Override public IBinder onBind(Intent intent) { // TODO: Return the communication channel to the service. throw new UnsupportedOperationException("Not yet implemented"); } private class TcpServer implements Runnable { @Override public void run() { ServerSocket serverSocket; try { //監聽8688連接埠 serverSocket = new ServerSocket(8688); } catch (IOException e) { return; } while (!isServiceDestroyed) { try { // 接受用戶端請求,並且阻塞直到接收到訊息 final Socket client = serverSocket.accept(); new Thread() { @Override public void run() { try { responseClient(client); } catch (IOException e) { e.printStackTrace(); } } }.start(); } catch (IOException e) { e.printStackTrace(); } } } } private void responseClient(Socket client) throws IOException { // 用於接收用戶端訊息 BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream())); // 用於向用戶端發送訊息 PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())), true); out.println("您好,我是服務端"); while (!isServiceDestroyed) { String str = in.readLine(); Log.i("moon", "收到用戶端發來的資訊" + str); if (TextUtils.isEmpty(str)) { //用戶端斷開了串連 Log.i("moon", "用戶端中斷連線"); break; } String message = "收到了用戶端的資訊為:" + str; // 從用戶端收到的訊息加工再發送給用戶端 out.println(message); } out.close(); in.close(); client.close(); } @Override public void onDestroy() { isServiceDestroyed = true; super.onDestroy(); }}
3.實現聊天程式用戶端
用戶端Activity會在onCreate方法中啟動服務端,並開啟線程串連服務端Socket。為了確保能串連成功,採用了逾時重連的策略,每次串連失敗時都會重建立立串連。串連成功後,用戶端會收到服務端發送的訊息:“您好,我是服務端”,我們也可以在EditText輸入字元並發送到服務端。
package com.example.liuwangshu.moonsocket;import android.content.Intent;import android.os.SystemClock;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.text.TextUtils;import android.view.View;import android.widget.Button;import android.widget.EditText;import android.widget.TextView;import java.io.BufferedReader;import java.io.BufferedWriter;import java.io.IOException;import java.io.InputStreamReader;import java.io.OutputStreamWriter;import java.io.PrintWriter;import java.net.Socket;public class SocketClientActivity extends AppCompatActivity { private Button bt_send; private EditText et_receive; private Socket mClientSocket; private PrintWriter mPrintWriter; private TextView tv_message; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_socket); initView(); Intent service = new Intent(this, SocketServerService.class); startService(service); new Thread() { @Override public void run() { connectSocketServer(); } }.start(); } private void initView() { et_receive= (EditText) findViewById(R.id.et_receive); bt_send= (Button) findViewById(R.id.bt_send); tv_message= (TextView) this.findViewById(R.id.tv_message); bt_send.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { final String msg = et_receive.getText().toString(); //向伺服器發送資訊 if(!TextUtils.isEmpty(msg)&&null!=mPrintWriter) { mPrintWriter.println(msg); tv_message.setText(tv_message.getText() + "\n" + "用戶端:" + msg); et_receive.setText(""); } } }); } private void connectSocketServer() { Socket socket = null; while (socket == null) { try { //選擇和伺服器相同的連接埠8688 socket = new Socket("localhost", 8688); mClientSocket = socket; mPrintWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true); } catch (IOException e) { SystemClock.sleep(1000); } } try { // 接收伺服器端的訊息 BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); while (!isFinishing()) { final String msg = br.readLine(); if (msg != null) { runOnUiThread(new Runnable() { @Override public void run() { tv_message.setText(tv_message.getText() + "\n" + "服務端:" + msg); } } ); } } mPrintWriter.close(); br.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); } }}
布局很簡單(activity_socket.xml):
<code class="hljs xml"><!--{cke_protected}{C}%3C!%2D%2D%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%2D%2D%3E--><relativelayout android:layout_height="match_parent" android:layout_width="match_parent" xmlns:android="http://schemas.android.com/apk/res/android"> <textview android:id="@+id/tv_message" android:layout_height="400dp" android:layout_width="match_parent"> <linearlayout android:layout_alignparentbottom="true" android:layout_height="50dp" android:layout_width="match_parent" android:orientation="horizontal"> <edittext android:id="@+id/et_receive" android:layout_height="match_parent" android:layout_weight="2" android:layout_width="0dp"> </edittext></linearlayout></textview></relativelayout></code><button android:id="@+id/bt_send" android:layout_height="match_parent" android:layout_weight="1" android:layout_width="0dp" android:text="向伺服器發訊息"><code class="hljs xml"> </code></button>
4.運行聊天程式
運行程式,我們可以看到用戶端和服務端是兩個進程:
用戶端首先會收到服務端的資訊:”您好,我是服務端”,接下來我們向服務端發送“我想要怒放的生命”。這時候服務端收到了這條資訊並返回給用戶端加工後的這條資訊: