ios develops cocoa AsyncSocket and protobuf using swift 5 version, including sticky package and unpacking

Recently, I did some instant messaging. In order to cooperate with the use of the server (netty 4 + protobuf3), I played a trick on the ios client.

protobuf is less used in ios client, less used with cocoa AsyncSocket and less swift version.

In the swift version, there is almost no data for protobuf packaging/unpacking. So share it. I hope it will be helpful to some friends.

1. Import the necessary packages first.

Carthage is used as management, importing cocoa AsyncSocket and protobuf, respectively.

//Using protobuf in swift requires importing swift-protobuf
github "apple/swift-protobuf" ~> 1.0
github "robbiehanson/CocoaAsyncSocket" "master"

How to generate proto file is not introduced here. You can refer to it by yourself. https://github.com/apple/swift-protobuf

2. Go directly to the key code, the comments have been written very clearly.

//
//  SocketClient.swift
//  BestTravel
//
//  Created by gj on 16/11/1.
//
//

import UIKit
import SwiftProtobuf

class SocketClient: NSObject{
    
    fileprivate var clientSocket: GCDAsyncSocket!
    //Data Buffer
    fileprivate var receiveData:Data=Data.init();
  
    //Singleton pattern
    static let sharedInstance=SocketClient();
    private override init() {
        super.init();
        clientSocket = GCDAsyncSocket(delegate: self, delegateQueue: DispatchQueue.main)
    }
    
    
}

extension SocketClient {
    // Start the connection
    func startConnect(){
        startReConnectTimer();
    }
    
    //Disconnect
    func stopConnect(){
        if(clientSocket.isConnected){
            clientSocket.disconnect()
        }
    }
    

}

//MARK: Monitoring
extension SocketClient: GCDAsyncSocketDelegate {
    
    // Disconnect
    func socketDidDisconnect(_ sock: GCDAsyncSocket, withError err: Error?) {
        
    }
    
    // Successful connection
    func socket(_ sock: GCDAsyncSocket, didConnectToHost host: String, port: UInt16) {
   
        
    }
    
    
    // Receive a message
    func socket(_ sock: GCDAsyncSocket, didRead data: Data, withTag tag: Int) {
        
        //Receiving data into buffer
        self.receiveData.append(data);
        
        //Header bytes read from data and length of content read from head
        //Verification results: Header byte occupancy is 1 for data comparison hour and 2 for data comparison.
        var headL:Int  = 0;
        let contentL:Int32 = try! ReadRawVarint32Decode.readRawVarint32(buffer: [UInt8](data), bufferPos: &headL)
        
        //If there is no content, continue receiving
        if (contentL < 1){
            sock.readData(withTimeout: -1, tag: 0);
            return;
        }
        
        //In the case of unpacking: Continue to receive the next message until all unpacking of the message has been received and parsed.
        if (Int32(headL) + contentL > self.receiveData.count){
            sock.readData(withTimeout: -1, tag: 0);
            return;
        }
        
        //When the length of receiveData is not less than the length of the first message content, start parsing receiveData
        self.parseContentData(headL: Int32(headL), contentL: contentL);
        sock.readData(withTimeout: -1, tag: 0);
    }
    
    
    
    //-----------------------------------------------------------------------------
    
    
    /** Parsing Binary Data: NSData - > Custom Model Objects */
    func parseContentData( headL: Int32, contentL: Int32) {
      
        let range = Range(0...Int(headL + contentL-1)) //Scope of data parsing this time
        let data = receiveData.subdata(in: range) //data parsed
        
        do {
            //Converting messages to stream s
            let stream = InputStream.init(data: data);
            stream.open()
            var message = Message();
            //It's important to note that this section must be glued before it can be interpreted properly.
            try BinaryDelimited.merge(into:&message, from: stream)
            stream.close();
            
            //Processing messages, which can write your code, such as sending messages out
            
            
            //Remove parsed data to avoid repeated parsing of messages
            receiveData.removeSubrange(range)
            if (receiveData.count < 1) {
                return
            }
            
            //For multiple messages that are merged in the case of sticky packages, recursion is repeated until all messages are parsed.
            var headL:Int = 0
            let contentL = try ReadRawVarint32Decode.readRawVarint32(buffer: [UInt8](receiveData), bufferPos: &headL)
            if headL + Int(contentL) > receiveData.count {
                return //Actual package is not parsed enough, continue to receive the next package
            }
            
        } catch  {
            print(error)
        }
        
        parseContentData(headL: headL, contentL: contentL) //Continue to parse the next article
    }
    
    
    
}

