php socket網路程式設計基礎知識(二):socket函式
阿新 • • 發佈:2020-07-19
說明
- 我們都知道通過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_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
接收客戶端連線(udp通訊時,不需要用到此函式)
至此就可以和客戶端聯通,進行讀寫操作了,需要注意的是其返回值,是一個新的socket,而後續讀寫是在這個新的socket下進行的而不是一開始建立的socket。
-
客戶端相關函式
socket_connect
( resource $socket , string $address [, int $port = 0 ] ) : bool
客戶端連線服務端,同服務端一樣,首先要呼叫socket_create
建立一個socket,然後再呼叫此函式連線到服務端
-
讀寫函式
socket_send
( resource $socket , string $buf , int $len , int $flags ) : int
傳送資料給socketsocket_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
關閉socketsocket_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多路複用,後續會總結。