编程语言-PHP

PHP Socket 编程实战总结

2017-04-28  本文已影响232人  fingerQin

在进入 PHP Socket 编程实战之前,我们先来了解一下 PHP Socket 的一些基础知识。

一、基础部分#

1、什么是 TCP/IP、UDP、Http、Socket?##

TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,是一个工业标准的协议集,它是为广域网(WANs)设计的。

UDP(User Data Protocol,用户数据报协议)是与 TCP 相对应的协议。它是属于TCP/IP 协议族中的一种。

HTTP 协议即超文本传送协议(Hypertext Transfer Protocol ),是Web联网的基础,也是手机联网常用的协议之一,HTTP协议是建立在 TCP 协议之上的一种应用。

Socket 是对 TCP/IP 协议的封装,Socket 本身并不是协议,而是一个调用接口(API),通过 Socket,我们才能使用 TCP/IP 协议。

2、Socket 是什么呢?##

Socket 就是应用层与 TCP/IP 协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket 其实就是一个门面模式,它把复杂的 TCP/IP 协议族隐藏在 Socket 接口后面,对用户来说,一组简单的接口就是全部,让 Socket 去组织数据,以符合指定的协议。

socket示意图

3、PHP 中的 Socket

Socket 扩展是基于流行的 BSD socket,实现了和socket通讯功能的底层接口,它可以和客户端一样当做一个 socket 服务器。

PHP 官方 socket 手册

4、PHP Socket 相关函数##

由于 PHP 官方手册对 socket 翻译度不是很高。所以,这里特意注重一下这些函数的中文说明。

socket_accept() 接收一个 socket 连接
socket_bind() 把 socket 绑定在一个 IP 地址和端口上
socket_clear_error() 清除 socket 的错误或者最后的错误代码
socket_close() 关闭 socket 资源
socket_connect() 开启一个 socket 连接
socket_create_listen() 在指定端口打开一个 socket 监听
socket_create_pair() 产生一对没有区别的 socket 到一个数组里
socket_create() 产生一个socket,相当于产生一个socket的数据结构
socket_get_option() 获取 socket 选项
socket_getpeername() 获取远程类似主机的ip地址
socket_getsockname() 获取本地 socket 的 ip 地址
socket_iovec_add() 添加一个新的向量到一个分散/聚合的数组
socket_iovec_alloc() 这个函数创建一个能够发送接收读写的 iovec 数据结构
socket_iovec_delete() 删除一个已经分配的 iovec
socket_iovec_fetch() 返回指定的 iovec 资源的数据
socket_iovec_free() 释放一个 iovec 资源
socket_iovec_set() 设置 iovec 的数据新值
socket_last_error() 获取当前 socket 的最后错误代码
socket_listen() 监听由指定socket的所有连接
socket_read() 读取指定长度的数据
socket_recv() 从已连接的 socket 接收数据
socket_recvfrom() 接受数据从指定的 socket,如果没有指定则默认当前 socket
socket_recvmsg() 从 iovec 里接受消息
socket_select() 多路选择
socket_send() 这个函数发送数据到已连接的 socket
socket_sendmsg() 发送消息到 socket
socket_sendto() 发送消息到指定地址的 socket
socket_set_block() 在 socket 里设置为块模式
socket_set_nonblock() socket 里设置为非块模式
socket_set_option() 设置 socket 选项
socket_shutdown() 这个函数允许你关闭读、写、或者指定的 socket
socket_strerror() 返回指定错误号的详细错误
socket_write() 写数据到 socket 缓存
socket_writev() 写数据到分散/聚合数组

5、实现一个简单的 socket 示例##

socket 服务端 server.php

<?php
/**
 * socket 服务端。
 * @author winerQin
 * @date 2017-05-02
 */
