美文网首页安卓面试
Android中使用Socket进行TCP通信基础篇

Android中使用Socket进行TCP通信基础篇

作者: Taonce | 来源:发表于2018-11-15 00:13 被阅读219次
    佟丽娅.jpeg

    TCP的理解:

    TCP:[传输控制协议],(Transmission Control Protocol),通过面向连接的、端到端的可靠数据报发送来保证可靠性。

    TCP是通过Socket完成的,是双向的,端与端之间可以互相发送和接收消息

    Socket属于传输层,因为 TCP / IP协议属于传输层,解决的是报文如何在网络中传输的问题,功能是建立端与端的连接。


    建立和断开

    TCP的建立需要三次握手,断开需要四次挥手

    三次握手:
    1. 第一次握手:Client向Server发送了一个报文,表示请求连接

      可以理解为:Server确认Client发送报文成功,自己接收成功;Client什么都不可确认。

    2. 第二次握手:Server向Client发送了一个报文,表示同意了你的请求

      可以理解为:Server确认Client发送报文成功,自己接收成功;Client确认自己发送、接收都成功,Server发送成功。

    3. 第三次握手:Client向Server发送了一个报文,表示收到了你的同意信息

      可以理解为:Server和Client都确认自己和对方发送、接收都成功。

    四次挥手:保证所有数据都发送接收完成
    1. 第一次:A对B说:我吃饱了,可以走了

    2. 第二次:B对A说:好的,我知道你吃饱了

    3. 第三次:B对A说:我也吃饱了,可以走了

    4. 第四次:A对B说:好的,走吧

      经过四次对话之后,A和B都知道对方已经吃饱了,就可以离开了。


    使用

    在这个Demo中结合Service使用的, 因为实现的是定时持续发送和接收,结合Service可保证在后台更好的存活。如果不使用Service的话,也可以使用Handler,这样也可以保证线程的安全,避免下面提到的异常。

    1. 申请权限:
     <uses-permission android:name="android.permission.INTERNET"/>
         <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
         <uses-permission android:name="android.permission.WAKE_LOCK"/>
         <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    
    1. 创建Socket对象:mSocket = Socket()

      这里需要注意:如果你只是创建了一个Socket对象,使用的是无参构造函数,那么你这一步可以在主线程中操作,但是如果你使用的是有参构造函数:socket = Socket("192.168.1.1", 8080),那么你千万不能再主线程中执行了,会报:android.os.NetworkOnMainThreadException异常。第二步的执行连接iphostName也是同理,不可再主线程中执行。

    2. 连接ip或者hostName,并且指定超时时间为多少,切记不可在主线程中完成,在Kotlin中可以使用协程来完成,代码如下:

         GlobalScope.launch {
         mSocket = Socket()
         try {
         mSocket!!.connect(InetSocketAddress("10.64.1.213", 8080), 2000)
         if (mSocket!!.isConnected) {
         sendData("taonce")
         receiverData()
         }
         } catch (e: Exception) {
         Log.d("taonce", "连接出错:${e.message}")
         }
         }
    
    1. 定时循环发送报文:使用TimerTimerTask相结合,启动定时加循环作用
         /**
         * 定时发送数据
         */
         private fun sendData(message: String) {
         if (mSendTimer == null) {
         mSendTimer = Timer()
         }
         if (mSendTimerTask == null) {
         mSendTimerTask = timerTask {
         try {
         outputStream = mSocket!!.getOutputStream()
         outputStream!!.write(message.toByteArray())
         outputStream!!.flush()
         Log.d("taonce", "发送给服务端内容为:$message")
         } catch (e: Exception) {
         e.printStackTrace()
         }
         }
         }
         // 每10秒发送一次
         mSendTimer!!.schedule(mSendTimerTask!!, 0, 10 * 1000)
         }
    
    1. 获取服务端的报文:实现的是定时获取

      注意点:在readLine()的时候有可能会造成阻塞,如果服务端发送完报文之后不是以/n结尾或者没有close掉输出流,那么客户端在接收的时候会一直处于阻塞状态。

        /**
        * 定时接收数据
        */
       private fun receiverData() {
        if (mReceiverTimer == null) {
        mReceiverTimer = Timer()
        }
        if (mReceiverTimerTask == null) {
        mReceiverTimerTask = timerTask {
        try {
        inputStream = mSocket!!.getInputStream()
        val data = inputStream!!.bufferedReader().use {
        it.readLine()
        }
        Log.d("taonce", "获取服务端内容为:$data")
        } catch (e: Exception) {
        e.printStackTrace()
        }
        }
        }
        mReceiverTimer!!.schedule(mReceiverTimerTask, 0, 10 * 1000)
       }
    
    1. 开启服务,Mac可以使用Sokit调试Socket,Windows可以使用TCP调试助手,当然还有其他很多可以使用,因为这两种是我本人亲测,所以推荐大家使用。
    val intent = Intent(MainActivity.this, SocketService::java.class);
         startService(intent);
    

    结果如下图所示:


    tcp-client.png tcp-server.png

    注意的是:使用Sokit调试的时候需要将手机和电脑连在同一个个WiFi下。


    注意内存泄漏

    注意点:

    1. IO流的关闭也就是OutputStreamInputStream的关闭就会导致Socket的关闭,如果你想在关闭IO流之后还进行Socket的操作,那么可以使用socket?.shutdownOutput()socket?.shutdownInput(),这两个可以关闭IO流但是不会关闭Socket。

    2. 关闭任何一个IO流最好是用try-catch语句包含起来。如果将多个IO流的关闭放在一起,第一个没有关闭成功,那么后续的IO流将都不会关闭成功。

    3. 在Service的onDestroy()方法中需要释放掉所有的输入输出流和定时器还有Sokcet,代码如下:

        override fun onDestroy() {
        // 销毁定时器
        mSendTimer?.purge()
        mSendTimer?.cancel()
        mReceiverTimer?.purge()
        mReceiverTimer?.cancel()
       ​
        mReceiverTimerTask?.cancel()
        mSendTimerTask?.cancel()
       ​
        mSendTimer = null
        mReceiverTimer = null
        mSendTimerTask = null
        mReceiverTimerTask = null
       ​
        // 销毁输入输出流
        try {
        outputStream?.close()
        } catch (e: Exception) {
        e.printStackTrace()
        }
        try {
        inputStream?.close()
        } catch (e: Exception) {
        e.printStackTrace()
        }
       ​
        // 销毁Socket
        mSocket?.close()
        mSocket = null
        super.onDestroy()
        }
    

    通过上面代码可以发现,Kotlin在执行这些操作的时候比Java的写法要简单多了,不需要再一个个先判断是否为空,直接用安全运算符?.就行了,简洁方便。

    完整代码如下:

    package com.example.taoyongxiang.kotlintcp
    
    
    import android.app.Service
    import android.content.Intent
    import android.os.Binder
    import android.os.IBinder
    import android.util.Log
    import kotlinx.coroutines.GlobalScope
    import kotlinx.coroutines.launch
    import java.io.InputStream
    import java.io.OutputStream
    import java.net.InetSocketAddress
    import java.net.Socket
    import java.util.*
    import kotlin.concurrent.timerTask
    
    
    /**
     * Author: taoyongxiang
     * Date: 2018/11/10
     * Project: MyTCP
     * Desc:
     */
    
    class TcpService : Service() {
    
        private val mBinder = TcpBinder()
        private var mSocket: Socket? = null
    
        //定时发送
        private var mSendTimer: Timer? = null
        private var mSendTimerTask: TimerTask? = null
        private var outputStream: OutputStream? = null
    
        //定时接收
        private var mReceiverTimer: Timer? = null
        private var mReceiverTimerTask: TimerTask? = null
        private var inputStream: InputStream? = null
    
        override fun onBind(intent: Intent?): IBinder {
            return mBinder
        }
    
        inner class TcpBinder : Binder() {
            fun getService(): TcpService {
                return this@TcpService
            }
        }
    
        override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
            initTcpParams()
            return super.onStartCommand(intent, flags, startId)
        }
    
        private fun initTcpParams() {
            if (mSocket == null) {
                Log.d("taonce", "thread: ${Thread.currentThread()}")
                // 连接不可在主线程!!!!
                // 这里使用Kotlin中的协程,Kotlin版本为1.3.0,coroutines版本为1.0.1
                // Java中可使用Thread来连接
                GlobalScope.launch {
                    Log.d("taonce", "thread: ${Thread.currentThread()}")
                    mSocket = Socket()
                    try {
                        mSocket!!.connect(InetSocketAddress("192.168.0.104", 8080), 2000)
                        if (mSocket!!.isConnected) {
                            sendData("taonce")
                            receiverData()
                        }
                    } catch (e: Exception) {
                        Log.d("taonce", "连接出错:${e.message}")
                    }
                }
            }
        }
    
        /**
         * 定时发送数据
         */
        private fun sendData(message: String) {
            if (mSendTimer == null) {
                mSendTimer = Timer()
            }
            if (mSendTimerTask == null) {
                mSendTimerTask = timerTask {
                    try {
                        outputStream = mSocket!!.getOutputStream()
                        outputStream!!.write(message.toByteArray())
                        outputStream!!.flush()
                        Log.d("taonce", "发送给服务端内容为:$message")
                    } catch (e: Exception) {
                        e.printStackTrace()
                    }
                }
            }
            // 每10秒发送一次
            mSendTimer!!.schedule(mSendTimerTask!!, 0, 10 * 1000)
        }
    
        /**
         * 定时接收数据
         */
        private fun receiverData() {
            if (mReceiverTimer == null) {
                mReceiverTimer = Timer()
            }
            if (mReceiverTimerTask == null) {
                mReceiverTimerTask = timerTask {
                    try {
                        inputStream = mSocket!!.getInputStream()
                        val data = inputStream!!.bufferedReader().use {
                            it.readLine()
                        }
                        Log.d("taonce", "获取服务端内容为:$data")
                    } catch (e: Exception) {
                        e.printStackTrace()
                    }
                }
            }
            mReceiverTimer!!.schedule(mReceiverTimerTask, 0, 10 * 1000)
        }
    
        override fun onDestroy() {
            // 销毁定时器
            mSendTimer?.purge()
            mSendTimer?.cancel()
            mReceiverTimer?.purge()
            mReceiverTimer?.cancel()
    
            mReceiverTimerTask?.cancel()
            mSendTimerTask?.cancel()
    
            mSendTimer = null
            mReceiverTimer = null
            mSendTimerTask = null
            mReceiverTimerTask = null
    
            // 销毁输入输出流
            try {
                outputStream?.close()
            } catch (e: Exception) {
                e.printStackTrace()
            }
            try {
                inputStream?.close()
            } catch (e: Exception) {
                e.printStackTrace()
            }
    
            // 销毁Socket
            mSocket?.close()
            mSocket = null
            super.onDestroy()
        }
    }
    

    写在最后

    每个人不是天生就强大,你若不努力,如何证明自己,加油!

    Thank You!

    --Taonce

    如果你觉得这篇文章对你有所帮助,那么就动动小手指,长按下方的二维码,关注一波吧~~非常期待大家的加入

    专注Kotlin和Android知识的公众号

    相关文章

      网友评论

        本文标题:Android中使用Socket进行TCP通信基础篇

        本文链接:https://www.haomeiwen.com/subject/uvfefqtx.html