Swift gère les paquets persistants TCP

Swift gère les paquets persistants TCP

CocoaAsyncSocket

Si vous utilisez CocoaAsyncSocket pour communiquer avec le serveur avec TCP, il doit utiliser le type de données pour envoyer et recevoir des paquets de données TCP. comme suit:

class IMClient: GCDAsyncSocketDelegate {
    
    
	// connect
    func socket(_ sock: GCDAsyncSocket, didConnectToHost host: String, port: UInt16) {
    
    
        // 监听数据
        tcpClient?.readData(withTimeout: -1, tag: 0)
    }

	// disconnect
    func socketDidDisconnect(_ sock: GCDAsyncSocket, withError err: Error?) {
    
    
    }

	// receive data
    func socket(_ sock: GCDAsyncSocket, didRead data: Data, withTag tag: Int) {
    
    
        // 监听数据
        tcpClient?.readData(withTimeout: -1, tag: 0)
    }
}

Qu'est-ce que les données, comment lire et écrire? s'il vous plaît regardez ci-dessous

Principes de base de Swift Data

Ecrire et lire

data.append(other: Data) // 末尾追加Data
data.append(newElement: UInt8) // 末尾追加UInt8

// 拷贝指定区间的数据
let buffer = data.subdata(in: start..<data.count)

// 也可以直接通过下标访问
// 取1个
let item = data[0]
let itemValue = UInt8(item) // 转换成UInt8,可以打印
// 取区间
let slice = data[0..<12] // 不包括12,长度12
let sliceBytes = [UInt8](slice) // 转换成 UInt8数组

remplacer

Vous pouvez également copier de la mémoire comme dans C / C ++ memcpy

// 声明一组二进制数组,随机填入数字
let bytes: [UInt8] = [12, 32, 12, 23, 42, 24, 24, 24, 24, 24, 24, 24, 42, 123, 124, 12, 55, 36, 46, 77, 86]
// 构造data对象
var data = Data()
// Swift  [UInt8]数组转 Data
data.append(Data(bytes: bytes, count: bytes.count))
print("old:\(data)") // old:21 bytes

let start = 5

// 从data里面读取指定区间的数据,包下标,start能取到,data.count不会取到
// 模拟读取了部分数据(在xcode中,鼠标移动到该变量上,可以点击“!”查看)
let buffer = data.subdata(in: start..<data.count)
print("buffer:\(buffer)") // buffer:16 bytes 

// replace
data.replaceSubrange(0..<buffer.count, with: buffer)
print("replace:\(data)")

Production

// 即 [12, 32, 12, 23, 42, 24, 24, 24, 24, 24, 24, 24, 42, 123, 124, 12, 55, 36, 46, 77, 86]
old:21 bytes

// 5-21:即 [24, 24, 24, 24, 24, 24, 24, 42, 123, 124, 12, 55, 36, 46, 77, 86]
buffer:16 bytes 

// 即 [24, 24, 24, 24, 24, 24, 24, 42, 123, 124, 12, 55, 36, 46, 77, 86, 55, 36, 46, 77, 86]
// 使用5-21的数据覆盖了0-16位置的数据
replace:21 bytes  

Traitement des paquets persistants TCP

Paraphrase

Il y a de nombreuses raisons pour les paquets persistants TCP. Je l'ai dessiné en me basant sur ma propre compréhension: La
Insérez la description de l'image ici
première situation: l'
envoi d'un paquet de 2048 octets, il y aura deux rappels au CocoaAsyncSocket du destinataire, et le premier paquet peut être 1408 (Data1), le second est 640 (Data2).

À ce stade, vous devez réunir les deux packages pour être complets. Bien sûr, il n'est pas nécessaire de considérer la question du désordre, car TCP l'a déjà traité pour nous, ce qui est également différent de l'UDP, sinon nous devons traiter la question du désordre.

Deuxième cas

Je ne l'ai pas testé, mais je vous suggère de vous en occuper.

Solution

Insérez la description de l'image ici
Habituellement, pour résoudre ce problème, nous devons définir un en-tête de longueur fixe et enregistrer la longueur de la partie de données dans l'en-tête, afin que le déballage ultérieur puisse être géré facilement. Pour plus de détails, reportez-vous à la section suivante: la première section de l'accord.

Exemple

