-
Notifications
You must be signed in to change notification settings - Fork 172
Socket
Bruce edited this page Sep 11, 2023
·
15 revisions
moon框架是多线程的,每个线程都有一个asio::io_context
, 所以运行在worker线程中的所有服务
都有网络通信的能力。框架为lua提供了基础的Socket API,为了方便游戏开发,封装了常用协议的(如websocket),同时也支持编写自定义协议。
moon中socket api 都是异步的(除非有特殊标识), 通用API有:
socket.listen
socket.accept
socket.connect
-
socket.sync_connect
同步连接 -
socket.write
可以发送字符串, 或者buffer指针 -
socket.write_message
直接发送message指针, 减少拷贝 -
socket.write_then_close
发送完毕后, 关闭连接 socket.settimeout
socket.setnodelay
-
socket.set_send_queue_limit
流量控制 socket.close
socket.getaddress
现在支持三种协议(每种协议都标注了特有的socket api):
-
PTYPE_SOCKET_TCP
提供了tcp流式读写相关API, 主要用于解析自定义协议, moon中的数据库client驱动和http-server,http-client都是使用它编写的。socket.read
-
PTYPE_SOCKET_WS
Websocket协议-
socket.write_text
socket.write_ping
socket.write_pong
-
socket.wson
注册websocket网络消息回调 -
socket.start
配合socket.wson
注册的网络事件回调, 自动循环accept
-
-
PTYPE_SOCKET_MOON
tcp协议 2Byte(big-endian)+Data, 常用作网关协议, 更加高效, 数据帧:-
socket.on
注册网络消息回调 -
socket.start
配合socket.on
注册的网络事件回调, 自动循环accept socket.set_enable_chunked
-
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-----------------------------+
| |
| |
| Len(16bit) |
| |
| |
+-+-----------------------------+
: |
+ Data... |
| |
+-------------------------------+
Len
不包括头部2字节, 默认支持最大 65534
大小的数据包, 对于moon
可以使用socket.set_enable_chunked(fd, "w")
设置标记允许对超过这个长度的数据包进行分包 r:表示读,w:表示写。如果客户端需要对接该协议可以参考如下代码
local MESSAGE_CONTINUED_FLAG = 65535
local function send_message(fd,data)
if not fd then
return false
end
local len = #data
local onesize = 0
local offset = 0
repeat
if len >= MESSAGE_CONTINUED_FLAG then --需要分包
onesize = MESSAGE_CONTINUED_FLAG
else
onesize = len
end
socket.write(fd, string.pack(">H",onesize)..string.sub(data, offset+1, offset + onesize))
offset = offset + onesize
len = len - onesize
--print("write", onesize, "left", len, "offset", offset)
until len == 0
if onesize == MESSAGE_CONTINUED_FLAG then --这里注意,如果数据大小刚好是65535的倍数,则需要发送一个header=0的数据包,表示分包结束
socket.write(fd, string.pack(">H", 0))
--print("write", 0)
end
end
local function read_message( fd )
if not fd then
return false
end
local message = {}
repeat
local data,err = socket.read(fd, 2)
if not data then
print(fd,"fd read error",err)
return false
end
local len = string.unpack(">H",data)
local data2,err2 = socket.read(fd, len)
if not data2 then
print(fd,"fd read error",err2)
return false
end
message[#message+1] = data2
--print("recv", len)
until len<MESSAGE_CONTINUED_FLAG --等于65535的包,都是拆分的包,需要全部读完后再合并
return table.concat(message)
end
客户端端想和服务器通信,只需要遵循这个分包协议,可以编写C/C++
, C#
, lua
,python等客户端。数据内容的格式可以自定义,如google protocol buffers
或者json
,或者自定义的协议。
local listenfd = socket.listen(host,port,moon.PTYPE_SOCKET_MOON)
socket.start(listenfd)--auto accept
--注册网络事件
socket.on("accept",function(fd, msg)
print("accept ", fd, moon.decode(msg, "Z"))
socket.settimeout(fd, 10)
--socket.setnodelay(fd)
--socket.set_enable_chunked(fd, "w")
end)
socket.on("message",function(fd, msg)
socket.write(fd, moon.decode(msg, "Z"))
end)
socket.on("close",function(fd, msg)
print("close ", fd, moon.decode(msg, "Z"))
end)
socket.on("error",function(fd, msg)
print("error ", fd, moon.decode(msg, "Z"))
end)
Socket API的使用与常规的socket编程非常类似,并且有一定程度的简化。
下面的代码完全是异步,采用协程封装,编写起来像同步代码一样。
local function send(fd,data)
if not fd then
return false
end
local len = #data
return socket.write(fd, string.pack(">H",len)..data)
end
local function session_read( fd )
if not fd then
return false
end
local data,err = socket.read(fd, 2)
if not data then
print(fd,"fd read error",err)
return false
end
local len = string.unpack(">H",data)
data,err = socket.read(fd, len)
if not data then
print(fd,"fd read error",err)
return false
end
return data
end
moon.async(function()
local fd,err = socket.connect(HOST,PORT,moon.PTYPE_SOCKET_TCP)
if not fd then
print("connect failed", err)
return
end
local send_data = "Hello world"
send(fd, send_data)
local rdata = session_read(fd)
socket.close(fd)
assert(rdata == send_data)
end)
socket.connect
第三个参数表示协议类型。
对于游戏业务来说,一般来说单个线程处理一个进程的网络收发是足够的,但有时需要多线程处理网络消息。Lua Socket API 提供了相应的功能,具体做法是:提前创建好服务,accept
时从这个服务所在的worker申请asio::ip::tcp::socket
对象,并和这个服务绑定,这样就可以把accept
到的连接分散到不同线程中。
local listenfd = socket.listen(host,port,moon.PTYPE_SOCKET_MOON)
local slave = {}
moon.async(function()
for _=1,servicenum do
local sid = moon.new_service({name="slave",file="network_benchmark.lua"})
table.insert(slave,sid)
end
local balance = 1
while true do
if balance>#slave then
balance = 1
end
socket.accept(listenfd,slave[balance])
balance = balance + 1
end
end)
socket.udp
socket.sendto
socket.udp_connect
socket.make_endpoint
socket.close