SOCKET编程详解_php技巧_脚本之家

作者:奥门金沙手机娱乐网址    发布时间:2019-12-18 14:44    浏览:176 次

[返回]

1. 预备知识

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

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

php的socket连接函数

1、集成于内核的socket

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

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

php4.x 以后有这么一个模块extension=php_sockets.dll,Linux上是一个extension=php_sockets.so。当打开这个此模块以后就意味着php拥有了强大的socket功能,包括listen端口,阻塞及非阻塞模式的切换,multi-client 交互式处理等这个系列的函数列表参看

  1. 使用PHP socket扩展

    使用cli方式启动server:php server.php这里注意socket_read函数:可选的类型参数是一个命名的常数:PHP_BINARY_READ - 使用系统recv()函数。用于读取二进制数据的安全。 PHP_NORMAL_READ - 读停在 n或r针对参数PHP_NORMAL_READ ,如果服务器的响应结果没有 n。造成socket_read(): unable to read from socket3. PHP socket内部源码从PHP内部源码来看,PHP提供的socket编程是在socket,bind,listen等函数外添加了一个层,让其更加简单和方便调用。但是一些业务逻辑的程序还是需要程序员自己去实现。下面我们以socket_create的源码实现来说明PHP的内部实现。前面我们有说到php的socket是以扩展的方式实现的。在源码的ext目录,我们找到sockets目录。这个目录存放了PHP对于socket的实现。直接搜索PHP_FUNCTION,在sockets.c文件中找到了此函数的实现。如下所示代码:/ {{{ 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 { long arg1, arg2, arg3; php_socket *php_sock = emalloc; if (zend_parse_parameters TSRMLS_CC, "lll", &arg1, &arg2, &arg3) == FAILURE) { efree; 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 { 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; php_sock->type = arg1; if (IS_INVALID_SOCKET { SOCKETS_G = errno; php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to create socket [%d]: %s", errno, php_strerror; efree; 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。

//初始化Socket if( (socket_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){ printf("create socket error: %sn",strerror; exit; }

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() 删除一个已分配的iovecsocket_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,如果没有指定则默认当前socketsocket_recvmsg() 从iovec里接受消息socket_select 这个函数发送数据到已连接的socketsocket_sendmsg() 发送消息到socketsocket_sendto() 发送消息到指定地址的socketsocket_set_block() 在socket里设置为块模式socket_set_nonblock() socket里设置为非块模式socket_set_option() 设置socket选项socket_shutdown() 这个函数允许你关闭读、写、或指定的socketsocket_strerror() 返回指定错误号的周详错误socket_write() 写数据到socket缓存socket_writev() 写数据到分散/聚合数组

5. PHP Socket模拟请求

我们使用stream_socket来模拟:

/** * * @param $data= array=array */ function post_contents { $post = $data ? http_build_query : ''; $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 . "n"; $header .= "Content-Type: application/x-www-form-urlencoded" . "n"; $header .= "rn"; $ddd = $header . $post; $fp = stream_socket_client("tcp://localhost:80", $errno, $errstr, 30); $response = ''; if  { echo "$errstr 
n"; } else { fwrite; $i = 1; while  { $r = fgets; $response .= $r; //处理这一行 } } fclose; return $response; } 

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

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

while  { $r = fgets; $response .= $r; } 

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

$fp = fopen; while  { $current_line = fgets; //对结果做进一步处理,防止进入死循环 }

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

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

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

4)fgets获取最后一行数据

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

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

搜索