// receive data
func socket(_ sock: GCDAsyncSocket, didRead data: Data, withTag tag: Int) {
    
    
    IMLog.debug(item: "IMClient socket receive data,len=\(data.count)")
    
    // 是否足够长,数据包完整,否则加入到缓冲区
    if IMHeader.isAvailable(data: data) {
    
    
        recvBufferLen = 0 // 重置,即使还有数据,以免持续恶化
        let len = _resolveData(data: data)
        // 这里没有处理第2种情况,即收到了一个大包,里面包含多个小包,需要拆分。后续发现了修复 FIXME
        if data.count != len{
    
    
            IMLog.error(item: "data is reset,fix me")
        }
    } else {
    
    
        IMLog.warn(item: "data is not enough!")
        
        // 追加上去之后,尝试解析
        let newLen = recvBufferLen + data.count
        recvBuffer.replaceSubrange(recvBufferLen..<newLen, with: data)
        recvBufferLen = newLen
        
        var start = 0
        while true {
    
    
            let reset = recvBuffer.subdata(in: start..<recvBufferLen)
            // 不足够长
            if !IMHeader.isAvailable(data: reset) {
    
    
                break
            }
            let len = _resolveData(data: reset)
            if len == 0 {
    
    
                IMLog.error(item: "bad data")
            } else {
    
    
                start += len
            }
        }
        
        // 去除解析过的数据
        if start != 0 {
    
    
            if start == recvBufferLen{
    
    
                // 读取完毕,不用拷贝
                recvBufferLen = 0
            }else{
    
    
                // 把后面没有解析的数据移动到最开始
                let resetBuffer = data.subdata(in: start..<recvBufferLen)
                recvBuffer.replaceSubrange(0..<resetBuffer.count, with: resetBuffer)
                recvBufferLen = resetBuffer.count
            }
        }
    }
    
    // 监听数据
    tcpClient?.readData(withTimeout: -1, tag: 0)
}

fileprivate func _resolveData(data: Data) -> Int {
    
    
    // 解析协议头
    let header = IMHeader()
    if !header.readHeader(data: data) {
    
    
        IMLog.error(item: "readHeader error!")
    } else {
    
    
        IMLog.debug(item: "parse IMHeader success,cmd=\(header.commandId),seq=\(header.seqNumber)")
        
        // 处理消息
        let bodyData = data[Int(kHeaderLen)..<data.count] // 去掉头部,只放裸数据
        
        // 这里解析完了,可以用了,我这边是回调出去的
        // 回调 FIXME 非线程安全
        //for item in delegateDicData {
    
    
        //    item.value.onHandleData(header, bodyData)
        //}
        
        return Int(header.length)
    }
    
    return 0
}

En-tête de protocole

Joindre la classe d'analyse de tête que j'ai utilisée, y compris l'écriture et la lecture:

//
//  IMHeader.swift
//  Coffchat
//
//  Created by xuyingchun on 2020/3/12.
//  Copyright © 2020 Xuyingchun Inc. All rights reserved.
//

import Foundation

/// 协议头长度
let kHeaderLen: UInt32 = 16
let kProtocolVersion: UInt16 = 1

/// 消息头部,自定义协议使用TLV格式
class IMHeader {
    
    
    var length: UInt32 = 0 // 4 byte,消息体长度
    var version: UInt16 = 0 // 2 byte,default 1
    var flag: UInt16 = 0 // 2byte,保留
    var serviceId: UInt16 = 0 // 2byte,保留
    var commandId: UInt16 = 0 // 2byte,命令号
    var seqNumber: UInt16 = 0 // 2byte,包序号
    var reversed: UInt16 = 0 // 2byte,保留

    var bodyData: Data? // 消息体

    /// 设置消息ID
    /// - Parameter cmdId: 消息ID
    func setCommandId(cmdId: UInt16) {
    
    
        commandId = cmdId
    }

    /// 设置消息体
    /// - Parameter msg: 消息体
    func setMsg(msg: Data) {
    
    
        bodyData = msg
    }

    /// 设置消息序号,请使用 [SeqGen.singleton.gen()] 生成
    /// - Parameter seq: 消息序列号
    func setSeq(seq: UInt16) {
    
    
        seqNumber = seq
    }

    /// 判断消息体是否完整
    /// - Parameter data: 数据
    class func isAvailable(data: Data) -> Bool {
    
    
        if data.count < kHeaderLen {
    
    
            return false
        }

