早几天写了一篇用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
和一个回调给RESPHandler
的block
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
方法,这是用来获取详细内容的方法。
根据不同的数据类型进行对应的解析:
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