## s-socket
```php
<?php
/**
* 学习 tcp/ip socket 能够让你了解计算机网络是如何工作的,掌握它就是掌握了进入互联网的钥匙
* 熟练掌握计算机网络原理能够让你的技术上一个新的台阶,而不再只局限于应用层的CURD了
* socket 是计算机程序通信的基础,有了套接字,不同的程序和不同的计算机就能够相互通信
* 从此站在底层看上层应用就会有一览众山小的感觉,如 redis mysql pdo ,http 请求、响应是什么原理?
* 平时在业务中使用时有没有想过它们背后是怎么工作的呢,性能瓶颈在哪里呢?
* 从底层去看,这些都了无秘密,这样优化上层应用的性能问题就很容易了,对各种眼花缭乱的中间件也不会再觉得望而生畏了
* 不过最为重要的是,学习这些设计背后的思想,思考为什么这样设计,并逐渐理解软件设计过程中的难处,以及如何平衡和取舍
* 最后忘记你学到的全部内容,剩下的就是你真正学会的了
*/
/**
* socket 五步曲
* 1. 创建
* 2. 绑定
* 3. 监听
* 4. 接受
* 5. 收发
*/
// cmd: telnet 127.0.0.1 5000
function getSockErrorCode($sock = null)
{
return $sock ? socket_last_error($sock) : socket_last_error();
}
function getSockError($sock = null)
{
$code = getSockErrorCode($sock);
if ($code > 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 连接。
[ReactPHP: Event-driven, non-blocking I/O with PHP - ReactPHP](https://reactphp.org/)
> ReactPHP is non-blocking by default. Use workers for blocking I/O.
> 大量的连接复用少数的进程,socket fd 事件循环是非阻塞的,但是 工人进程处理业务的部分是阻塞的。
[The Illustrated QUIC Connection: Every Byte Explained](https://quic.xargs.org/)
[图解 QUIC](https://cangsdarm.github.io/illustrate/quic)
- 开始
- 公益
- 更好的使用看云
- 推荐书单
- 优秀资源整理
- 技术文章写作规范
- SublimeText - 编码利器
- PSR-0/PSR-4命名标准
- php的多进程实验分析
- 高级PHP
- 进程
- 信号
- 事件
- IO模型
- 同步、异步
- socket
- Swoole
- PHP扩展
- Composer
- easyswoole
- php多线程
- 守护程序
- 文件锁
- s-socket
- aphp
- 队列&并发
- 队列
- 讲个故事
- 如何最大效率的问题
- 访问式的web服务(一)
- 访问式的web服务(二)
- 请求
- 浏览器访问阻塞问题
- Swoole
- 你必须理解的计算机核心概念 - 码农翻身
- CPU阿甘 - 码农翻身
- 异步通知,那我要怎么通知你啊?
- 实时操作系统
- 深入实时 Linux
- Redis 实现队列
- redis与队列
- 定时-时钟-阻塞
- 计算机的生命
- 多进程/多线程
- 进程通信
- 拜占庭将军问题深入探讨
- JAVA CAS原理深度分析
- 队列的思考
- 走进并发的世界
- 锁
- 事务笔记
- 并发问题带来的后果
- 为什么说乐观锁是安全的
- 内存锁与内存事务 - 刘小兵2014
- 加锁还是不加锁,这是一个问题 - 码农翻身
- 编程世界的那把锁 - 码农翻身
- 如何保证万无一失
- 传统事务与柔性事务
- 大白话搞懂什么是同步/异步/阻塞/非阻塞
- redis实现锁
- 浅谈mysql事务
- PHP异常
- php错误
- 文件加载
- 路由与伪静态
- URL模式之分析
- 字符串处理
- 正则表达式
- 数组合并与+
- 文件上传
- 常用验证与过滤
- 记录
- 趣图
- foreach需要注意的问题
- Discuz!笔记
- 程序设计思维
- 抽象与具体
- 配置
- 关于如何学习的思考
- 编程思维
- 谈编程
- 如何安全的修改对象
- 临时
- 临时笔记
- 透过问题看本质
- 程序后门
- 边界检查
- session
- 安全
- 王垠
- 第三方数据接口
- 验证码问题
- 还是少不了虚拟机
- 程序员如何谈恋爱
- 程序员为什么要一直改BUG,为什么不能一次性把代码写好?
- 碎碎念
- 算法
- 实用代码
- 相对私密与绝对私密
- 学习目标
- 随记
- 编程小知识
- foo
- 落盘
- URL编码的思考
- 字符编码
- Elasticsearch
- TCP-IP协议
- 碎碎念2
- Grafana
- EFK、ELK
- RPC
- 依赖注入
- 开发笔记
- 经纬度格式转换
- php时区问题
- 解决本地开发时调用远程AIP跨域问题
- 后期静态绑定
- 谈tp的跳转提示页面
- 无限分类问题
- 生成微缩图
- MVC名词
- MVC架构
- 也许模块不是唯一的答案
- 哈希算法
- 开发后台
- 软件设计架构
- mysql表字段设计
- 上传表如何设计
- 二开心得
- awesomes-tables
- 安全的代码部署
- 微信开发笔记
- 账户授权相关
- 小程序获取是否关注其公众号
- 支付相关
- 提交订单
- 微信支付笔记
- 支付接口笔记
- 支付中心开发
- 下单与支付
- 支付流程设计
- 订单与支付设计
- 敏感操作验证
- 排序设计
- 代码的运行环境
- 搜索关键字的显示处理
- 接口异步更新ip信息
- 图片处理
- 项目搭建
- 阅读文档的新方式
- mysql_insert_id并发问题思考
- 行锁注意事项
- 细节注意
- 如何处理用户的输入
- 不可见的字符
- 抽奖
- 时间处理
- 应用开发实战
- python 学习记录
- Scrapy 教程
- Playwright 教程
- stealth.min.js
- Selenium 教程
- requests 教程
- pyautogui 教程
- Flask 教程
- PyInstaller 教程
- 蜘蛛
- python 文档相似度验证
- thinkphp5.0数据库与模型的研究
- workerman进程管理
- workerman网络分析
- java学习记录
- docker
- 笔记
- kubernetes
- Kubernetes
- PaddlePaddle
- composer
- oneinstack
- 人工智能 AI
- 京东
- pc_detailpage_wareBusiness
- doc
- 电商网站设计
- iwebshop
- 商品规格分析
- 商品属性分析
- tpshop
- 商品规格分析
- 商品属性分析
- 电商表设计
- 设计记录
- 优惠券
- 生成唯一订单号
- 购物车技术
- 分类与类型
- 微信登录与绑定
- 京东到家库存系统架构设计
- crmeb
- 命名规范
- Nginx https配置
- 关于人工智能
- 从人的思考方式到二叉树
- 架构
- 今日有感
- 文章保存
- 安全背后: 浏览器是如何校验证书的
- 避不开的分布式事务
- devops自动化运维、部署、测试的最后一公里 —— ApiFox 云时代的接口管理工具
- 找到自己今生要做的事
- 自动化生活
- 开源与浆果
- Apifox: API 接口自动化测试指南