TCP控制器

协议规则与解析

假定,客户端与服务端都是明文传输。控制格式为

  1. sericeName:actionName:args

实现解析器

  1. namespace Tcp;
  2. use EasySwoole\Core\Socket\AbstractInterface\ParserInterface;
  3. use EasySwoole\Core\Socket\Common\CommandBean;
  4. class Parser implements ParserInterface
  5. {
  6. public static function decode($raw, $client): ?CommandBean
  7. {
  8. // TODO: Implement decode() method.
  9. $list = explode(":",trim($raw));
  10. $bean = new CommandBean();
  11. $controller = array_shift($list);
  12. if($controller == 'test'){
  13. $bean->setControllerClass(Test::class);
  14. }
  15. $bean->setAction(array_shift($list));
  16. $bean->setArg('test',array_shift($list));
  17. return $bean;
  18. }
  19. public static function encode(string $raw, $client): ?string
  20. {
  21. // TODO: Implement encode() method.
  22. return $raw."\n";
  23. }
  24. }

实现一个控制服务

  1. namespace Tcp;
  2. use EasySwoole\Core\Socket\Response;
  3. use EasySwoole\Core\Socket\AbstractInterface\TcpController;
  4. use EasySwoole\Core\Swoole\ServerManager;
  5. use EasySwoole\Core\Swoole\Task\TaskManager;
  6. class Test extends TcpController
  7. {
  8. function actionNotFound(?string $actionName)
  9. {
  10. $this->response()->write("{$actionName} not found");
  11. }
  12. function test()
  13. {
  14. $this->response()->write(time());
  15. }
  16. function args()
  17. {
  18. var_dump($this->request()->getArgs());
  19. }
  20. function delay()
  21. {
  22. $client = $this->client();
  23. TaskManager::async(function ()use($client){
  24. sleep(1);
  25. Response::response($client,'this is delay message at '.time());//为了保持协议一致,实际生产环境请调用Parser encoder
  26. });
  27. }
  28. function close()
  29. {
  30. $this->response()->write('you are goging to close');
  31. $client = $this->client();
  32. TaskManager::async(function ()use($client){
  33. sleep(2);
  34. ServerManager::getInstance()->getServer()->close($client->getFd());
  35. });
  36. }
  37. function who()
  38. {
  39. $this->response()->write('you fd is '.$this->client()->getFd());
  40. }
  41. }

开启子服务

在EasySwooleEvent中注册。

  1. public static function mainServerCreate(ServerManager $server,EventRegister $register): void
  2. {
  3. // TODO: Implement mainServerCreate() method.
  4. $tcp = $server->addServer('tcp',9502);
  5. EventHelper::registerDefaultOnReceive($register,\Tcp\Parser::class,function($errorType,$clientData,$client){
  6. //第二个回调是可有可无的,当无法正确解析,或者是解析出来的控制器不在的时候会调用
  7. TaskManager::async(function ()use($client){
  8. sleep(3);
  9. \EasySwoole\Core\Socket\Response::response($client,"Bye");
  10. ServerManager::getInstance()->getServer()->close($client->getFd());
  11. });
  12. return "{$errorType} and going to close";
  13. });
  14. }

测试

  1. telnet 127.0.0.1 9502
  2. Trying 127.0.0.1...
  3. Connected to localhost.
  4. Escape character is '^]'.
  5. test
  6. not found
  7. test:test
  8. 1518078988
  9. test:args
  10. test:delay
  11. this is delay message at 1518079006
  12. TARGET_CONTROLLER_NOT_FOUND and going to close
  13. ByeConnection closed by foreign host.

HTTP往TCP推送

HTTP控制器

  1. namespace App\HttpController;
  2. use EasySwoole\Core\Http\AbstractInterface\Controller;
  3. use EasySwoole\Core\Swoole\ServerManager;
  4. class Tcp extends Controller
  5. {
  6. function index()
  7. {
  8. // TODO: Implement index() method.
  9. $this->actionNotFound(null);
  10. }
  11. /*
  12. * 请调用who,获取fd
  13. * http://ip:9501/tpc/push/index.html?fd=xxxx
  14. */
  15. function push()
  16. {
  17. $fd = intval($this->request()->getRequestParam('fd'));
  18. $info = ServerManager::getInstance()->getServer()->connection_info($fd);
  19. if(is_array($info)){
  20. ServerManager::getInstance()->getServer()->send($fd,'push in http at '.time());
  21. }else{
  22. $this->response()->write("fd {$fd} not exist");
  23. }
  24. }
  25. }

实际生产中,一般是用户TCP连接上来后,做验证,然后以userName=>fd的格式,存在redis中,需要http,或者是其他地方,
比如定时器往某个连接推送的时候,就是以userName去redis中取得对应的fd,再send。注意,通过addServer形式创建的子服务器,
以再完全注册自己的网络事件,你可以注册onclose事件,然后在连接断开的时候,删除userName=>fd对应。