上星期用SwiftNIO写了一套程序,然后需要用到redis做缓存,然后就去GitHub上找了一下发现有一个开源库,但是用的时候发现有Bug,向作者提了issue之后至今没有修复... 所以就自己动手造了个轮子,顺便学习一下。
要做redis客户端的话有两个东西必须要了解,RESP协议和redis命令。
简单介绍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"
需要注意的是不管是发送的命令还是收到来自redis服务端的回复,都是以\r\n
结尾。
Java那边的叫Jedis,用Netty写的叫Nedis,那么我就把这个命名为Sedis了!
首先创建一个struct
,用来存储连接信息:
struct SedisOptions {
let prot: Int
let host: String
var password: String?
var database: Int?
}
然后创建SedisClient类:
这个类里面需要根据SedisOptions
的信息来创建连接,包括身份验证。
class SedisClient {
private let options: SedisOptions
private var bootstrap: ClientBootstrap?
private var loopGroup: EventLoopGroup!
init(options: SedisOptions) {
self.options = options
loopGroup = MultiThreadedEventLoopGroup(numThreads: System.coreCount)
bootstrap = ClientBootstrap(group: loopGroup)
.channelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET),
SO_REUSEADDR), value: 1)
.channelInitializer({ (channel) -> EventLoopFuture<Void> in
channel.pipeline.add(handler: RESPHandler())
})
}
private func _connect() -> EventLoopFuture<Channel> {
assert(bootstrap != nil, "init failure")
return bootstrap!.connect(host: options.host, port: options.prot)
}
}
之前写了一遍用SwiftNIO建立UDP通讯的,用的是DatagramBootstrap
,我们这里需要当作客户端连接,所以用的是ClientBootstrap
。
最后加上一个RESPHandler
class RESPHandler: ChannelDuplexHandler {
typealias InboundIn = ByteBuffer
func channelRead(ctx: ChannelHandlerContext, data: NIOAny) {
var value = unwrapInboundIn(data)
print(value.readString(length: value.writerIndex))
}
}
这里接收到服务端回复后先不做任何操作直接输出。
现在在SedisClient
的init
方法末尾加上一段测试代码测试是否能正常通讯
init(options: SedisOptions) {
self.options = options
loopGroup = MultiThreadedEventLoopGroup(numThreads: System.coreCount)
bootstrap = ClientBootstrap(group: loopGroup)
.channelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET),
SO_REUSEADDR), value: 1)
.channelInitializer({ (channel) -> EventLoopFuture<Void> in
channel.pipeline.add(handler: RESPHandler())
})
let channel = try? _connect().wait()
let command = "set a 1\r\n".utf8
var byteBuffer = ByteBufferAllocator().buffer(capacity: command.count)
byteBuffer.write(bytes: command)
channel?.writeAndFlush(byteBuffer, promise: nil)
try? channel?.closeFuture.wait()
}
测试运行
let sdies = SedisClient(options: SedisOptions(prot: 6379, host: "127.0.0.1", password: nil, database: 0))
可以看到控制台输出
Optional("+OK\r\n")
至此,第一部分就已经finish