因为做游戏服务器开发,大多数都跟脚本打交道,要么是lua,要么是python,要么是php,方便热更新的只有lua与php, php相关的游戏服务器开发,参考我另外的文章

https://blog.csdn.net/guoyilongedu/article/details/121049511

lua脚本相关的,自己也实现了一套,只不过不支持多线程,单进程的,基于libevent与luajit,上线过两款游戏项目,功能是挺全的,支持mysql,redis,定时任务相关。后面会贴出来,一起参考一下。

学skynet,因为skynet太出名了,是所有游戏服务器开发人员都绕不过去的,有必要或多或少熟悉一些,最好能自己动手实操一下,才能真正感受到skynet的魅力。

https://github.com/cloudwu/skynet

下载zip 文件,解压

cd skynet    #进入skynet目录
make linux    #编译

注意 因为skynet 用到c++11 相关的特性,所以gcc 必须支持C++11,所以版本必须大于4.9

初始怎么用 参考这篇文章

https://blog.csdn.net/qq_37717687/article/details/121766657

我们要关注的是游戏服务器的搭建,所以第一步 我们要关注的skynet 网络功能

之前用上面链接里面的一个echo 例子 试了一下,发现 一个问题 ,读取出来的数据包不完整 ,代码如下

local skynet = require "skynet"
local socket = require "skynet.socket"local clients = {}function connect(fd, addr)--启用连接,开始等待接收客户端消息print(fd .. " connected addr:" .. addr)socket.start(fd)clients[fd] = {}--消息处理while true dolocal readdata = socket.read(fd) --利用协程实现阻塞模式--正常接收if readdata ~= nil thenprint(fd .. " recv " .. readdata)for k,v in pairs(clients) do --广播socket.write(k, readdata)end--断开连接else print(fd .. " close ")socket.close(fd)clients[fd] = nilend    end
endskynet.start(function()local listenfd = socket.listen("0.0.0.0", 8888) --监听所有ip,端口8888socket.start(listenfd, connect) --新客户端发起连接时,conncet方法将被调用。
end)

这就很要命,研究了一下,是因为 skynet 的网络消息格式是固定的 2字节头(消息长度)+消息内容。

skynet 网络相关的是流式读取,也就是尝试读取的,一个数据包过来,可能要读好几次,至于为什么这么设计,可能是与内存池有关,内存池的分配都是按块的,所以才分批读取,后面单独再研究

单个socket每次从内核尝试读取的数据字节数为sz(第6行),这个值保存在s->p.size中,初始是MIN_READ_BUFFER(64b),当实际读到的数据等于sz时,sz扩大一倍(8-9行);如果小于sz的一半,则设置sz为原来的一半(10-11行)。

比如,客户端发了一个1kb的数据,socket线程会从内核里依次读取64b,128b,256b,512b,64b数据,总共需读取5次,即会向gateserver服务发5条消息,一个TCP包被切割成5个数据块。第5次尝试读取1024b数据,所以可能会读到其他TCP包的数据(只要客户端有发送其他数据)。接下来,客户端再发一个1kb的数据,socket线程只需从内核读取一次即可。

https://www.cnblogs.com/cnxkey/articles/15945319.html

