溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

PHP中如何實(shí)現(xiàn)socket服務(wù)器

發(fā)布時(shí)間:2023-02-02 10:37:26 來(lái)源:億速云 閱讀:187 作者:iii 欄目:編程語(yǔ)言

這篇文章主要介紹了PHP中如何實(shí)現(xiàn)socket服務(wù)器的相關(guān)知識(shí),內(nèi)容詳細(xì)易懂,操作簡(jiǎn)單快捷,具有一定借鑒價(jià)值,相信大家閱讀完這篇PHP中如何實(shí)現(xiàn)socket服務(wù)器文章都會(huì)有所收獲,下面我們一起來(lái)看看吧。

socket的中文名字叫做套接字,這種東西就是對(duì)TCP/IP的“封裝”?,F(xiàn)實(shí)中的網(wǎng)絡(luò)實(shí)際上只有四層而已,從上至下分別是應(yīng)用層、傳輸層、網(wǎng)絡(luò)層、數(shù)據(jù)鏈路層。最常用的http協(xié)議則是屬于應(yīng)用層的協(xié)議,而socket,可以簡(jiǎn)單粗暴的理解為是傳輸層的一種東西。如果還是很難理解,那再粗暴地點(diǎn)兒tcp://218.221.11.23:9999,看到?jīng)]?這就是一個(gè)tcp socket。

socket賦予了我們操控傳輸層和網(wǎng)絡(luò)層的能力,從而得到更強(qiáng)的性能和更高的效率,socket編程是解決高并發(fā)網(wǎng)絡(luò)服務(wù)器的最常用解決和成熟的解決方案。任何一名服務(wù)器程序員都應(yīng)當(dāng)掌握socket編程相關(guān)技能。

在php中,可以操控socket的函數(shù)一共有兩套,一套是socket_系列的函數(shù),另一套是stream_系列的函數(shù)。socket_是php直接將C語(yǔ)言中的socket抄了過來(lái)得到的實(shí)現(xiàn),而stream_系則是php使用流的概念將其進(jìn)行了一層封裝。下面用socket_*系函數(shù)簡(jiǎn)單為這一系列文章開個(gè)篇。

先來(lái)做個(gè)最簡(jiǎn)單socket服務(wù)器:

<?php
$host = '0.0.0.0';
$port = 9999;
// 創(chuàng)建一個(gè)tcp socket
$listen_socket = socket_create( AF_INET, SOCK_STREAM, SOL_TCP );
// 將socket bind到IP:port上
socket_bind( $listen_socket, $host, $port );
// 開始監(jiān)聽socket
socket_listen( $listen_socket );
// 進(jìn)入while循環(huán),不用擔(dān)心死循環(huán)死機(jī),因?yàn)槌绦驅(qū)?huì)阻塞在下面的socket_accept()函數(shù)上
while( true ){
  // 此處將會(huì)阻塞住,一直到有客戶端來(lái)連接服務(wù)器。阻塞狀態(tài)的進(jìn)程是不會(huì)占據(jù)CPU的
  // 所以你不用擔(dān)心while循環(huán)會(huì)將機(jī)器拖垮,不會(huì)的 
  $connection_socket = socket_accept( $listen_socket );
  // 向客戶端發(fā)送一個(gè)helloworld
  $msg = "helloworld\r\n";
  socket_write( $connection_socket, $msg, strlen( $msg ) );
  socket_close( $connection_socket );
}
socket_close( $listen_socket );

將文件保存為server.php,然后執(zhí)行php server.php運(yùn)行起來(lái)??蛻舳宋覀兪褂胻elnet就可以了,打開另外一個(gè)終端執(zhí)行telnet 127.0.0.1 9999按下回車即可。運(yùn)行結(jié)果如下:

PHP中如何實(shí)現(xiàn)socket服務(wù)器

