这篇文章主要介绍了PHP SOCKET编程详解,需要的朋友可以参考下

1. 预备知识

一直以来很少看到有多少人使用php的socket模块来做一些事情,大概大家都把它定位在脚本语言的范畴内吧,但是其实php的socket模块可以做很多事情,包括做ftplist,http post提交,smtp提交,组包并进行特殊报文的交互(如smpp协议),whois查询。这些都是比较常见的查询。

特别是php的socket扩展库可以做的事情简直不会比c差多少。

php的socket连接函数

1、集成于内核的socket

这个系列的函数仅仅只能做主动连接无法实现端口监听相关的功能。而且在4.3.0之前所有socket连接只能工作在阻塞模式下。
此系列函数包括
fsockopen,pfsockopen
这两个函数的具体信息可以查询php.net的用户手册
他们均会返回一个资源编号对于这个资源可以使用几乎所有对文件操作的函数对其进行操作如fgets(),fwrite(), fclose()等单注意的是所有函数遵循这些函数面对网络信息流时的规律,例如:
fread() 从文件指针 handle 读取最多 length 个字节。 该函数在读取完 length 个字节数,或到达 EOF 的时候,或(对于网络流)当一个包可用时就会停止读取文件,视乎先碰到哪种情况。 
可以看出对于网络流就必须注意取到的是一个完整的包就停止。

2、php扩展模块带有的socket功能。

php4.x 以后有这么一个模块extension=php_sockets.dll,Linux上是一个extension=php_sockets.so。
当打开这个此模块以后就意味着php拥有了强大的socket功能,包括listen端口,阻塞及非阻塞模式的切换,multi-client 交互式处理等
这个系列的函数列表参看http://www.php.net/manual/en/ref.sockets.php
看过这个列表觉得是不是非常丰富呢?不过非常遗憾这个模块还非常年轻还有很多地方不成熟,相关的参考文档也非常少:(
我也正在研究中,因此暂时不具体讨论它,仅给大家一个参考文章

http://www.zend.com/pecl/tutorials/sockets.php

2. 使用PHP socket扩展

服务器端代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<?php
/**
 * File name server.php
 * 服务器端代码
 *
 * @author guisu.huang
 * @since 2012-04-11
 *
 */
  
//确保在连接客户端时不会超时
set_time_limit(0);
//设置IP和端口号
$address = "127.0.0.1";
$port = 2046; //调试的时候,可以多换端口来测试程序!
/**
 * 创建一个SOCKET
 * AF_INET=是ipv4 如果用ipv6,则参数为 AF_INET6
 * SOCK_STREAM为socket的tcp类型,如果是UDP则使用SOCK_DGRAM
*/
$sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP) or die("socket_create() 失败的原因是:" . socket_strerror(socket_last_error()) . "/n");
//阻塞模式
socket_set_block($sock) or die("socket_set_block() 失败的原因是:" . socket_strerror(socket_last_error()) . "/n");
//绑定到socket端口
$result = socket_bind($sock, $address, $port) or die("socket_bind() 失败的原因是:" . socket_strerror(socket_last_error()) . "/n");
//开始监听
$result = socket_listen($sock, 4) or die("socket_listen() 失败的原因是:" . socket_strerror(socket_last_error()) . "/n");
echo "OK\nBinding the socket on $address:$port ... ";
echo "OK\nNow ready to accept connections.\nListening on the socket ... \n";
do { // never stop the daemon
 //它接收连接请求并调用一个子连接Socket来处理客户端和服务器间的信息
 $msgsock = socket_accept($sock) or die("socket_accept() failed: reason: " . socket_strerror(socket_last_error()) . "/n");
   
 //读取客户端数据
 echo "Read client data \n";
 //socket_read函数会一直读取客户端数据,直到遇见\n,\t或者\0字符.PHP脚本把这写字符看做是输入的结束符.
 $buf = socket_read($msgsock, 8192);
 echo "Received msg: $buf \n";
   
 //数据传送 向客户端写入返回结果
 $msg = "welcome \n";
 socket_write($msgsock, $msg, strlen($msg)) or die("socket_write() failed: reason: " . socket_strerror(socket_last_error()) ."/n");
 //一旦输出被返回到客户端,父/子socket都应通过socket_close($msgsock)函数来终止
 socket_close($msgsock);
} while (true);
socket_close($sock);

客户端代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
/**
 * File name:client.php
 * 客户端代码
 *
 * @author guisu.huang
 * @since 2012-04-11
 */
set_time_limit(0);
  
$host = "127.0.0.1";
$port = 2046;
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)or die("Could not create socket\n"); // 创建一个Socket
  
$connection = socket_connect($socket, $host, $port) or die("Could not connet server\n"); // 连接
socket_write($socket, "hello socket") or die("Write failed\n"); // 数据传送 向服务器发送消息
while ($buff = socket_read($socket, 1024, PHP_NORMAL_READ)) {
 echo("Response was:" . $buff . "\n");
}
socket_close($socket);

使用cli方式启动server:

php server.php

