目录
服务端程序 acceptSever.php
客户端程序 acceptClient.php
代码解析
首页 后端开发 php教程 PHP网络编程 之Accept 阻塞模型的介绍

PHP网络编程 之Accept 阻塞模型的介绍

Jul 28, 2018 am 10:33 AM
php

本篇文章给大家分享的内容是关于PHP网络编程 之Accept 阻塞模型的介绍,内容很详细,有需要的朋友可以参考一下,希望可以帮助到大家。

Accept 阻塞模型是一种相对古老的模型,不过里面蕴含了许多有趣的知识,比如阻塞/非阻塞、锁、超时重传...

服务端程序 acceptSever.php

<?php


set_time_limit(0);  # 设置脚本执行时间无限制

class SocketServer 
{
    private static $socket;
    function SocketServer($port) 
    {
        global $errno, $errstr;
        if ($port < 1024) {
            die("Port must be a number which bigger than 1024/n");
        }
        
        $socket = stream_socket_server("tcp://0.0.0.0:{$port}", $errno, $errstr);
        if (!$socket) die("$errstr ($errno)");
        
        
        while ($conn = stream_socket_accept($socket, -1)) { // 这样设置不超时才有用
            static $id = 0; # 进程 id
            static $ct = 0; # 接收数据的长度  
            $ct_last = $ct; 
            $ct_data = &#39;&#39;; # 接受的数据
            $buffer = &#39;&#39;;  # 分段读取数据
        
            $id++; 
            echo "Client $id come" . PHP_EOL;
            
            # 持续监听
            while (!preg_match(&#39;{/r/n}&#39;, $buffer)) { // 没有读到结束符,继续读
//                if (feof($conn)) break; // 防止 popen 和 fread 的 bug 导致的死循环
                $buffer = fread($conn, 1024);
                echo &#39;R&#39; . PHP_EOL; #  打印读的次数
                $ct += strlen($buffer);
                $ct_data .= preg_replace(&#39;{/r/n}&#39;, &#39;&#39;, $buffer);
            }
            $ct_size = ($ct - $ct_last) * 8;
            echo "[$id] " . __METHOD__ . " > " . $ct_data . PHP_EOL;
            fwrite($conn, "Received $ct_size byte data./r/n");
            fclose($conn);
        }
        
        fclose($socket);
    }
}
new SocketServer(2000);
登录后复制

客户端程序 acceptClient.php

<?php

# 日志记录
function debug ($msg)
{
    error_log($msg, 3, &#39;./socket.log&#39;);
}

if ($argv[1]) {
    
    $socket_client = stream_socket_client(&#39;tcp://0.0.0.0:2000&#39;, $errno, $errstr, 30);
    
    /*    设置脚本为非阻塞    */
#    stream_set_blocking($socket_client, 0);

    /*    设置脚本超时时间    */
#    stream_set_timeout($socket_client, 0, 100000);
    
    if (!$socket_client) {
        die("$errstr ($errno)");
    } else {
        # 填充容器
        $msg = trim($argv[1]);

        for ($i = 0; $i < 10; $i++) {
            $res = fwrite($socket_client, "$msg($i)");
            usleep(100000);
            echo &#39;W&#39;; // 打印写的次数
#            debug(fread($socket_client, 1024)); // 将产生死锁,因为 fread 在阻塞模式下未读到数据时将等待
        }
        fwrite($socket_client, "/r/n"); // 传输结束符
        # 记录日志
        debug(fread($socket_client, 1024));
        fclose($socket_client);
    }
}
else {
    
//    $phArr = array();
//    for ($i = 0; $i < 10; $i++) {
//        $phArr[$i] = popen("php ".__FILE__." &#39;{$i}:test&#39;", &#39;r&#39;);
//    }
//    foreach ($phArr as $ph) {
//        pclose($ph);
//    }
    
    for ($i = 0; $i < 10; $i++) {
        system("php ".__FILE__." &#39;{$i}:test&#39;");    # 这里等于 php "当前文件" "脚本参数"
    }
}
登录后复制

代码解析

首先,解释一下以上的代码逻辑:客户端 acceptClient.php 循环发送数据,最后发送结束符;服务端 accept Server.php 使用 accept 阻塞方式接收 socket 连接,然后循环接收数据,直到收到结束符,返回结果数据(接收到的字节数)客户端收到服务器返回的数据,写入日志。虽然逻辑很简单,但是其中有几种情况很值得分析一下:

A> 默认情况下,运行 php socket_client.php test,客户端打出 10 个 W,服务端打出若干个 R 后面是接收到的数据,socket.log 记录下服务端返回的接收结果数据,效果如下:
1500280071-5b5b18674c9ed_articlex.png
这种情况很容易理解,不再赘述。然后,使用 telnet 命令同时打开多个客户端,你会发现服务器一个时间只处理一个客户端,如图所示:
3477710993-5b5b1a8e66d59_articlex.png
其他需要在后面“排队”;这就是阻塞 IO 的特点,这种模式的弱点很明显,效率极低。

B> 只打开 socket_client.php 第 29 行的注释代码,再次运行 php socket_client.php test 客户端打出一个 W,服务端也打出一个 R,之后两个程序都卡住了。这是为什么呢,分析逻辑后你会发现,这是由于客户端在未发送结束符之前就向服务端要返回数据;而服务端由于未收到结束符,也在向客户端要结束符,造成死锁。而之所以只打出一个 W 和 R,是因为 fread 默认是阻塞的。要解决这个死锁,必须打开 socket_client.php 第 17 行的注释代码,给 socket 设置一个 0.1 秒的超时,再次运行你会发现隔 0.1 秒出现一个 W 和 R 之后正常结束,服务端返回的接收结果数据也正常记录了。可见 fread 缺省是阻塞的,我们在编程的时候要特别注意,如果没有设置超时,就很容易会出现死锁。

C> 只打开 14 行注释设置脚本为非阻塞,运行 php socket_client.php test,结果基本和情况 A 相同,唯一不同的是 socket.log 没有记录下返回数据,这是因为当我们非阻塞下客户端不必等待接收到服务器的响应结果就可以继续往下执行,当执行到 debug 的时候,读取的数据还是空的,所以 socket.log 也是空的。这里可以看出客户端运行在阻塞和非阻塞模式的区别,当然在客户端不在乎接受结果的情况下,可以使用非阻塞模式来获得最大效率。

D> 运行 php socket_client.php 是连续运行 10 次上面的逻辑,这个没什么问题;但是很奇怪的是如果你使用 39 - 45 行的代码,用 popen 同时开启 10 个进程来运行,就会造成服务器端的死循环,十分怪异!后来经调查发现只要是用 popen 打开的进程创建的连接会导致 fread 或者 socket_read 出错直接返回空字串,从而导致死循环,查阅 PHP 源代码后发现 PHP 的 popen 和 fread 函数已经完全不是 C 原生的了,里面都插入了大量的 php_stream_* 实现逻辑,初步估计是其中的某个 bug 导致的 Socket 连接中断所导致的,解决方法就是打开 socket_server.php 中 33 行的代码,如果连接中断则跳出循环,但是这样一来就会有很多数据丢失了,这个问题需要特别注意!

相关推荐:

PHP和Apache的基本应用

什么是PHP正则表达式?PHP正则表达式的使用方法(附代码)

以上是PHP网络编程 之Accept 阻塞模型的介绍的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解锁Myrise中的所有内容
3 周前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

适用于 Ubuntu 和 Debian 的 PHP 8.4 安装和升级指南 适用于 Ubuntu 和 Debian 的 PHP 8.4 安装和升级指南 Dec 24, 2024 pm 04:42 PM

PHP 8.4 带来了多项新功能、安全性改进和性能改进,同时弃用和删除了大量功能。 本指南介绍了如何在 Ubuntu、Debian 或其衍生版本上安装 PHP 8.4 或升级到 PHP 8.4

CakePHP 使用数据库 CakePHP 使用数据库 Sep 10, 2024 pm 05:25 PM

在 CakePHP 中使用数据库非常容易。本章我们将了解CRUD(创建、读取、更新、删除)操作。

CakePHP 日期和时间 CakePHP 日期和时间 Sep 10, 2024 pm 05:27 PM

为了在 cakephp4 中处理日期和时间,我们将使用可用的 FrozenTime 类。

CakePHP 文件上传 CakePHP 文件上传 Sep 10, 2024 pm 05:27 PM

为了进行文件上传,我们将使用表单助手。这是文件上传的示例。

CakePHP 路由 CakePHP 路由 Sep 10, 2024 pm 05:25 PM

在本章中,我们将学习以下与路由相关的主题?

讨论 CakePHP 讨论 CakePHP Sep 10, 2024 pm 05:28 PM

CakePHP 是 PHP 的开源框架。它的目的是使应用程序的开发、部署和维护变得更加容易。 CakePHP 基于类似 MVC 的架构,功能强大且易于掌握。模型、视图和控制器 gu

CakePHP 创建验证器 CakePHP 创建验证器 Sep 10, 2024 pm 05:26 PM

可以通过在控制器中添加以下两行来创建验证器。

CakePHP 日志记录 CakePHP 日志记录 Sep 10, 2024 pm 05:26 PM

登录 CakePHP 是一项非常简单的任务。您只需使用一项功能即可。您可以记录任何后台进程(如 cronjob)的错误、异常、用户活动、用户采取的操作。在 CakePHP 中记录数据很容易。提供了 log() 函数

See all articles