十四 、 Android 中的 IPC 方式(6) --- 使用 Socket

    xiaoxiao2023-11-13  154

        什么是Socket?

        两台计算机进行通信,在网络中我们可以通过 IP 地址定位到具体的某一台计算机,然后再通过端口号定位到这台计算机的某一个应用程序。这样我们的一台电脑就可以通过 IP 地址和端口号和另一台电脑进行相应的通讯了。那我们定位到了具体的某台计算机怎么和它进行相应的通信呢?他们之间的通信方式就是使用 Socket 提供的编程接口。Socket 称为 "套接字",通俗的理解就是它提供了进程间通信的方式,然后提供了供进程间通信的相应的编程接口。

        总体来讲,Socket 通信分为流式套接字和用户数据报套接字两种,分别对应于网络传输控制层的 TCP 和 UDP 协议。

        TCP 协议是面向连接的协议,提供稳定的双向通信功能,TCP 连接的建立需要经过 "三次握手" 才能完成,为了提供稳定的数据传输功能,其本身提供了超时重传机制,因此具有很高的稳定性;而 UDP 是无连接的,提供不稳定的单项通信功能,当然 UDP 也可以实现双向通信功能。在性能上,UDP 具有更好的效率,其缺点是不保证数据一定能够正确传输,尤其是在网络拥塞的情况下。

        简单说一下 UDP 和 TCP:

        1. UDP

        UDP 通信模型图:

        图解: UDP Client (客户端)通过 ip 地址和端口 port 定位到相应的 UDP Server(服务端),然后把信息通过 DatagramPacket 类打包后再通过由 DatagramSocket 类中的 send 方法发送给服务端,服务端接收到之后通过 DatagramSocket 类解析然后再以同样的传递方式回复给客户端。

        UDP 通信的方式很像写信的方式,发送方把信息写到一封信中,然后将信再传递给收信方,然后收信方收到信后打开信封读信,然后再以同样的方式决定是否回信给发信方。由于我们不能确定信发出之后对方是否能收到,所以通常把 UDP 定义为不安全的一种通信协议。

    涉及到的 API :

    - InetAddress (ip、port 的封装类)

    - DatagramSocket( receive, send)

    - DatagramPacket

        2. TCP

        TCP 通信模型图:

        TCP类似于打电话的方式。一旦接通之后我们可以进行不间断的通信,实际上它是一种比较安全的协议。

    涉及到的 API:

    - ServerSocket (Server端)

    - Socket (Client端)

    - 以及相关 IO 类

        

        实战: 使用 Socket 通信中的 TCP 方式完成进程间通信

        1. 首先因为要使用网络通信,所以要在 AndroidManifest.xml 文件中声明权限:

    <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

        2. 服务端代码(使用 ServerSocket),TCPServerService.java:

    package com.cfm.sockettest; public class TCPServerService extends Service {     private static final String TAG = "cfmtest";     // 当服务销毁时,结束服务端与客户端之间通信的线程     private boolean mIsServiceDestoryed = false;     private String[] mDefinedMessages = new String[]{             "坚决的信心,能使平凡的人们,做出惊人的事业。——马尔顿",             "人生是个圆,有的人走了一辈子也没有走出命运画出的圆圈,其实,圆上的每一个点都有一条腾飞的切线。",             "要有自信,然后全力以赴——假如具有这种观念,任何事情十之八九都能成功。——威尔逊",             "行动是治愈恐惧的良药,而犹豫、拖延将不断滋养恐惧。",             "天道酬勤。也许你付出了不一定得到回报,但不付出一定得不到回报。",             "一个人是否有成就只有看他是否具有自尊心和自信心两个条件。——苏格拉底",             "宝剑锋从磨砺出,梅花香自苦寒来。"     };     @Override     public void onCreate() {         super.onCreate();         new Thread(new TcpServer()).start();     }     @Override     public IBinder onBind(Intent intent) {         return null;     }     private class TcpServer implements Runnable{         @Override         public void run() {             ServerSocket serverSocket = null;             try {                 // 创建服务端 Socket                 serverSocket = new ServerSocket(8868);             } catch (IOException e) {                 e.printStackTrace();             }             // 接收客户端的请求             while (!mIsServiceDestoryed){                 try {                     if (serverSocket != null) {                         // 连接到服务端的客户端 Socket 对象                         final Socket socket = serverSocket.accept();                         Log.d(TAG, "服务端接收到客户端的连接请求!");                         new Thread(new Runnable() {                             @Override                             public void run() {                                 try {                                     responseClient(socket);                                 } catch (IOException e) {                                     e.printStackTrace();                                 }                             }                         }).start();                     }                 } catch (IOException e) {                     e.printStackTrace();                 }             }         }     }     private void responseClient(Socket clientSocket) throws IOException{         // 用于接收客户端消息         BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));         // 用于向客户端发送消息         PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream())), true);         while (!mIsServiceDestoryed){             String str = in.readLine();             Log.d(TAG, "服务端接收到客户端发送的消息: " + str);             if(str == null)break;             // 随机回复一句箴言             int i = new Random().nextInt(mDefinedMessages.length);             String msg = mDefinedMessages[i];             out.println(msg); // 服务端向客户端回复消息             Log.d(TAG, "服务端发送给客户端的箴言: " + msg);         }         Log.d(TAG, "服务端连接断开!");         in.close();         out.close();         clientSocket.close();     }     @Override     public void onDestroy() {         super.onDestroy();         mIsServiceDestoryed = true;     } }

        3. 客户端代码,TCPClientActivity.java:

    package com.cfm.sockettest; public class TCPClientActivity extends AppCompatActivity {     private static final String TAG = "cfmtest";     private static final int MESSAGE_RECEIVE_NEW_MSG = 1;     private static final int MESSAGE_SOCKET_CONNECTED = 2;     private Button mSendButton;     private TextView mMessageTextView;     private EditText mMessageEditText;     // 向服务端发送数据     private PrintWriter mPrintWriter;     // 客户端 Socket     private Socket mClientSocket;     // 通过 Handler 从子线程切换到主线程以方便更新 UI     private Handler mHandler = new Handler() {         @Override         public void handleMessage(Message msg) {             switch (msg.what) {                 case MESSAGE_RECEIVE_NEW_MSG:                     mMessageTextView.setText(mMessageTextView.getText() + (String) msg.obj);                     break;                 case MESSAGE_SOCKET_CONNECTED:                     // 与服务端 Socket 连接成功,使能发送按钮                     mSendButton.setEnabled(true);                     break;                 default:                     break;             }         }     };     @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_tcpclient);         mMessageEditText = findViewById(R.id.msg);         mMessageTextView = findViewById(R.id.msg_container);         mSendButton = findViewById(R.id.send);         // 开始远程 Service         Intent intent = new Intent(this, TCPServerService.class);         startService(intent);         // 连接服务端 Socket         new Thread(new Runnable() {             @Override             public void run() {                 connectTCPServer();             }         }).start();         mSendButton.setOnClickListener(new View.OnClickListener() {             @Override             public void onClick(View v) {                 final String msg = mMessageEditText.getText().toString();                 Log.d(TAG,"客户端向服务端发送的消息: " + msg);                 if (!TextUtils.isEmpty(msg) && mPrintWriter != null) {                     new Thread(new Runnable() {                         @Override                         public void run() {                             // 发送消息给客户端,注意这里要在线程中发送,否则会 crash                             mPrintWriter.println(msg);                         }                     }).start();                     mMessageEditText.setText("");  // 清空输入框                     String time = formatDateTime(System.currentTimeMillis());                     String showedMsg = "self " + time + ":" + msg + "\n";                     // 将客户端的发送消息的时间和内容显示出来                     mMessageTextView.setText(mMessageTextView.getText() + showedMsg);                 }             }         });     }     private String formatDateTime(long time) {         return new SimpleDateFormat("(HH:mm:ss)", Locale.CHINA).format(new Date(time));     }     private void connectTCPServer(){         Socket socket = null;         while (socket == null){             // 如果没连接成功就一直尝试连接             try {                 socket = new Socket("localhost", 8868);                 mClientSocket = socket;                 mPrintWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);                 // 连接成功                 mHandler.sendEmptyMessage(MESSAGE_SOCKET_CONNECTED);                 Log.d(TAG, "客户端连接服务端成功!");             } catch (IOException e) {                 SystemClock.sleep(1000);                 e.printStackTrace();             }         }         // 接收服务端消息         try {             BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));             while (!TCPClientActivity.this.isFinishing()){                 String msg = in.readLine();                 Log.d(TAG, "客户端接收到的服务端消息: " + msg);                 if(msg != null){                     String time = formatDateTime(System.currentTimeMillis());                     String showedMsg = "server " + time + ":" + msg + "\n";                     mHandler.obtainMessage(MESSAGE_RECEIVE_NEW_MSG, showedMsg).sendToTarget();                 }             }             Log.d(TAG, "客户端连接中断!");             in.close();             mPrintWriter.close();             socket.close();         } catch (IOException e) {             e.printStackTrace();         }     }     @Override     protected void onDestroy() {         super.onDestroy();         if(mClientSocket != null){             try {                 mClientSocket.shutdownInput();                 mClientSocket.close();             } catch (IOException e) {                 e.printStackTrace();             }         }         finish();     } }

    activity_tcpclient.xml:

    <?xml version="1.0" encoding="utf-8"?> <LinearLayout     xmlns:android="http://schemas.android.com/apk/res/android"     xmlns:tools="http://schemas.android.com/tools"     android:layout_width="match_parent"     android:layout_height="match_parent"     android:orientation="vertical"     android:padding="8dp"     android:background="#ffffff"     tools:context=".TCPClientActivity">     <ScrollView         android:layout_width="match_parent"         android:layout_height="0dp"         android:layout_weight="1">         <LinearLayout             android:layout_width="match_parent"             android:layout_height="wrap_content"             android:orientation="vertical">             <TextView                 android:id="@+id/msg_container"                 android:layout_width="match_parent"                 android:layout_height="wrap_content"/>         </LinearLayout>     </ScrollView>     <LinearLayout         android:layout_width="match_parent"         android:layout_height="wrap_content">         <EditText             android:id="@+id/msg"             android:layout_width="0dp"             android:layout_height="35dp"             android:layout_weight="1"             android:ems="10"             android:padding="8dp"/>         <Button             android:id="@+id/send"             android:layout_width="wrap_content"             android:layout_height="35dp"             android:enabled="false"             android:text="发送"/>     </LinearLayout> </LinearLayout>

    AndroidManifest.xml:

    <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android"           package="com.cfm.sockettest">     <uses-permission android:name="android.permission.INTERNET"/>     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>     <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">         <service             android:name=".TCPServerService"             android:enabled="true"             android:exported="true"             android:process=":remote">         </service>         <activity android:name=".TCPClientActivity">             <intent-filter>                 <action android:name="android.intent.action.MAIN"/>                 <category android:name="android.intent.category.LAUNCHER"/>             </intent-filter>         </activity>     </application> </manifest>

    Log打印信息:

    // 服务端 2019-05-25 19:29:40.546 31371-31395/com.cfm.sockettest:remote D/cfmtest: 服务端接收到客户端的连接请求! 2019-05-25 19:29:44.447 31371-31417/com.cfm.sockettest:remote D/cfmtest: 服务端接收到客户端发送的消息: 123 2019-05-25 19:29:44.449 31371-31417/com.cfm.sockettest:remote D/cfmtest: 服务端发送给客户端的箴言: 天道酬勤。也许你付出了不一定得到回报,但不付出一定得不到回报。 2019-05-25 19:29:47.760 31371-31417/com.cfm.sockettest:remote D/cfmtest: 服务端接收到客户端发送的消息: 456 2019-05-25 19:29:47.761 31371-31417/com.cfm.sockettest:remote D/cfmtest: 服务端发送给客户端的箴言: 坚决的信心,能使平凡的人们,做出惊人的事业。——马尔顿 2019-05-25 19:29:50.301 31371-31417/com.cfm.sockettest:remote D/cfmtest: 服务端接收到客户端发送的消息: 789 2019-05-25 19:29:50.302 31371-31417/com.cfm.sockettest:remote D/cfmtest: 服务端发送给客户端的箴言: 一个人是否有成就只有看他是否具有自尊心和自信心两个条件。——苏格拉底 2019-05-25 19:29:52.963 31371-31417/com.cfm.sockettest:remote D/cfmtest: 服务端接收到客户端发送的消息: 147 2019-05-25 19:29:52.964 31371-31417/com.cfm.sockettest:remote D/cfmtest: 服务端发送给客户端的箴言: 行动是治愈恐惧的良药,而犹豫、拖延将不断滋养恐惧。 2019-05-25 19:31:17.807 31371-31417/com.cfm.sockettest:remote D/cfmtest: 服务端接收到客户端发送的消息: null 2019-05-25 19:31:17.807 31371-31417/com.cfm.sockettest:remote D/cfmtest: 服务端连接断开! // 客户端 2019-05-25 19:29:40.546 31335-31376/com.cfm.sockettest D/cfmtest: 客户端连接服务端成功! 2019-05-25 19:29:44.442 31335-31335/com.cfm.sockettest D/cfmtest: 客户端向服务端发送的消息: 123 2019-05-25 19:29:44.450 31335-31376/com.cfm.sockettest D/cfmtest: 客户端接收到的服务端消息: 天道酬勤。也许你付出了不一定得到回报,但不付出一定得不到回报。 2019-05-25 19:29:47.755 31335-31335/com.cfm.sockettest D/cfmtest: 客户端向服务端发送的消息: 456 2019-05-25 19:29:47.762 31335-31376/com.cfm.sockettest D/cfmtest: 客户端接收到的服务端消息: 坚决的信心,能使平凡的人们,做出惊人的事业。——马尔顿 2019-05-25 19:29:50.293 31335-31335/com.cfm.sockettest D/cfmtest: 客户端向服务端发送的消息: 789 2019-05-25 19:29:50.302 31335-31376/com.cfm.sockettest D/cfmtest: 客户端接收到的服务端消息: 一个人是否有成就只有看他是否具有自尊心和自信心两个条件。——苏格拉底 2019-05-25 19:29:52.960 31335-31335/com.cfm.sockettest D/cfmtest: 客户端向服务端发送的消息: 147 2019-05-25 19:29:52.965 31335-31376/com.cfm.sockettest D/cfmtest: 客户端接收到的服务端消息: 行动是治愈恐惧的良药,而犹豫、拖延将不断滋养恐惧。 2019-05-25 19:31:54.944 31335-31996/com.cfm.sockettest D/cfmtest: 客户端接收到的服务端消息: null 2019-05-25 19:31:54.944 31335-31996/com.cfm.sockettest D/cfmtest: 客户端连接中断!

    效果图:

     

    最新回复(0)