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

    什么是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: 客户端连接中断!

效果图:

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值