3. There is a very key class here, which is used to judge prototbuf messages. There is no swift version on the Internet, but an objc version. Here's a swift version written by myself:

//
//  SocketDelegate.swift
//  gim
//
//  Created by imac on 2019/6/21.
//  Copyright © 2019 gim. All rights reserved.
//

import UIKit


class ReadRawVarint32Decode: NSObject {
    
    //static var bufferPos:Int = 0
    
    class  func readRawVarint32(buffer:[UInt8],bufferPos:inout Int) throws -> Int32 {
        var tmp = try readRawByte(buffer: buffer,bufferPos: &bufferPos);
        if (tmp >= 0) {
            return Int32(tmp);
        }
        var result : Int32 = Int32(tmp) & 0x7f;
        tmp = try readRawByte(buffer: buffer,bufferPos: &bufferPos)
        if (tmp >= 0) {
            result |= Int32(tmp) << 7;
        } else {
            result |= (Int32(tmp) & 0x7f) << 7;
            tmp = try readRawByte(buffer: buffer,bufferPos: &bufferPos)
            if (tmp >= 0) {
                result |= Int32(tmp) << 14;
            } else {
                result |= (Int32(tmp) & 0x7f) << 14;
                tmp = try readRawByte(buffer: buffer,bufferPos: &bufferPos)
                if (tmp >= 0) {
                    result |= Int32(tmp) << 21;
                } else {
                    result |= (Int32(tmp) & 0x7f) << 21;
                    tmp = try readRawByte(buffer: buffer,bufferPos: &bufferPos)
                    result |= (Int32(tmp) << 28);
                    if (tmp < 0) {
                        // Discard upper 32 bits.
                        for _ in 0..<5 {
                            let byte = try readRawByte(buffer: buffer,bufferPos: &bufferPos)
                            if (byte >= 0) {
                                return result;
                            }
                        }
                        
                        throw ProtocolBuffersError.invalidProtocolBuffer("MalformedVarint")
                    }
                }
            }
        }
        return result;
    }
    
    class func readRawByte(buffer:[UInt8], bufferPos:inout Int) throws -> Int8 {
        if (bufferPos == buffer.count) {
            return -1
        }
        let res = buffer[Int(bufferPos)]
        bufferPos+=1
        
        var convert:Int8 = 0
        convert = convertTypes(convertValue: res, defaultValue: convert)
        return convert
    }
    
    
    class  func convertTypes<T, ReturnType>(convertValue value:T, defaultValue:ReturnType) -> ReturnType
    {
        var retValue = defaultValue
        var curValue = value
        memcpy(&retValue, &curValue, MemoryLayout<T>.size)
        return retValue
    }
    
    
    
    
    
    public enum ProtocolBuffersError: Error {
        case obvious(String)
        //Streams
        case invalidProtocolBuffer(String)
        case illegalState(String)
        case illegalArgument(String)
        case outOfSpace
    }
    
}

4. It is also important to pay attention to the fact that messages are transmitted by binary system, so encoding is also necessary when they are sent. For example, if 1024 bytes are sent each time, but the length of your message exceeds 1024, then the message will be sent by subcontracting, if encoding is not done first. Then there will be problems with receiving parsing. Therefore, when creating a message, you should add a protobuf header to read the code:

 var messageBuilder = Message.init();
  //Messages need to be converted to data in the following way, otherwise parsing will be problematic
 let stream1 = OutputStream.toMemory()
            stream1.open()
            //crux
            try BinaryDelimited.serialize(message: messageBuilder, to: stream1);
            stream1.close()
            //Converting to data
            let nsData = stream1.property(forKey: .dataWrittenToMemoryStreamKey) as! NSData
            data = Data(referencing: nsData)

 

At this point, basically with the server side of the transceiver has been completed is not a problem.

Tags: Swift less github iOS

Posted on Thu, 08 Aug 2019 21:51:28 -0700 by rayden