UDP数据包是有界的,不存在数据包边界确定的问题,但TCP数据包是无界的,如果使用的自定义格式数据包,就需要自定义协议,一般来说,自定义协议主要有两个工作:
完成数据包的解析
网络事件处理
以一个简单的echo server为例子说明整个过程:
这部分代码已经包含在源码包中:
vendor/beyoio/beyoio/protocol/text/Parser.php中的内容:
<?php
namespace beyod\protocol\text;
use beyod\Connection;
class Parser extends \beyod\Parser
{
//数据包结尾定界符
public $delimiter = "\r\n";
public $max_packet_size = 65536; //设置数据包最大长度
/**
* @param string $buffer
* @param Connection $connection 当前连接,对udp协议此值为null
* @return int
*/
public function input($buffer, $connection){
//调用父类方法,检测数据包是否过大
$len = parent::input($buffer, $connection);
//如果数据中没有CRLF, 返回0,说明完整数据未收到,继续等待下一次数据段到达。
$pos = strpos($buffer, $this->delimiter);
if ($pos === false) {
return 0;
}
//包含CRLF, 说明已经收到完整数据包,返回整个数据包的长度。
return $pos + 2;
}
/**
* 一旦收到数据包后,可以解码处理,因为对我们来说,是不必要关心的,所以清理后返回给handler
*/
public function decode($buffer, $connection){
return trim($buffer);
}
//向客户端发送文本时,自动追加边界符。
public function encode($buffer, $connection){
return $buffer.$this->delimiter;
}
}
vendor/beyoio/beyoio/protocol/text/Handler.php
<?php
namespace beyod\protocol\text;
use Yii;
use yii\base\Event;
use beyod\IOEvent;
use beyod\MessageEvent;
use beyod\CloseEvent;
use beyod\ErrorEvent;
class Handler extends \beyoio\Handler
{
public $tag = null;
public function init()
{
$this->tag = "Beyod Text Echo Server 1.0.1"
."\r\nServer:\t".Yii::$app->server->server_token
."\r\nServer Start At:\t".date('Y-m-d H:i:s')
."\r\nGPID:\t".Yii::$app->server->getGPID()
. "\r\n";
}
/**
* @param IOEvent $event
* {@inheritDoc}
* @see \beyod\Handler::onConnect()
*/
public function onConnect(IOEvent $event)
{
//连接后向当前客户端发送提示信息
$resp = $this->tag."your connection id ".$event->sender."\r\nPlease input message, quit to disconnect:\r\n";
$event->sender->send($resp);
//向其它连接正常的客户端发送上线通知
foreach($event->sender->listenner->connections as $id => $conn){
if($id == $event->sender->id || $conn->isClosed() ) continue;
$conn->send($conn." connected");
}
}
/**
* 当收到数据包时的处理
* @param MessageEvent $event
*/
public function onMessage(MessageEvent $event)
{
//如果收到quit,向其它客户端发送上线通知
if($event->message == 'quit'){
foreach($event->sender->listenner->connections as $id => $conn){
if($id == $event->sender->id || $conn->isClosed() ) continue;
$conn->send($conn.' quit !');
}
//向当前客户端发送消息并断开连接
return $event->sender->close('bye bye !');
}
//向其它客户端发送消息内容,实现消息群聊
foreach($event->sender->listenner->connections as $id => $conn){
if($id == $event->sender->id || $conn->isClosed() ) continue;
$conn->send($conn.": ".$event->message);
}
//当前客户端的提示
$event->sender->send("you said: ".$event->message);
}
//当连接断开时,向其它客户端发送下线通知
public function onClose(CloseEvent $event){
foreach($event->sender->listenner->connections as $id => $conn){
if($id == $event->sender->id || $conn->isClosed() ) continue;
$conn->send($conn." disconnected");
}
}
//当数据包解析错误时,向客户端发送错误消息(并未断开连接)
public function onBadPacket(ErrorEvent $event)
{
$event->sender->send($event->errstr);
}
}
config/main.php中的配置此服务功能:
'components' =>[
'server' => [
//...
'listeners' => [
'text' => [
'class' => 'beyod\Listener',
'listen' => 'tcp://0.0.0.0:7723',
'parser' => 'beyod\protocol\text\Parser',
'handler' => 'beyod\protocol\text\Handler'
],
//其它服务协议
]
]
]
同时打开多个telnet,连接至7723端口即可测试。