2.4 写Echo,练习网络编程
游戏服务端要处理客户端请求,作为服务端引擎,网络编程也是Skynet的核心功能。
2.4.1 功能需求
图2-14是开启处理客户端消息的服务,它会把收到的内容原封不动地发回给客户端。
图2-14 Echo程序示意图
2.4.2 学习网络模块
skynet.socket模块提供了网络编程的API,Echo程序会用到它们,如表2-6所示。
表2-6 处理网络消息的API
更多API参见https://github.com/cloudwu/skynet/wiki/Socket,本节暂不列举太多,后面用到时再做介绍。socket.read中所谓的阻塞模式和skynet.call一样,都利用了Lua的协程机制。调用socket.read,服务有可能被挂起,直到接收到数据,才会往下执行。2.9节将对阻塞模式做进一步说明。
2.4.3 代码实现
本例只需开启一个服务。修改主服务Pmain,程序结构如代码2-5所示。先引入skynet和skynet.socket这两个模块,在服务启动后(使用skynet.start的回调方法),依次调用socket.listen和socket.start来监听8888端口。socket.start的回调方法connect见代码2-6。
代码2-5 examples/Pmain.lua
(资源:Chapter2/3_echo.lua)
local skynet = require "skynet" local socket = require "skynet.socket" skynet.start(function() local listenfd = socket.listen("0.0.0.0", 8888) socket.start(listenfd ,connect) end)
新客户端发起连接时,connect方法将被调用。在while循环里,程序先用socket.read接收数据,如果收到数据(if readdata~=nil的真分支),则通过socket.write将数据发回客户端;如果客户端断开了连接(if readdata~=nil的假分支),则调用socket.close关闭连接。代码中的print方法用于打印调试信息,与skynet.error类似。
代码2-6 examples/Pmain.lua
function connect(fd, addr) --启用连接 print(fd.." connected addr:"..addr) socket.start(fd) --消息处理 while true do local readdata = socket.read(fd) --正常接收 if readdata ~= nil then print(fd.." recv "..readdata) socket.write(fd, readdata) --断开连接 else print(fd.." close ") socket.close(fd) end end end
2.4.4 运行结果
执行./skynet examples/Pconfig运行服务端程序。
说明:如果开启服务端时提示“init service failed: ./lualib/skynet/socket.lua:360: Listen error”,意味着监听端口8888被占用,可能是多次运行服务端所致,可以(在测试环境下)执行“killall-9 skynet”关闭所有的Skynet进程。
再启动客户端程序(如telnet),连接服务端。
知识拓展:telnet是Linux下的一个程序,可用于调试TCP连接。如果尚未安装,可在CentOS下执行“yum install telnet”安装。输入“telnet [ip] [端口]”即可向指定服务器发起连接(图2-15所示为连接127.0.0.1:8888),还可以在telnet中输入内容,按回车键可将字符串发给服务端。
如果使用云服务器时能够在本机上调试,但无法跨机器连接,很可能是云服务器防火墙屏蔽了客户端连接,可以设置云服务器的“安全策略”以开放端口。
Echo程序的运行结果如图2-16所示,这里先后开启了两个客户端,分别输入“lpy”和“helloskynet”,服务端将会给出回应。图中客户端部分白色字体代表用户输入,灰色字体代表程序输出,灰色箭头代表消息的流向。
图2-15 向指定服务器发起连接
图2-16 Echo程序的运行结果