这里注意socket_read函数:
可选的类型参数是一个命名的常数:
PHP_BINARY_READ - 使用系统recv()函数。用于读取二进制数据的安全。 (在PHP>“默认= 4.1.0)
PHP_NORMAL_READ - 读停在\ n或\r(在PHP <= 4.0.6默认)

针对参数PHP_NORMAL_READ ,如果服务器的响应结果没有\ n。造成socket_read(): unable to read from socket

3. PHP socket内部源码

从PHP内部源码来看,PHP提供的socket编程是在socket,bind,listen等函数外添加了一个层,让其更加简单和方便调用。但是一些业务逻辑的程序还是需要程序员自己去实现。
下面我们以socket_create的源码实现来说明PHP的内部实现。
前面我们有说到php的socket是以扩展的方式实现的。在源码的ext目录,我们找到sockets目录。这个目录存放了PHP对于socket的实现。直接搜索PHP_FUNCTION(socket_create),在sockets.c文件中找到了此函数的实现。如下所示代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/* {{{ proto resource socket_create(int domain, int type, int protocol) U
 Creates an endpoint for communication in the domain specified by domain, of type specified by type */
PHP_FUNCTION(socket_create)
{
  long   arg1, arg2, arg3;
  php_socket  *php_sock = (php_socket*)emalloc(sizeof(php_socket));
  
  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "lll", &arg1, &arg2, &arg3) == FAILURE) {
    efree(php_sock);
    return;
  }
  
  if (arg1 != AF_UNIX
#if HAVE_IPV6
    && arg1 != AF_INET6
#endif
    && arg1 != AF_INET) {
    php_error_docref(NULL TSRMLS_CC, E_WARNING, "invalid socket domain [%ld] specified for argument 1, assuming AF_INET", arg1);
    arg1 = AF_INET;
  }
  
  if (arg2 > 10) {
    php_error_docref(NULL TSRMLS_CC, E_WARNING, "invalid socket type [%ld] specified for argument 2, assuming SOCK_STREAM", arg2);
    arg2 = SOCK_STREAM;
  }
  
  php_sock->bsd_socket = socket(arg1, arg2, arg3);
  php_sock->type = arg1;
  
  if (IS_INVALID_SOCKET(php_sock)) {
    SOCKETS_G(last_error) = errno;
    php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to create socket [%d]: %s", errno, php_strerror(errno TSRMLS_CC));
    efree(php_sock);
    RETURN_FALSE;
  }
  
  php_sock->error = 0;
  php_sock->blocking = 1;
                                   1257,1-8  61%
  ZEND_REGISTER_RESOURCE(return_value, php_sock, le_socket);
}

Zend API实际对c函数socket做了包装,供PHP使用。 而在c的socket编程中,我们使用如下方式初始化socket。

?
1
2
3
4
5
//初始化Socket 
 if( (socket_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){
   printf("create socket error: %s(errno: %d)\n",strerror(errno),errno);
   exit(0);
 }

4. 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_readv() 读取从分散/聚合数组过来的数据
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. PHP Socket模拟请求

我们使用stream_socket来模拟:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
 *
 * @param $data= array=array('key'=>value)
 */
function post_contents($data = array()) {
 $post = $data ? http_build_query($data) : '';
 $header = "POST /test/ HTTP/1.1" . "\n";
 $header .= "User-Agent: Mozilla/4.0+(compatible;+MSIE+6.0;+Windows+NT+5.1;+SV1)" . "\n";
 $header .= "Host: localhost" . "\n";
 $header .= "Accept: */*" . "\n";
 $header .= "Referer: http://localhost/test/" . "\n";
 $header .= "Content-Length: ". strlen($post) . "\n";
 $header .= "Content-Type: application/x-www-form-urlencoded" . "\n";
 $header .= "\r\n";
 $ddd = $header . $post;
 $fp = stream_socket_client("tcp://localhost:80", $errno, $errstr, 30);
 $response = '';
 if (!$fp) {
  echo "$errstr ($errno)<br />\n";
 } else {
  fwrite($fp, $ddd);
  $i = 1;
  while ( !feof($fp) ) {
   $r = fgets($fp, 1024);
   $response .= $r;
   //处理这一行
  }
 }
 fclose($fp);
 return $response;
}

注意,以上程序可能会进入死循环;

这个PHP的feof($fp) 需要注意的地方了,我们来分析为什么进入死循环。

?
1
2
3
4
while ( !feof($fp) ) {
 $r = fgets($fp, 1024);
 $response .= $r;
}

实际上,feof是可靠的,但是结合fgets函数一块使用的时候,必须要小心了。一个常见的做法是:

?
1
2
3
4
5
$fp = fopen("myfile.txt", "r");
while (!feof($fp)) {
 $current_line = fgets($fp);
 //对结果做进一步处理,防止进入死循环
}

当处理纯文本的时候,fgets获取最后一行字符后,foef函数返回的结果并不是TRUE。实际的运算过程如下:

1) while()继续循环。

2) fgets 获取倒数第二行的字符串

3) feof返回false,进入下一次循环

4)fgets获取最后一行数据

