9.1 skynet.socket 常用api
--这样就可以在你的服务中引入这组 api 。
local socket = require "skynet.socket"
--建立一个 TCP 连接。返回一个数字 id 。
socket.open(address, port)
--关闭一个连接,这个 API 有可能阻塞住执行流。因为如果有其它 coroutine
--正在阻塞读这个 id 对应的连接,会先驱使读操作结束,close 操作才返回。
socket.close(id)
--在极其罕见的情况下,需要粗暴的直接关闭某个连接,而避免 socket.close 的阻塞等待流程,可以使用它。
socket.close_fd(id)
--强行关闭一个连接。和 close 不同的是,它不会等待可能存在的其它 coroutine 的读操作。
--一般不建议使用这个 API ,但如果你需要在 __gc 元方法中关闭连接的话,
--shutdown 是一个比 close 更好的选择(因为在 gc 过程中无法切换 coroutine)。与close_fd类似
socket.shutdown(id)
--[[
从一个 socket 上读 sz 指定的字节数。
如果读到了指定长度的字符串,它把这个字符串返回。
如果连接断开导致字节数不够,将返回一个 false 加上读到的字符串。
如果 sz 为 nil ,则返回尽可能多的字节数,但至少读一个字节(若无新数据,会阻塞)。
--]]
socket.read(id, sz)
--从一个 socket 上读所有的数据,直到 socket 主动断开,或在其它 coroutine 用 socket.close 关闭它。
socket.readall(id)
--从一个 socket 上读一行数据。sep 指行分割符。默认的 sep 为 "\n"。读到的字符串是不包含这个分割符的。
--如果另外一端就关闭了,那么这个时候会返回一个nil,如果buffer中有未读数据则作为第二个返回值返回。
socket.readline(id, sep)
--等待一个 socket 可读。
socket.block(id)
--把一个字符串置入正常的写队列,skynet 框架会在 socket 可写时发送它。
socket.write(id, str)
--把字符串写入低优先级队列。如果正常的写队列还有写操作未完成时,低优先级队列上的数据永远不会被发出。
--只有在正常写队列为空时,才会处理低优先级队列。但是,每次写的字符串都可以看成原子操作。
--不会只发送一半,然后转去发送正常写队列的数据。
socket.lwrite(id, str)
--监听一个端口,返回一个 id ,供 start 使用。
socket.listen(address, port)
--[[
accept 是一个函数。每当一个监听的 id 对应的 socket 上有连接接入的时候,都会调用 accept 函数。
这个函数会得到接入连接的 id 以及 ip 地址。你可以做后续操作。
每当 accept 函数获得一个新的 socket id 后,并不会立即收到这个 socket 上的数据。
这是因为,我们有时会希望把这个 socket 的操作权转让给别的服务去处理。accept(id, addr)
]]--
socket.start(id , accept)
--[[
任何一个服务只有在调用 socket.start(id) 之后,才可以读到这个 socket 上的数据。
向一个 socket id 写数据也需要先调用 start 。
socket 的 id 对于整个 skynet 节点都是公开的。也就是说,你可以把 id 这个数字
通过消息发送给其它服务,其他服务也可以去操作它。skynet 框架是根据调用 start 这个
api 的位置来决定把对应 socket 上的数据转发到哪里去的。
--]]
socket.start(id)
--清除 socket id 在本服务内的数据结构,但并不关闭这个 socket 。
--这可以用于你把 id 发送给其它服务,以转交 socket 的控制权。
socket.abandon(id)
--[[
当 id 对应的 socket 上待发的数据超过 1M 字节后,系统将回调 callback 以示警告。
function callback(id, size) 回调函数接收两个参数 id 和 size ,size 的单位是 K 。
如果你不设回调,那么将每增加 64K 利用 skynet.error 写一行错误信息。
--]]
socket.warning(id, callback)
9.2 写一个skynet TCP监听端
示例代码:socketservice.lua
local skynet = require "skynet"
local socket = require "skynet.socket"
--简单echo服务
function echo(cID, addr)
socket.start(cID)
while true do
local str = socket.read(cID)
if str then
skynet.error("recv " ..str)
socket.write(cID, string.upper(str))
else
socket.close(cID)
skynet.error(addr .. " disconnect")
return
end
end
end
function accept(cID, addr)
skynet.error(addr .. " accepted")
skynet.fork(echo, cID, addr) --来一个连接,就开一个新的协程来处理客户端数据
end
--服务入口
skynet.start(function()
local addr = "0.0.0.0:8001"
skynet.error("listen " .. addr)
local lID = socket.listen(addr)
assert(lID)
socket.start(lID, accept)
end)
运行:
$ ./skynet examples/config socketserver #终端中手动输入该服务,例如,输入“socketserver”回车 [:01000010] LAUNCH snlua socketserver [:01000010] listen 0.0.0.0:8001
来一个c写的客户端:
示例代码:socketclient.c
void* readthread(void* arg) { pthread_detach(pthread_self()); int sockfd = (int)arg; int n = 0; char buf[MAXLINE]; while (1) { n = read(sockfd, buf, MAXLINE); if (n == 0) { printf("the other side has been closed.\n"); close(sockfd); exit(0); } else write(STDOUT_FILENO, buf, n); } return (void*)0; } int main(int argc, char *argv[]) { struct sockaddr_in servaddr; int sockfd; char buf[MAXLINE]; sockfd = socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); servaddr.sin_port = htons(SERV_PORT); connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); pthread_t thid; pthread_create(&thid, NULL, readthread, (void*)sockfd); while (fgets(buf, MAXLINE, stdin) != NULL) write(sockfd, buf, strlen(buf)); close(sockfd); return 0; }
编译与运行:
$ gcc socketclient.c -lpthread -o socketclient $ ./socketclient helloworld #发送请求 HELLOWORLD #收到响应 ^C # ctrl+c终止掉客户端 $
skynet服务器端显示:
socketserver #终端输入 [:01000010] LAUNCH snlua socketserver [:01000010] listen 0.0.0.0:8001 [:01000010] 127.0.0.1:41644 accepted #产生请求 [:01000010] recv helloworld #接受请求,并响应 [:01000010] 127.0.0.1:41644 disconnect #另一端已经断开连接
9.3 socket.readline使用
local skynet = require "skynet"
local socket = require "skynet.socket"
--简单echo服务
function echo(cID, addr)
socket.start(cID)
while true do
local str, endstr = socket.readline(cID)
--local str, endstr = socket.readline(cID, "\n")
if str then
skynet.error("recv " ..str)
socket.write(cID, string.upper(str))
else
socket.close(cID)
if endstr then
skynet.error("last recv " ..endstr)
end
skynet.error(addr .. " disconnect")
return
end
end
end
function accept(cID, addr)
skynet.error(addr .. " accepted")
skynet.fork(echo, cID, addr)
end
--服务入口
skynet.start(function()
local addr = "0.0.0.0:8001"
skynet.error("listen " .. addr)
local lID = socket.listen(addr)
assert(lID)
socket.start(lID, accept)
end)
9.4 socket.readall的使用
local skynet = require "skynet"
local socket = require "skynet.socket"
--简单echo服务
function echo(cID, addr)
socket.start(cID)
local str = socket.readall(cID)
if str then
skynet.error("recv " ..str)
end
skynet.error(addr .. " close")
socket.close(cID)
return
end
function accept(cID, addr)
skynet.error(addr .. " accepted")
skynet.fork(echo, cID, addr)
end
--服务入口
skynet.start(function()
local addr = "0.0.0.0:8001"
skynet.error("listen " .. addr)
local lID = socket.listen(addr)
assert(lID)
socket.start(lID, accept)
end)
9.5 低优先级的发送函数
local skynet = require "skynet"
local socket = require "skynet.socket"
--简单echo服务
function echo(cID, addr)
socket.start(cID)
while true do
local str = socket.read(cID)
if str then
skynet.error("recv " ..str)
--由于cpu处理非常快,无法看到效果,只有当cpu复核过高的时候,才会出现低优先级后发送的现象
socket.lwrite(cID, "l:" .. string.upper(str))
socket.write(cID, "h:" .. string.upper(str))
else
socket.close(cID)
skynet.error(addr .. " disconnect")
return
end
end
end
function accept(cID, addr)
skynet.error(addr .. " accepted")
--如果不开协程,那么同一时刻肯定只能处理一个客户端的连接请求
skynet.fork(echo, cID, addr)
end
--服务入口
skynet.start(function()
local addr = "0.0.0.0:8001"
skynet.error("listen " .. addr)
local lID = socket.listen(addr)
assert(lID)
socket.start(lID, accept)
end)
9.6 skynet的socket代理服务
我们可以把监听的服务拆分为两个,一个是专门负责监听的服务,一旦有新的连接产生,那么监听的服务会启动一个agent服务,专门用来处理数据请求与应答。这样可以让每个服务分工明确,各司其职,服务拆分成更小的服务也便于书写业务逻辑。
示例代码:socketlisten.lua
local skynet = require "skynet"
local socket = require "skynet.socket"
skynet.start(function()
local addr = "0.0.0.0:8001"
skynet.error("listen " .. addr)
local lID = socket.listen(addr)
assert(lID)
socket.start(lID , function(cID, addr)
skynet.error(addr .. " accepted")
skynet.newservice("socketagent", cID, addr)
end)
end)
示例代码:socketagent.lua
local skynet = require "skynet"
local socket = require "skynet.socket"
function echo(cID, addr)
socket.start(cID)
while true do
local str = socket.read(cID)
if str then
skynet.error("recv " ..str)
socket.write(cID, string.upper(str))
else
socket.close(cID)
skynet.error(addr .. " disconnect")
return
end
end
end
local cID, addr = ...
cID = tonumber(cID)
skynet.start(function()
skynet.fork(function()
echo(cID, addr)
skynet.exit()
end)
end)
运行如下:
启动socketlisten
$ ./skynet examples/config socketlisten #终端输入 [:01000010] LAUNCH snlua socketlisten [:01000010] listen 0.0.0.0:8001 [:01000010] 127.0.0.1:41936 accepted #产生新连接 [:01000012] LAUNCH snlua socketagent 9 127.0.0.1:41936 #启动代理服务 [:01000012] recv aaaaaaaaaaa #接受请求,并响应
启动c语言写的socketclient
$ ./socketclient aaaaaaaaaaa #stdin输入aaaaaaaaaaa AAAAAAAAAAA #收到服务器的应答
9.7 转交socket控制权
示例代码:socketabandon.lua
local skynet = require "skynet"
local socket = require "skynet.socket"
skynet.start(function()
local addr = "0.0.0.0:8001"
skynet.error("listen " .. addr)
local lID = socket.listen(addr)
assert(lID)
socket.start(lID , function(cID, addr)
skynet.error(addr .. " accepted")
--当前服务开始使用套接字
socket.start(cID)
local str = socket.read(cID)
if(str) then
socket.write(cID, string.upper(str))
end
--不想使用了,这个时候遗弃控制权
socket.abandon(cID)
skynet.newservice("socketagent", cID, addr) --代理服务不变
end)
end)
运行:先运行socketabandon,再运行c写的socketclient
skynet服务端输出:
$ ./skynet examples/config socketabandon #终端输入 [:01000010] LAUNCH snlua socketabandon #启动socketabandon服务 [:01000010] listen 0.0.0.0:8001 #另外一个终端启动socketclient,这边接受请求,并且使用新的socket,read write,然后遗弃控制权 [:01000010] 127.0.0.1:41950 accepted [:01000012] LAUNCH snlua socketagent 9 127.0.0.1:41950 #启动代理服务,把socket控制权交给它 [:01000012] recv bbbbbbbbbbbbbb [:01000012] 127.0.0.1:41950 disconnect [:01000012] KILL self
socketclient客户端输出:
$ ./socketclient aaaaaaaaaaaaaa AAAAAAAAAAAAAA bbbbbbbbbbbbbb BBBBBBBBBBBBBB ^C
9.8 skynet的TCP主动连接端
1、有些时候服务要跟其他的外部服务进行交互,那么这个时候skynet的服务会是主动去连接的一端。
2、不仅如此,其实skynet中的两个服务也可以通过socket进行通信。
示例代码:socketclient.lua
local skynet = require "skynet"
local socket = require "skynet.socket"
function client(id)
local i = 0
while(i < 3) do
skynet.error("send data"..i)
socket.write(id, "data"..i.."\n")
local str = socket.readline(id)
if str then
skynet.error("recv " .. str)
else
skynet.error("disconnect")
end
i = i + 1
end
socket.close(id) --不主动关闭也行,服务退出的时候,会自动将套接字关闭
skynet.exit()
end
skynet.start(function()
local addr = "127.0.0.1:8001"
skynet.error("connect ".. addr)
local id = socket.open(addr)
assert(id)
--启动读协程
skynet.fork(client, id)
end)
监听端代码如下:serverreadline.lua
local skynet = require "skynet"
local socket = require "skynet.socket"
--简单echo服务
function echo(cID, addr)
socket.start(cID)
while true do
local str = socket.readline(cID)
if str then
skynet.fork(function()
skynet.error("recv " ..str)
skynet.sleep(math.random(1, 5) * 100)
socket.write(cID, string.upper(str) .. "\n")
end)
else
socket.close(cID)
skynet.error(addr .. " disconnect")
return
end
end
end
function accept(cID, addr)
skynet.error(addr .. " accepted")
skynet.fork(echo, cID, addr)
end
--服务入口
skynet.start(function()
local addr = "0.0.0.0:8001"
skynet.error("listen " .. addr)
local lID = socket.listen(addr)
assert(lID)
socket.start(lID, accept)
end)
运行结果:
$ ./skynet examples/config serverreadline #终端输入 [:01000010] LAUNCH snlua serverreadline #启动监听服务 [:01000010] listen 0.0.0.0:8001 socketclient #终端输入 [:01000012] LAUNCH snlua socketclient #启动另一个充当客户端的服务 [:01000012] connect 127.0.0.1:8001 #客户端服务连接监听服务 [:01000010] 127.0.0.1:44034 accepted #监听服务接受连接 [:01000012] send data0 #依次处理 [:01000010] recv data0 [:01000012] recv DATA0 [:01000012] send data1 [:01000010] recv data1 [:01000012] recv DATA1 [:01000012] send data2 [:01000010] recv data2 [:01000012] recv DATA2 [:01000012] KILL self #客户端服务退出,主动断开连接 [:01000010] 127.0.0.1:44034 disconnect #监听服务也关闭连接
需要注意的是:
虽然这样也可以让两个服务之间进行通信,但是如果在同一个节点的服务,通信一般就用lua消息来通信就好,毕竟维护套接字的成本远比本地消息调度要高的多。
9.9 发请求与收应答分离
9.8中的例子我们只能严格按照发送请求,然后等待响应的时序来完成与其他服务的主动交互。现在我们在发送请求的时候启动一个协程,而读取响应的时候也启动一个协程来处理。
代码如下:socketforkclient.lua
local skynet = require "skynet"
local socket = require "skynet.socket"
local function recv(id)
local i = 0
while(i < 3) do
local str = socket.readline(id)
if str then
skynet.error("recv " .. str)
else
skynet.error("disconnect")
end
i = i + 1
end
socket.close(id) --未接收完不要关闭
skynet.exit()
end
local function send(id) --不用管有没接受到数据直接发送三次
local i = 0
while(i < 3) do
skynet.error("send data"..i)
socket.write(id, "data"..i.."\n")
i = i + 1
end
end
skynet.start(function()
local addr = "127.0.0.1:8001"
skynet.error("connect ".. addr)
local id = socket.open(addr)
assert(id)
--启动读协程
skynet.fork(recv, id)
--启动写协程
skynet.fork(send, id)
end)
运行结果(serverreadline.lua还是使用9.8中的例子):
$ ./skynet examples/config serverreadline [:01000010] LAUNCH snlua serverreadline [:01000010] listen 0.0.0.0:8001 socketforkclient [:01000012] LAUNCH snlua socketforkclient [:01000012] connect 127.0.0.1:8001 [:01000010] 127.0.0.1:44072 accepted [:01000012] send data0 #一次性发完三次命令不用等待 [:01000012] send data1 [:01000012] send data2 [:01000010] recv data0 [:01000010] recv data1 [:01000010] recv data2 [:01000019] recv DATA2 #但是接收回来的数据并不是有序的了 [:01000019] recv DATA0 [:01000019] recv DATA1 [:01000012] KILL self [:01000010] 127.0.0.1:44072 disconnect
可以看到发送请求的一方可以不受响应速度的影响直接发送,但是由于每个请求的处理时间是不一样的,所以接受到的响应信息并不是有序的了。这也是这种模型带来的问题,解决办法就是每个请求发送都携带一个session,接受到的响应信息也携带一个session,那么这样我们就能把请求与响应一一对应,在这一节我们不做扩展了。