swoole—csp编程模型

协程

不需要操作系统参与,创建销毁和切换的成本非常低,遇到io会自动让出cpu执行权,交给其它协程去执行。

协程执行流程

Swoole协程

非协程代码:

1
2
3
4
5
6
7
<?php
$start = time();
for ($i = 0; $i < 500; $i++) {
file_get_contents('http://www.easyswoole.com/');
echo '任务' . $i . '完成' . PHP_EOL;
}
echo '非协程总耗时' . (time() - $start) . 's' . PHP_EOL;

执行结果:

1
2
3
4
5
6
任务495完成
任务496完成
任务497完成
任务498完成
任务499完成
非协程总耗时13s

协程代码:

1
2
3
4
5
6
7
8
9
<?php
$start = time();
for ($i = 0; $i < 500; $i++) {
Swoole\Coroutine::create(function () use ($i, $start) {
$client = new Swoole\Coroutine\Http\Client('www.easyswoole.com', 80);
$client->get('/');
echo '任务' . $i . '完成' . '耗时' . (time() - $start) . 's' . PHP_EOL;
});
}

执行结果:

1
2
3
4
5
6
7
任务389完成耗时1s
任务395完成耗时1s
任务434完成耗时1s
任务477完成耗时1s
任务469完成耗时1s
任务385完成耗时1s
任务498完成耗时1s

可以发现速度相当快,但任务的id,不是顺序执行的,这就是遇到了ioswoole底层自动切换让出cpu执行权。

Channle

用于协程间通讯,支持多生产者协程和多消费者协程。底层自动实现了协程的切换和调度。

执行下面代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$start = time();
function task1()
{
Swoole\Coroutine::sleep(3); // 模拟io阻塞
echo "发短信\n";
}

function task2()
{
Swoole\Coroutine::sleep(3); // 模拟io阻塞
echo "发邮件\n";
}

Swoole\Coroutine::create('task1');
Swoole\Coroutine::create('task2');
echo '总耗时' . (time() - $start) . 's' . PHP_EOL;

执行结果:

1
2
3
总耗时0s
发短信
发邮件

却发现以上代码,先执行的echo '总耗时' . (time() - $start) . 's' . PHP_EOL;

要等待task1task2执行成功后输出,该怎么半呢,这就利用了channel,来实现csp并发编程。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php
Swoole\Coroutine::create(function (){
$start = time();
$channel = new Swoole\Coroutine\Channel();
function task1($channel)
{
/** @var Swoole\Coroutine\Channel $channel */
Swoole\Coroutine::sleep(3); // 模拟io阻塞
$channel->push("发短信\n");
}

function task2($channel)
{
/** @var Swoole\Coroutine\Channel $channel */
Swoole\Coroutine::sleep(3); // 模拟io阻塞
$channel->push("发邮件\n");
}

Swoole\Coroutine::create('task1', $channel);
Swoole\Coroutine::create('task2', $channel);

for ($i = 0; $i < 2; $i++) {
echo $channel->pop();
}

echo '总耗时' . (time() - $start) . 's' . PHP_EOL;
});

执行结果:

1
2
3
发短信
发邮件
总耗时3s

可以看到耗时3s,但我们在增加一个任务,for里面的$i就要修改,使得我们的代码非常繁琐,所以就有了WaitGroup

channel可以实现协程通信,依赖管理,协程同步。

实现连接池功能可以看我之前的文章,传送门

WaitGroup

基于Channel实现的Golangsync.WaitGrup功能。

方法:

  • add 方法增加计数
  • done 表示任务已完成
  • wait 等待所有任务完成恢复当前协程的执行
  • WaitGroup 对象可以复用,adddonewait 之后可以再次使用

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php
Swoole\Coroutine::create(function () {

$start = time();
$waitGroup = new Swoole\Coroutine\WaitGroup();
function task1($waitGroup)
{
/** @var WaitGroup $waitGroup */
Swoole\Coroutine::sleep(3); // 模拟io阻塞
echo "发短信\n";
$waitGroup->done();;
}

function task2($waitGroup)
{
/** @var WaitGroup $waitGroup */
Swoole\Coroutine::sleep(3); // 模拟io阻塞
echo "发邮件\n";
$waitGroup->done();

}

$waitGroup->add();
Swoole\Coroutine::create('task1', $waitGroup);

$waitGroup->add();
Swoole\Coroutine::create('task2', $waitGroup);

$waitGroup->wait();

echo '总耗时' . (time() - $start) . 's' . PHP_EOL;
});

执行结果跟之前一样,也是好时3s,但是不是更简单了呢。

Context

协程原有的异步逻辑同步化,但是在协程切换是隐式发生的,所有协程切换的前后不能保证全局遍历及static变量的一致性。

context用协程id做隔离,来保存上下文内容。

代码复现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php
class Email
{
static $email = null;
}

Swoole\Coroutine::create(function () {

function task1($email)
{
Email::$email = $email;
Swoole\Coroutine::sleep(3); // 模拟io阻塞
echo "发邮件:" . Email::$email . PHP_EOL;
}

function task2($email)
{
Email::$email = $email;
echo "发邮件:" . Email::$email . PHP_EOL;

}

Swoole\Coroutine::create('task1', '[email protected]');

Swoole\Coroutine::create('task2', '[email protected]');

});

从感觉啥觉得会输出两个邮箱地址,但其实:

1
2
发邮件:[email protected]
发邮件:[email protected]

这就是变量生命周期,需要注意,我们可以封装一个类来保存上下文。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<?php
class Context
{
/**
* [
* 'cid' => [ // 就是协程的id
* 'key' => 'value' // 保存的全局变量的信息
* ]
* ]
* @var [type]
*/
public static $pool = [];

static function get($key)
{
$cid = Swoole\Coroutine::getuid();// 获取当前运行的协程id
if ($cid < 0) {
return null;
}
if (isset(self::$pool[$cid][$key])) {
return self::$pool[$cid][$key];
}
return null;
}

static function put($key, $item)
{
$cid = Swoole\Coroutine::getuid();// 获取当前运行的协程id
if ($cid > 0) {
self::$pool[$cid][$key] = $item;
}
}

static function delete($key = null)
{
$cid = Swoole\Coroutine::getuid();
if ($cid > 0) {
if ($key) {
unset(self::$pool[$cid][$key]);
} else {
unset(self::$pool[$cid]);
}
}
var_dump(self::$pool);
}
}

运行以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
Swoole\Coroutine::create(function () {

function task1($email)
{
Context::put('email',$email);
Swoole\Coroutine::sleep(3); // 模拟io阻塞
echo "发邮件:" . Context::get('email') . PHP_EOL;
}

function task2($email)
{
Context::put('email',$email);
echo "发邮件:" . Context::get('email') . PHP_EOL;
}

Swoole\Coroutine::create('task1', '[email protected]');

Swoole\Coroutine::create('task2', '[email protected]');

var_dump(Context::$pool);
});

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
发邮件:[email protected]
array(2) {
[2]=>
array(1) {
["email"]=>
string(19) "[email protected]"
}
[3]=>
array(1) {
["email"]=>
string(20) "[email protected]"
}
}
发邮件:[email protected]

可以看到,两个邮箱都输出成功了,但是我们的变量没有销毁,如何销毁呢,Swoole提供了defer方法,在协程关闭之前会调用defer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php
Swoole\Coroutine::create(function () {

function task1($email)
{
Context::put('email',$email);
Swoole\Coroutine::sleep(3); // 模拟io阻塞
echo "发邮件:" . Context::get('email') . PHP_EOL;
Swoole\Coroutine::defer(function (){
Context::delete();
});
}

function task2($email)
{
Context::put('email',$email);
echo "发邮件:" . Context::get('email') . PHP_EOL;
Swoole\Coroutine::defer(function (){
Context::delete();
});
}

Swoole\Coroutine::create('task1', '[email protected]');

Swoole\Coroutine::create('task2', '[email protected]');


});

运行结果:

1
2
3
4
5
6
7
8
9
10
11
发邮件:[email protected]
array(1) {
[2]=>
array(1) {
["email"]=>
string(19) "[email protected]"
}
}
发邮件:[email protected]
array(0) {
}

可以发现到最后为空,已经被清空掉了。

是不是觉得这样写很麻烦,以及不确定在什么时候销毁,然而Swoole提供的Context可以让协程退出后上下文自动清理 (如无其它协程或全局变量引用)。

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
Swoole\Coroutine::create(function () {

function task1($email)
{
$context = Swoole\Coroutine::getContext();
$context->email = $email;
Swoole\Coroutine::sleep(3); // 模拟io阻塞
echo "发邮件:" . $context->email . PHP_EOL;
}

function task2($email)
{
$context = Swoole\Coroutine::getContext();
$context->email = $email;
echo "发邮件:" . $context->email . PHP_EOL;
}

Swoole\Coroutine::create('task1', '[email protected]');

Swoole\Coroutine::create('task2', '[email protected]');

});

sl-im 基于 Swoft 微服务协程框架和 Layim 网页聊天系统 开发出来的聊天室

简介

sl-im 是基于 Swoft 微服务协程框架和 Layim 网页聊天系统 所开发出来的聊天室。

体验地址

sl-im https://im.stitch.cn

演示图

sl-im

功能

  • 登录注册(Http)
  • 单点登录(Websocket)
  • 私聊(Websocket)
  • 群聊(Websocket)
  • 在线人数(Websocket)
  • 获取未读消息(Websocket)
  • 好友在线状态(Websocket)
  • 好友 查找 添加 同意 拒绝(Http+Websocket)
  • 群 创建 查找 添加 同意 拒绝(Http+Websocket)
  • 聊天记录存储
  • 心跳检测
  • 消息重发
  • 断线重连

Requirement

部署方式

Composer

1
composer update

bean

app/bean.php

1
2
3
4
5
6
7
8
9
10
11
12
13
'db' => [
'class' => Database::class,
'dsn' => 'mysql:dbname=im;host=127.0.0.1:3306',
'username' => 'root',
'password' => 'gaobinzhan',
'charset' => 'utf8mb4',
],
'db.pool' => [
'class' => \Swoft\Db\Pool::class,
'database' => bean('db'),
'minActive' => 5, // 自己调下连接池大小
'maxActive' => 10
],

数据表迁移

php bin/swoft mig:up

env配置

vim .env

1
2
3
4
5
6
7
8
9
10
11
# basic
APP_DEBUG=0
SWOFT_DEBUG=0

# more ...
APP_HOST=https://im.stitch.cn/
WS_URL=ws://im.stitch.cn/im
# 是否开启静态处理 这里我关了 让nginx去处理
ENABLE_STATIC_HANDLER=false
# swoole v4.4.0以下版本, 此处必须为绝对路径
DOCUMENT_ROOT=/data/wwwroot/IM/public

nginx配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
server{
listen 80;
server_name im.stitch.cn;
return 301 https://$server_name$request_uri;
}

