php socket IO阻塞方式的Server/Client

php的socket編程和java的socket編程區別還是蠻大的,默認情況下php的socket不能像java socket開啓多線程來同時接收多個客戶端的連接。

使用 telnet 命令同時打開多個客戶端測試,你會發現服務器一個時間只處理一個客戶端,其他需要在後面“排隊”;只有當前的客戶端端口才會處理下一個連接

這就是阻塞 IO 的特點,這種模式的弱點很明顯,效率極低

網絡編程時,我們常常見到同步(Sync)/異步(Async),阻塞(Block)/非阻塞(Unblock)四種調用方式

同步、異步和阻塞、非阻塞是組合關係,因此有4種方式:

同步阻塞、同步非阻塞、異步阻塞、異步非阻塞

linux有五種I/O模型

1)阻塞I/O(blocking I/O)

2)非阻塞I/O (nonblocking I/O)

3)I/O複用(select 和poll) (I/O multiplexing)

4)信號驅動I/O (signal driven I/O (SIGIO))

5)異步I/O (asynchronous I/O (the POSIX aio_functions))

前四種都是同步,只有最後一種纔是異步IO

詳細瞭解:淺談socket同步和異步、阻塞和非阻塞、I/O模型_php技巧

 

以下是php cli模式下阻塞I/O方式的socekt server/client 示例代碼


server.php

<?php
/**
 * socket_server.php.
 * User: lvfk
 * Date: 2017/11/20 0020
 * Time: 17:28
 * Desc: php socket server
 */

/*
+-------------------------------
*    @socket通信整個過程
+-------------------------------
*    @socket_create
*    @socket_bind
*    @socket_listen
*    @socket_accept
*    @socket_read
*    @socket_write
*    @socket_close
*    @socket_strerror
*    @socket_getpeername
+--------------------------------
*/

set_time_limit(0);//確保連接客戶端不會超時


$ip = '127.0.0.1';
$port = 5555;

//創建socket
if(($socket = socket_create(AF_INET, STREAM_SOCK_STREAM, SOL_TCP)) < 0){
    echo "socket_create() 失敗的原因是:".socket_strerror(socket_last_error())."\n";
}

//阻塞模式
socket_set_block($socket) or die("socket_set_block() 失敗的原因是:" . socket_strerror(socket_last_error()) . "\n");

//綁定ip和端口
if(($ret = socket_bind($socket, $ip, $port)) < 0){
    echo "socket_bind() 失敗的原因是:".socket_strerror($ret)."\n";
}

//監聽
if(($ret = socket_listen($socket)) < 0){
    echo "socket_listen() 失敗的原因是:".socket_strerror($ret)."\n";
}
echo "PHP Socket Server create suc!\n";
$buf = null;
do{
    // 接受一個Socket客戶端連接
    if(($client = socket_accept($socket)) < 0){
        echo "socket_accept() failed: reason: " . socket_strerror(socket_last_error($client)) . "\n";
    }else{
        socket_getpeername($client, $client_addr, $client_port);
        //告知客戶端連接成功
        $msg = "connect suc!";
        socket_write($client, $msg, strlen($msg));
        do{
            //打印客戶端發送來的信息
            if(($buf = @socket_read($client, 8192)) === false){
                echo "socket_read() failed: reason: " . socket_strerror(socket_last_error($client)) . "\n";
                socket_close($client);
                break;
            }
            echo "rev client[{$client_addr}]:".$buf."\n";

            //服務器發送給客戶端
            $buf = 'server send:'.$buf;
            socket_write($client, $buf, strlen($buf));

        }while(true);
    }

    if('exit' == $buf){
        break;
    }
}while(true);
//關閉socket
socket_close($socket);

client.php

<?php
/**
 * socket_client.php.
 * User: lvfk
 * Date: 2017/11/21 0021
 * Time: 9:57
 * Desc: php socket client
 */

/*
+-------------------------------
*    @socket通信整個過程
+-------------------------------
*    @socket_create
*    @socket_connect
*    @socket_read
*    @socket_write
*    @socket_close
*    @socket_strerror
*    @socket_getpeername
+--------------------------------
*/

error_reporting(E_ALL);
set_time_limit(0);

echo "start connect server\n";

$ip = '127.0.0.1';
$port = 5555;

//創建socket
if(($socket = socket_create(AF_INET, STREAM_SOCK_STREAM, SOL_TCP)) < 0){
    echo "socket_create() failed: reason: " . socket_strerror(socket_last_error()) . "\n";
}

/***設置socket連接選項,這兩個步驟可以省略***/
//接收套接流的最大超時時間1秒,後面是微秒單位超時時間,設置爲零,表示不管它
socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, array("sec" => 1, "usec" => 0));
//發送套接流的最大超時時間爲6秒
socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, array("sec" => 6, "usec" => 0));
/***設置socket連接選項,這兩個步驟可以省略***/

$info = "connect $ip:$port...\n";
echo $info;

//連接到127.0.0.1:5555這個socket server上面
if(($ret = socket_connect($socket, $ip, $port)) < 0){
    echo "socket_connect() failed.\nReason: ($ret) " . socket_strerror($ret) . "\n";
}

do{
    //讀取服務器發送信息
    $out = socket_read($socket, 8192);
    echo "client rev:",$out."\n";

    //讀取鍵盤輸入併發送給服務器
    $in = trim(fgets(STDIN));

    if(! socket_write($socket, $in, strlen($in))){
        echo "socket_write() failed: reason: " . socket_strerror(socket_last_error($socket)) . "\n";
    }

    //判斷是否退出命令
    if('exit' == $in){
        echo "client quit\n";
        break;
    }

}while(true);
//關閉socket
socket_close($socket);

運行效果

Server:


Client1:

可以正常的通信數據


Client2

當Client1繼續連接時,Client2會被阻塞



當此時斷掉Client1時,Client2就可以正常通信



所以,這種簡單的socket通信就是IO阻塞的

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章