PHP 編寫基本的 Socket 程式

2020-07-16 10:06:04
告誡年輕人

空想是沒有用的,個人的能力來源於每一天的努力,而不是一步登天,不要畏懼任何新的知識,水滴石穿,總有一天會柳暗花明。

我的目的

因為在以後的學習中,我可能會用到網路方面的內容,但同時很多寫 PHP 的 coder 都沒寫過 socket 程式,但是肯定聽說過它,也肯定聽說過網路程式設計這個詞,所以為了今後的學習,我打算在這裡先簡單的講解下相關知識,本篇博文自帶範例程式,程式碼託管在碼雲(php-socket-base-code:https://gitee.com/obamajs/php-socket-base-code),你只需要下載下來,設定好相關環境,按照說明即可執行。

環境設定

socket 程式設計需要開啟 php 的 socket 擴充套件,我用的電腦是 windows,所以這裡你只需要開啟 php.ini 檔案,找到這一行去掉註釋就可以了

extension=sockets

官方文件

php 的 socket 程式設計的官方地址為:php socket(https://www.php.net/manual/en/book.sockets.php)

伺服器端程式設計

socket 程式設計遵循一定的程式設計步驟,這幾個步驟缺一不可,用戶端和伺服器端程式設計有所區別,我們首先來看一下伺服器端。

56b30652b8dca9b58d550fde4b20a1d.png

建立通訊端

通訊端屬於系統資源,我們首先呼叫 socket_create 方法(參考官方文件:https://www.php.net/manual/en/function.socket-create.php),呼叫如下:

$this->socket_handle = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if (!$this->socket_handle) {
      //建立失敗丟擲異常,socket_last_error獲取最後一次socket操作錯誤碼,socket_strerror列印出對應錯誤碼所對應的可讀性描述
     throw new Exception(socket_strerror(socket_last_error($this->socket_handle)));
} else {
          echo "create socket successfuln";
}

第一個引數指定了,當前通訊端是採用 ipv4 還是 ipv6,如果是前者的話,那麼傳遞 AF_INET,否則 AF_INET6,當然還有一種型別,就是 AF_UNIX,這個暫時不討論,我們一般選擇 AF_INET(ipv6 不是很普及)。

第二個引數,指定了協定的型別,一般選擇 TCP 或者是 UDP,TCP 是可靠的流傳輸(生活當中用的最為廣泛,保證了可靠性和安全性),UDP 則不是,這個引數一般選擇 TCP。

第三個如果你之前選擇了 TCP,那麼它就是 SOL_TCP,否則就是 SOL_UDP。

係結地址和埠號

因為一台主機可能存在多個 ip 地址,所以你需要指定你的 socket 監聽的是哪一個,常用的值為 127.0.0.1,或者是監聽所有地址 0.0.0.0,那麼這裡可能有人不明白了,127.0.0.1 和 0.0.0.0 有啥區別呢?127.0.0.1 只是一個回環地址,只能用於本機存取,說白了就是自己玩自己的,因為這個 ip 不對外部開放,所以有人也就無法存取這個地址,所以如果你的伺服器地址設定為 127.0.0.1,別人想要存取,只能去屎吧。

0.0.0.0 嚴格來說不算是一個 ip 地址,它的意思是本機的所有 IP 地址,都是我的,哈哈。

明白了上面這個,我們來看這個呼叫的程式碼

if (!socket_bind($this->socket_handle, $this->addr, $this->port)) {
         throw new Exception(socket_strerror(socket_last_error($this->socket_handle)));
    } else {
         echo "bind addr successfuln";
 }

是不是很簡單,第一個引數就是 socket_create 返回的結果,第二個引數就是地址了,上面已經說過了,第三個引數是埠號。

監聽通訊端

經過上面的這些步驟,我們只是建立了一個通訊端並且給它繫結了埠號和地址,但是系統怎麼知道它是監聽通訊端呢?所以呢,我們的事情還沒有做完,所以我們得告訴它啊,別告訴我你和系統心有靈犀啊!!!

if (!socket_listen($this->socket_handle, $this->back_log)) {
      throw new Exception(socket_strerror(socket_last_error($this->socket_handle)));
  } else {
      echo "socket  listen successfuln";
 }

第二個引數值得說明一哈,請聽我細細道來,對於 linux 系統中的每一個進程而言,系統都維護著待處理通訊端的佇列(先進先出,總得講個先來後到吧),上層程式處理業務邏輯總得需要時間吧,所以讓你你等著你就等著唄。那麼這個佇列的大小設定為多大呢?它的值就是這第二個引數,那麼我是不是可以設定的很大呢?騷年,你想多了吧?不同的系統這個值有所不同,別說我忽悠你,看下面。

The maximum number passed to the backlog parameter highly depends on the underlying platform. On Linux, it is silently truncated to SOMAXCONN. On win32, if passed SOMAXCONN, the underlying service provider responsible for the socket will set the backlog to a maximum reasonable value. There is no standard provision to find out the actual backlog value on this platform.

你也不必關心這個值精確的資料,沒有什麼意義。

萬事俱備,只欠東風

經過上面的一通操作之後,我們可以開始接受來自用戶端的連線了,這個函數就更簡單了

$client_socket_handle = socket_accept($this->socket_handle);

這個函數的返回值也是一個通訊端控制代碼,所以你可以對它進行讀寫操作,在當前的範例程式中,我們做的事情很簡單,簡單到你可以懷疑人生了。

 $client_socket_handle = socket_accept($this->socket_handle);
        if (!$client_socket_handle) {
            echo "socket_accept call failedn";
            exit(1);
        } else {
            while (true) {
                $bytes_num = socket_recv($client_socket_handle, $buffer, 100, 0);
                if (!$bytes_num) {
                    echo "socket_recv  failedn";
                    exit(1);
                } else {
                    echo "content from client:" . $buffer . "n";
                }
            }
        }

讀取通訊端

以上面的例子為例,我們使用 socket_recv 讀取來自用戶端的內容,這個函數很簡單,函數原型如下

socket_recv ( resource $socket , string &$buf , int $len , int $flags ) : int

讀取的內容會在第二個引數返回,第二個引數傳遞我們想要讀取的字元數,第四個引數可以直接設定為 0,該函數的返回值為實際讀取的位元組數。

用戶端程式設計

用戶端相對於伺服器端來說,就很簡單了,流程如下

6bc3e48bbecdd36112efc0960aacffd.png

建立通訊端前面已經講過了,不再詳述,用戶端只需要連線伺服器即可,函數為 socket_create,我們來看一哈在當前的例子中,我們是如何呼叫的。

if (!socket_connect($this->socket_handle, $this->server_addr, $this->server_port)) {
            echo socket_strerror(socket_last_error($this->socket_handle)) . "n";
            exit(1);
        } else {
            while (true) {
                $data = fgets(STDIN);
                //如果使用者輸入quit,那麼退出程式
                if (strcmp($data, "quit") == 0) {
                    break;
                }
                socket_write($this->socket_handle, $data);
            }
        }

該函數只需要指定伺服器的地址和埠號即可,引數是不是很簡單

練習範例

在講解基本函數呼叫的時候,我就把自帶程式的核心部分,複製出來了,如果要完整的程式,這裡是地址(php-socket-base-code:https://gitee.com/obamajs/php-socket-base-code),程式碼非常簡單,再次提醒,這些程式碼完全是用於給大家講解基本的 socket 變成操作,為大家以後的學習打下基礎,那麼如何使用這個例子程式呢?

進入到命令列,開啟伺服器程式

php TcpServer.php,

開啟另外一個命令列介面,

php TcpClient.php,

在用戶端介面,輸入任何文字,再輸入回車,再切換到伺服器介面,您將會看到用戶端輸入的內容

在筆者的電腦上操作範例截圖如下:

6bc3e48bbecdd36112efc0960aacffd.png

以上就是PHP 編寫基本的 Socket 程式的詳細內容,更多請關注TW511.COM其它相關文章!