Swoole 之网络通信引擎(TCP、HTTP、WebSocket)

1. TCP 服务器;

# 进入项目目录
cd /data/project/test
mkdir -p swoole/demo/server

# 创建文件
vim swoole/demo/server/tcp.php
  • 写入如下内容
<?php
// 参考:https://wiki.swoole.com/wiki/page/476.html
// 创建Server对象,监听 127.0.0.1:9501端口
// 参数传递,参考:https://wiki.swoole.com/wiki/page/14.html
// $serv = new Server(string $host, int $port = 0, int $mode = SWOOLE_PROCESS,
//    int $sock_type = SWOOLE_SOCK_TCP);
// 默认 4 个参数,不传就是默认参数
// $mode 参数默认多进程模式
// $sock_type 类似 TCP、UDP,默认基于 TCP 的服务
$serv = new swoole_server("127.0.0.1", 9501);

// 参考:https://wiki.swoole.com/wiki/page/13.html
$serv->set([
	'worker_num' => 8,        // 需要开启的进程数, cpu 核数的 1 - 4 倍
	'max_request' => 10000	// 每个 worker 进程允许处理的最大用户数
]);

// 监听连接进入事件
// $fd 是客户端连接的唯一标识
// $reactor_id 线程 id
$serv->on('Connect', function ($serv, $fd, $reactor_id) {
    echo "Client: {$reactor_id} - {$fd} - Connect.\n";
});

// 监听数据接收事件
$serv->on('Receive', function ($serv, $fd, $reactor_id, $data) {
    $serv->send($fd, "Server: {$reactor_id} - {$fd} - " . $data);
});

// 监听连接关闭事件
$serv->on('Close', function ($serv, $fd) {
    echo "Client: Close.\n";
});

// 启动服务器 Swoole\Server
$serv->start(); 
  • 实例操作
# 终端 1 操作:
cd /data/project/test/swoole/demo/server/
php tcp.php

# 方法 1 :
# 终端 2 操作:
netstat -anp | grep 9501
# 返回
tcp        0      0 127.0.0.1:9501          0.0.0.0:*               LISTEN      4435/php  

# 方法 2 :或者用客户端连接
# 新开终端 3 操作:
telnet 127.0.0.1 9501
# 返回
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
# 此时终端 1 返回
Client: 0 - 1 - Connect.
# 0 是线程,1 是客户端标识

# 再新开终端 4 (相当于再开一个用户)操作:
telnet 127.0.0.1 9501
# 返回
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
# 此时终端 1 返回
Client: 0 - 2 - Connect.
# 输入信息
test
# 返回
Server: 0 - 2 - test

# 终端 1 关闭 tcp:
ctrl + C
# 此时终端 3 和 4 返回:
Connection closed by foreign host.
# 因为 Server 关闭了, Client 连接失败,就自动关闭

# 另外的知识点:
# 参考: https://wiki.swoole.com/wiki/page/41.html
# tcp.php 中的 “监听连接进入事件” 相当于回调函数,有四种写法
# 参考: https://wiki.swoole.com/wiki/page/458.html
  • 总结
    • 以上通过编写 PHP 代码起一个 TCP 服务器,然后通过 telnet 工具作为客户端去连接 TCP 服务
    • tcp.php 脚本起一个 TCP 服务,这个服务会监听到某一个端口。右下通过 telnet 工具去连 server,把它作为一个客户端。
      在这里插入图片描述

2. TCP 客户端;

  • 通过 client.php 脚本去连接 server
  • 创建文件 tcp_client.php
# 新开一个终端 1:
cd /data/project/test/swoole/demo/
mkdir client
cd client
vim tcp_client.php
  • 写入如下内容
<?php

// 连接 Swoole tcp 服务
// 参考:https://wiki.swoole.com/wiki/page/p-client.html
// 参数传递,参考:https://wiki.swoole.com/wiki/page/29.html
$client = new swoole_client(SWOOLE_SOCK_TCP);
if (!$client->connect('127.0.0.1', 9501)){
    echo '连接失败';
    exit;
}