5) 一旦fegets函数被调用,feof函数仍然返回的是false。所以继续执行循环

6) fget试图获取另外一行,但实际结果是空的。实际代码没有意识到这一点,试图处理另外根本不存在的一行,但fgets被调用了,feof放回的结果仍然是false

7) .....

8) 进入死循环

来源:http://www.jb51.net/article/66594.htm

PHP SOCKET编程详解相关推荐

  1. Linux的SOCKET编程详解

    Linux的SOCKET编程详解 一. 网络中进程之间如何通信 进程通信的概念最初来源于单机系统.由于每个进程都在自己的地址范围内运行,为保证两个相互通信的进 程之间既互不干扰又协调一致工作,操作系统 ...

  2. Linux的SOCKET编程详解——非常叼

    http://blog.csdn.net/hguisu/article/details/7445768/ 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[+] Linux的SOCKE ...

  3. php里面的socket编程,详解PHP Socket 编程过程

    详解PHP Socket 编程过程 作者:PHPYuan 时间:2019-03-13 03:41:20 概述 Socket用于进程间通信.进程间通信通常基于客户端-服务端模型.此时,客户端-服务端是可 ...

  4. Linux的SOCKET编程详解(转载)

    一. 网络中进程之间如何通信 进程通信的概念最初来源于单机系统.由于每个进程都在自己的地址范围内运行,为保证两个相互通信的进程之间既互不干扰又协调一致工作,操作系统为进程通信提供了相应设施,如: UN ...

  5. Netty的Socket编程详解-搭建服务端与客户端并进行数据传输

    场景 Netty在IDEA中搭建HelloWorld服务端并对Netty执行流程与重要组件进行介绍: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article ...

  6. Java Socket编程详解

    Java Socket编程是Java网络编程很重要的内容,现参考了许多资料,总结如下: 1. Java网络编程原理+Socket编程 http://www.cnblogs.com/linzheng/a ...

  7. C# Socket 编程详解

    Microsoft.Net Framework为应用程序访问Internet提供了分层的.可扩展的以及受管辖的网络服务,其名字空间System.Net和 System.Net.Sockets包含丰富的 ...

  8. C#Socket编程详解(一)TCP与UDP简介

    一.TCP与UDP(转载) 1.TCP 1.1 定义 TCP(TransmissionControl Protocol)传输控制协议. 是一种可靠的.面向连接的协议(eg:打电话).传输效率低全双工通 ...

  9. java 网络io详解_Java网络socket编程详解

    或许有点长 但是一步步教你 我想你也愿意看7.2面向套接字编程 我们已经通过了解Socket的接口,知其所以然,下面我们就将通过具体的案例,来熟悉Socket的具体工作方式7.2.1使用套接字实现基于 ...

最新文章

  1. 干货 | 20多门AI网络课程资源(附链接)
  2. pytorch中lstm学习
  3. PAT甲级1029 Median:[C++题解]贪心、二路归并
  4. JAVA编程心得-JAVA实现CRC-CCITT(XMODEM)算法
  5. Web Storage中的sessionStorage和localStorage
  6. NCC CAP 6.0 发布 —— 新增支持 OpenTelemetry
  7. python常用命令格式_python常用命令有哪些
  8. 禅道的基本使用(创建项目、维护部门、用户、产品、提出需求、创建测试用例等)
  9. 网络编程和反射的基本知识点的总结
  10. 4-Spring Boot 的视图
  11. oracle 创建数据库表 如果此表存在则删除后再重建
  12. python 字符串format格式化一
  13. 【Vue】Aliplayer 视音频播放的实践与思考
  14. 如何下载高分辨率卫星影像
  15. web用pdfobject 对pdf 预览文件
  16. 顺序表的基本操作(含全部代码c)
  17. Excel如何隔行插入图片?
  18. linux如何初始化硬盘,Linux硬盘简易初始化(LVM)
  19. 蓝桥杯:调和级数————Python
  20. 腾讯云Elasticsearch集群多可用区容灾实现原理及最佳实践

热门文章

  1. 「Python-Bug」matplotlib画图时报错Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)
  2. strom-1.1.0模拟单词统计功能,Spout编写,Bolt编写,TopologyDriver编写,本地模式运行,集群模式运行,集群模式下看输出结果
  3. 01_JNI是什么,为什么使用,怎么用JNI,Cygwin环境变量配置,NDK案例(使用Java调用C代码),javah命令使用
  4. 编写手机端自适应页面案例,springMVC代码,SpringMVC上传代码,去掉input框中原有的样式,使ios按钮没有圆角,css中的border-radius类似
  5. tomcat虚拟目录和虚拟主机等相关配置
  6. s:if的用法(判断用户是否登录过了的操作)
  7. oracle 最小系统,基于最小化CENTOS6.6最小化安装,oracle 11g 数据安装过程!
  8. python__和_区别_【Python】对_和__差别的理解
  9. python爬斗鱼直播_Python爬取2000万直播数据——看20万游戏主播能否月入100万
  10. faster-rcnn原理及相应概念解释