封装TCP请求框架

目的:由于公司项目需求,现需实现一个基于TCP的网络请求框架。
功能:包含心跳机制、请求缓冲、请求回调、基本的网络通信。

    Tip:由于该项目是由kotlin编写的,所以不太熟悉kotlin的老铁可以先看看kotlin的基本语法,没看过kotlin的也没关系,因为kotlin和java语法差别不是很大。相信大部分还是能看懂的。

该框架大致可分为下面几个部分:

conn();//用于链接服务器

reconn();//用于重连

handshake();//用于与服务器握手

startTCPServer();//用于接收服务器的数据

sendRequest();//用于发送请求

PingTask.class;//用于监听心跳

RequestTask.class;//用于发送请求到服务器

lockThread();//锁住线程,由于请求和返回的数据是一对一的,所以发送完一条请求后会等待服务器返回数据,直到服务器返回数据或者请求超时才会解除锁发送下一条数据。

    代码里面注释也比较详细,其他的我就不多哔哔了直接上代码,由于本人也是第一次写TCP框架所以还有很多不完善的地方,请各位老铁多多包容。

import android.os.Handler
import android.util.Log
import com.google.gson.Gson
import com.google.gson.JsonParseException
import com.google.gson.JsonParser
import com.mmy.acassistant.data.bean.ConnBean
import com.mmy.acassistant.data.bean.Request
import java.io.BufferedWriter
import java.io.OutputStreamWriter
import java.io.PrintWriter
import java.net.Socket
import kotlin.concurrent.thread

/**
 * @file       TCPClient.kt
 * @brief      TCP服务
 * @author     lucas
 * @date       2018/4/27 0027
 * @version    V1.0
 * @par        Copyright (c):
 * @par History:
 *             version: zsr, 2017-09-23
 */
object TCPClient {
    //服务器地址
    val ip = "122.xx.xxx.xx"
    //端口
    val port = 1234
    //心跳包
    val ping = "{\"type\":\"ping\"}"
    //通道
    var socket: Socket? = null
    //服务器链接状态
    var isConn = false
    //链接异常回调
    var _onConnFail: (Exception) -> Unit = {}
    //握手失败回调
    var _onHandshakFail: (Exception) -> Unit = {}
    val gson = Gson()
    val handler = Handler()
    //请求任务
    var requestTask: RequestTask? = null

    val pingCheckTask = PingTask()

    //同步锁标记
    val syncTag = this.javaClass

    /**
     * 链接服务器
     * Tip:该方法被调用的地方必须长期保活,因为里面创建子线程在与服务器链接。比如在activity中调用后,界面关闭了就会导致
     * 线程被杀掉,也就意味着与服务器的链接断开了。建议在MainActivity中创建,或者在server中创建。
     */
    fun conn() {
        thread {
            println("准备链接")
            try {
                socket = Socket(ip, port)
                println("链接成功,开始接受握手数据..")
                handshake()
            } catch (e: Exception) {
                println("链接失败,原因:${e.printStackTrace()}")
                //链接异常
                _onConnFail(e)
            }
        }
    }

    /**
     * 重连
     */
    private fun reConn() {
        TCPClient.println("准备重连..")
        handler.post {
            //关闭任务
            requestTask?.foreClose()
            //关闭心跳检查任务
            pingCheckTask.stopTask()
            //关闭通道
            socket?.shutdownInput()
            socket?.shutdownOutput()
            socket?.close()
            conn()
        }
    }

