PHP Socket 网络编程

栏目: PHP · 发布时间: 4年前

内容简介:在做PHP开发的过程中,大部分我们都在和http协议打交道,在ISO模型里面,http属于应用层协议,它底层会用到TCP协议。http协议非常简单,它是一个文本协议,一个请求对应一个响应,客户端发起一个请求,服务端响应这个请求。http是一个一问一答的对话,每次请求都得重新建立对话(这里暂不讨论Keep-Alive),如果你想通过一个请求进行多次对话,那就是长连接通信,必须使用TCP或者UDP协议。互联网运行的基石是建立在一些协议上的,目前而言主要是TCP/IP协议族,大部分协议都是公开开放的,计算机遵循这

在做 PHP 开发的过程中,大部分我们都在和http协议打交道,在ISO模型里面,http属于应用层协议,它底层会用到TCP协议。http协议非常简单,它是一个文本协议,一个请求对应一个响应,客户端发起一个请求,服务端响应这个请求。http是一个一问一答的对话,每次请求都得重新建立对话(这里暂不讨论Keep-Alive),如果你想通过一个请求进行多次对话,那就是长连接通信,必须使用TCP或者UDP协议。

互联网运行的基石是建立在一些协议上的,目前而言主要是TCP/IP协议族,大部分协议都是公开开放的,计算机遵循这些协议我们才能通信,当然也有一些私有协议,私有协议只有自己知道如何去解析,相当来说更安全,比如QQ所用的协议就是自己定义的。在ISO模型里面,咱们常用的有http、ftp、ssh、dns等,但是不常用的数不胜数,发明一个协议不难,难的是如何设计的更好用,而且大家都喜欢用。

Socket

Socket并不是一个协议,本质上说Socket是对 TCP/IP 协议的封装,它是一组接口,在 设计模式 中,Socket 其实就是一个门面(facade)模式,它把复杂的 TCP/IP 协议族隐藏在 Socket 接口后面,对用户来说,一组简单的接口就是全部,让 Socket 去组织数据,以符合指定的协议。

下图展示了Socket在ISO模型里面大概位置:

PHP Socket 网络编程

PHP Socket

虽然PHP的强项是处理文本,一般用来写网页和http接口,但是官方依然提供了Socket扩展,编译PHP时在配置中添加--enable-sockets 配置项来启用,如果使用apt或yum安装,默认情况下是已启用。

官方文档里面列出了大概40个函数,但是常用的也就那几个,跟着文档,咱们一起来学学如何使用,首先声明一下,本人对Socket编程并不熟悉,如有错误的地方,希望大家指出来。

咱们先看一幅图,关于TCP客户端和服务端之间的通信过程,咱们平时写http接口的时候并未做这么多工作,那是客户端给封装好了:

PHP Socket 网络编程

1.服务端代码

<?php
set_time_limit(0);

$ip = '127.0.0.1';
$port = 8888;

$sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);

socket_bind($sock, $ip, $port);
socket_listen($sock, 4);

echo "Server Started, Listen On $ip:$port\n";

$accept = socket_accept($sock);

socket_write($accept, "Hello World!\n", 8192);

$buf = socket_read($accept, 8192);

echo "Receive Msg: " . $buf . "\n";

socket_close($sock);
复制代码

简单说一下,为便于演示,所以省略了所有的错误处理代码,可以看到分为create、bind、listen、accept、write\read、close这几步,看上去非常简单!具体参数大家可以看一下文档!在服务端启动之后,当收到一个请求之后,我们首先返回了一个 Hello World\n ,然后又读取了8192个字节的数据,打印出来!最后关闭连接。

由于这里,咱还没有写客户端,所以暂时使用curl访问一下,运行效果如下:

===>服务端:

PHP Socket 网络编程

===>客户端:

PHP Socket 网络编程

从这个例子里面我们可以看出来,curl发出是一个标准的http请求,实际上它的每一行后面是有\n的,在http协议里面,这几行文本其实是头(header),但是在这个例子里面,对于我们来说,它就是一段文本而已,服务端只是把它的内容打印出来了,并没有去按照http协议去解析。虽然我们返回了 Hello World!\n ,但是这也并没有按照http协议的格式去做,缺少响应头。我只能说curl比较强大,如果使用浏览器访问的话会失败,提示 127.0.0.1 sent an invalid response

