资讯专栏INFORMATION COLUMN

PHP回顾之socket编程

tomorrowwu / 1217人阅读

摘要:如果你想体验原味编程,用开头的比较适合否则建议使用流函数。有关流的知识,请参考本人之前的博文回顾之流。接下来我们用流函数实现一个简单的客户端和服务端。流函数中的和两个函数是我们想要的。本文目的是简要介绍中的编程,行文到此已经达到目的。

转载请注明文章出处: https://tlanyan.me/php-review...
PHP回顾系列目录

PHP基础

web请求

cookie

web响应

session

数据库操作

加解密

Composer

创建自己的Composer包

发送邮件

IO

web开发一直是PHP的主战场,也是PHP最为被世人所熟知的一面。其实只要你愿意去发掘,PHP除了做网页在许多其他方面也是小能手。

本文简要介绍PHP的Socket编程。

准备知识

在开始之前,希望你已经知道网络编程中的一些基本概念。比如OSI七层模型、TCP/IP四层模型;TCP中的三次握手、四次挥手等。这些概念是网络编程的理论基础,实践中不一定用得到,但能让你把握整体脉络,更快的定位编程中出现的问题。

再说一下Socket。我们常说的网络编程就是指Socket编程,它既指代实现了TCP/IP协议簇的一套网络编程API,也指代一个客户端与服务器的连接。socket是插座/接口的意思,计算机中常翻译成“套接字”。实际中可以简单的认为网络编程与Socket编程等价,一个tcp连接的说法等价于一个socket。

PHP中的API

PHP中有以socket开头的一套函数API用于Socket编程,PHP5引入“流”的抽象概念后,以stream开头的一套API也可以用于网络编程。两者的主要区别是:

流是PHP中的核心概念,所以stream开头的函数总是可用;sockets是PHP的一个拓展,虽然大部分情况下都默认启用;

socket系列函数相对底层,而stream系列函数是高层的抽象。

如果你想体验原味Socket编程,用socket开头的API比较适合;否则建议使用流函数。有关流的知识,请参考本人之前的博文:PHP回顾之流。

接下来我们用流函数实现一个简单的TCP客户端和服务端。

客户端

客户端网络编程可以归结为简单的三步:

连接服务端(connect);

收发消息(receive/send);

关闭连接(close)。

下面是客户端的代码,发送10条消息到服务端:

// client.php
$host = "127.0.0.1";
$port = 8000;

