Home > Article > PHP Framework > Share Workerman’s custom protocol to solve the sticky and unpacking problem
workermanHow to solve sticky and unpacking of customized protocols? The following article will introduce to you how Workerman's custom protocol solves the problem of sticky packets and unpacking. I hope it will be helpful to everyone.
Since I have recently been using workererman to implement the server side of Unity3D online games, although it can also communicate directly through the TCP protocol , but some minor problems were discovered during the actual test. [Related recommendations: "workermanTutorial"]
For example, are the data packets on both sides in the form of strings? Also, because they are strings, they need to be cut, and sometimes when the customer An error will occur when the client or server receives it. After printing the logs, it was found that the packets received by both ends were in a format that was not agreed upon in advance. This is the TCP sticky and unpacking phenomenon. The solution to this is very simple, and there are many on the Internet, but here I want to use the protocol I implemented to solve it, and I will leave it for later.
I have also seen some conventions on the communication data packet format of online games on the Internet. If you don't use a weakly typed language to do server-side scripts, others often use byte arrays. But when PHP receives the byte array, it is actually a string, but the premise is that the byte array does not have some specific conversion. Take C# as an example. When solving problems such as sticky packets, the byte length (BitConverter.GetBytes (len)) will be added before the byte array. But when this is passed to the PHP server for reception, the first 4 bytes of the string cannot be displayed, and even after many methods of conversion, they cannot be retrieved. Later, I also thought about using the Protobuf data method. Although PHP can convert data, I gave up because I was not familiar with the client C#.
Another problem is that in fact, most of the frame synchronization used by others in online game servers uses the UDP protocol, which is also shared by TCP and UDP. But if it is just a small multiplayer online game, it is completely possible to use PHP as the server and TCP protocol communication. Next, let’s return to workerman’s custom protocol and sticky and unpacking issues.
Workerman has encapsulated several socket functions of PHP (regarding the socket function, if you are willing to bother, PHP can also write a file transfer function Gadget), based on TCP, it also comes with several application layer protocols, such as Http, Websocket, Frame, etc. It also reserves the intersection for users to define their own protocols. They only need to implement its ProtocolInterface interface. The following is a brief introduction to several methods that need to be implemented for the following interfaces.
1. Input method
In this method, the data packet can be unpacked, checked for packet length, filtered, etc. before being received by the server. Returning 0 means putting the data packet into the buffer of the receiving end and continuing to wait. Returning the specified length means taking out the length in the buffer. If there is an exception, you can also return false to directly close the client connection.
2. encode method
This method is the processing of the data packet format by the server before sending the data packet to the client, that is, packet encapsulation. This method is The front and back ends are agreed upon.
3. decode method
This method is also unpacking, which is to take the specified length from the buffer to the place where it needs to be processed before onMessage is received, such as logic Adjustment and so on.
Because TCP is based on streams, and because it is the transport layer, applications in the upper layer use sockets (understood as interface) communication, he does not know where the beginning and end of the passed data packet are. Just send the glue or unbundle according to TCP's set of congestion algorithms. So literally, sticky packets are several data packets sent together. Originally there were two packets, but the client only received one packet. Unpacking is to split a data packet into several packets. It should have received one data packet, but only one was received. Therefore, if this is not solved, as mentioned earlier and the string is transmitted according to the agreement, an error may be reported when unpacking.
1. Add the packet length to the header
<?php /** * This file is part of game. * * Licensed under The MIT License * For full copyright and license information, please see the MIT-LICENSE.txt * Redistributions of files must retain the above copyright notice. * * @author beiqiaosu * @link http://www.zerofc.cn */ namespace Workerman\Protocols; use Workerman\Connection\TcpConnection; /** * Frame Protocol. */ class Game { /** * Check the integrity of the package. * * @param string $buffer * @param TcpConnection $connection * @return int */ public static function input($buffer, TcpConnection $connection) { // 数据包前4个字节 $bodyLen = intval(substr($buffer, 0 , 4)); $totalLen = strlen($buffer); if ($totalLen < 4) { return 0; } if ($bodyLen <= 0) { return 0; } if ($bodyLen > strlen(substr($buffer, 4))) { return 0; } return $bodyLen + 4; } /** * Decode. * * @param string $buffer * @return string */ public static function decode($buffer) { return substr($buffer, 4); } /** * Encode. * * @param string $buffer * @return string */ public static function encode($buffer) { // 对数据包长度向左补零 $bodyLen = strlen($buffer); $headerStr = str_pad($bodyLen, 4, 0, STR_PAD_LEFT); return $headerStr . $buffer; } }
2. Specific character segmentation
<?php namespace Workerman\Protocols; use Workerman\Connection\ConnectionInterface; /** * Text Protocol. */ class Tank { /** * Check the integrity of the package. * * @param string $buffer * @param ConnectionInterface $connection * @return int */ public static function input($buffer, ConnectionInterface $connection) { if (isset($connection->maxPackageSize) && \strlen($buffer) >= $connection->maxPackageSize) { $connection->close(); return 0; } $pos = \strpos($buffer, "#"); if ($pos === false) { return 0; } // 返回当前包长 return $pos + 1; } /** * Encode. * * @param string $buffer * @return string */ public static function encode($buffer) { return $buffer . "#"; } /** * Decode. * * @param string $buffer * @return string */ public static function decode($buffer) { return \rtrim($buffer, "#"); } }
Here we only demonstrate the solution to specific string segmentation, because the 4-byte addition on the homepage above There is still a problem with the longer package. That is, the first time the packet is sent without the packet length, subsequent simulations of sticking or unpacking will stay in the buffer. You can refer to the above code for the following demonstration.
1. Service startup and client connection
2. 服务业务端代码
数据包格式说明一下,字符串以逗号分割,数据包以 #分割,逗号分割第一组是业务方法,如 Login 表示登陆传递,Pos 表示坐标传递,后面带的就是对应方法需要的参数了。
<?php use Workerman\Worker; require_once __DIR__ . '/vendor/autoload.php'; // #### create socket and listen 1234 port #### $worker = new Worker('tank://0.0.0.0:1234'); // 4 processes //$worker->count = 4; $worker->onWorkerStart = function ($connection) { echo "游戏协议服务启动……"; }; // Emitted when new connection come $worker->onConnect = function ($connection) { echo "New Connection\n"; $connection->send("address: " . $connection->getRemoteIp() . " " . $connection->getRemotePort()); }; // Emitted when data received $worker->onMessage = function ($connection, $data) use ($worker, $stream) { echo "接收的数据:" . $data . "\n"; // 简单实现接口分发 $arr = explode(",", $data); if (!is_array($arr) || !count($arr)) { $connection->close("数据格式错误", true); } $func = strtoupper($arr[0]); $client = $connection->getRemoteAddress(); switch($func) { case "LOGIN": $sendData = "Login1"; break; case "POS": $positionX = $arr[1] ?? 0; $positionY = $arr[2] ?? 0; $positionZ = $arr[3] ?? 0; $sendData = "POS,$client,$positionX,$positionY,$positionZ"; break; } $connection->send($sendData); }; // Emitted when connection is closed $worker->onClose = function ($connection) { echo "Connection closed\n"; }; // 接收缓冲区溢出回调 $worker->onBufferFull = function ($connection) { echo "清理缓冲区吧"; }; Worker::runAll(); ?>
3. 粘包测试
只需要在客户端模拟两个数据包连在一起,但是要以 #分隔,看看服务端接收的时候是一几个包进行处理的。
4. 拆包测试
拆包模拟只需要将一个数据包分成两次发送,看看服务端接收的时候能不能显示或者说能不能按约定好的格式正确显示。
更多编程相关知识,请访问:编程教学!!
The above is the detailed content of Share Workerman’s custom protocol to solve the sticky and unpacking problem. For more information, please follow other related articles on the PHP Chinese website!