skynet框架应用 (十) socketChannel

10 socketChannel

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

  1. 每个请求包对应一个回应包,由 TCP 协议保证时序。

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

​ 对于第一种模式,用 skynet 的 Socket API 很容易实现,但如果在一个 coroutine 中读写一个 socket 的话,由于读的过程是阻塞的,这会导致吞吐量下降(前一个回应没有收到时,无法发送下一个请求,9.8我们就是这么设计的)。

​ 对于第二种模式,需要用 skynet.fork 开启一个新线程来收取回响应包,并自行和请求对应起来,实现比较繁琐,比如9.9中我们遇到的困惑。

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

10.1 第一种模式的socketChannel

示例代码如下:

local skynet = require "skynet"
require "skynet.manager"
local sc = require "skynet.socketchannel"

local channel = sc.channel {  --创建一个 channel 对象出来,其中 host 可以是 ip 地址或者域名,port 是端口号。
  host = "127.0.0.1",
  port = 8001,
}

--接收响应的数据必须这么定义,sock就是与远端的TCP服务相连的套接字,通过这个套接字可以把数据读出来
function response(sock)    
--返回值必须要有两个,第一个如果是true表示响应数据是有效的,
    return true, sock:read()   
end

local function task()
    local resp
    local i = 0
    while(i < 3) do
        --第一参数是需要发送的请求,第二个参数是一个函数,用来接收响应的数据。
        --调用channel:request会自动连接指定的TCP服务,并且发送请求消息。
        --该函数阻塞,返回读到的内容。
        resp = channel:request("data"..i.."\n", response)  
        skynet.error("recv", resp)
        i = i + 1
    end
    --channel:close()   --channel可以不用关闭,当前服务退出的时候会自动关闭掉
    skynet.exit()
end

skynet.start(function()
    skynet.fork(task)
end)

看下运行结果(serverreadline.lua是9.8中编写的):

$ ./skynet examples/config
serverreadline #先运行serverreadline.lua
[:01000010] LAUNCH snlua serverreadline
[:01000010] listen 0.0.0.0:8001
channelclient
[:01000012] LAUNCH snlua channelclient
[:01000010] 127.0.0.1:44098 accepted
[:01000010] recv data0
[:01000012] recv DATA0
[:01000010] recv data1
[:01000012] recv DATA1
[:01000010] recv data2
[:01000012] recv DATA2
[:01000012] KILL self
[:01000010] 127.0.0.1:44098 disconnect

​ sock 是由 request 方法传入的一个对象,sock 有两个方法:read(self, sz)readline(self, sep)

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

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

​ 比如将上面的response函数第一个返回值改为false,运行结果如下:

$ ./skynet examples/config
serverreadline
[:01000010] LAUNCH snlua serverreadline
[:01000010] listen 0.0.0.0:8001
channelclient
[:01000012] LAUNCH snlua channelclient
[:01000010] 127.0.0.1:44120 accepted
[:01000010] recv data0
[:01000012] lua call [0 to :1000012 : 0 msgsz = 24] error : ./lualib/skynet.lua:534: ./lualib/skynet.lua:156: ./lualib/skynet/socketchannel.lua:377: DATA0
stack traceback:
    [C]: in function 'assert'
    ./lualib/skynet/socketchannel.lua:377: in function <./lualib/skynet/socketchannel.lua:360>
    (...tail calls...)
    ./my_workspace/channelclient.lua:19: in upvalue 'func'
    ./lualib/skynet.lua:468: in upvalue 'f'
    ./lualib/skynet.lua:106: in function <./lualib/skynet.lua:105>
stack traceback:
    [C]: in function 'assert'
    ./lualib/skynet.lua:534: in function 'skynet.manager.dispatch_message'

10.2 第二种模式的socketChannel

​ 第二种模式需要在 channel 创建时给出一个通用的 response 解析函数。

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

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

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

示例代码:channelclient2.lua

local skynet = require "skynet"
require "skynet.manager"
local sc = require "skynet.socketchannel"



function dispatch(sock)   
    local r = sock:readline() 
    local session = tonumber(string.sub(r,5))
    return session, true, r  --返回值必须要有三个,第一个session
end
 --创建一个 channel 对象出来,其中 host 可以是 ip 地址或者域名,port 是端口号。
local channel = sc.channel { 
  host = "127.0.0.1",
  port = 8001,
  response = dispatch   --处理消息的函数
}


local function task()
    local resp
    local i = 0
    while(i < 3) do
        skynet.fork(function(session)
            resp = channel:request("data"..session.."\n", session)  
            skynet.error("recv", resp, session)
        end, i)
        i = i + 1
    end
end

skynet.start(function()
    skynet.fork(task)
end)

运行结果:

$ ./skynet examples/config
serverreadline
[:01000010] LAUNCH snlua serverreadline
[:01000010] listen 0.0.0.0:8001
channelclient2
[:01000012] LAUNCH snlua channelclient2
[:01000010] 127.0.0.1:44172 accepted
[:01000010] recv data0
[:01000010] recv data1
[:01000010] recv data2
[:01000012] recv DATA1 1   #能够知道DATA1就是对应session 1的应答
[:01000012] recv DATA2 2
[:01000012] recv DATA0 0

  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值