SocketChannel

请求回应模式是和外部服务交互时所用到的最常用模式之一,通常的协议设计方式有两种

1.每个请求包对应一个回应包,由TCP协议保证时序,redis的协议就是一个典型,每个redis请求都必须有一个回应,但不必收到回应才可以发送下一个请求.

2.发起每个请求时带一个唯一session标识,在发送回应时,带上这个标识,这样设计可以不要求每个请求都一定要有回应,且不必遵循先提出的请求先回应的时序,MongoDB的通讯协议就是这样设计的.


第一种模式,用skynet的Socket API很容易实现,只要在一个coroutine中读写一个socket就行,但是读的过程是阻塞的,这回导致吞吐量下降,在前一个回应没有收到时,无法发送下一个请求.


第二种模式,需要用skynet.fork开启一个新线程来收取回应包,并自行和请求对应起来,实现比较繁琐.


所以,skynet提供了一个更高层的封装:socket channel.

local sc = require "socketchannel"

local channel = sc.channel {
  host = "127.0.0.1",
  port = 3271,
}

这样就可以创建一个channel对象出来,其中host可以是ip地址或者域名,port是端口号.


这时,如果要在模式1下工作.只要这样

local resp = channel:request(req [, response[, padding]])

这里,req是一个字符串,即请求包,response是一个function,用来收取回应宝,返回值是一个字符串,是由response函数返回的回应包的内容(可以是任意类型),response函数需要定义成这个样子

function response(sock)
  return true, sock:readline()
end
sock是由request方法传入的一个对象,sock有两个方法:read(self,sz)和readline(self,sep).read可以读制定字节数;readline可以读以seq分割(默认为\n)的一个字符串(不包含分割符).


response 函数的第一个返回值需要时一个boolean,如果为true表示协议解析正常;如果为false表示协议出错,这回导致连接断开且让request的调用者也获得一个error.


在response函数内的任何异常以及sock:read或sock:readline读取出错,都一以error的形式抛给request的调用者.



如果协议模式是第2种情况,那么你需要在channel创建时给出一个通用的response解析函数.

local channel = sc.channel {
  host = "127.0.0.1",
  port = 3271,
  response = dispatch,
}
这里dispatch是一个解析回应包的函数,和上面提到的模式1中的解析函数类似,但其返回值需要有三个,第一个是这个回应宝的session,第二个是包是否解析正确(同模式1),第三个是回应内容.


socket channel 就是依靠创建时是否提供response函数来决定工作在模式1还是模式2下.


在模式2下,request的参数有所变化,第2个参数不再是response函数,(它已经在创建时给出),而是一个session.这个session可以是任意类型,但需要和response函数返回的类型一直,socket channel 会帮你匹配session 而让request返回正确的值.


channel:close() 可以关闭一个channel,通常你可以不必主动关闭它,gc会回收channel占用的资源.


socket channel 在创建时,并不会立即建立连接,如果你什么都不做,那么连接建立会推迟到第一次request请求时,这种被动建立的连接过程会不断的尝试,即使第一次没有连接上,也会重试.


可以用channel:connect(true)主动尝试一个,如果失败,抛出error,这里参数true表示只尝试一次,如果不填这个参数,则一直重试下去.


由于连接可能发生在任何request之前,(只要钱一次操作检测到连接时断开状态就是重新发起连接),所以socket channel支持认证流程,允许在建立连接后,立刻做一些交互,如果开启这个功能,需要在创建channel时,填写一个auth函数,.和response函数一样,会给他传入一个sock对象,auth函数不需要返回值,如果认证失败,在auth函数中抛出error即可.


由于对端有可能在任何时候断开连接,所以任何一次request都有可能抛出error而失败,socket channel 将在下一次 request时重新建立连接,注意:重连并不会重发过去发生的error的请求,连接断开并不是隐藏在内部的,所以,如果有必要,你应该在请求失败时,业务层重新提出请求.


socket channel 也可用于仅发包而不接受回应,只需要在request调用时不填写response即可.

channel:response则可以用来单向接受一个包.

channel:request(req)
local resp = channel:response(dispatch)

-- 等价于

local resp = channel:request(req, dispatch)

request还有第三个参数padding,这是用来将体积巨大的消息拆分成多个包发出用的(如果协议支持,),padding需要是一个table,里面有若干字符串.如果提供了padding参数,socket channel 将连同req 以及padding数组里的字符串,利用socket的低优先级通道发出,(使用socket.lwrite).


这种用法下的response函数,应该多返回一个padding值,即,对于模式1返回succ:boolean  data:string   padding:boolean  三个值,对于模式2,返回 session:number succ:boolean data:string  padding:boolean 四个值


padding 标明了后续是否还有该长消息的后续部分.


如果回应消息是由对个短小的消息合成,channel:request 将返回一个table,里面有所有短消息的内容,由调用者来连接这些短消息.


关于socket channel的具体用法除了阅读lualib/socketchannel.lua (同时这也是理解socket模块的好材料)的实现外,也可以阅读lualib/redis.lua和lualib/mongo.lua 这两个为skynet编写的数据库driver.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值