說明
- 我們都知道通過IP,埠等可以實作兩臺機器之間的資料互通,但具體要怎么操作,系統給我們提供了socket介面,通過呼叫socket函式就可以實作互通,
- php的socket擴展和C本身的非常相似,如果找不到php相關的資料,可以對照著C的socket函式來學習,例如:C語言SOCKET編程指南
- php的socket檔案,檔案中有很多函式,我們只找主要通訊流程的函式理解其流程,其它函式后期用到再去查看即可
通信流程

圖片參考自sockets百度百科詞條,tcp通信的流程,其它方式的通信可參考
相關函式
-
服務端相關函式
-
socket_create( int $domain , int $type , int $protocol ) : resource- 創建一個socket,例如
$socket = socket_create(AF_INET, SOCK_STREAM, 0); - $domain是選擇
IP4或者IP6或者UNIX本地通訊,配置過nginx的話應該會知道引數fastcgi_pass用來連接php-fpm的,有兩種方式,一種是tcp,一種是unix socket,就是對應這里的IP方式和UNIX本地方式 - $type是
SOCK_STREAM(tcp)或者SOCK_DGRAM(udp)或者其它 - $protocol 一般為0是IP協議,
- 上方陳述句創建了ip4地址的tpc套接字,更多引數自行查看檔案,
- 創建一個socket,例如
-
socket_bind( resource $socket , string $address [, int $port = 0 ] ) : bool- 將上方創建的socket系結具體的IP和埠,到時候客戶端連接需要填寫此IP和介面
-
socket_listen( resource $socket [, int $backlog = 0 ] ) : bool- 監聽客戶端的鏈接,(udp通信時,不需要用到此函式)
- 第二個引數backlog是accept之前握手佇列的大小,如果我們配置過php-fpm的話應該有接觸過,叫
listen.backlog,這個引數就對應著socket_listen中的第二個引數,目前php-fpm默認值是511,而workerman中默認值是102400 - 相關參考:socket_listen里面第二個引數backlog的用處;TCP SOCKET中backlog引數的用途是什么?
-
socket_accept( resource $socket ) : resource- 接收客戶端連接(udp通信時,不需要用到此函式)
至此就可以和客戶端聯通,進行讀寫操作了,需要注意的是其回傳值,是一個新的socket,而后續讀寫是在這個新的socket下進行的而不是一開始創建的socket,
- 接收客戶端連接(udp通信時,不需要用到此函式)
-
-
客戶端相關函式
socket_connect( resource $socket , string $address [, int $port = 0 ] ) : bool- 客戶端連接服務端,同服務端一樣,首先要呼叫
socket_create創建一個socket,然后再呼叫此函式連接到服務端
- 客戶端連接服務端,同服務端一樣,首先要呼叫
-
讀寫函式
socket_send( resource $socket , string $buf , int $len , int $flags ) : int- 發送資料給socket
socket_write( resource $socket , string $buffer [, int $length = 0 ] ) : int- 向socket中寫資料
- 上兩個函式基本一樣,socket_send中最后一位$flags引數設定為0,就等于socket_write,網上關于這兩個函式的比較非常少,基本搜索不到,而且費解的是查看php原始碼會發現這兩個方法內部有些許差異,
socket_write中參考#ifndef PHP_WIN32來判定如果是非windows系統,則呼叫系統的write方法來實作,windows系統則呼叫系統的send方法來實作,但是socket_send則沒有判定,統一呼叫系統的send來實作,不明白socket_write為什么要增加判定? socket_sendto( resource $socket , string $buf , int $len , int $flags , string $addr [, int $port = 0 ] ) : int- 主要是udp通信時,發送資料
socket_recv( resource $socket , string &$buf , int $len , int $flags ) : int- 從socket中接收資料,需要注意的是回傳值是int表示接收的位元組數,而內容存在第二個參考型別的引數$buf中
socket_read( resource $socket , int $length [, int $type = PHP_BINARY_READ ] ) : string- 從socket中讀資料,回傳結果就是讀的資料
- 上面兩個函式也很相近,除了回傳值的區別,socket_recv最后$flags為0和socket_read最后$type為PHP_BINARY_READ時讀取的資料是一樣的,但是如果flags或者type變化,則不一定一樣了,
socket_recvfrom( resource $socket , string &$buf , int $len , int $flags , string &$name [, int &$port ] ) : int- 主要是udp通信是,接收資料
-
其它常用函式
socket_close( resource $socket ) : void- 關閉socket
socket_set_nonblock( resource $socket ) : bool- 設定為非阻塞
socket_set_block( resource $socket ) : bool- 設定為阻塞
- 上面兩個影響的函式為:
socket_connect、socket_accept以及上方的各種讀寫函式,以socket_accept為例,阻塞就是呼叫此方法后,如果沒有接受到客戶端,則此方法一直卡住,等待客戶端加入后才會進行下一步動作,而非阻塞則是如果沒有客戶端加入,則直接回傳false,具體的會在后面的IO模型中總結 socket_select( array &$read , array &$write , array &$except , int $tv_sec [, int $tv_usec = 0 ] ) : int- 呼叫系統select()相關IO模型
- 詳細的在后面的IO模型中會總結
示例
- 服務端socket_service.php:
<?php //IP和埠 $address = '127.0.0.1'; $port = 8888; //創建 $listenSocket = socket_create(AF_INET, SOCK_STREAM, 0); //系結 socket_bind($listenSocket, $address, $port); //監聽 socket_listen($listenSocket, 5); //回圈接入客戶端 while (true) { //接入客戶端 $connectSocket = socket_accept($listenSocket); $msg = "hello\r\n"; //發送給客戶端,注意此時用的是$connectSocket,而不是$listenSocket socket_write($connectSocket, $msg, strlen($msg)); //關閉 socket_close($connectSocket); } //關閉 socket_close($listenSocket); - 客戶端socket_client.php:
<?php //服務端IP和埠 $address = '127.0.0.1'; $port = 8888; //創建 $socket = socket_create(AF_INET, SOCK_STREAM, 0); //連接 $result = socket_connect($socket, $address, $port); //讀取 $out = socket_read($socket, 2048); echo $out; //關閉 socket_close($socket); - 上方是簡單的示例代碼,在命令列cli模式下運行
php socket_service.php,然后再新起一視窗運行php socket_client.php就能看到客戶端聯通后收到了服務端發送的資料并顯示出來, - 我們在普通的編程中,通常是盡量避免使用while(true)的,害怕無限回圈會導致卡死,但在網路編程中會經常用到,其實只要控制好就沒問題,上方之所以可以用是因為默認socket_accept是阻塞的,也就是沒有客戶端接入時會卡在這里等待接入,所以不會造成性能影響,
- 也可以查看php檔案中的示例,按照提示來測驗,
- 通過示例我們可以看到是可以連通,但是有很大的局限性,只能一個服務端連接一個客戶端,如果想同時連接多個客戶端是不行的(上方簡單的代碼可能不明顯,畢竟連接后接著就關閉斷開了,但是運行php檔案中的例子會比較明顯,同時打開幾個視窗用
telnet來鏈接,會發現只有一個結束后,另一個才能接入),解決方法就是可以用fork子行程或者是IO多路復用,后續會總結,
關聯
- 上篇:php socket網路編程基礎知識(一):開篇
- 下篇:php socket網路編程基礎知識(三):stream函式
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/5059.html
標籤:PHP
