## 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筆記 · 看云](http://www.hmoore.net/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 接口自動化測試指南