PHP 編寫基本的 Socket 程式

Dennis_Ritchie發表於2019-11-13

告誡年輕人

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

我的目的

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

環境配置

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

extension=sockets

官方文件

php的socket程式設計的官方地址為:php socket

服務端程式設計

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

PHP編寫基本的Socket程式

建立套接字

套接字屬於系統資源,我們首先呼叫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 successful\n";
}

第一個引數指定了,當前套接字是採用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地址,都是我的,哈哈。
PHP編寫基本的Socket程式

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

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 successful\n";
 }

是不是很簡單,第一個引數就是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 successful\n";
 }

第二個引數值得說明一哈,請聽我細細道來,對於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 failed\n";
            exit(1);
        } else {
            while (true) {
                $bytes_num = socket_recv($client_socket_handle, $buffer, 100, 0);
                if (!$bytes_num) {
                    echo "socket_recv  failed\n";
                    exit(1);
                } else {
                    echo "content from client:" . $buffer . "\n";
                }
            }
        }

讀取套接字

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

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

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

客戶端程式設計

客戶端相對於服務端來說,就很簡單了,流程如下

PHP編寫基本的Socket程式

建立套接字前面已經講過了,不再詳述,客戶端只需要連線伺服器即可,函式為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),程式碼非常簡單,再次提醒,這些程式碼完全是用於給大家講解基本的socket變成操作,為大家以後的學習打下基礎,那麼如何使用這個例子程式呢?

進入到命令列,開啟伺服器程式
php TcpServer.php,
開啟另外一個命令列介面,
php TcpClient.php,
在客戶端介面,輸入任何文字,再輸入回車,再切換到伺服器介面,您將會看到客戶端輸入的內容

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

PHP編寫基本的Socket程式

相關文章