// php 内置 cli 常量
// Swoole 大部分功能都是在 cli 模式下
fwrite(STDOUT, "请输入消息:");
$msg = trim(fgets(STDIN));

// 发送消息给 tcp server
// 参考:https://wiki.swoole.com/wiki/page/31.html
$client->send($msg);

// 接收来自 server 的数据
$result = $client->recv();
echo $result;
  • 实例操作
# 新开一个终端 2:
# 进入项目目录
cd /data/project/test/swoole/demo/server

# 开启 TCP 服务
php tcp.php

# 回到刚才编辑 tcp_client.php 的终端 1:
php tcp_client.php
# 输出
输入消息:text
# 返回
Server: 0 - 4 - text

# 另外一个知识点:
# tcp.php 里有设置 worker_num = 8
# 在哪里查看实际开启几个进程?
ps aft| grep tcp.php
# 返回
4985 pts/0    S+     0:00  \_ grep --color=auto tcp.php
 4922 pts/1    Sl+    0:00  \_ php tcp.php
 4923 pts/1    S+     0:00      \_ php tcp.php
 4925 pts/1    S+     0:00          \_ php tcp.php
 4926 pts/1    S+     0:00          \_ php tcp.php
 4927 pts/1    S+     0:00          \_ php tcp.php
 4928 pts/1    S+     0:00          \_ php tcp.php
 4929 pts/1    S+     0:00          \_ php tcp.php
 4930 pts/1    S+     0:00          \_ php tcp.php
 4931 pts/1    S+     0:00          \_ php tcp.php
 4932 pts/1    S+     0:00          \_ php tcp.php
 # 8 个进程,和代码设置的 worker_num 一样

3. UDP 服务;

  • UDP 在 Swoole 中和 TCP 非常相似,类基本是一样的,只是传的参数不一样。($socket_type)

4. HTTP 服务;

  • Http Server 在实际工作当中用到的场景还是比较多的,可以作为应用服务器。上一层是通过 Nginx 做代理转发到 Swoole 的 Http Server 到正常的 App 逻辑代码。
    在这里插入图片描述

  • Swoole 的 HttpServer 是基于 Swoole_server,也就是说 Swoole_server 里所有的方法,HttpServer 都是可以使用的。HttpServer 还新增了几个方法、包括一些配置。在 Swoole_server 里,在上层增加了 HTTP 协议,组装成了 HttpServer。下面是请求和响应。对应了两个基础的类库:Swoole_http_request 类和 Swoole_http_response 类。

  • 常规的 HttpServer 是这么一个流程:用户发起了一个请求,服务器是 Nginx,Nginx 会通过 Fastcgi 发到 PHP 里面去,最终执行代码逻辑返回给前端用户。这里面会用到 fpm(fastcgi process manager),是 fastcgi 的一个进程管理器。fpm 会通过用户的配置来管理 fastcgi 进程。常规的 HttpServer 比如 Nginx 会用 fpm 去处理相关内容。用了 Swoole 之后,去做 HttpServer 就不需要用到 fpm 了。它的流程就是 HTTP 请求过来直接走到 Swoole,Swoole 再走 PHP 的正常逻辑。

  • 实例 1:

# 新开一个终端 1:
cd /data/project/test/swoole/demo/server
vim http_server.php
  • 写入如下内容
<?php
// 0.0.0.0 表示监听所有地址:包括本机地址、内网 ip 和外网 ip
// 参考: https://wiki.swoole.com/wiki/page/14.html
// 实例化 http server 对象
$http = new swoole_http_server('0.0.0.0', 8811);
// 绑定 request
// 参考: https://wiki.swoole.com/wiki/page/327.html
$http->on('request', function($request, $response){
	// 参考: https://wiki.swoole.com/wiki/page/339.html
	$response->end("<h1>HTTP SERVER</h1>");
	
	// 2. 获取参数
	// 注意:只能执行一次 end() 方法
	// print_r($request->get);	// 控制台输出
	// $response->end(json_encode($request->get));	// 地址栏输入后,输出字符串
	
	// 3. 插入 cookie
	// 注意:在 end() 方法前执行
	// $response->cookie("hualaoshuan", "xxxxxx", time() + 1800);
	// 还可以通过类似的方法设置 header 头,等等。
});
$http->start();

  • 实例操作 1:
# 终端 1 输入
php http_server.php

# 新开终端 2:
curl http://127.0.0.1:8811
# 返回:
<h1>HTTP SERVER</h1>

# 或者浏览器访问:http://192.168.2.214:8811
# 同样返回:HTTP SERVER
  • 实例 2:
    • 如果请求一个 HTTP 地址比如:http://192.168.2.214:8811/1.html ,这时候需要去返回 1.html 的内容。而不是去走 http_server.php 的内容。这个就非常类似于 Nginx 服务器,当服务器里有静态的内容,最终就不会去走 PHP 的逻辑。
    • Swoole 处理以上问题,原则上是和 Nginx 处理静态资源是一样的,只需要做一个配置就可以了。但是配置有些不一样。
    • 修改 http_server.php
<?php
$http = new swoole_http_server('0.0.0.0', 8811);
// 配置
$http->set([
	'enable_static_handler' => true,
	'document_root'	=> "/data/project/test/swoole/demo/data",	// 设置静态资源存放的路径
]);

$http->on('request', function($request, $response){
	$response->end("<h1>HTTP SERVER</h1>");
});
$http->start();
  • 实例操作 2:
mkdir /data/project/test/swoole/demo/data
vim /data/project/test/swoole/demo/data/index.html
# 写入如下内容
<h1>我是静态资源</h1>

# 重新启动 http_server.php
cd /data/project/test/swoole/demo/server
php http_server.php

# 浏览器访问:http://192.168.2.214:8811/index.html

5. WebSocket 服务基本概述;

什么是 WebSocket
WebSocket 协议是基于 TCP 的一种新的网络协议(HTTP 也基于 TCP)。它实现了浏览器与服务器全双工(full-duplex)通信: 允许你服务器主动发送信息给客户端
WebSocket 可以做类似于服务器去推送消息给 WEB 客户端,WEB 聊天室等。
为什么需要 WebSocket
HTTP 缺陷:HTTP 通信只能由客户端发起。HTTP 协议是做不到服务器主动向客户端推送信息的。HTTP 要做的话,目前的方案是轮询(浏览器每一秒去轮询 HTTP,去查询有没有数据,有的话把数据退给浏览器)。轮询效率很低,非常的耗费资源,因为需要不断的去建立连接。
WebSocket 特点
建立在 TCP 协议之上
性能开销小,通信高效
客户端可以与任意服务器通信
协议标识符 ws、wss(类似 http 和 https)
持久化网络通信协议
总结
WebSocket 是一个长链接,可以通过 WebSocket 去做服务端主动向客户端推送消息,也可以做聊天室
Swoole 提供了 WebSocket 的文档: https://wiki.swoole.com/wiki/page/397.html

6. WebSocket 服务案例实现;

  • 实例
# 新开一个终端 1:
cd /data/project/test/swoole/demo/server
vim ws_server.php
  • 写入如下内容
<?php
// 参考:https://wiki.swoole.com/wiki/page/397.html

// 创建一个对象
$server = new Swoole\WebSocket\Server("0.0.0.0", 8812);
// $server->set([]);

// $server->on('open', function (Swoole\WebSocket\Server $server, $request) {
//     echo "server: handshake success with fd{$request->fd}\n";
// });

// 监听 WebSocket 连接打开事件
$server->on('open', 'onOpen');
function onOpen($server, $request){
    print_R("请求来自" . $request->fd . "\n");  // 打印请求来自哪个客户端
}

// 必须的回调函数,监听 ws 消息事件
// 回调的函数也可以仿照上面的写,看个人喜好
$server->on('message', function (Swoole\WebSocket\Server $server, $frame) {
    echo "receive from {$frame->fd}:{$frame->data},opcode:{$frame->opcode},fin:{$frame->finish}\n";
    // 数据 push 到客户端上
    // 参考:https://wiki.swoole.com/wiki/page/399.html
    $server->push($frame->fd, "hualaoshuan server push success");
});

// 关闭事件
$server->on('close', function ($ser, $fd) {
    echo "client {$fd} closed\n";
});

$server->start();
  • 编写前端 js 代码
vim /data/project/test/swoole/demo/data/ws_client.html 
  • 写入如下内容
<h1>hualaoshuan-swoole-ws测试</h1>

<script>
var wsUrl = "ws://192.168.2.214:8812";

var websocket = new WebSocket(wsUrl);

// 实例对象的 onopen 属性
websocket.onopen = function(evt){
	// 发送数据
	websocket.send("hello world!");
	console.log("connected-swoole-success");
}

// 实例化 onmessage
websocket.onmessage = function(evt){
	console.log("ws-server-return-data:" + evt.data);
}

// 实例化 close
websocket.onclose = function(evt){
	console.log("close");
}

// onerror
websocket.onerror = function(evt, e){
	console.log("error" + evt.data);
}
</script>


  • 实例操作
# 终端 1 输入如下代码,打开客户端
php ws_server.php

# 终端 2 输入
netstat -anp | grep 8812
# 返回
tcp        0      0 0.0.0.0:8812            0.0.0.0:*               LISTEN      5094/php
# 成功监听

# 新开一个终端 3,打开 http server
cd /data/project/test/swoole/demo/client
php http_server.php
# 打开浏览器,访问 http://192.168.2.214:8811/ws_client.html,见下图 1
# 然后返回看终端 1 ,输出内容如下(也可见图 2)
请求来自1
receive from 1:hello world!,opcode:1,fin:1
# 刷新 ws_client.html,再看终端 1
client 1 closed
请求来自2
receive from 2:hello world!,opcode:1,fin:1

在这里插入图片描述
在这里插入图片描述

  • 上面请求的页面 http://192.168.2.214:8811/ws_client.html 是通过 HTTP SERVER 来开启的、然后去加载页面的内容。也可以在 ws_server.php 里设置
<?php
$server = new Swoole\WebSocket\Server("0.0.0.0", 8812);

// 配置
$server->set([
	'enable_static_handler' => true,
	'document_root'	=> "/data/project/test/swoole/demo/data",	// 设置静态资源存放的路径
]);

$server->on('open', 'onOpen');
function onOpen($server, $request){
    print_R("请求来自" . $request->fd . "\n");  // 打印请求来自哪个客户端
}

$server->on('message', function (Swoole\WebSocket\Server $server, $frame) {
    echo "receive from {$frame->fd}:{$frame->data},opcode:{$frame->opcode},fin:{$frame->finish}\n";
    $server->push($frame->fd, "hualaoshuan server push success");
});

$server->on('close', function ($ser, $fd) {
    echo "client {$fd} closed\n";
});

$server->start();
  • 实例操作
# 终端 1 输入如下代码,打开客户端
php ws_server.php
# 访问 http://192.168.2.214:8812/ws_client.html,页面返回如下图
# Reponse Header 和 Request Header 里的 Upgrade 为 websocket
# 终端 1 返回
请求来自3
receive from 3:hello world!,opcode:1,fin:1

在这里插入图片描述

7. WebSocket 服务优化;

  • 用面向对象的思想优化上面的代码
  • 实例
# 新开一个终端 1:
cd /data/project/test/swoole/demo/server
vim ws.php
  • 写入如下内容
<?php

class Ws{

    // CONST HOST = "0.0.0.0";
    // CONST PORT = 8812;

    public $ws = null;

    public function __construct(){
      
        $this->ws = new swoole_websocket_server("0.0.0.0", 8812);
		
		// 对象的不要函数的回调写法,需要实现方法
        $this->ws->on("open", [$this, 'onOpen']);
        $this->ws->on("message", [$this, 'onMessage']);
        $this->ws->on("close", [$this, 'onClose']);

        $this->ws->start();
    }

    // 监听 ws 连接事件
    public function onOpen($ws, $request){
        var_dump($request->fd);
    }

    // 监听 ws 消息事件
    // 当收到来自客户端的消息的时候,会发送后一个消息给客户端
    public function onMessage($ws, $frame){
        echo "server-push-message:{$frame->data}\n";
        $ws->push($frame->fd, "server-push:" . date("Y-m-d H:i:s"));
    }

    // 关闭
    public function onClose($ws, $fd){
        echo "clientId: {$fd} \n";
    }

}

$obj = new Ws();
  • 实例操作
# 终端 1 输入如下代码,打开客户端
php ws.php
# 新开一个终端 2,打开 http server
cd /data/project/test/swoole/demo/client
php http_server.php
# 打开浏览器,访问 http://192.168.2.214:8811/ws_client.html

在这里插入图片描述
在这里插入图片描述

8. task 任务使用。

使用场景
执行耗时的操作(发送邮件、广播等)
Swoole 提供的异步任务处理 task,就是把异步的任务放到 task_work 进程池里面,就不会影响当前请求的处理速度
如何使用
onTask()
onFinish()
设置 task_worker_num
task 可以基于 Http Server、TCP、WebSocket
  • 实例
# 新开一个终端 1:
cd /data/project/test/swoole/demo/server
vim ws.php
  • 修改 ws.php
<?php

class Ws{

    CONST HOST = "0.0.0.0";
    CONST PORT = 8812;

    public $ws = null;

    public function __construct(){
      
        $this->ws = new swoole_websocket_server("0.0.0.0", 8812);
        // 设置
        $this->ws->set([
            'worker_num' => 2,
            'task_worker_num' => 2
        ]);
        $this->ws->on("open", [$this, 'onOpen']);
        $this->ws->on("message", [$this, 'onMessage']);
        // 注册 task 和 finish 事件
        $this->ws->on("task", [$this, 'onTask']);
        $this->ws->on("finish", [$this, 'onFinish']);
        $this->ws->on("close", [$this, 'onClose']);

        $this->ws->start();
    }

    // 监听 ws 连接事件
    public function onOpen($ws, $request){
        var_dump($request->fd);
    }

    // 监听 ws 消息事件
    // 当收到来自客户端的消息的时候,会发送后一个消息给客户端
    public function onMessage($ws, $frame){
        echo "server-push-message:{$frame->data}\n";
        // todo 10s
        // 假设这里的代码逻辑要 10 秒才能返回内容
        // 客户端 ws_client.html 要 10 秒才能收到消息,用户体验很差
        // 此时要用到 task 机制
        $data = [
            'task' => 1,
            'fd'   => $frame->fd
        ];
        $ws->task($data);
        $ws->push($frame->fd, "server-push:" . date("Y-m-d H:i:s"));
    }

    // 参考:https://wiki.swoole.com/wiki/page/54.html
    public function onTask($serv, $task_id, $workerId, $data){
        print_r($data);
        // 模拟耗时场景
        sleep(10);
        // 告诉 worker
        return 'on task finish';

    }

    public function onFinish($serv, $taskId, $data){
        // 这里的 $data 是 onTask() 方法 return 的内容
        echo "taskId:{$taskId}\n";
        echo "finish-data-success:{$data}\n";

    } 

    // 关闭
    public function onClose($ws, $fd){
        echo "clientId: {$fd} \n";
    }

}

$obj = new Ws();
  • 实例操作
# 终端 1 输入如下代码,打开客户端
php ws.php
# 新开一个终端 2,打开 http server
cd /data/project/test/swoole/demo/client
php http_server.php
# 打开浏览器,访问 http://192.168.2.214:8811/ws_client.html

在这里插入图片描述
在这里插入图片描述

发布了119 篇原创文章 · 获赞 12 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/hualaoshuan/article/details/100577782