$ip   = '127.0.0.1';
$port = 8099;
// 创建 socket
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
// 把 socket 绑定在一个IP地址和端口上
socket_bind($socket, $ip, $port);
// 监听由指定 socket 的所有连接
socket_listen($socket, 4);
do {
    // 接收一个 Socket 连接
    if (($msgsock = socket_accept($socket)) < 0) {
        echo "socket_accept() failed: reason: " . socket_strerror(socket_last_error()) . "\n";
        break;
    } else {
        // 发送到客户端
        $datetime = date('Y-m-d H:i:s', time());
        echo "Server datetime:{$datetime}\n";
        $msg = "Hello client!\n";
        socket_write($msgsock, $msg, strlen($msg));
        // 获得客户端的输入
        $buf = socket_read($msgsock, 2048);
        echo "Receive client message: {$buf}\n\n";
    }
    // 关闭socket
    socket_close($msgsock);
} while(true);

socket 客户端 client.php

<?php
/**
 * socket 客户端。
 * @author winerQin
 * @date 2017-05-02
 */

$ip   = '127.0.0.1';
$port = 8099;
// 创建 socket
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
// 连接 socket
socket_connect($socket, $ip, $port);
$in  = "hello world!\n";
$out = '';
// 写数据到 socket 缓存
socket_write($socket, $in, strlen($in));
// 读取指定长度的数据
while($out = socket_read($socket, 2048)) {
    echo "Server response success!\n"; // 接收服务器回传信息成功
    echo "Receive message:{$out}";      // 服务器返回的信息
}
socket_close($socket);

6、运行示例代码##

  1. 将示例代码分别保存为 server.php 和 client.php 两个 PHP 脚本文件。
  2. 打开两个 windows 的命令提示符,进入到这两个 PHP 脚本所在目录。
  3. 第一个命令提示符窗口执行服务端 PHP 脚本: php server.php
  4. 第二个命令提示符窗口执行客户端 PHP 脚本:php client.php

这时会发现 php client.php 执行成功之后,会返回如下信息:

Server response success!
Receive message:Hello client!

php server.php 所在的窗口返回如下信息:

Server datetime:2017-05-02 11:32:41
Receive client message: hello world!

7、问题##

注:在 windows 命令提示符执行 PHP 脚本的时候,记得首先要在 windows 的环境变量中把 php.exe 所在目录添加到Path环境变量当中。

比如,我的 php.exe 所在目录是:

D:\wamp64\bin\php\php5.6.25

然后点击电脑桌面的"我的电脑" -> "设置" -> "高级系统设置" -> "高级" -> "环境变量" -> "系统变量"。

在列表当中找到 Path,然后选中之后点击"编辑"按钮。
把上面的路径追加到 Path 的末尾。如果你是 win10 的话,会出现一个新的弹出框界面。直接点击"新建"添加即可。

环境变量设置

二、高级部分#

上面我们讲了 socket 的基础理论知识,也教大家创建了一个简单的 socket 服务器且怎样连接这个 socket 服务器。如果大家觉得对 socket 的理论知识了解得还是不够。可以,搜索更多相关的知识补补。

高而全的理论通过一两篇文章也很难讲得完整。本小节,我以我对接过的民生银行的支付系统进行讲解。

背景:我们公司是做金融的。用户提现打款啥的,不可能通过人工一个一个用公对私账号给用户打款。于是跟民生银行合作,民生银行提供一个接口给我们,我们连接他们的 socket 服务器进行付款。这样就能实时且批量地进行给用户付款了。

这里有几个细节:

  1. 民生银行提供一个 VPN,这样可以直连民生内网的支付服务器。
  2. 民生银行只提供一个 socket 连接。也就是说,发起两个连接,会失败一个。
  3. 民生银行的支付是异步进行的。
  4. 民生银行自行定义了一套通信协议。注:HTTP 底层也是通过 socket 上层封装的。

第一个问题很好解决,直接在我们的 Linux 服务器部署 VPN 即可。

第二个问题也好解决。我们只发起一个 socket 连接。一直保持这个链接不断掉,重复利用这个 socket 连接进行数据发送与接收即可。

第三个问题比较棘手。对 PHP 有深入了解的都知道,原生的 PHP 是不支付异步的。当然可以通过第三方扩展支付异步操作。所以,我的解决方案是发送数据用时3秒,然后切换到数据接收一端读取一次数据。一直这样来回切换。

第三个问题只能根据民生银行定义的协议内容进行组装数据。

上一篇下一篇

猜你喜欢

热点阅读