Socket学习系列之使用php socket扩展实现非阻塞TCP Server/Client

前言

前一篇文章基于PHP的socket扩展实现了一个阻塞的tcpserver服务端,本篇文章将借用socket_set_nonblock实现非阻塞服务端,关于阻塞与非阻塞的区分请查看之前文章

代码实现

服务端

<?php

// 非阻塞IO服务端
// tcp socket服务端分需要实现以下几步
// 1. 创建socket
// 2. 设置socket为非阻塞模式
// 3. 给socket绑定IP:端口
// 4. 监听socket
// 5. 接收处理请求
//      4.1 accept请求【非阻塞】
//      4.2 read数据 & write数据
//      4.3 关闭accept的链接
// 6.关闭socket(关闭服务)

// 创建socket
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if (!$socket) {
    die('create server fail');
}

// 设置socket为非阻塞模式,这样在没有客户端连接被服务端accept时,服务端所在进程/线程不会被操作系统挂起,而是返回错误
socket_set_nonblock($socket);

// 给套接字绑定ip+port
$ret = socket_bind($socket, '0.0.0.0', 8093);
if (!$ret) {
    die('bind server fail');
}

// 监听socket上的连接
$ret = socket_listen($socket, 2);
if (!$ret) {
    die('listen server fail');
}
echo "waiting client...\n";


while (true) {
    // 非阻塞accept等待客户端连接
    // 如果没有成功accept到客户端,accept将返回false,我们手动将进程睡眠2秒,然后再次accept(也可以不sleep,但是容易cpu100%)
    // 如果accept成功,我们将连接交给onRecv处理
    $conn = socket_accept($socket);
    var_dump($conn);
    if (!$conn) {
        echo 'accept server fail,但是我没被阻塞住哦';
        sleep(2);
        continue;
    }

    echo "client connect succ.\n";
    // 接收请求、返回响应、关闭连接
    onRecv($conn);
}

echo 'bye';


// 关闭socket
socket_close($socket);

/**
 * 解析客户端消息
 * 协议:换行符(\n)
 *
 * @param $conn
 */
function onRecv($conn)
{
    //实际接收到的消息
    $recv = '';

    //循环读取消息
    while (true) {
        $buffer = socket_read($conn, 100); //每次读取100byte
        if ($buffer === false || $buffer === '') {
            echo "client closed\n";
            //关闭本次连接
            socket_close($conn);
            break;
        }

        //解析单次消息,协议:换行符
        $pos = strpos($buffer, "\n");

        //消息未读取完毕,继续读取
        if ($pos === false) {
            $recv .= $buffer;
        }

        //消息读取完毕
        else {
            //去除换行符及空格
            $recv .= trim(substr($buffer, 0, $pos + 1));

            //客户端主动端口连接,关闭本次连接
            if ($recv === 'quit') {
                exit('server closed');
                break;
            }

            // 响应数据
            echo "recv: $recv \n";
            socket_write($conn, "hello this is server, you send data:$recv \n");

            //清空消息,准备下一次read(连接不是单次处理完就关闭)
            $recv = '';
        }
    }
}

客户端

<?php

// tcp socket客户端分需要实现以下几步
// 1. 创建socket
// 2. 使用socket连接指定的server IP:端口【可省略】
// 3. connect服务端
// 4. 发送请求,接收响应
// 5. 关闭socket 连接

// 创建连接
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if (!$socket) {
    die('create server fail:');
}

// 绑定socket ip和端口
// $ret = socket_bind($socket, '0.0.0.0', 80902);
// if (!$ret) {
//     die('bind server fail');
// }

// 连接server
$response = socket_connect($socket, '127.0.0.1', 8093);
if (!$response) {
    die('connect fail');
}

while (true) {
    // 发送消息
    echo '请输入您要发送的内容' . PHP_EOL;
    $str = trim(fgets(STDIN));
    socket_write($socket, $str . "\n");

    // 读取消息
    $recv = '';
    while (true) {
        $buffer = socket_read($socket, 1024);
        if ($buffer === false) {
            socket_close($socket);
            exit('read socket error' . "\n");
        }

        //解析单次消息,协议:换行符
        $pos = strpos($buffer, "\n");

        // 消息未读取完毕,继续读取
        if ($pos !== false) {
            break;
        }

        $recv .= $buffer;
    }
    echo "server response: {$buffer} \n";

    if (strpos($buffer, 'bye')) {
        break;
    }
}

// 关闭连接
socket_close($socket);

发表评论

电子邮件地址不会被公开。 必填项已用*标注