早幾天寫了一篇用SwitNIO造Redis客戶端的輪子,寫到了鏈接Redis服務端,和發送簡單的Redis命令,如今開始解析服務端返回來的數據,在造這個輪子的時候我把GitHub上的源碼都看了一遍,發現很編碼技巧上的東西我都沒想過居然還能這麼用..... 果真是見識太少了。git
接着上一篇的代碼, 咱們須要新增一個RESPParser
的類用來解析服務端傳回來的數據,新增一個RESPValue
的枚舉用來存儲對應的數據,還有一個RESPError
的結構體記錄解析過程當中出現的錯誤。github
public enum RESPValue {
case SimpleStrings(ByteBuffer)
case Errors(RESPError)
case Integer(Int)
case BulkStrings(ByteBuffer?)
case Arrays(ContiguousArray<RESPValue>?)
}
複製代碼
public struct RESPError: Error, CustomStringConvertible {
var code: Int
var message: String
public var description: String {
return "<RESPError> code:\(code) message:\(message)"
}
}
複製代碼
接下來開始RESPParser
的編寫,先回顧一下RESP
協議:數組
"+OK\r\n"
"-Error message\r\n"
":0\r\n"
"$6\r\nfoobar\r\n"
"*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n"
從例子裏面能夠看出有兩個規律,開頭的第一個字符表明數據類型,結尾都是\r\n
app
先寫上一個parse
的方法,參數是從RESPHandler
傳過來的ByteBuffer
和一個回調給RESPHandler
的block
post
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
根據不一樣的數據類型進行對應的解析:
SimpleStrings
和Integers
只需把開頭的一個字符和結尾的\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