簡(jiǎn)單解析一下上述代碼來(lái)說(shuō)明一下tcp socket服務(wù)器的流程:

  • 1.首先,根據(jù)協(xié)議族(或地址族)、套接字類型以及具體的的某個(gè)協(xié)議來(lái)創(chuàng)建一個(gè)socket。

  • 2.第二,將上一步創(chuàng)建好的socket綁定(bind)到一個(gè)ip:port上。

  • 3.第三,開啟監(jiān)聽linten。

  • 4.第四,使服務(wù)器代碼進(jìn)入無(wú)限循環(huán)不退出,當(dāng)沒有客戶端連接時(shí),程序阻塞在accept上,有連接進(jìn)來(lái)時(shí)才會(huì)往下執(zhí)行,然后再次循環(huán)下去,為客戶端提供持久服務(wù)。

上面這個(gè)案例中,有兩個(gè)很大的缺陷:

  • 1.一次只可以為一個(gè)客戶端提供服務(wù),如果正在為第一個(gè)客戶端發(fā)送helloworld期間有第二個(gè)客戶端來(lái)連接,那么第二個(gè)客戶端就必須要等待片刻才行。

  • 2.很容易受到攻擊,造成拒絕服務(wù)。

分析了上述問題后,又聯(lián)想到了前面說(shuō)的多進(jìn)程,那我們可以在accpet到一個(gè)請(qǐng)求后就fork一個(gè)子進(jìn)程來(lái)處理這個(gè)客戶端的請(qǐng)求,這樣當(dāng)accept了第二個(gè)客戶端后再fork一個(gè)子進(jìn)程來(lái)處理第二個(gè)客戶端的請(qǐng)求,這樣問題不就解決了嗎?OK!擼一把代碼演示一下:

<?php
$host = '0.0.0.0';
$port = 9999;
// 創(chuàng)建一個(gè)tcp socket
$listen_socket = socket_create( AF_INET, SOCK_STREAM, SOL_TCP );
// 將socket bind到IP:port上
socket_bind( $listen_socket, $host, $port );
// 開始監(jiān)聽socket
socket_listen( $listen_socket );
// 進(jìn)入while循環(huán),不用擔(dān)心死循環(huán)死機(jī),因?yàn)槌绦驅(qū)?huì)阻塞在下面的socket_accept()函數(shù)上
while( true ){
  // 此處將會(huì)阻塞住,一直到有客戶端來(lái)連接服務(wù)器。阻塞狀態(tài)的進(jìn)程是不會(huì)占據(jù)CPU的
  // 所以你不用擔(dān)心while循環(huán)會(huì)將機(jī)器拖垮,不會(huì)的 
  $connection_socket = socket_accept( $listen_socket );
  // 當(dāng)accept了新的客戶端連接后,就fork出一個(gè)子進(jìn)程專門處理
  $pid = pcntl_fork();
  // 在子進(jìn)程中處理當(dāng)前連接的請(qǐng)求業(yè)務(wù)
  if( 0 == $pid ){
    // 向客戶端發(fā)送一個(gè)helloworld
    $msg = "helloworld\r\n";
    socket_write( $connection_socket, $msg, strlen( $msg ) );
    // 休眠5秒鐘,可以用來(lái)觀察時(shí)候可以同時(shí)為多個(gè)客戶端提供服務(wù)
    echo time().' : a new client'.PHP_EOL;
    sleep( 5 );
    socket_close( $connection_socket );
    exit;
  }
}
socket_close( $listen_socket );

將代碼保存為server.php,然后執(zhí)行php server.php,客戶端依然使用telnet 127.0.0.1 9999,只不過這次我們開啟兩個(gè)終端來(lái)執(zhí)行telnet。重點(diǎn)觀察當(dāng)?shù)谝粋€(gè)客戶端連接上去后,第二個(gè)客戶端時(shí)候也可以連接上去。運(yùn)行結(jié)果如下:

PHP中如何實(shí)現(xiàn)socket服務(wù)器

