一.stream_socket_server
stream_socket_server - 创建一个Internet或Unix域服务器套接字。
描述:
资源stream_socket_server(字符串local_socket [摘要和错误号[,串errstr [摘要标志[,资源环境]]]])
创建指定流或数据报套接字 local_socket。:该类型创建由运输决定的插座的使用标准URL格式指定 运输://目标。对于Internet域套接字(AF_INET),如TCP和UDP,该目标的一部分remote_socket参数应该由一个主机名或IP地址,后跟一个冒号和一个端口号。对于Unix域套接字,该目标部分应指向文件系统上的套接字文件。 标志是可被设置为套接字创建标记的任何组合的位掩码字段。标志的默认值为 STREAM_SERVER_BIND | STREAM_SERVER_LISTEN。
注: 对于UDP套接字,必须使用STREAM_SERVER_BIND作为标志参数。
此功能仅创建一个套接字,开始接受连接使用 stream_socket_accept()
如果调用失败,它将返回FALSE,如果可选 的errno和errstr 参数是目前他们将被设置为表明发生在系统级实际的系统级错误 插座() ,bind()的,和 听()调用。如果返回值 错误号为0和该函数返回FALSE,那就是前发生的错误指示bind()的调用。这很可能是由于在初始化插座的问题。请注意,错误号和 errstr参数将始终通过引用传递。
参考代码:https://www.cnblogs.com/setevn/p/8598355.html
二.stream_socket_accept
stream_socket_accept — 接受由 stream_socket_server() 创建的套接字连接
描述
stream_socket_accept ( resource $server_socket [, float $timeout = ini_get("default_socket_timeout") [, string &$peername ]] ) : resource
接受由 stream_socket_server() 创建的套接字连接。
参数
server_socket
需要接受的服务器创建的套接字连接。
timeout
覆盖默认的套接字接受的超时时限。输入的时间需以秒为单位。
peername
如果包含该参数并且是可以从选中的传输数据中获取到,则将被设置给连接中的客户端主机的名称(地址)(怕出入很大,附带上原文:Will be set to the name (address) of the client which connected, if included and available from the selected transport.)
Note:
也可以之后通过 stream_socket_get_name() 来确定。
返回
返回接受套接之后的资源流 或者在失败时返回 FALSE。
参考文档:https://www.php.net/manual/zh/function.stream-socket-accept.php
三.stream_select
简单说stream_select 函数就是返回参数 $read,$write已经可读或者可写状态的的数量,如果基本可读也不可写则超时时间过后就会返回0。
function stream_select (array &$read, array &$write, array &$except, $tv_sec,$tv_usec = null) {}
stream_select函数返回的条件为:
$read数组中的任何一个连接发生可读事件
$write数组中任何一个连接发生可写事件
$except数组中的任何一个连接有带外数据到来(OOB)
当前进程收到系统信号(signal), 如:pcntl_alarm产生的闹钟信号,也就是说信号可以中断stream_select的调用,使其立即返回
stream_select阻塞等待时间超过($tv_sec+$tv_usec)的时间总值,如果$tv_sec=&null时,则无限阻塞直到上面4种返回条件中的任何一种发生了
返回值
返回值为监听的所有连接中发生状态改变的连接数,比如当有3个连接发生可写事件,导致方法返回,则返回值为3(integer类型)
如果是被系统信号中断返回值为false并输出警告(被信号中断).
如果等待直到超时都没任何事件发生,则返回0
关于带外数据
带外数据长度只支持1个字符,用于发生紧急的数据,比如暂停这类的功能,就是发生一个紧急字符优先发送到对端,进而紧急处理
stream_select是如何判断连接可读可写的
每一个socket连接都有一个读缓冲区(readBuffer)和一个写缓冲区(writeBuffer). 每个缓冲区都有一个低水位标记. 接收缓存区低水位标记(用于判断可读)和发送缓存区低水位标记(用于判断可写)stream_select底层通过遍历所有待监听的socket连接,并通过下面的方式判断每个连接的可读可写:
- 可读: 当readBuffer中的数据量”大等于”接收低水位标记值时. 默认接收低水位标记值=1; 还有另一种情况发生可读事件, 就是对端关闭连接时, 这时可以使用 !is_resource($fd) || feof($fd)==true 判断连接已经断开
- 可写: 当writeBuffer中剩余的可用空间”大等于”发送低水位标记值时, 默认发送低水位标记值=2048
四.fread() 函数
fread() 函数读取打开的文件。
函数会在到达指定长度或读到文件末尾(EOF)时(以先到者为准),停止运行。
该函数返回读取的字符串,如果失败则返回 FALSE。
string fread ( resource $handle , int $length )
参数
handle
文件系统指针,是典型地由 fopen() 创建的 resource(资源)。
length
必需。规定要读取的最大字节数。
五.feof() 函数
feof() 函数检测是否已到达文件末尾 (eof)。
如果文件指针到了 EOF 或者出错时则返回 TRUE,否则返回一个错误(包括 socket 超时),其它情况则返回 FALSE。
feof(file)
参数
file
必需。规定要检查的打开文件。
说明
file 参数是一个文件指针。这个文件指针必须有效,并且必须指向一个由 fopen() 或 fsockopen() 成功打开(但还没有被 fclose() 关闭)的文件。
参考:https://www.w3school.com.cn/php/func_filesystem_feof.asp
六.pcntl扩展系列函数
1.pcntl_fork
pcntl_fork — 在当前进程当前位置产生分支(子进程)。译注:fork是创建了一个子进程,父进程和子进程 都从fork的位置开始向下继续执行,不同的是父进程执行过程中,得到的fork返回值为子进程 号,而子进程得到的是0。
$pid = pcntl_fork();
//父进程和子进程都会执行下面代码
if ($pid == -1) {
//错误处理:创建子进程失败时返回-1.
die('could not fork');
} elseif ($pid) {
//父进程会得到子进程号,所以这里是父进程执行的逻辑
pcntl_wait($status); //等待子进程中断,防止子进程成为僵尸进程。
} else {
//子进程得到的$pid为0, 所以这里是子进程执行的逻辑。
}
2.pcntl_wait
pcntl_wait — 等待或返回fork的子进程状态。
wait函数刮起当前进程的执行直到一个子进程退出或接收到一个信号要求中断当前进程或调用一个信号处理函数。 如果一个子进程在调用此函数时已经退出(俗称僵尸进程),此函数立刻返回。子进程使用的所有系统资源将 被释放。关于wait在您系统上工作的详细规范请查看您系统的wait(2)手册。
pcntl_wait ( int &$status [, int $options = 0 ] ) : int
status:pcntl_wait()将会存储状态信息到status
参数上,这个通过status
参数返回的状态信息可以用以下函数 pcntl_wifexited(), pcntl_wifstopped(), pcntl_wifsignaled(), pcntl_wexitstatus(), pcntl_wtermsig()以及 pcntl_wstopsig()获取其具体的值。
options:如果您的操作系统(多数BSD类系统)允许使用wait3,您可以提供可选的options 参数。如果这个参数没有提供,wait将会被用作系统调用。如果wait3不可用,提供参数 options不会有任何效果。options的值可以是0 或者以下两个常量或两个常量“或运算”结果(即两个常量代表意义都有效)。可选值:WNOHANG(如果没有子进程退出立刻返回。)、WUNTRACED(子进程已经退出并且其状态未报告时返回。)
DEFINE(MAXPROCESS,25);
for ($i=0;$i<100;$i++){
$pid = pcntl_fork();
if ($pid == -1) {
//错误处理:创建子进程失败时返回-1.
die("could not fork");
} elseif ($pid) {
//父进程会得到子进程号,所以这里是父进程执行的逻辑
echo "I'm the Parent $i\n";
//execute 执行数量
$execute++;
if ($execute>=MAXPROCESS){
pcntl_wait($status);
$execute--;
}
} else {
echo "I am the child, $i pid = $pid \n";
sleep(rand(1,3));
echo "Bye Bye from $i\n";
exit;
}
}
3.pcntl_signal
pcntl_signal — 安装一个信号处理器
函数pcntl_signal()为signo指定的信号安装一个新 的信号处理器。
pcntl_signal ( int
$signo
, callback$handler
[, bool$restart_syscalls
= true ] ) : boolsigno:信号编号。
handler:信号处理器可以是用户创建的函数或方法的名字,也可以是系统常量 SIG_IGN(译注:忽略信号处理程序)或SIG_DFL(默认信号处理程序).
restart_syscalls:指定当信号到达时系统调用重启是否可用。(译注:经查资料,此参数意为系统调用被信号打断时,系统调用是否从 开始处重新开始,但根据http://bugs.php.net/bug.php?id=52121,此参数存在bug无效。)
//使用ticks需要PHP 4.3.0以上版本
declare(ticks = 1);
//信号处理函数
function sig_handler($signo)
{
switch ($signo) {
case SIGTERM:
// 处理SIGTERM信号
exit;
break;
case SIGHUP:
//处理SIGHUP信号
break;
case SIGUSR1:
echo "Caught SIGUSR1...\n";
break;
default:
// 处理所有其他信号
}
}
echo "Installing signal handler...\n";
//安装信号处理器
pcntl_signal(SIGTERM, "sig_handler");
pcntl_signal(SIGHUP, "sig_handler");
pcntl_signal(SIGUSR1, "sig_handler");
// 或者在PHP 4.3.0以上版本可以使用对象方法
// pcntl_signal(SIGUSR1, array($obj, "do_something");
echo "Generating signal SIGTERM to self...\n";
//向当前进程发送SIGUSR1信号
posix_kill(posix_getpid(), SIGUSR1);
echo "Done\n"
4.pcntl_signal_dispatch
pcntl_signal_dispatch — 调用等待信号的处理器。
函数pcntl_signal_dispatch()调用每个等待信号通过pcntl_signal() 安装的处理器。
pcntl_signal_dispatch ( void ) : bool
echo "安装信号处理器...\n";
pcntl_signal(SIGHUP, function($signo) {
echo "信号处理器被调用\n";
});
echo "为自己生成SIGHUP信号...\n";
posix_kill(posix_getpid(), SIGHUP);
echo "分发...\n";
pcntl_signal_dispatch();
echo "完成\n";
5.pcntl_wifexited
pcntl_wifexited — 检查状态代码是否代表一个正常的退出。
pcntl_wifexited ( int $status ) : bool
status:参数
status
是提供给成功调用 pcntl_waitpid() 时的状态参数。
七.打包函数
数据通信(通过二进制格式与其它语言通信)
数据加密(如果不告诉第三方你的打包方式,对方解包的难度就相对很大)
节省空间(比如比较大的数字按字符串储存会浪费很多空间,打包成二进制格式才需要4位<32位数字>)
1.pack() 函数
PHP中有两个函数pack和unpack,很多PHPer在实际项目中从来没有使用过,甚至也不知道这两个方法是用来干嘛的。这篇文章来为大家介绍一下它俩到底是用来干啥的。
string pack ( string $format [, mixed $args [, mixed $... ]] )
该函数用来将对应的参数($args)打包成二进制字符串
其中第一个参数$format,有如下选项(可选参数很多,后面会选几个常用的讲解)
a 以NUL字节填充字符串空白
A 以SPACE(空格)填充字符串
h 十六进制字符串,低位在前
H 十六进制字符串,高位在前
c 有符号字符
C 无符号字符
s 有符号短整型(16位,主机字节序)
S 无符号短整型(16位,主机字节序)
n 无符号短整型(16位,大端字节序)
v 无符号短整型(16位,小端字节序)
i 有符号整型(机器相关大小字节序)
I 无符号整型(机器相关大小字节序)
l 有符号长整型(32位,主机字节序)
L 无符号长整型(32位,主机字节序)
N 无符号长整型(32位,大端字节序)
V 无符号长整型(32位,小端字节序)
q 有符号长长整型(64位,主机字节序)
Q 无符号长长整型(64位,主机字节序)
J 无符号长长整型(64位,大端字节序)
P 无符号长长整型(64位,小端字节序)
f 单精度浮点型(机器相关大小)
d 双精度浮点型(机器相关大小)
x NUL字节
X 回退一字节
Z 以NUL字节填充字符串空白(new in PHP 5.5)
@ NUL填充到绝对位置
# 这么多参数看下来,我第一次是真心懵逼了,大部分说明都很好理解,但是其中的主机、大端、小端等字节序是什么鬼呢?接下里的内容比较枯燥,但必须理解才行,坚持吧。
# 字节序是什么?
就是字节的顺序,说白了就是多字节数据的存放顺序(一个字节显然不需要顺序)。
比如A和B分别对应的二进制表示为0100 0001、0100 0010。对于储存字符串AB,我们可以0100 0001 0100 0010也可以0100 0010 0100 0001,这个顺序就是所谓的字节序。
# 高/低位字节
比如字符串AB,左高右低(我们正常的阅读顺序),A为高字节,B为低字节
# 高/低地址
假设0x123456是按从高位到底位的顺序储存,内存中是这样存放的:
高地址 -> 低地址
12 -> 34 -> 56
# 大端字节序(网络字节序)
大端就是将高位字节放到内存的低地址端,低位字节放到高地址端。网络传输中(比如TCP/IP)低地址端(高位字节)放在流的开始,对于2个字节的字符串(AB),传输顺序为:A(0-7bit)、B(8-15bit)。
那么小端字节序自然和大端相反。
# 主机字节序
表示当年机器的字节序(也就是网络字节序是确定的,而主机字节序是依机器确定的),一般为小端字节序。
# a和A(打包字符串,用NUL或者空格填充)
$string = pack('a6', 'china');
var_dump($string); //输出结果: string(6) "china",最后一个字节是不可见的NUL
echo ord($string[5]); //输出结果: 0(ASCII码中0对应的就是nul)
//A同理
$string = pack('A6', 'china');
var_dump($string); //输出结果: string(6) "china ",最后一个字节是空格
echo ord($string[5]); //输出结果: 32(ASCII码中32对应的就是空格)
# h和H
$string = pack('H3', 281);
var_dump($string); //输出结果: string(2) "("
for($i=0;$i<strlen($string);$i++) {
echo ord($string[$i]) . PHP_EOL;
}
h和H需要特殊说明一下,它们是将对应的参数看做十六进制字符然后打包。什么意思呢?比如上面的281,打包前会将281转换为0x281,因为十六进制的一位对应二进制的四位,上面的0x281只有1.5个字节,后面会默认补0变成0x2810,0x28对应的十进制为40((),0x10对应的十进制为16(dle不可见字符),懂了吧?不懂可以给我留言。。
# c和C
$string = pack('c3', 67, 68, -1);
var_dump($string); //输出:string(3) "CD�"
for($i=0;$i<strlen($string);$i++) {
echo ord($string[$i]) . PHP_EOL;
}
//输出: 67 68 255
最后输出本能应该觉得是67 68 -1
ord获取的是字符的ASCII码(范围0-255),这时-1(0000 0001)对应的字符将以补码的形式输出也就是255(1111 1110 + 0000 0001 = 1111 1111)
# 整型相关
所有的整型类型使用方法完全一样,主要注意它们的位和字节序就可以了,下面以L作为例子展示
$string = pack('L', 123456789);
var_dump($string); //输出:string(4) "�["
for($i=0;$i<strlen($string);$i++) {
echo ord($string[$i]) . PHP_EOL;
}
//输出: 21 205 91 7
# f和d
$string = pack('f', 12345.123);
var_dump($string);
//输出:string(4) "~�@F"
var_dump(unpack('f', $string)); //这里提前用到了unpack,后面会讲解
//输出:float(12345.123046875)
f和d是针对浮点数打包,至于为什么打包前是12345.123解包后是12345.123046875,这个和浮点数的储存有关系
# x、X、Z、@
$string = pack('x'); //打包一个nul字符串
echo ord($string); //输出: 0
# 关于X(大写X)
$string = pack('Z2', 'abc5'); //其实就是将从Z后面的数字位置开始,全部设置为nul
var_dump($string); //输出:string(2) "a"
for($i=0;$i<strlen($string);$i++) {
echo ord($string[$i]) . PHP_EOL;
}
//输出: 97 0
string = pack('@4'); //我理解为填充N个nul
var_dump($string); //输出: string(4) ""
for($i=0;$i<strlen($string);$i++) {
echo ord($string[$i]) . PHP_EOL;
}
//输出: 0 0 0 0
2.unpack
unpack的使用相当简单,就是讲pack打包的数据解包,打包的时候用的什么参数,就用什么参数解包,具体使用懒得说了,列几个小例子
array unpack ( string $format , string $data )
$string = pack('L4', 1, 2, 3, 4);
var_dump(unpack('L4', $string));
//输出:
array(4) {
[1]=>
int(1)
[2]=>
int(2)
[3]=>
int(3)
[4]=>
int(4)
}
$string = pack('L4', 1, 2, 3, 4);
var_dump(unpack('Ll1/Ll2/Ll3/Ll4', $string)); //可以指定key,用/分割
//输出:
array(4) {
["l1"]=>
int(1)
["l2"]=>
int(2)
["l3"]=>
int(3)
["l4"]=>
int(4)
}