        let buffer = [UInt8](data)

        // get total len
        var len: UInt32 = UInt32(buffer[0])
        for i in 0...3 {
    
     // 4 Bytes
            len = (len << 8) + UInt32(buffer[i])
        }
        return len <= data.count
    }

    /// 从二进制数据中尝试反序列化Header
    /// - Parameter data: 消息体
    func readHeader(data: Data) -> Bool {
    
    
        if data.count < kHeaderLen {
    
    
            return false
        }

        let buffer = [UInt8](data)

        // get total len
        // 按big-endian读取
        let len: UInt32 = UInt32(buffer[0]) << 24 + UInt32(buffer[1]) << 16 + UInt32(buffer[2]) << 8 + UInt32(buffer[3])
        if len < data.count {
    
    
            return false
        }

// big-endian
//        length(43):
//        - 0 : 0
//        - 1 : 0
//        - 2 : 0
//        - 3 : 43
//
//        version:
//        - 4 : 0
//        - 5 : 1
//
//        flag:
//        - 6 : 0
//        - 7 : 0
//
//        serviceId:
//        - 8 : 0
//        - 9 : 0
//
//        cmdid(257):
//        - 10 : 1
//        - 11 : 1
//
//        seq(3):
//        - 12 : 0
//        - 13 : 3
//
//        reversed:
//        - 14 : 0
//        - 15 : 0

        length = len
        version = UInt16(buffer[4]) << 8 + UInt16(buffer[5]) // big-endian
        flag = UInt16(buffer[6]) << 8 + UInt16(buffer[7])
        serviceId = UInt16(buffer[8]) << 8 + UInt16(buffer[9])
        commandId = UInt16(buffer[10]) << 8 + UInt16(buffer[11])
        seqNumber = UInt16(buffer[12]) << 8 + UInt16(buffer[13])
        reversed = UInt16(buffer[14]) << 8 + UInt16(buffer[15])
        return true
    }

    /// 转成2字节的bytes
    class func uintToBytes(num: UInt16) -> [UInt8] {
    
    
        // big-endian
        var bytes = [UInt8]()
        bytes.append(UInt8(num >> 8) )
        bytes.append(UInt8(num & 0xFF))
        // return [UInt8(truncatingIfNeeded: num << 8), UInt8(truncatingIfNeeded: num)]
        return bytes
    }

    /// 转成 4字节的bytes
    class func uintToFourBytes(num: UInt32) -> [UInt8] {
    
    
        return [UInt8(truncatingIfNeeded: num << 24), UInt8(truncatingIfNeeded: num << 16), UInt8(truncatingIfNeeded: num << 8), UInt8(truncatingIfNeeded: num)]
    }

    /// 获取消息体
    func getBuffer() -> Data? {
    
    
        if bodyData == nil {
    
    
            return nil
        }

        // this.seqNumber = SeqGen.singleton.gen();
        length = kHeaderLen + UInt32(bodyData!.count)
        version = kProtocolVersion

        var headerData = Data()
        headerData.append(contentsOf: IMHeader.uintToFourBytes(num: length)) // 总长度
        headerData.append(contentsOf: IMHeader.uintToBytes(num: version)) // 协议版本号
        headerData.append(contentsOf: IMHeader.uintToBytes(num: flag)) // 标志位
        headerData.append(contentsOf: IMHeader.uintToBytes(num: serviceId))
        headerData.append(contentsOf: IMHeader.uintToBytes(num: commandId)) // 命令号
        headerData.append(contentsOf: IMHeader.uintToBytes(num: seqNumber)) // 消息序号
        headerData.append(contentsOf: IMHeader.uintToBytes(num: reversed))

        return headerData + bodyData!
    }
}

Parmi eux, la fonction isAvailable () peut être utilisée pour déterminer si un paquet de données est complet.

sur

Cela vient de mon projet open source: https://github.com/xmcy0011/CoffeeChat
serveur utilise le
client Golang pour utiliser iOS (Swift) et Flutter (Dart)
est toujours en amélioration continue. . .

Version Swift:
Insérez la description de l'image ici
Version Flutter:
Insérez la description de l'image ici
Insérez la description de l'image ici

Je suppose que tu aimes

Origine blog.csdn.net/xmcy001122/article/details/105182016
conseillé
Classement