SwiftNIO-解析Redis RESP協議(二)

早幾天寫了一篇用SwitNIO造Redis客戶端的輪子,寫到了鏈接Redis服務端,和發送簡單的Redis命令,如今開始解析服務端返回來的數據,在造這個輪子的時候我把GitHub上的源碼都看了一遍,發現很編碼技巧上的東西我都沒想過居然還能這麼用..... 果真是見識太少了。git

接着上一篇的代碼, 咱們須要新增一個RESPParser的類用來解析服務端傳回來的數據,新增一個RESPValue的枚舉用來存儲對應的數據,還有一個RESPError的結構體記錄解析過程當中出現的錯誤。github

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\napp

先寫上一個parse的方法,參數是從RESPHandler傳過來的ByteBuffer和一個回調給RESPHandlerblockpost

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枚舉表明的是當前解析到的位置是開頭仍是內容ui

RESPType枚舉表明當前解析的數據的數據類型,由開頭第一個字符決定編碼

解析來寫_read方法spa

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方法,這是用來獲取詳細內容的方法。code

根據不一樣的數據類型進行對應的解析:

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

相關文章
相關標籤/搜索