    /**
     * 接受握手数据
     *
     */
    private fun handshake() {
        println("开始接受握手数据")
        try {
            if (socket != null && socket?.isConnected!!) {
                //判断输入流是否关闭
                if (socket != null && !socket?.isInputShutdown!!) {
                    val reader = socket?.getInputStream()
                    var content = ""
                    var size = 0
                    //等待服务器推数据过来,并获取数据大小
                    while (size == 0)
                        size = reader?.available()!!
                    val buffer = ByteArray(size)
//                    Log.d("lucas111", "size:" + size.toString())
                    reader?.read(buffer)
                    content = String(buffer, 0, size)
                    println("收到握手数据:$content")
                    val connBean = gson.fromJson<ConnBean>(content, ConnBean::class.java)
                    if (connBean.status == 1) {
                        println("握手成功,启动服务开始保持长连接")
                        //开启心跳检查任务
                        pingCheckTask.startTask()
                        isConn = true
                        startTCPServer()
                    } else {
                        isConn = false
                        _onHandshakFail(Exception("握手失败,重新链接"))
                        println("握手失败,重新链接")
                        reConn()
                    }
                }
                Thread.sleep(1000 * 5)
            } else {
                println("aaa")
            }
        } catch (e: JsonParseException) {
            //json解析异常
            TCPClient.println("握手数据解析异常")
            _onHandshakFail(e)
        } catch (e: Exception) {
            println("握手失败,重新链接:${e.printStackTrace()}")
            _onHandshakFail(e)
            reConn()
        }
    }

    /**
     * 开启长连接服务
     */
    private fun startTCPServer() {
        while (true)
            try {
                if (!socket?.isInputShutdown!!) {
                    val reader = socket?.getInputStream()
                    var content = ""
                    var size = 0
                    //等待服务器推数据过来,并获取数据大小
                    while (size == 0)
                        size = reader?.available()!!
                    val buffer = ByteArray(size)
//                    Log.d("lucas111", "size:" + size.toString())
                    reader?.read(buffer)
                    content = String(buffer, 0, size)
                    //判断数据格式是否为json格式
                    if (isJson(content)) {
                        pingCheckTask.receiverPing()
                        //分离出心跳
                        if (ping == content) {
                            TCPClient.println("ping.")
                            //回复心跳
                            replyHeartbeat()
                        } else {
                            //解析数据
                            TCPClient.println("收到数据:$content")
                            //释放锁,继续发送下一条请求
                            synchronized(lock,{ lock.notifyAll()})
                            TODO("回调接口")
                        }
                    } else {
                        println("数据格式错误:$content")
                        TODO("数据格式错误")
                    }
                }
            } catch (e: Exception) {
                println("服务异常")
                TODO("服务异常")
            }
    }

    /**
     * 加入请求
     * Tip:该方法一般都是在主线程或者其他线程中调用,而请求体要加入到缓冲区中,这里必须要用同步锁来处理。
     * @param request 请求内容
     */
    fun sendRequest(request: Request) {
        if (requestTask == null) {
            requestTask = RequestTask()
            requestTask?.startTask()
        }
        //加入任务
        requestTask?.addTask(request)
    }

    /**
     * 心跳检查任务
     * Tip:由于心跳是由服务器主动发送断开的,所以我们自己也要有一套检查心跳的程序来保证链接
     */
    class PingTask : Runnable {
        //上次收到心跳时间
        private var lastReceiverPingTime = System.currentTimeMillis()
        //心跳间隔 30S  一般这个间隔要设置的比服务器的大,因为网络有延时,比如服务器的是10s,我就设置30s
        private val pingSpace = 30 * 1000L
        //检查间隔 5S
        private val checkSpace = 5 * 1000L

        override fun run() {
            if (System.currentTimeMillis() - lastReceiverPingTime > pingSpace) {
                TCPClient.println("心跳异常,尝试重新链接..")
                handler.removeCallbacks(this)
                reConn()
            } else {
                handler.postDelayed(this, checkSpace)
            }
        }

        //重置接受心跳时间
        fun receiverPing() {
            lastReceiverPingTime = System.currentTimeMillis()
        }

        //开始任务
        fun startTask() {
            handler.post(this)
        }

        //结束任务
        fun stopTask() {
            handler.removeCallbacks(this)
        }
    }

