s-socket
最后更新于:2022-04-02 04:28:01
## s-socket
```php
0) {
} else {
return '';
}
$msg = socket_strerror($code);
$msg = iconv("GBK", "utf-8", $msg);
return "socket error: [{$code}] {$msg}";
}
/**
* 创建一个套接字资源
* 1. 规定了:通讯协议域簇、套接字类型、传输协议
*/
$server_sock = socket_create(AF_INET, SOCK_STREAM, getprotobyname('tcp'));
/**
* 为套接字绑定一个端口
*
* 1. 默认情况一个端口只能被一个进程绑定,重复绑定会报端口被占用的错误
* 2. 如果 内核支持 so_reuseport 参数时就可以实现多个进程绑定同一个端口
* socket_set_option($server_sock, SOL_SOCKET, 'so_reuseport', 1);
*
* https://blog.csdn.net/u010565545/article/details/99244959
* 127.0.0.1 本地回环地址(之一),本机ip,虚拟网卡的ip,如果你不知道本地外网ip的话就用这个
* 0.0.0.0 所有 本机ip
*/
socket_bind($server_sock, '127.0.0.1', 2347);
/**
* 开始监听此套接字
*/
socket_listen($server_sock);
/**
* 套接字操作:连接、接受、接收、发送......
*
* 当一个操作在一个阻塞的套接字上执行时,脚本将暂停执行,直到它收到一个信号或者它可以执行该操作。
*
* socket_set_nonblock() 函数在由socket参数指定的socket上设置 O_NONBLOCK 标志。
* socket_set_block() 函数删除了由socket参数指定的套接字上的 O_NONBLOCK 标志。
*
* 阻塞的 socket 上:
* 1. 在套接字上进行操作时会被阻塞,直到收到信号或可操作时才会执行该操作
* 2. 信号即 socket 收到操作系统的信号,表示可以进行 可读、可写 等操作了
* 3. 套接字默认是阻塞模式的
*
* 非阻塞的 socket 上:
* 1. 在套接字上进行操作时不会被阻塞,但不一定调用成功
* 2. 只有在收到信号或可操作时才能执行该套接字操作,否则会导致调用失败,返回 false
* 3. 注意,非阻塞会使阻塞性质(原本会导致阻塞)的套接字操作调用失败,返回 false
* 所以需要判断当返回 false 时属于哪种情况,真有错,还是 非阻塞模式时调用了会导致阻塞的套接字操作
*
*/
// socket_set_nonblock($server_sock); // 将 server_sock 设为非阻塞
/*
* 接受一个客户端的连接(每次接受一个客户端连接,返回值为一个 客户端 socket)
*
* 1. 可能会阻塞,取决于 server_sock 是否为阻塞的
* 2. 它会阻塞在等待客户端的连接上,直到有新的客户端连接
* 3. 如果该套接字上有多个排队的连接,将使用第一个。如果没有待处理的连接,socket_accept()将阻塞
* 4. 为非阻塞模式时,在没有客户端连接时,立即返回 false,所以非阻塞模式 应该使用 socket_select
* 5. 返回 false 可能是 客户端 socket 已关闭等原因,也有可能是 非阻塞模式下 连接队列为空时,需要使用 socket_last_error socket_strerror 来判断具体情况(socket error: [10035] 无法立即完成一个非阻止性套接字操作。)
* 6. 返回的 socket 实例不能被用来接受新的连接
*/
$client_sock = socket_accept($server_sock);
// echo getSockError($server_sock);
// var_dump($client_sock);exit;
// 客户端sock 还可以设置非阻塞
socket_set_nonblock($client_sock);
/**
* 从客户端 socket 上读取数据
*
* 1. 可能会阻塞,取决于 server_sock 是否为阻塞的
* 2. 它会阻塞在读取内核 socket 可读缓冲区上,直到有可读数据,可读时返回读取到的数据
* 3. 为非阻塞模式时,在没有可读数据时,立即返回 false,有可读数据时,返回读取到的数据
* 4. 可设置每次最大读取字节,当然最终取决于实际可读的内容长度,或者也可以使用第三个参数 每次 \r, \n, \0 时结束
* 5. 建议设置最大读取长度,以控制每次从内核读取数据的大小,使内存消耗在可控范围内(建议 65535 一个数据包 最大的数据部分长度)
* 6. 在读取错误时返回 false(并且会报错 Warning) 这可能是 客户端 socket 已关闭等原因,可使用 socket_last_error socket_strerror 来获取错误信息
* 7. 阻塞模式时,如果返回空串,可能时远端报错了,不可再读了
*
* 同样的,返回 false 时要注意判断:socket error: [10035] 无法立即完成一个非阻止性套接字操作。
*
* 可读吗,可读,但只能读一点点
*/
// $buf = socket_read($client_sock, 1024);
// echo getSockError($client_sock);
// var_dump($buf);exit;
socket_clear_error($client_sock);
// socket_set_nonblock($client_sock);
$msg = 'you input: ' . $buf;
// socket_set_option($client_sock, SOL_SOCKET, SO_SNDBUF, 2);
// // 65536
// $sndbuf = socket_get_option($client_sock, SOL_SOCKET, SO_SNDBUF);
// echo 'sndbuf: ' . $sndbuf . PHP_EOL;
$msg = str_repeat($msg, 1000000) . PHP_EOL . 'xiaobu' . PHP_EOL;
echo 'msg len: ' . strlen($msg) . PHP_EOL;
/**
* 往客户端 socket 上写入数据
*
* 1. 可能会阻塞,取决于 server_sock 是否为阻塞的
* 2. 它会阻塞在写入内核 socket 写入缓冲区上,直到有可写入空间
* 3. 为非阻塞模式时,在没有可写入空间时,立即返回 0或 false,可写入时,返回实际写入的数据字节数
* 4. 可设置每次最大写入字节,当然最终写入数据长度取决于缓冲区大小
* 5. 建议设置最大写入长度为要写入的内容长度,期望是能一次写入完毕
* 6. 在写入错误时 返回 返回 false 这可能是 客户端 socket 已关闭等原因,可使用 socket_last_error socket_strerror 来获取错误信息
*
* socket_write()不一定会写入给定缓冲区中的所有字节。根据网络缓冲区等情况,虽然你的缓冲区更大,但只有一定数量的数据,甚至一个字节被写入,这是有效的。你必须注意,以免你无意中忘记传输其余的数据。
*
* 可写吗,可写,但只能写一点点
*/
$l = socket_write($client_sock, $msg, strlen($msg)); // 也可能阻塞,或者根本没有完全写完数据
echo getSockError($client_sock);
var_dump($l);
sleep(3);
exit;
/**
* 与此客户端断开连接,通常是业务交互完毕了,并且不需要保持长连接
*/
socket_close($client_sock);
/**
* 关闭服务端 socket,结束服务,这意味着服务关闭,通常在关闭服务器时才会这么做
*/
socket_close($server_sock);
// =================================
// 上面这样 很多操作是阻塞的,并且操作不是一次能完成的,这样写起来很麻烦
// 1. 如果能监听 socket 何时可操作就好了
// 2. 如果能知晓 一个 socket 何时可操作,提前将 操作设置成回调,这就是事件了
// 3. 如果不断的监听,并触发回调,就是事件循环了
// 监听 socket 状态
// socket_select( array|null &$read, array|null &$write, array|null &$except, int|null $seconds, int $microseconds = 0) : int|false
$read = $write = $except = [$client_sock];
$ret = socket_select($read, $write, $except, 1, 0);
// 打断一下,先抛出两个问题,慢慢思考
// 1. 客户端 与 服务端 收发数据的长度是不可控的,怎么判断收到了一个完整的内容呢,这就是粘包问题
// 答:每个内容末尾加一个特殊标记位,如 末尾\n,或者如 http 协议中的 Content-Length: xx 来确定内容长度进行区分
// 2. http 协议 如何 确定 请求-响应 之间的对应关系呢?
// 答:有可能多个请求和响应是连续的,且顺序不可预测,那么只能通过请求标记与响应标记来确定关联性了
// 但是 http 协议没有规定这方面的内容,现在是由 浏览器实现的:要复用 tcp连接 只能一个 请求-响应 完毕才会发送第二个请求
// 所以也就不存在这个问题了。
// https://www.cnblogs.com/everlose/p/12779773.html
// https://zhuanlan.zhihu.com/p/29609078 http2 才支持这个功能
// 3. 一个连接上能同时交替发送两个内容吗?
// 答:不能,除非自己实现帧数据协议。没必要实现这样的功能,没意义,与其两个包都不能快点发送完,还不如早点让一个包发送完
// 感想:客户端的情况要简单些(虽然也可以做的复杂,但没必要只是徒增烦恼),因为不像服务端一样要考虑为多人服务(不能使用阻塞),所以可以粗暴一些,这样能简化和避免很多问题。如 可以使用阻塞的方式收发消息
// 这样减少了复杂度,更容易实现也更稳定。如 直接避免了并发交替发送数据包的情况。
// 客户端编码习惯了同步阻塞的方式,这样开发更简单直观,试想 你写的 curd 不都是同步操作 mysql 的
// onRead: 一次可读信号
// onMessage: 一个完整的消息内容接收完毕
// onWrite: 一次可写信号
// onSend: 一个完整的消息内容发送完毕
// ==========================
// https://blog.csdn.net/wangjiben/article/details/40679587?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_title~default-4.no_search_link&spm=1001.2101.3001.4242.3
// 计算信息缓冲区大小
// Calculate message buffer size
// socket_cmsg_space()
// 向连接的套接字发送数据(数据已从缓冲区发出才返回)
// Sends data to a connected socket
// socket_send( Socket $socket, string $data, int $length, int $flags) : int|false
// 向一个套接字发送消息,无论它是否连接。
// Sends a message to a socket, whether it is connected or not
// socket_sendto( Socket $socket, string $data, int $length, int $flags, string $address, int|null $port = null) : int|false
// 发送消息
// socket_sendmsg( Socket $socket, array $message, int $flags = 0) : int|false
// 写入一个套接字 写到缓冲区(数据可能还没被网卡发出去)
// Write to a socket
// socket_write( Socket $socket, string $data, int|null $length = null) : int|false
// ----
// 从一个套接字中读取最大长度的字节
// Reads a maximum of length bytes from a socket
// socket_read( Socket $socket, int $length, int $mode = PHP_BINARY_READ) : string|false
// 从已连接的socket接收数据
// 与 socket_read 类似 但可以 使用参数 flags 控制函数功能,如 指定至少读到某个字节长度,和 “重复读”
// socket_recv( resource $socket, string &$buf, int $len, int $flags) : int
// 从一个套接字接收数据,无论它是否面向连接。同 socket_recv 类似
// Receives data from a socket whether or not it is connection-oriented
// socket_recvfrom( Socket $socket, string &$data, int $length, int $flags, string &$address, int &$port = null) : int|false
// 读取消息
// Read a message
// socket_recvmsg( Socket $socket, array &$message, int $flags = 0) : int|false
// 读写超时控制 任何时候超时控制都是必不可少的,并且要特别小心 feof 之类的可能会陷入无限循环的情况
// https://blog.csdn.net/q1007729991/article/details/71078044
// stream_set_timeout
// socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, array('sec'=>$sec, 'usec'=>$usec));
// socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, array('sec'=>$sec, 'usec'=>$usec));
// 关闭一个套接字的接收、发送或两者都关闭。
// 相关的一个或多个缓冲区可能被清空,也可能不被清空。
// Shuts down a socket for receiving, sending, or both
// socket_shutdown( Socket $socket, int $mode = 2) : bool
// 服务端
// socket_write
// socket_read
// 客户端
// socket_send
// socket_sendto
// socket_sendmsg
// socket_write
// socket_read
// socket_recv
// socket_recvfrom
// socket_recvmsg
// https://blog.csdn.net/csdn_zhang99/article/details/81669793
// 异步架构程序设计原则
// 1、回调函数不可以执行过长时间,因为一个loop中可能包含其他事件,尤其是会影响一些准确度要求比较高的timer。
// 2、尽量采用库中所缓存的时间,有时候需要根据时间差来执行timeout之类的操作。当然能够利用库中的timer最好。
// 任务不要做复杂的事,不要在io上阻塞
```
----
[TCP-IP协议 · php笔记 · 看云](https://www.kancloud.cn/xiak/php-node/2545482)
> 接收方 read buffer 满(说明应用程序处理能力过载),**不再接受数据, 不 ack 了,那么 发送方 write buffer 很快也就满了(等不到 ack 就不删除 write buffer)**,不能再发了。这算是一种 常规的TCP拥塞控制。
[libev_大张-CSDN博客_libev](https://blog.csdn.net/csdn_zhang99/article/details/81669793
)
~~~
异步架构程序设计原则
1、回调函数不可以执行过长时间,因为一个loop中可能包含其他事件,尤其是会影响一些准确度要求比较高的timer。
2、尽量采用库中所缓存的时间,有时候需要根据时间差来执行timeout之类的操作。当然能够利用库中的timer最好。
任务不要做复杂的事,不要在io上阻塞
~~~
[HTTP 的前世今生:一次性搞懂 HTTP、HTTPS、SPDY、HTT_请求](https://www.sohu.com/a/275505518_497161)
> SPDY 引入了一个新的二进制分帧数据层,以实现多向请求和响应、优先次序、最小化及消除不必要的网络延迟,目的是更有效地利用底层 TCP 连接。
';