去年在网上发了一篇《我用php构建了魔兽世界服务器,只为证实php是世界上最好的语言》的帖子反应还不错,github骗了不少赞。年初因为疫情在家里待了很久,无聊之中又开了一个新我的项目筹备持续骗赞~哈哈哈
说起传奇,8090的童鞋预计都有所接触,这可是比魔兽世界还早的网络游戏,记得那时候中国区开服,网吧里基本上都是玩传奇的童鞋,楼主那时候还小,一个叔叔带着我玩了一段时间,每次玩到7级就换号,没钱去充值,哈哈哈,然而感觉也很有乐趣~
随着年纪越来越大,工作越来越忙,玩游戏的工夫简直没有,偶然灵机一动去玩玩,感觉也是物是人非,玩着玩着也没什么意思了,然而仍旧喜爱游戏,那就罗唆本人写点游戏过过瘾吧
言归正传,看看怎么用php实现传奇服务端吧
咱们选用的扩大仍然为Swoole,框架应用Hyperf做底层,这个框架不错,很多货色不必本人再去写了,比方连接池、依赖注入容器、注解、AOP面向切面、基于 PSR-15 的中间件、自定义过程等等
相比于之前的魔兽世界模拟器用户验证服务及游戏服务,本次传奇模仿服务端将多个Worker过程作为网关服务,而后将数据包转发到游戏过程(其实就是一个自定义过程),在多过程模式下存在过程内存隔离,不同过程之间无奈同步
对应的解决方案就是应用内部存储服务:
数据库,如:MySQL、MongoDB
缓存服务器,如:Redis、Memcache
磁盘文件,多过程并发读写时须要加锁。
一般的数据库和磁盘文件操作,存在较多 IO 等待时间。
Redis 内存数据库,读写速度十分快,然而有 TCP 连贯等问题,性能也不是最高的。
/dev/shm 内存文件系统,读写操作全副在内存中实现,无 IO 耗费,性能极高,然而数据不是格式化的,还有数据同步的问题。
最初还要扩大内置的Swoole\Table,然而这种计划是须要先设置内存大小和字段类型不够灵便。
最初通过各种折腾决定应用自定义过程多协程的计划,网关能够开任意个过程解决并发业务,自定义过程去解决外围的数据业务,单过程多协程解决内存隔离问题同时也满足了性能要求~
接下来看看TCP数据的解包与封包
传奇应用BinaryReader来进行数据包的封包与解包,但php如同没有相干的类,但能够用pack/unpack函数去封装
<?phpnamespace App\Controller\Packet;class BinaryReader{ private $packetString = [ 'string' => 'c', 'int8' => 'c', 'int16' => 's', 'int32' => 'l', 'int64' => 'q', 'uint8' => 'C', 'uint16' => 'v', 'uint32' => 'V', 'uint64' => 'P', 'bool' => 'c', 'float32' => 'f', ]; public function unPackString(string $type, string $packet) { return unpack($this->packetString[$type], $packet)[1]; } public function packString(string $type, string $packet) { return pack($this->packetString[$type], $packet); } public function read(array $struct, string $packet): array { $data = []; foreach ($struct as $k => $v) { switch ($v) { case 'string': if ($packet) { $len = $this->unPackString($v, $packet); $data[$k] = substr($packet, 1, $len); $packet = substr($packet, $len + 1); } else { $data[$k] = ''; } break; case 'int8': if ($packet) { $data[$k] = $this->unPackString($v, $packet); $packet = substr($packet, 1); } else { $data[$k] = 0; } break; case 'int16': if ($packet) { $data[$k] = $this->unPackString($v, $packet); $packet = substr($packet, 2); } else { $data[$k] = 0; } break; case 'int32': if ($packet) { $data[$k] = $this->unPackString($v, $packet); $packet = substr($packet, 4); } else { $data[$k] = 0; } break; case 'int64': if ($packet) { $data[$k] = $this->unPackString($v, $packet); $packet = substr($packet, 8); } else { $data[$k] = 0; } break; case 'uint8': if ($packet) { $data[$k] = $this->unPackString($v, $packet); $packet = substr($packet, 1); } else { $data[$k] = 0; } break; case 'uint16': if ($packet) { $data[$k] = $this->unPackString($v, $packet); $packet = substr($packet, 2); } else { $data[$k] = 0; } break; case 'uint32': if ($packet) { $data[$k] = $this->unPackString($v, $packet); $packet = substr($packet, 4); } else { $data[$k] = 0; } break; case 'uint64': if ($packet) { $data[$k] = $this->unPackString($v, $packet); $packet = substr($packet, 8); } else { $data[$k] = 0; } break; case '[]int8': if ($packet) { $len = strlen($packet); $info = []; for ($i = 0; $i < $len; $i++) { $info[] = $this->unPackString('int8', $packet); $packet = substr($packet, 1); } $data[$k] = $info; } else { $data[$k] = 0; } break; case '[]int32': if ($packet) { $len = 4; $info = []; for ($i = 0; $i < $len; $i++) { $info[] = $this->unPackString('int8', $packet); $packet = substr($packet, $len); } $data[$k] = $info; } else { $data[$k] = 0; } break; } } return $data; } public function write(array $struct, array $packet) { $data = ''; foreach ($struct as $k => $v) { if (isset($packet[$k]) && $packet[$k] !== null) { if (is_array($v)) { if (!empty($packet[$k][0]) && is_array($packet[$k][0])) { foreach ($packet[$k] as $k1 => $v1) { $data .= $this->write($v, $v1); } } else { $data .= $this->write($v, $packet[$k]); } } else { switch ($v) { case 'string': $len = $this->packString($v, strlen($packet[$k])); $data .= $len . $packet[$k]; break; case 'bool': $packet[$k] = $packet[$k] ? 1 : 0; $data .= $this->packString($v, $packet[$k]); break; case '[]int32': if (is_array($packet[$k])) { foreach ($packet[$k] as $k1 => $v1) { $data .= $this->packString('int32', $v1); } } break; case '[]uint8': if (is_array($packet[$k])) { foreach ($packet[$k] as $k1 => $v1) { $data .= $this->packString('uint8', $v1); } } break; case '[]string': if (is_array($packet[$k])) { foreach ($packet[$k] as $k1 => $v1) { $len = $this->packString('string', strlen($v1)); $data .= $len . $v1; } } break; default: $data .= $this->packString($v, $packet[$k]); break; } } } } return $data; }}
封装了11中数据格式,能够满足大部分游戏的封包及解包了,看看是如何应用的
比方先定义好一个数据包的构造,依据字段对应的类型进行封包或解包就能获取数据
到此就能够去写业务逻辑了~
放出地址,具体代码曾经开源,有须要的童鞋能够去试试,记得点赞哈~求赞求赞求赞:https://github.com/fan3750060/pmir2