但是稍加改造,我们就可以返回一个标准的http响应:

$response = "HTTP/1.1 200 OK\r\n";
$response .= "Server: Socket-Http\r\n";
$response .= "Content-Type: text/html\r\n";
$response .= "Content-Length: 13\r\n\r\n";
$response .= "Hello World!\n";

socket_write($accept, $response, 8192);
复制代码

这时候如果再用浏览器访问,就可以看到 Hello World!了,但是这个服务端目前是一次性的,就是说它只能处理一次请求,然后就结束了,正常的服务端是可以处理多次请求的,很简单,加一个死循环就行了!

只贴一下改动的部分,代码如下:

while (true) {
    $accept = socket_accept($sock);

    $buf = socket_read($accept, 8192);

    echo "Receive Msg: " . $buf . "\n";

    $response = "HTTP/1.1 200 OK\r\n";
    $response .= "Server: Socket-Http\r\n";
    $response .= "Content-Type: text/html\r\n";
    $response .= "Content-Length: 13\r\n\r\n";
    $response .= "Hello World!\n";

    socket_write($accept, $response, 8192);

    socket_close($accept);
}
复制代码

摇身一变,就是一个http服务了,使用ab测了一下,并发上万,是不是有点小激动?

PHP Socket 网络编程

然而,之所以这么快是因为逻辑简单,假如你在while里面任何位置加一个 sleep(1) 你就会发现,原来这特么是串行的,一个个执行的,并不是并行,这段脚本一次只能处理一个请求!

解决这个问题方法有很多种,具体可以参考PHP并发IO编程之路, 看看前半段就行了,后半段是广告!该文章总结了3种方法:最早是采用多进程多线程方式,由于进程线程开销大,这种方式效率最低。后来演进出master-worker模型,也就是类似现在fpm采用的方式。目前最先进的方式就是异步io多路复用,基于epoll实现的。 理论上讲C能实现的,PHP都能通过扩展去实现 ,而且PHP确实提供了相关扩展,其思想和C写的都差不多,然而今天咱不是说高并发编程的,还是接着说Socket吧!

2.客户端代码

之前的例子里面我们使用的是curl访问的,也可以使用浏览器或者telnet,这些 工具 都可以算作是客户端,客户端也可以自己实现。

set_time_limit(0);

$port = 8888;
$ip = '127.0.0.1';

$sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);

echo "Connecting $ip:$port\n";

socket_connect($sock, $ip, $port);

$input = "Hello World Socket";

socket_write($sock, $input, strlen($input));

$out = socket_read($sock, 8192);

echo "Receive Msg: $out\n";

socket_close($sock);
复制代码

这段代码同样省略了错误处理代码,可以看到第一步都是create,但是第二步变成connect,然后是read\write、最后close。

具体运行效果这里不再展示,和curl访问没多大区别,但是这个客户端也是一次性的,执行完了就结束!

实例

接下来,我们来写一个基于TCP通信的应用,这个应用非常简单,就是加减乘除!

(1)服务端代码:

<?php
set_time_limit(0);

$ip = '127.0.0.1';
$port = 8888;

$sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);

socket_bind($sock, $ip, $port);

socket_listen($sock, 4);

echo "Server Started, Listen On $ip:$port\n";

while (true) {
    $accept = socket_accept($sock);

    $buf = socket_read($accept, 8192);

    echo "Receive Msg: " . $buf . "\n";

    $params = json_decode($buf, true);
    $m = $params['m'];
    $a = $params['a'];
    $b = $params['b'];

    switch ($m) {
        case '+';
            $response = $a + $b;
            break;
        case '-';
            $response = $a - $b;
            break;
        case '*';
            $response = $a * $b;
            break;
        case '/';
            $response = $a / $b;
            break;
        default:
            $response = $a + $b;
    }

    socket_write($accept, $response."\n", 8192);

    socket_close($accept);
}
复制代码

(2)客户端代码:

<?php
set_time_limit(0);

$port = 8888;
$ip = '127.0.0.1';

$sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);

echo "Connecting $ip:$port\n";

socket_connect($sock, $ip, $port);

$input = json_encode([
    'a' => 15,
    'b' => 10,
    'm' => '+'
]);

socket_write($sock, $input, strlen($input));

$out = socket_read($sock, 8192);

echo "Receive Msg: $out\n";