$socket = @stream_socket_client("tcp://{$host}:{$port}", $errno, $errMsg);
if ($socket === false) {
    throw new RuntimeException("unable to create socket: " . $errMsg);
}
fwrite(STDOUT, "success connect to server: [{$host}:{$port}]...
");

foreach (range(1, 10) as $i) {
    if ($i % 5 === 0) {
        $method = "broadcast";
    } else {
        $method = "echo";
    }
    $args = [sprintf("The %dth greeting", $i)];
    $message = json_encode([
        "method" => $method,
        "args" => $args,
    ]);
    fwrite(STDOUT, "
send to server: $message
");
    $len = @fwrite($socket, $message);
    if ($len === 0) {
        fwrite(STDOUT, "socket closed
");
        break;
    }

    $msg = @fread($socket, 4096);
    if ($msg) {
        fwrite(STDOUT, "receive server: $msg
");
    }
    elseif (feof($socket)) {
       fwrite(STDOUT, "socket closed
");
       break;
    }

    sleep(2);
}

fwrite(STDOUT, "close connnection...
");
fclose($socket);

客户端已经搞定,接下来看服务端。

服务端

服务端编程也很简单,四步搞定:

监听端口(listen);

接受新连接(accept);

收发网络消息(receive/send);

循环第二步和第三步(loop)。

由于服务端一般是长时间运行,除非重启或进程被杀死,极少会主动关闭服务。另外服务端一般需要长时间运行,所以应当运行在CLI模式下(短连的客户端代码可以在web中使用,例如代替CURL获取网页内容,连接redis/MQ等)。

我们简单的将收到的消息返回客户端(Echo服务器):

// server.php
$port = 8000;
$socket = @stream_socket_server("tcp://0.0.0.0:$port", $errno, $errMsg);
if ($socket === false) {
    throw new RuntimeException("fail to listen on port: {$port}!");
}
fwrite(STDOUT, "socket server listen on port: {$port}" . PHP_EOL);

while (true) {
    $client = @stream_socket_accept($socket);
    if ($client == false) {
        continue;
    }

    fwrite(STDOUT, "client:" . (int)$client . " connnected.
");
    @fwrite($client, "Welcome aboard!
");

    while (true) {
        $msg = @fread($client, 4096);
        if ($msg) {
            fwrite(STDOUT, "
receive client: $msg
");
            // echo
            @fwrite($client, $msg);
        } elseif (feof($client)) {
            fwrite(STDOUT, "client:" . (int)$client . " disconnnect!
");
            fclose($client);
            break;
        }
    }
}

先启动服务端脚本:php server.php, 然后打开新的窗口启动客户端:php client.php。可以看到消息被正确的发送和接收。客户端退出后,可多次重新运行客户端脚本查看效果。

并发

同时运行两个或以上客户端,会发现第二个起卡住,前面的客户端退出后才继续运行。回顾服务端代码,可以看到accept一个客户端后,服务端就专心为其服务,直到断开才服务下一个。

同时服务多个客户端,这才是我们期望的。默认情况下socket处于阻塞模式,无数据时fread函数会一直等待,导致程序不能抽身服务其他客户端。要同时服务多个客户端,第一步是设置非阻塞模式,第二步是更改轮询方式。流函数中的stream_set_blockingstream_select两个函数是我们想要的。

将服务端的代码更改如下:

// server.php
 $client) {
        while (true) {
            $msg = @fread($client, 4096);
            if ($msg) {
                fwrite(STDOUT, "receive client " . (int)$client . " message: $msg
");
                $json = json_decode($msg, true);
                if ($json) {
                    $method = $json["method"];
                    if ($method === "echo") {
                        @fwrite($client, $msg);
                    } else {
                        foreach ($clients as $cl) {
                            @fwrite($cl, "message from " . (int)$client . ": $msg");
                        }
                    }
                }
            } else {
                if (feof($client)) {
                    fwrite(STDOUT, "
client " . (int)$client . " closed.
");
                    fclose($client);
                    $key = array_search($client, $clients);
                    unset($clients[$key]);
                }
                break;
            }
        }
    }
}

然后启动服务端:php server.php,再同时启动多个客户端,或者用多个进程同时发送消息(需安装pcntl拓展):

// client.php
for ($index = 0; $index < 10; ++ $index) {
    $pid = pcntl_fork();
    if ($pid < 0) {
        fwrite(STDERR, "fail to fork!
");
        exit;
    }

    if ($pid === 0) {
        connectServer();  // connectServer就是上文中client.php中的代码
        exit;
    }
}
// 父进程先退出,不会出现僵尸进程,忽略孤儿进程的处理

启动客户端后,可以看到服务端正确的同时处理多个客户端,这正是我们期待的。

缺憾

上述代码实现了客户端和可并发的服务端,作为演示基本够用。如果要投入到实践中使用,至少有以下方面的不足:

多进程/多线程/协程缺失,除处理网络消息外,不能(难)做其他逻辑业务;

没有协议解析,会导致多条信息合并成一条读取(或者一条信息被拆成多条);

select低效且有并发连接数目限制,客户端量大时需要poll/epoll等技术;

每个方面展开来说至少都是一篇长文。本文目的是简要介绍PHP中的Socket编程,行文到此已经达到目的。由于网络协议十分繁杂,想深入网络编程请参阅更多权威文档。

总结

本文基于PHP5引入的流简要介绍了PHP中的Socket编程,并给出了一个简单并发服务器的实现。文中代码仅做演示用,在生产环境中,请使用成熟的网络框架/库。

参考

http://php.net/manual/en/book...

http://www.unixguide.net/netw...

http://php.net/manual/en/book...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/28901.html

相关文章

  • PHP回顾多进程编程

    摘要:多进程中与多进程相关的两个重要拓展是和。函数执行期间,主进程除了等待无法处理其他任务,所以一般不认为这是多进程编程。回收子进程有两种方式,一种是主进程调用函数等待子进程结束另外一种是处理信号。 转载请注明文章出处: https://tlanyan.me/php-review... PHP回顾系列目录 PHP基础 web请求 cookie web响应 session 数据库操作 加解...

    lifesimple 评论0 收藏0
  • PHP回顾执行流程及相关概念

    摘要:通过,脚本层无需过多考虑执行的具体环境,而本身则可以让针对自己的特点给出特有实现。模式下,也只执行一次。这几个概念的关系如下网关协议,与语言无关,所以与关系也不大。总结本文简要回顾了程序的架构和执行流程,并对几个容易混淆概念做了介绍。 转载请注明文章出处:https://tlanyan.me/php-review... PHP回顾系列目录 PHP基础 web请求 cookie we...

    jsdt 评论0 收藏0
  • PHP回顾协程

    摘要:本文先回顾生成器,然后过渡到协程编程。其作用主要体现在三个方面数据生成生产者,通过返回数据数据消费消费者,消费传来的数据实现协程。解决回调地狱的方式主要有两种和协程。重点应当关注控制权转让的时机,以及协程的运作方式。 转载请注明文章出处: https://tlanyan.me/php-review... PHP回顾系列目录 PHP基础 web请求 cookie web响应 sess...

    Java3y 评论0 收藏0
  • PHP回顾IO

    摘要:命令行时返回值为,标准输入输出均指向终端可用进程号查看。会在脚本执行完毕后关闭三个流,无需用户手动关闭。与远程网址交互是一个请求和响应的过程,其中细节可参考本人之前的文章回顾之请求和回顾之响应,也可参考协议的权威文档。 转载请注明文章出处: https://tlanyan.me/php-review... PHP回顾系列目录 PHP基础 web请求 cookie web响应 ses...

    happen 评论0 收藏0
  • SegmentFault D-Day 2016 「北京:后端」 活动回顾

    摘要:今年从北京站开始,分享主题与后端相关。嘉宾汇总高驰涛性能之路姜季廷的前后之道孙宏亮生态中的现状与实践信海龙异步化探索今年还会在其他九个城市巡回分享,感谢大家的关注与分享。 今年 SegmentFault D-Day 从北京站开始,分享主题与「后端」相关。当然,我们还会在其他九个城市巡回分享,欢迎大家关注,帮忙扩散。 开场介绍 首先是 youku 美女星宇对 SegmentFault 社...

    PingCAP 评论0 收藏0

发表评论

0条评论

tomorrowwu

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<