server{
listen 443 ssl;
root /data/wwwroot/IM/public/;
add_header Strict-Transport-Security "max-age=31536000";
server_name im.stitch.cn;
access_log /data/wwwlog/im-stitch.cn.access.log;
error_log /data/wwwlog/im-stitch.cn.error.log;
client_max_body_size 100m;
ssl_certificate /etc/nginx/ssl/full_chain.pem;
ssl_certificate_key /etc/nginx/ssl/private.key;
ssl_session_timeout 5m;
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
location / {
proxy_pass http://127.0.0.1:9091;
proxy_set_header Host $host:$server_port;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Real-PORT $remote_port;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /im {
proxy_pass http://127.0.0.1:9091;
proxy_http_version 1.1;
proxy_read_timeout 3600s;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location ~ .*\.(js|ico|css|ttf|woff|woff2|png|jpg|jpeg|svg|gif|htm)$ {
root /data/wwwroot/IM/public;
}
}

Start

  • 挂起
1
php bin/swoft ws:start
  • 守护进程化
1
php bin/swoft ws:start -d
  • 访问

怎么访问还用写吗???点个star吧 ✌️

联系方式

  • QQ:975975398

Swoole协程模式实现Mysql连接池

连接池定义

永不断开,要求我们的这个程序是一个常驻内存的程序。数据库连接池(Connection pooling)是程序启 动时建立足够的数据库连接,并将这些连接组成一个连接池,由程序动态地对池中的连接进行申请,使用,释放。

为什么需要连接池?

当并发量很低的时候,连接可以临时建立,但当服务吞吐达到几百、几千的时候,频繁 建立连接 Connect销毁连接 Close 就有可能会成为服务的一个瓶颈,那么当服务启动的时候,先建立好若干个连接并存放于一个队列中,当需要使用时从队列中取出一个并使用,使用完后再反还到队列去,而对这个队列数据结构进行维护的,就是连接池。

使用channel实现连接池

必须在协程模式下

Pool.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
<?php


class PdoPool
{
/**
* @var Swoole\Coroutine\Channel
*/
protected $channel;

/**
* @var int 最大连接数
*/
protected $maxActive = 30;

/**
* @var int 最少连接数
*/
protected $minActive = 10;

/**
* @var int 最大等待连接数
*/
protected $maxWait = 200;

/**
* @var float 最大等待时间 -1 永不超时
*/
protected $maxWaitTime = 1.2;

/**
* @var int 最大空闲时间s
*/
protected $maxIdleTime = 5;

/**
* @var int 自动检查时间ms
*/
protected $checkTime = 3000;

/**
* @var int 当前连接数
*/
protected $count = 0;

/**
* @var self
*/
protected static $instance = null;


/**
* PdoPool constructor.
*/
private function __construct()
{
// 初始化连接池
$this->init();
// 定时器进行空闲连接释放
$this->recovery();
}

/**
* @return PdoPool|null
*/
public static function getInstance()
{
if (!self::$instance instanceof self) {
self::$instance = new self();
}
return self::$instance;
}

/**
* 连接池初始化
*/
protected function init()
{
for ($i = 0; $i < $this->minActive; $i++) {
$connection = $this->getConnection();
if ($connection) $this->channel->push($connection);
}
}

public function getConnection(){
return $this->getConnectionByChannel();
}

private function getConnectionByChannel()
{
// 创建Channel
if ($this->channel === null) {
$this->channel = new Swoole\Coroutine\Channel($this->maxActive);
}

// 小于连接池内最小连接数
if ($this->count < $this->minActive) {
return $this->createConnection();
}

// 取出连接
$connection = null;
if (!$this->channel->isEmpty()) {
$connection = $this->popConnection();
}

//检测连接是否正常
if ($connection !== null && $connection['connection'] instanceof PDO) {
return $connection;
}

//未取出连接 判断是否大于最大连接数进行创建
if ($this->count < $this->maxActive) {
return $this->createConnection();
}


//查看协程挂起数
$stats = $this->channel->stats();
if ($this->maxWait > 0 && $stats['consumer_num'] >= $this->maxWait) {
echo '协程挂起数已大于最大等待数' . PHP_EOL;
}

//重新取出连接
$connection = $this->channel->pop($this->maxWaitTime);
if ($connection == false) {
echo '获取连接失败' . PHP_EOL;
}
return $connection;
}

private function createConnection()
{
// 因为堵塞问题 会造成当前连接数大于最大连接数 先进行++
$this->count++;
try {
$connection = new PDO('mysql:host=localhost;dbname=test', 'root', 'gaobinzhan');
$connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
return [
'connection' => $connection,
'lastUsedTime' => time()
];
} catch (\Throwable $throwable) {
//失败--
$this->count--;
}
}


private function popConnection()
{
while (!$this->channel->isEmpty()) {
$connection = $this->channel->pop();
return $connection;
}
return null;
}

public function freeConnection($connection)
{
//放入连接池
$stats = $this->channel->stats();
if ($stats['queue_num'] < $this->maxActive) {
$connection['lastUsedTime'] = time();
$this->channel->push($connection);
}
}

/**
* 自动回收空闲连接
*/
private function recovery()
{
swoole_timer_tick($this->checkTime, function () {
while ($this->count > $this->minActive && !$this->channel->isEmpty()) {
$connection = $this->channel->pop($this->maxWaitTime);
if (!$connection) {
continue;
}
if ((time() - $connection['lastUsedTime']) > $this->maxIdleTime) {
$this->count--;
$connection['connection'] = null;
echo "回收成功" . PHP_EOL;
} else {
$this->channel->push($connection);
}
}
});
}

private function __clone()
{

}

}

这里生成的是Pdo连接池同理可自行修改createConnection方法生成其它连接池

可用AST语法树进行多种连接池配置

大佬勿喷 嘿嘿!


Swoole处理Tcp粘包问题(面向过程)

TCP通信特点

  • TCP 是流式协议没有消息边界,客户端向服务器端发送一次数据,可能会被服务器端分成多次收到。客户端向服务器端发送多条数据。服务器端可能一次全部收到。
  • 保证传输的可靠性,顺序。
  • TCP拥有拥塞控制,所以数据包可能会延后发送。

TCP粘包

介绍

TCP 粘包是指发送方发送的若干包数据 到 接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。

原因

发送方:发送方需要等缓冲区满才发送出去,造成粘包
接收方:接收方不及时接收缓冲区的包,造成多个包接收
下图为tcp协议在传输数据的过程:
Tcp协议传输数据过程.jpg

swoole处理粘包

  • EOF结束协议:
    EOF切割需要遍历整个数据包的内容,查找EOF,因此会消耗大量CPU资源。上手比较简单

  • 固定包头+包体协议
    长度检测协议,只需要计算一次长度,数据处理仅进行指针偏移,性能非常高

重现TCP粘包问题

服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php

//创建Server对象,监听 127.0.0.1:9501端口
$server = new Swoole\Server("127.0.0.1", 9501);

//监听连接进入事件
$server->on('Connect', function ($server, $fd) {
echo "Client: Connect.\n";
});

//监听数据接收事件
$server->on('Receive', function ($server, $fd, $from_id, $data) {

echo '接到消息'.$data.PHP_EOL;

});

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

//启动服务器
$server->start();

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
$client = new swoole_client(SWOOLE_SOCK_TCP);
//连接到服务器
if (!$client->connect('127.0.0.1', 9501, 0.5)) die("connect failed.");



//向服务器发送数据


for ($i = 0; $i <= 100; $i++) $client->send('12345678');

//从服务器接收数据
$data = $client->recv();
if (!$data) die("recv failed.");



//关闭连接
$client->close();

如下图:此时数据并不是完整的数据
服务端.png

固定包头+包体协议

服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?php

//创建Server对象,监听 127.0.0.1:9501端口
$server = new Swoole\Server("127.0.0.1", 9501);

$server->set(
[
'open_length_check' => true, // 打开包长检测特性
'package_max_length' => 1024 * 1024 * 2, // 包最大长度
'package_length_type' => 'N', // 长度值的类型
'package_length_offset' => 0, // 从第几个字节开始计算长度
'package_body_offset' => 4, // 长度值在包头的第几个字节
]
);

//监听连接进入事件
$server->on('Connect', function ($server, $fd) {
echo "Client: Connect.\n";
});

//监听数据接收事件
$server->on('Receive', function ($server, $fd, $from_id, $data) {
//解包 去掉包头数据
$body = unpack('N',$data);
$content = substr($data,4,reset($body)); // 这里的 4 的计算公式 N 为32字节序 1字节等于8bit 32 / 8 = 4
var_dump($content);

//回复客户端消息 如果客户端设置了 open_length_check 也需要将数据打包
$message = 'this is Server !';
$send = pack('N',strlen($message)).$message;


$server->send($fd,$send);
});

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

//启动服务器
$server->start();

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?php
$client = new swoole_client(SWOOLE_SOCK_TCP);
$client->set(
[
'open_length_check' => true, // 打开包长检测特性
'package_max_length' => 1024 * 1024 * 2, // 包最大长度
'package_length_type' => 'N', // 长度值的类型
'package_length_offset' => 0, // 从第几个字节开始计算长度
'package_body_offset' => 4, // 长度值在包头的第几个字节
]
);
//连接到服务器
if (!$client->connect('127.0.0.1', 9501, 0.5)) die("connect failed.");


$content = 'Hello world';
$body = pack('N', strlen($content)) . $content;

//向服务器发送数据
if (!$client->send($body)) die("send failed.");


//从服务器接收数据
$data = $client->recv();
if (!$data) die("recv failed.");


//解包 去掉包头数据
$body = unpack('N',$data);
$content = substr($data,4,reset($body)); // 这里的 4 的计算公式 N 为32字节序 1字节等于8bit 32 / 8 = 4
var_dump($content);

//关闭连接
$client->close();

swoole-version 4.4.12


Swoole TCP和UDP(同步和异步)

Tcp: 舔狗行为 可靠 先连接然后发消息等待回复

Udp: 渣男行为 不可靠 不需要建立连接 通信不需要一直保持

  • tcp服务端

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <?php

    //创建Server对象,监听 127.0.0.1:9501端口
    $serv = new Swoole\Server("127.0.0.1", 9501);

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

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

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

    //启动服务器
    $serv->start();
  • tcp同步客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
$client = new swoole_client(SWOOLE_SOCK_TCP);

//连接到服务器
if (!$client->connect('127.0.0.1', 9501, 0.5))
{
die("connect failed.");
}
//向服务器发送数据
if (!$client->send("hello world"))
{
die("send failed.");
}
//从服务器接收数据
$data = $client->recv();
if (!$data)
{
die("recv failed.");
}
echo $data.PHP_EOL;
//关闭连接
$client->close();

echo '这里是同步客户端!'.PHP_EOL;
  • tcp异步客户端(只能在cli模式下运行)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php

$client = new Swoole\Async\Client(SWOOLE_SOCK_TCP);

$client->on("connect", function($cli) {
$cli->send("GET / HTTP/1.1\r\n\r\n");
});
$client->on("receive", function($cli, $data){
echo "Receive: $data";
$cli->send(time()."\n");
sleep(1);
});
$client->on("error", function($cli){
echo "error\n";
});
$client->on("close", function($cli){
echo "Connection close\n";
});
$client->connect('127.0.0.1', 9501);

echo '这里是异步客户端'.PHP_EOL;
  • udp服务端
1
2
3
4
5
6
7
8
9
10
11
12
<?php
//创建Server对象,监听 127.0.0.1:9502端口,类型为SWOOLE_SOCK_UDP
$serv = new Swoole\Server("127.0.0.1", 9502, SWOOLE_PROCESS, SWOOLE_SOCK_UDP);

//监听数据接收事件
$serv->on('Packet', function ($serv, $data, $clientInfo) {
$serv->sendto($clientInfo['address'], $clientInfo['port'], "Server ".$data);
var_dump($clientInfo);
});

//启动服务器
$serv->start();
  • udp客户端
1
2
3
4
5
6
<?php
$client = new Swoole\Client(SWOOLE_SOCK_UDP);

$client->sendto('127.0.0.1',9502,'哈哈哈');
$result = $client->recv();
echo $result;

swoole-version 4.4.12