阅读 306

SwiftNIO-解析Redis RESP协议(二)

早几天写了一篇用SwitNIO造Redis客户端的轮子,写到了连接Redis服务端,和发送简单的Redis命令,现在开始解析服务端返回来的数据,在造这个轮子的时候我把GitHub上的源码都看了一遍,发现很编码技巧上的东西我都没想过竟然还能这么用..... 果然是见识太少了。

接着上一篇的代码, 我们需要新增一个RESPParser的类用来解析服务端传回来的数据,新增一个RESPValue的枚举用来存储对应的数据,还有一个RESPError的结构体记录解析过程中出现的错误。

RESPValue

public enum RESPValue {
    case SimpleStrings(ByteBuffer)
    case Errors(RESPError)
    case Integer(Int)
    case BulkStrings(ByteBuffer?)
    case Arrays(ContiguousArray<RESPValue>?)
}
复制代码

RESPError

public struct RESPError: Error, CustomStringConvertible {
    var code: Int
    var message: String
    
    public var description: String {
        return "<RESPError> code:\(code) message:\(message)"
    }
}
复制代码

接下来开始RESPParser的编写,先回顾一下RESP协议:

  • 单行字符串(Simple Strings), 开头字符为:'+' "+OK\r\n"
  • 错误信息(Errors),开头字符为:'-' "-Error message\r\n"
  • 整形数字(Integers),开头字符为:':' ":0\r\n"
  • 多行字符串(Bulk Strings),开头字符为:'$' "$6\r\nfoobar\r\n"
  • 数组(Arrays),开头字符为:'*' "*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n"

从例子里面可以看出有两个规律,开头的第一个字符代表数据类型,结尾都是\r\n

先写上一个parse的方法,参数是从RESPHandler传过来的ByteBuffer和一个回调给RESPHandlerblock

public struct RESPParser {

    enum BufferIndex {
        case start
        case content
    }
    
    enum RESPType {
        case SimpleStrings
        case Integers
        case BulkStrings
        case Arrays
    }

    public mutating func parse(_ buffer: ByteBuffer, callback: (RESPValue) -> Void) {
        // 把ByteBuffer转换成[UInt8]
        guard let bytes = buffer.getBytes(at: 0, length: buffer.writerIndex) else {
            callback(.Errors(RESPError(code: 0, message: "read bytes error")))
            return
        }
        
        callback(_read(bytes, buffer))
    }
}
复制代码

这里回调的内容都交给_read方法返回了

BufferIndex枚举代表的是当前解析到的位置是开头还是内容

RESPType枚举代表当前解析的数据的数据类型,由开头第一个字符决定

解析来写_read方法

private func _read(_ bytes: [UInt8], _ buffer: ByteBuffer? = nil) -> RESPValue {
        var type: RESPType!
        var bufferIndex: BufferIndex = .start
        
        for index in 0..<bytes.count {
            switch bufferIndex {
            case .start:
                switch bytes[index] {
                case 58: // :
                    type = .Integers
                case 43: // +
                    type = .SimpleStrings
                case 36: // $
                    type = .BulkStrings
                case 42: // *
                    type = .Arrays
                default:
                    guard let message = buffer?.getString(at: 1, length: bytes.count - 3) else {
                        return .Errors(RESPError(code: 0, message: "read bytes error"))
                    }
                    return .Errors(RESPError(code: 1, message: message))
                }
                bufferIndex = .content
            case .content:
                return  _getContent(by: bytes, type: type)
            }
        }
        
        return .Errors(RESPError(code: 0, message: "read bytes error"))
    }
复制代码

这里开始遍历bytes数组,如果开头的字符代表的是Errors那就直接return了,否则把bufferIndex改为.content进入到下一个case,这里调用了_getContent方法,这是用来获取详细内容的方法。

根据不同的数据类型进行对应的解析:

SimpleStringsIntegers只需把开头的一个字符和结尾的\r\n 除去即可

BulkStrings需要先判断开头字符后的数字,数字表示的字符串的长度,可以分为两种情况,大于0和小于等于0,如果是小于等于0则返回nil(这里我只做了等于0的判断),否则把除去\r\n的其他内容返回


private func _getContent(by bytes: [UInt8], type: RESPType) -> RESPValue {
        var value = ByteBufferAllocator().buffer(capacity: bytes.count)
        var temp = [UInt8]()
        switch type {
        case .SimpleStrings, .Integers:
            for index in 1..<bytes.count - 2 {
                temp.append(bytes[index])
            }
            value.write(bytes: temp)
            return type == .SimpleStrings ? RESPValue.SimpleStrings(value) : RESPValue.Integer(1)
        case .BulkStrings:
            var begin = 0
            var count = 0
            // 从下标为1开始遍历,直到遇到\r(13)\n(10)为止
            for index in 1..<bytes.count {
                if bytes[index] >= 48 && bytes[index] <= 57 {
                    count *= 10
                    count += _byteToInt(bytes[index])
                } else if bytes[index] == 13 && bytes[index + 1] == 10 {
                    begin = index + 2
                    break
                }
            }
            
            if count == 0 {
                return .BulkStrings(nil)
            }
            
            for index in begin..<bytes.count - 2 {
                temp.append(bytes[index])
            }
            value.write(bytes: temp)
            return .BulkStrings(value)
        default:
            return .Arrays(_parseArray(bytes))
        }
        
    private func _byteToInt(_ byte: UInt8) -> Int {
        // 48等于0,57等于9
        return Int(byte) - 48
    }
复制代码

Arrays用了_parseArray方法来解析,首先是要判断数组里面有多少个元素,这跟获取BulkStrings的长度是一样的,从下标为1的元素开始遍历直到遇到\r\n

private func _parseArray(_ bytes: [UInt8]) -> ContiguousArray<RESPValue>? {
        var count = 0
        var beginIndex: Int = 0
        var result = ContiguousArray<RESPValue>()
        
        for index in 1..<bytes.count {
            if bytes[index] >= 48 && bytes[index] <= 57 {
                count *= 10
                count += _byteToInt(bytes[index])
            } else if bytes[index] == 13 && bytes[index + 1] == 10 {
                beginIndex = index + 2
                break
            }
        }
        
        if count == 0 {
            return nil
        }
        
        while result.count != count {
            // 新建一个temp数组来存储每一个元素
            var temp = [UInt8]()
            for index in beginIndex..<bytes.count {
                if bytes[index] == 13 && bytes[index + 1] == 10 {
                    beginIndex = index + 2
                    break
                }
                temp.append(bytes[index])
            }
            // 每个元素分别调用_read方法来解析,最后结果存储到ContiguousArray<RESPValue>
            result.append(_read(temp))
        }
        
        return result
    }
复制代码

现在在RESPHandler写上解析的代码:

class RESPHandler: ChannelDuplexHandler {
    typealias InboundIn = ByteBuffer
    private var parser = RESPParser()
    
    func channelRead(ctx: ChannelHandlerContext, data: NIOAny) {
        let value = unwrapInboundIn(data)
        parser.parse(value) {
            print($0)
        }
    }
}
复制代码

现在运行输出就能看到返回的RESPValue,这一部分到这也就完成了,剩下的就是优化的内容,下一篇就应该是处理从RESPHandler传回Client的内容。

代码已经同步更新到GitHub

文章分类
iOS