    //同步锁
    val lock = java.lang.Object()
    //锁状态
    var isLock = false

    class RequestTask : Thread() {
        //请求缓冲区
        val requestBuffer = ArrayList<Request>()
        //运行状态
        var isRunning = false

        override fun run() {
            synchronized(lock) {
                while (true) {
                    TCPClient.println(isRunning.toString())
                    if (isRunning) {
                        //取出一条等待发送的请求
                        var request: Request? = getRequest()
                        if (request == null) {
                            TCPClient.println("缓冲区暂无可发送的请求,结束任务。")
                            isRunning = false
                            continue
                        }
                        //发送请求
                        try {
                            if (socket != null && socket?.isConnected!! && !socket?.isOutputShutdown!!) {
                                val writer = PrintWriter(BufferedWriter(OutputStreamWriter(socket?.getOutputStream())))
                                val content = gson.toJson(request)
                                writer.print(content)
                                writer.flush()
                                println("send data:$content")
                                //标记发送成功
                                synchronized(syncTag, {
                                    request.sendTag = SendTag.SUCCESS
                                })
                                //锁住线程,等待数据接收完毕
                                lockThread()
                            }
                        } catch (e: Exception) {
                            println("请求发送失败")
                            //移除失败的任务
                            synchronized(syncTag, {
                                request.sendTag = SendTag.FAIL
                                request.onFail(Exception("请求发送失败"))
                                requestBuffer.filter { request == it }
                            })
                        }
                    } else {
                        lockThread()
                    }
                }
            }
        }

        private fun getRequest(): Request? {
            synchronized(syncTag, {
                requestBuffer.forEach {
                    if (it.sendTag == SendTag.WAIT_SEND) {
                        it.sendTag = SendTag.SENDING
                        return it
                    }
                }
            })
            return null
        }

        /**
         * 开启任务
         */
        fun startTask() {
            start()
        }

        /**
         * 恢复任务
         */
        fun addTask(request: Request) {
            synchronized(syncTag, {
                //判断请求是否存在,如果存在就移除,并使用新的请求
                requestBuffer.filter {
                    request.msg_source == it.msg_source && request.cmd == it.cmd_rec
                }
                requestBuffer.add(request)
            })
            if (!isConn) {
                TCPClient.println("服务器正在链接或者链接失败")
                return
            }
            if (isRunning || requestBuffer.isEmpty()) return
            if (isLock)
                synchronized(lock, {
                    lock.notifyAll()
                })
            isRunning = true
        }

        /**
         * 暂停任务
         * Tip:软关闭,当当前的一条请求发送完才关闭
         */
        fun parseTask() {
            isRunning = false
        }

        /**
         * 强制关闭
         */
        fun foreClose() {
            interrupt()
        }
    }

    /**
     * 锁住线程
     */
    private fun lockThread() {
        isLock = true
        //强制关闭线程会导致InterruptedException异常,暂时不处理它
        try {
            lock.wait()
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    /**
     * 回复心跳
     */
    private fun replyHeartbeat() {
        try {
            if (socket != null && socket?.isConnected!! && !socket?.isOutputShutdown!!) {
                val writer = PrintWriter(BufferedWriter(OutputStreamWriter(socket?.getOutputStream())))
                writer.print(ping)
                writer.flush()
                println("send ping.")
            }
        } catch (e: Exception) {
            println("心跳回复失败")
        }
    }

    private fun println(msg: String) {
        Log.d("lucas111", "$msg\n")
//        print("$msg\n")
    }

    /**
     * 判断字符串是否为json格式
     */
    private fun isJson(json: String): Boolean {
        if (json.isEmpty()) return false
        return try {
            JsonParser().parse(json)
            true
        } catch (e: Exception) {
            false
        }
    }

    enum class SendTag {
        //发送失败 发送成功  发送中 未发送
        FAIL,
        SUCCESS,
        SENDING,
        WAIT_SEND
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值