static int
forward_message_tcp(struct socket_server *ss, struct socket *s, struct socket_lock *l, struct socket_message * result) {int sz = s->p.size;char * buffer = MALLOC(sz);int n = (int)read(s->fd, buffer, sz);if (n<0) {FREE(buffer);switch(errno) {case EINTR:break;case AGAIN_WOULDBLOCK:skynet_error(NULL, "socket-server: EAGAIN capture.");break;default:// close when errorforce_close(ss, s, l, result);result->data = strerror(errno);return SOCKET_ERR;}return -1;}if (n==0) {FREE(buffer);force_close(ss, s, l, result);return SOCKET_CLOSE;}if (s->type == SOCKET_TYPE_HALFCLOSE) {// discard recv dataFREE(buffer);return -1;}stat_read(ss,s,n);if (n == sz) {s->p.size *= 2;} else if (sz > MIN_READ_BUFFER && n*2 < sz) {s->p.size /= 2;}result->opaque = s->opaque;result->id = s->id;result->ud = n;result->data = buffer;return SOCKET_DATA;
}

第一次读取的字节 就是 #define MIN_READ_BUFFER 64

每次读满之后,字节扩大一倍

 if (n == sz) {s->p.size *= 2;
}

了解了这里 发现 改成如下这样

local skynet = require "skynet"local socket = require "skynet.socket"
local protobuf = require "protobuf"
local cjson = require "cjson"local clients = {}
local logic_service = nil-- skynet.register_protocol {-- name = "client",-- id = skynet.PTYPE_CLIENT,    -- unpack = skynet.tostring,   --- 将C point 转换为lua 二进制字符串
-- }function connect(fd, addr)-- 启用连接 开始等待接收客户端消息print(" fd = ".. fd .. " connected addr:" .. addr)socket.start(fd)clients[fd] = {}-- 消息处理 不断循环接收while true dolocal readdata = socket.read(fd) -- 利用协程实现阻塞模式-- 正常接收if readdata ~= nil then--local ret_data = "fd = " .. fd .. " ==> " .. string.byte(readdata, 2);--readdata = string.unpack('>', readdata);print("-----------> ret_data " .. readdata);local s = readdata:byte(1) * 256 + readdata:byte(2) --数据包的长度print(" s = " .. s)local s1 = readdata:byte(3) * 256 + readdata:byte(4) --base64的长度print(" s1 = " .. s1)-- local real_data = readdata:sub(5, 100);-- --local base64_data = string.unpack(">s2", real_data)-- print("-----------> ret_data " .. real_data);--readdata = skynet.tostring(readdata)--local data_tb = protobuf.decode("cs.Person", readdata)--print(data_tb)--print(data_tb.name)-- print(data_tb.id)-- for _,v in ipairs(data_tb.phone) do-- print("\t" .. v.number, v.type)-- end--print(fd .. " recv " .. readdata)--local ret_data = "fd = " .. fd .. " ==> " .. readdata;if logic_service ~= nil thenlocal msg_tb = {}msg_tb['fd'] = fdmsg_tb['content'] = readdata;local stringbuffer = protobuf.encode("cs.Person", { name = "linsh", id = 1,email = readdata,}) -- 发送protobuf 数据local ret_msg_protobuf = skynet.call(logic_service, "lua", "doMsg_protobuf_data", stringbuffer)--ret_data = "fd = " .. fd .. " ==> " .. ret_msglocal ret_tb = protobuf.decode("cs.Person", ret_msg_protobuf)print(ret_tb.name)print(ret_tb.id)for _,v in ipairs(ret_tb.phone) doprint("\t" .. v.number, v.type)endret_data = cjson.encode(ret_tb)print(" ret msg from logic_service fd = " .. fd .. " ==> " .. ret_data)endif fd % 2 == 1 thenskynet.sleep(1000)endfor k, v in pairs(clients) dosocket.write(k, ret_data)end-- 断开连接else print(fd .. "close")socket.close(fd)clients[fd] = nilendend
endlocal CMD = {}function CMD.broad_msg_to_user(source, msg_content)print("=========> " .. msg_content)for k, v in pairs(clients) dosocket.write(k, msg_content)end
endskynet.start(function()logic_service = skynet.newservice("logic")local listenfd = socket.listen("0.0.0.0", 8888) -- 监听所有ip,端口8888socket.start(listenfd, connect) --新客户端发起连接时,conncet方法将被调用protobuf.register_file "./examples/protos/Person.pb" skynet.error("register:Person.pb") skynet.dispatch("lua", function(session, source, cmd, ...)local f = assert(CMD[cmd])-- f(source, ...)f(source, ...)end)end)

发现读取时,还是不行, 读取出来的总是不完整,了解到skynet 是分批读取,必须这些分批读取拼接起来,组成一个完整的数据包,所以需要一个netpack的中间层 来拼接数据包

--[[
协议分析:长度信息法
skynet提供的C语言编写的netpack模块,它能高效解析2字节长度信息的协议
]]local skynet = require "skynet"
local socketdriver = require "skynet.socketdriver"
local netpack = require "skynet.netpack"--不能同时包含socketdriver和socket,因为socket里已经register_protocol了socket类型
-- local socket = require "skynet.socket"local queue -- message queue
local clients = {}--解码底层传来的SOCKET类型消息
function socket_unpack(msg, size)return netpack.filter(queue, msg, size)
end--处理底层传来的SOCKET类型消息
function socket_dispatch(_, _, q, type, ...)skynet.error("socket_dispatch type:" .. (type or "nil"))queue = qif type == "open" thenprocess_connect(...)    elseif type == "data" thenprocess_msg(...)    elseif type == "more" thenprocess_more(...)elseif type == "close" thenprocess_close(...)elseif type == "error" thenprocess_error(...)elseif type == "warning" thenprocess_warning(...)end
end--有新连接
function process_connect(fd, addr)skynet.error("new conn fd:" .. fd .. " addr:" .. addr)socketdriver.start(fd)    clients[fd] = {}clients[fd].last_time = skynet.time()
end--关闭连接
function process_close(fd)skynet.error("close fd:" .. fd)if clients[fd] thenclients[fd] = nilend
end--发送错误
function process_error(fd, error)skynet.error("error fd:" .. fd .. " error:" .. error)if clients[fd] thenclients[fd] = nilend
end--发送警告
function process_warning(fd, size)skynet.error("warning fd:" .. fd .. " size:" .. size)
end--刚好收到一条完整消息
function process_msg(fd, msg, size)local str = netpack.tostring(msg, size)skynet.error("recv from fd:" .. fd .. " str:" .. str .. " size = " .. size)for k, v in pairs(clients) doskynet.error(" all fd = " .. k)if k ~= fd then--socket.write(k, msg_content)socketdriver.send(k, str)endendif fd % 2 == 0 then--skynet.sleep(1000)endsocketdriver.send(fd, str)clients[fd].last_time = skynet.time()end--收到多于1条消息时
function process_more()for fd, msg, size in netpack.pop, queue doprint("process_more = fd = " .. fd .. " size = " .. size)skynet.fork(process_msg, fd, msg, size) --开启协程,协程保证了process_msg执行的时序性end
endfunction check_clients(timeout)while true doskynet.sleep(timeout)local now_time = skynet.time()skynet.error(" check clients now time = " .. now_time)local gap_time = 0for k, v in pairs(clients) doskynet.error(" all fd = " .. k .. " last_time = " .. v.last_time)gap_time = now_time - v.last_timeif gap_time >= 30 thensocketdriver.close(k)--clients[k] = nil--socketdriver.shutdown(k)end--socketdriver.send(k, " hearbeat fd = " .. k)endend
endskynet.start(function ()--注册SOCKET类型信息skynet.register_protocol({name = "socket",id = skynet.PTYPE_SOCKET,unpack = socket_unpack,dispatch = socket_dispatch,})--注册Lua类型消息--开启监听local listenfd = socketdriver.listen("0.0.0.0", 8887)socketdriver.start(listenfd)skynet.fork(check_clients, 3000)
end)

这样就能完整收包 解包

客户端测试代码 python脚本 有粘包 也能正常解

import socket
import threading
import struct
import sys
#import Person_pb2global recvThreadExitdef recvMsg(socket_obj):while True:print(" recvThreadExit = " + str(recvThreadExit))if recvThreadExit == 1:breakret = str(obj.recv(1024))print("recv msg :")print(ret)if len(ret) == 0 :#obj.shutdown(socket.SHUT_RDWR)#obj.close()print("close ==>")break# person = Person_pb2.Person()
# person.name = "ayuliao"
# person.id = 6
# person.email = "xxx@xx.com"
#person.phone = "13229483229"
# person_protobuf_data = person.SerializeToString()
#print(f'person_protobuf_data: {person_protobuf_data}')recvThreadExit = 0obj = socket.socket()obj.connect(('192.168.1.200', 8887))recv_thread = threading.Thread(target=recvMsg, args=(obj,))
recv_thread.start()while True:#a = raw_input()#print(a)inp = raw_input("input string: ").strip(' ') if inp == "quit" :#obj.shutdown(socket.SHUT_RDWR)obj.close()recvThreadExit = 1print("sssssssssssssssssseeeeeeeeeee")break#print(inp)##obj.sendall(bytes(inp, encoding="utf-8"))print("len = " + str(len(inp)) )#fmt_str = ">1h1h1h" + str(len(inp)) + "s"fmt_str = ">1h" + str(len(inp)) + "s"print(fmt_str)send_data = struct.pack(fmt_str, len(inp), bytes(inp))print(send_data + " len = " + str(len(send_data)) )obj.send(send_data)#obj.send(send_data)#obj.send(send_data)#obj.send(send_data)#obj.send(send_data)if inp == "q":breakprint(" exti ==>")
sys.exit(3)

网络的一个坑点就是这个,切不可拿其他人的示例来用,要自己多测试。

skynet 游戏服务器探索(1)--熟悉skynet(网络)相关推荐

  1. Skynet 游戏服务器开发实战

    Skynet 是一个使用 C 和 Lua 语言开发的轻量级游戏框架.本次课程中,我们将了解到一个游戏服务器从游戏逻辑方面存在的 5 个模块:注册和登录.网络协议.数据库.玩法逻辑.其他通用模块.并逐步 ...

  2. 游戏服务器开发丨采用skynet手撕万人同时在线游戏丨游戏客户端开发

    用skynet手撕一个万人同时在线游戏 1. 多核并发编程 2. actor详解 3. 游戏实现原理 [技术分享篇]游戏服务器开发丨采用skynet手撕万人同时在线游戏丨游戏客户端开发 更多精彩内容包 ...

  3. php 多人游戏_「谁会是下一个王者农药」云服务器如何搭建游戏服务器?

    手游越来越火了,听听业内人士的分析,他山之石,多多借鉴,那么手游的服务器到底如何搭建的? 从事游戏服务器开发差不多两年时间,两年间参与了不少项目,学到了很多游戏服务器开发技术,参与过几个不同架构的服务 ...

  4. 从零搭建游戏服务器,拢共分几步?

    现代的电子游戏,不管是端游还是手游,网络游戏还是单机游戏,或多或少都会需要一些网络功能.从验证正版,到登录注册,再到多人交互,实时对战,都需要服务器端的支持. 游戏服务器端,是一个长期运行的程序,还要 ...

  5. c++游戏服务器框架

    c++游戏服务器框架 skynet是一个开源的,轻量级的,为在线游戏服务器打造的框架 skynet muduo是一个基于 Reactor 模式的 C++ 网络库 muduo boost asio 是一 ...

  6. golang Leaf 游戏服务器框架简介

    Leaf 是一个由 Go 语言(golang)编写的开发效率和执行效率并重的开源游戏服务器框架.Leaf 适用于各类游戏服务器的开发,包括 H5(HTML5)游戏服务器. Leaf 的关注点: 良好的 ...

  7. 游戏服务器需要什么配置?

    在建站预备上线之前就需要做好服务器的挑选,所选用的游戏服务器是否适合,会影响到后续游戏运行的安稳性.一般状况下应该怎样去挑选适合装备的服务器呢?在装备的挑选上大多时间我们是需要根据用户的相关状况以及游 ...

  8. asp租用和saas租用_租用专用游戏服务器

    asp租用和saas租用 In recent years, nearly the whole gaming industry in world is preparing a game that can ...

  9. 功能服务器比拼大擂台――游戏服务器篇

    功能服务器就整体而言,国内的服务器品牌更显优势,无论是前面介绍的Web服务器.邮件服务器.VOD服务器.数据库服务器,还是本篇将要介绍的游戏服务器都一样,它们基本上都是在机架式服务器架构所诞生的产物, ...

最新文章

  1. 计算机应用基础网络统考操作,全国网络统考《计算机应用基础》完整最新题库及答案[整理].pdf...
  2. html中使用地图和area,根据参数对area的颜色进行改变,AE考试题
  3. 理解oracle中连接和会话
  4. 求两个datetime之间相差的天数
  5. HDR 拍照模式的原理,实现及应用
  6. SpringMVC关于json、xml自动转换的原理研究[附带源码分析 --转
  7. mysql5.7重新初始化_MySQl 5.7 初始化方式变更
  8. ap java内容_AP 计算机知识点总结
  9. nodeJS 的 path.resolve() 用法解析
  10. mysql函数保留小数_MySql自定义函数-关于保留小数位的特殊需求
  11. 【HRBUST - 1623】Relation(思维模拟,拆解字符串)
  12. 标准C语言库 Glibc 2.15
  13. SilverLight Test
  14. UDP套接字编程以及提高UDP可靠性的方法
  15. 中文文本聚类(切词以及Kmeans聚类)
  16. 51php服务器稳不稳定,百度经验:两步搞定PHP-FPM优化,让服务器更平稳
  17. 关于http的一点常识
  18. word-wrap控制长单词或URL地址换行
  19. 电脑的计算机自动打开文件,为什么电脑开机后会有一个文件夹自动打开
  20. JavaSpring全面总结

热门文章

  1. Python改变时间颗粒度
  2. 那些年解的疑难性能问题 --- ext4碎片整理
  3. 终端/SSH/Telnet ConnectBot v1.7.1中文版
  4. 景观智慧路灯:基于景观智慧灯杆的浙江嘉兴智慧旅游景区项目分享
  5. Coursera | Andrew Ng (02-week-1-1.12)—梯度的数值逼近
  6. php抓取页面方法汇总
  7. 做为中层管理者的你,应该扮演什么角色
  8. 为网站配置免费的HTTPS证书 1-4
  9. 华为matepad 鸿蒙,鸿蒙阵营再添一员猛将!华为全新平板曝光
  10. vcenter中修改vm配置硬盘失败问题分析处理