安卓面试

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

2018-11-15  本文已影响219人  Taonce
佟丽娅.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知识的公众号
上一篇下一篇

猜你喜欢

热点阅读