通過接受到客戶端請(qǐng)求的時(shí)間戳可以看到現(xiàn)在服務(wù)器可以同時(shí)為N個(gè)客戶端服務(wù)的。但是,接著想,如果先后有1萬(wàn)個(gè)客戶端來(lái)請(qǐng)求呢?這個(gè)時(shí)候服務(wù)器會(huì)fork出1萬(wàn)個(gè)子進(jìn)程來(lái)處理每個(gè)客戶端連接,這是會(huì)死人的。fork本身就是一個(gè)很浪費(fèi)系統(tǒng)資源的系統(tǒng)調(diào)用,1W次fork足以讓系統(tǒng)崩潰,即便當(dāng)下系統(tǒng)承受住了1W次fork,那么fork出來(lái)的這1W個(gè)子進(jìn)程也夠系統(tǒng)內(nèi)存喝一壺了,最后是好不容易費(fèi)勁fork出來(lái)的子進(jìn)程在處理完畢當(dāng)前客戶端后又被關(guān)閉了,下次請(qǐng)求還要重新fork,這本身就是一種浪費(fèi),不符合社會(huì)主義主流價(jià)值觀。如果是有人惡意攻擊,那么系統(tǒng)fork的數(shù)量還會(huì)呈直線上漲一直到系統(tǒng)崩潰。

所以,我們就再次提出增進(jìn)型解決方案。我們可以預(yù)估一下業(yè)務(wù)量,然后在服務(wù)啟動(dòng)的時(shí)候就fork出固定數(shù)量的子進(jìn)程,每個(gè)子進(jìn)程處于無(wú)限循環(huán)中并阻塞在accept上,當(dāng)有客戶端連接擠進(jìn)來(lái)就處理客戶請(qǐng)求,當(dāng)處理完成后僅僅關(guān)閉連接但本身并不銷毀,而是繼續(xù)等待下一個(gè)客戶端的請(qǐng)求。這樣,不僅避免了進(jìn)程反復(fù)fork銷毀巨大資源浪費(fèi),而且通過固定數(shù)量的子進(jìn)程來(lái)保護(hù)系統(tǒng)不會(huì)因無(wú)限fork而崩潰。

<?php
$host = '0.0.0.0';
$port = 9999;
// 創(chuàng)建一個(gè)tcp socket
$listen_socket = socket_create( AF_INET, SOCK_STREAM, SOL_TCP );
// 將socket bind到IP:port上
socket_bind( $listen_socket, $host, $port );
// 開始監(jiān)聽socket
socket_listen( $listen_socket );
// 給主進(jìn)程換個(gè)名字
cli_set_process_title( 'phpserver master process' );
// 按照數(shù)量fork出固定個(gè)數(shù)子進(jìn)程
for( $i = 1; $i <= 10; $i++ ){
  $pid = pcntl_fork();
  if( 0 == $pid ){
    cli_set_process_title( 'phpserver worker process' );
    while( true ){
      $conn_socket = socket_accept( $listen_socket );
      $msg = "helloworld\r\n";
      socket_write( $conn_socket, $msg, strlen( $msg ) );
      socket_close( $conn_socket );
    }
  }
}
// 主進(jìn)程不可以退出,代碼演示比較粗暴,為了不保證退出直接走while循環(huán),休眠一秒鐘
// 實(shí)際上,主進(jìn)程真正該做的應(yīng)該是收集子進(jìn)程pid,監(jiān)控各個(gè)子進(jìn)程的狀態(tài)等等
while( true ){
  sleep( 1 );
}
socket_close( $connection_socket );

將文件保存為server.php后php server.php執(zhí)行,然后再用ps -ef | grep phpserver | grep -v grep來(lái)看下服務(wù)器進(jìn)程狀態(tài):

PHP中如何實(shí)現(xiàn)socket服務(wù)器

可以看到master進(jìn)程存在,除此之外還有10個(gè)子進(jìn)程處于等待服務(wù)狀態(tài),再同一個(gè)時(shí)刻可以同時(shí)為10個(gè)客戶端提供服務(wù)。我們通過telnet 127.0.0.1 9999來(lái)嘗試一下,運(yùn)行結(jié)果如下圖:

PHP中如何實(shí)現(xiàn)socket服務(wù)器

關(guān)于“PHP中如何實(shí)現(xiàn)socket服務(wù)器”這篇文章的內(nèi)容就介紹到這里,感謝各位的閱讀!相信大家對(duì)“PHP中如何實(shí)現(xiàn)socket服務(wù)器”知識(shí)都有一定的了解,大家如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI