同步代码
- server.php
<?php
/**
* Created by PhpStorm.
* User: niuyueyang
* Date: 2019/3/19
* Time: 21:37
*/
//tcp协议
$server=new Swoole\Server("0.0.0.0",9800); //创建server对象
$server->set([
'worker_num'=>2, //设置进程
]);
//监听事件,连接事件
$server->on('connect',function ($server,$fd){
echo "新的连接进入:{$fd}".PHP_EOL;
});
//消息发送过来
$server->on('receive',function (swoole_server $server, int $fd, int $reactor_id, string $data){
echo "消息发送过来:".$fd.PHP_EOL;
$server->send($fd,'我是服务端');
});
//消息关闭
$server->on('close',function (){
echo "消息关闭".PHP_EOL;
});
//服务器开启
$server->start();
- client.php
<?php
/**
* Created by PhpStorm.
* User: niuyueyang
* Date: 2019/3/19
* Time: 23:05
*/
$client=new swoole\Client(SWOOLE_SOCK_TCP,SWOOLE_SYNC);
$client->connect('118.24.109.254',9800) || exit("连接失败");
//(fd+id)识别身份
//发数据
$client->send("我是客户端");
echo $client->recv(); //接收消息
//关闭
$client->close(); //
异步
- server.php
<?php
//tcp协议
$server=new Swoole\Server("0.0.0.0",9800); //创建server对象
//heartbeat_idle_time一般设置heartbeat_check_interval两倍多一点
$server->set([
'worker_num'=>1, //设置进程
'heartbeat_idle_time'=>10,//连接最大的空闲时间
'heartbeat_check_interval'=>3 //服务器定时检查
]);
//监听事件,连接事件
$server->on('connect',function ($server,$fd){
echo "新的连接进入:{$fd}".PHP_EOL;
});
//消息发送过来
$server->on('receive',function (swoole_server $server, int $fd, int $reactor_id, string $data){
echo "消息发送过来:".$fd.PHP_EOL;
//服务端
//$server->send($fd,'我是服务端');
});
//消息关闭
$server->on('close',function (){
echo "消息关闭".PHP_EOL;
});
//服务器开启
$server->start();
- client.php
<?php
$client = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_ASYNC);
//连接事件回调(必须注册所有事件)
$client->on("connect", function(swoole_client $cli){
$cli->send("GET / HTTP/1.1\r\n\r\n");
//sleep(5);
});
//异步回调客户端
$client->on("receive", function(swoole_client $cli, $data){
echo "Receive: $data";
//$cli->send(str_repeat('A', 100)."\n");
});
$client->on("error", function(swoole_client $cli){
echo "error\n";
});
$client->on("close", function(swoole_client $cli){
echo "Connection close\n";
});
$client->connect('127.0.0.1', 9800) || exit("");
//定时器,保持长连接
swoole_timer_tick(9000,function () use($client){
$client->send('1');
});
echo "写日志";
echo "请求api接口";
UDP
- server.php
<?php
//udp协议
$server=new Swoole\Server("0.0.0.0",9800,SWOOLE_PROCESS,SWOOLE_SOCK_UDP); //创建server对象
$server->set([
'worker_num'=>1, //设置进程
'heartbeat_idle_time'=>10,//连接最大的空闲时间
'heartbeat_check_interval'=>3 //服务器定时检查
]);
//客户端服务端没有任何联系
//制定地址跟端口,不关心消息是否发送成功
//心跳检测不能影响到客户端
//udp建立长连接
//监听事件,
$server->on('packet',function ($server,$data,$clientInfo){
var_dump($data,$clientInfo);
$server->sendto($clientInfo['address'],$clientInfo['port'],"服务端数据包");
});
//服务器开启
$server->start();
- client.php
<?php
$client=new swoole\Client(SWOOLE_SOCK_UDP);
//(fd+id)识别身份
//发数据
$client->sendto('127.0.0.1',9800,"我是客户端");
echo $client->recv(); //接收消息没有接收
心跳检测
swoole会在主进程独立起一个心跳线程,通过定时轮询所有的连接,来判断连接的生死,所以swoole的心跳不会堵塞任何业务逻辑。
设置完成了之后,你会发现设置了定时检测之后,如果客户端没在规定的时间之内发送数据就会关闭。
heartbeat_check_interval: 服务器定时检测在线列表的时间
heartbeat_idle_time: 连接最大的空闲时间 (如果最后一个心跳包的时间与当前时间之差超过这个值,则认为该连接失效)
配置建议
建议 heartbeat_idle_time 为 heartbeat_check_interval 的两倍多一点。
这个两倍是为了进行容错,允许丢一个包而多一点是考虑到网络的延时。
TCP与UDP理解
1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接
2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付
3、tcp通过校验和,重传控制,序号标识,滑动窗口、确认应答实现可靠传输。如丢包时的重发控制,还可以对次序乱掉的分包进行顺序控制。
3、UDP具有较好的实时性,工作效率比TCP高,适用于对高速传输和实时性有较高的通信或广播通信。
4、TCP对系统资源要求较多,UDP对系统资源要求较少。
TCP通信特点
- TCP 是流式协议没有消息边界,客户端向服务器端发送一次数据,可能会被服务器端分成多次收到。客户端向服务器端发送多条数据。服务器端可能一次全部收到。
- .保证传输的可靠性,顺序。
- TCP拥有拥塞控制,所以数据包可能会延后发送。
TCP 粘包是指发送方发送的若干包数据 到 接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。
TCP 出现粘包的原因?
发送方:发送方需要等缓冲区满才发送出去,造成粘包
接收方:接收方不及时接收缓冲区的包,造成多个包接收
通常我们直觉性的认为,客户端直接向网络中传输数据,对端从网络中读取数据,但是这是不正确的。
socket有缓冲区buffer的概念,每个TCP socket在内核中都有一个发送缓冲区和一个接收缓冲区。客户端send操作仅仅是把数据拷贝到buffer中,也就是说send完成了,数据并不代表已经发送到服务端了,之后才由TCP协议从buffer中发送到服务端。此时服务端的接收缓冲区被TCP缓存网络上来的数据,而后server才从buffer中读取数据。
所以,在onReceive中我们拿到的数据并没有办法保证数据包的完整性,swoole_server可能会同时收到多个请求包,也可能只收到一个请求包的一部分数据。
粘包问题解决方案
https://www.cnblogs.com/JsonM/articles/9283037.html
swoole解决方案一
- server.php
<?php
$server=new Swoole\Server("0.0.0.0",9800); //创建server对象
$server->set([
'worker_num'=>1, //设置进程
'heartbeat_idle_time'=>10,//连接最大的空闲时间
'heartbeat_check_interval'=>3, //服务器定时检查
'open_eof_check' => true, //打开EOF检测
'package_eof' => "\r\n", //设置EOF
]);
//监听事件,连接事件
$server->on('connect',function ($server,$fd){
echo "新的连接进入:{$fd}".PHP_EOL;
});
//消息发送过来
$server->on('receive',function (swoole_server $server, int $fd, int $reactor_id, string $data){
//var_dump("消息发送过来:".strlen($data));
//服务端
//$server->send($fd,'我是服务端');
$data=explode("\r\n",$data);
foreach ($data as $v){
if(!$v){
continue;
}
var_dump("消息发送过来:".$v);
}
});
//消息关闭
$server->on('close',function (){
echo "消息关闭".PHP_EOL;
});
//服务器开启
$server->start();
- client.php
<?php
$client=new swoole\Client(SWOOLE_SOCK_TCP);
//(fd+id)识别身份
//发数据
$client->connect('127.0.0.1',9800);
//约定一个分隔符
//一次性发送多条数据
for ($i=0;$i<10;$i++){
$client->send("123456\r\n");
}
swoole解决方案二(包头+包体)
- server.php
open_length_check:打开包长检测特性
package_length_type:长度字段的类型,固定包头中用一个4字节或2字节表示包体长度。
package_length_offset:从第几个字节开始是长度,比如包头长度为120字节,第10个字节为长度值,这里填入9(从0开始计数)
package_body_offset:从第几个字节开始计算长度,比如包头为长度为120字节,第10个字节为长度值,包体长度为1000。如果长度包含包头,这里填入0,如果不包含包头,这里填入120
package_max_length:最大允许的包长度。因为在一个请求包完整接收前,需要将所有数据保存在内存中,所以需要做保护。避免内存占用过大。
<?php
//tcp协议
$server=new Swoole\Server("0.0.0.0",9800); //创建server对象
$server->set([
'worker_num'=>1, //设置进程
'heartbeat_idle_time'=>10,//连接最大的空闲时间
'heartbeat_check_interval'=>3, //服务器定时检查
'open_length_check'=>true,
'package_length_type'=>'N',
'package_length_offset'=>0,
'package_body_offset'=>4,
'package_max_length'=>1024*1024*3, //包大小为3M
'buffer_output_size'=>1024*1024*3,//输出缓存区大小
]);
//监听事件,连接事件
$server->on('connect',function ($server,$fd){
echo "新的连接进入:{$fd}".PHP_EOL;
});
//消息发送过来
$server->on('receive',function (swoole_server $server, int $fd, int $reactor_id, string $data){
var_dump("消息发送过来:".substr($data,4));
$server->send($fd,'服务端已收到');
});
$server->on('close',function (){
echo "消息关闭".PHP_EOL;
});
$server->start();
- client.php
<?php
$client=new swoole\Client(SWOOLE_SOCK_TCP);
$client->connect('127.0.0.1',9800);
$body='客户端发送';
$data=pack('N',strlen($body)).$body;//包头+包体
$client->send($data);//发送
echo $client->recv();//接收信息
上面会出现信息没有发送完毕,客户端或者服务端就关闭,导致剩余信息无法发送,解决办法最好就是发送一个确认信息,再进行关闭