socket_close($sock);
复制代码

在这些代码里面,我按照自己的需求定义了一个“协议”,我把需要运算的数和方式通过一个json数组传输,约定了一个格式,这个协议只有我自己清楚,所以只有我才知道怎么调用。服务端在接受到参数之后,通过运算得出结果,然后把结果返回给客户端。

但是这个例子还有问题,客户端依然是一次性的,参数都被硬编码在代码里面,不够灵活,最关键是没有用到TCP长连接的特性,我们每次计算都得重新发起请求、重新建立连接,实际上,我需要的是一次连接,多次对话,也就是进行多次计算!

目前为止,这些演示代码都没有复用连接,因为在服务端最后我close了这个连接,这意味着每次都是一个新的请求,如果是http服务的话尚且可以用一下,如何去实现一个TCP长连接呢?

IO多路复用之select

select系统调用的目的是在一段指定时间内,监听用户感兴趣的文件描述符上的可读、可写和异常事件,虽然这个方式也比较低效,但是不妨了解一下,通过这种方式我们可以复用连接,完整的代码如下:

<?php
set_time_limit(0);

$ip = '127.0.0.1';
$port = 8888;

$sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);

socket_bind($sock, $ip, $port);

socket_listen($sock, 4);

echo "Server Started, Listen On $ip:$port\n";

socket_set_nonblock($sock);

$clients = [];

while (true) {
    $rs = array_merge([$sock], $clients);
    $ws = [];
    $es = [];

    //监听文件描述符变动
    $ready = socket_select($rs, $ws, $es, 3);
    if (!$ready) {
        continue;
    }

    if (in_array($sock, $rs)) {
        $clients[] = socket_accept($sock);
        $key = array_search($sock, $rs);
        unset($rs[$key]);
    }

    foreach ($rs as $client) {
        $input = socket_read($client, 8096);
        if ($input == null) {
            $key = array_search($client, $clients);
            unset($clients[$key]);
            continue;
        }
        echo "input: " . $input;

        //解析参数,计算结果
        preg_match("/(\d+)(\W)(\d+)/", $input, $params);
        if (count($params) === 4) {
            $a = intval($params[1]);
            $b = intval($params[3]);
            $m = $params[2];
        } else {
            continue;
        }

        switch ($m) {
            case '+';
                $result = $a + $b;
                break;
            case '-';
                $result = $a - $b;
                break;
            case '*';
                $result = $a * $b;
                break;
            case '/';
                $result = $a / $b;
                break;
            default:
                $result = $a + $b;
        }

        $output = "output: $result\n";
        echo $output;
        socket_write($client, $output, strlen($output));
    }
}
复制代码

然后我使用了telnet连接服务端进行操作,运行效果如下,一个基于TCP长连接的网络版简易计算器:

PHP Socket 网络编程

在这个例子,传参的“协议”稍微有点变化,只是为了更方便在telnet里面交互,但是很容易理解。这里面最关键是定义了一个全局变量用来存储连接资源描述符,然后通过select去监听变化,最后遍历整个数组,读取\写入数据!

总结

通过上面的简单介绍,希望大家都对PHP Socket编程有一些了解和认识,其实作为Web开发来说,很少会用到裸TCP去连接,大部分时候都是使用基于TCP的http协议,只有涉及到一些对响应速度要求非常高的应用,比如说游戏、实时通信、物联网才会用到,如果真的用到,不妨尝试一下Workman、Swoole这些成熟的框架!


以上所述就是小编给大家介绍的《PHP Socket 网络编程》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

图解网站分析(修订版)

图解网站分析(修订版)

[日] 小川卓 / 沈麟芸 / 人民邮电出版社 / 2014-10 / 69.00元

本书以图配文,结合实例详细讲解了如何利用从网站上获取的各种数据了解网站的运营状况,如何从数据中攫取最有用的信息,如何优化站点,创造更大的网站价值。本书适合各类网站运营人员阅读。 第1 部分介绍了进行网站分析必备的基础知识。第2 部分详细讲解了如何明确网站现状,发现并改善网站的问题。第3 部分是关于流量获取和网站内渠道优化的问题。第4 部分介绍了一些更加先进的网站分析方法,其中详细讲解了如何分......一起来看看 《图解网站分析(修订版)》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

SHA 加密
SHA 加密

SHA 加密工具