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. <?php
  2. /**
  3. * File name server.php
  4. * 服务器端代码
  5. *
  6. * @author guisu.huang
  7. * @since 2012-04-11
  8. *
  9. */
  10. //确保在连接客户端时不会超时
  11. set_time_limit(0);
  12. //设置IP和端口号
  13. $address = "127.0.0.1";
  14. $port = 2046; //调试的时候,可以多换端口来测试程序!
  15. /**
  16. * 创建一个SOCKET
  17. * AF_INET=是ipv4 如果用ipv6,则参数为 AF_INET6
  18. * SOCK_STREAM为socket的tcp类型,如果是UDP则使用SOCK_DGRAM
  19. */
  20. $sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP) or die("socket_create() 失败的原因是:" . socket_strerror(socket_last_error()) . "/n");
  21. //阻塞模式
  22. socket_set_block($sock) or die("socket_set_block() 失败的原因是:" . socket_strerror(socket_last_error()) . "/n");
  23. //绑定到socket端口
  24. $result = socket_bind($sock, $address, $port) or die("socket_bind() 失败的原因是:" . socket_strerror(socket_last_error()) . "/n");
  25. //开始监听
  26. $result = socket_listen($sock, 4) or die("socket_listen() 失败的原因是:" . socket_strerror(socket_last_error()) . "/n");
  27. echo "OK\nBinding the socket on $address:$port ... ";
  28. echo "OK\nNow ready to accept connections.\nListening on the socket ... \n";
  29. do { // never stop the daemon
  30. //它接收连接请求并调用一个子连接Socket来处理客户端和服务器间的信息
  31. $msgsock = socket_accept($sock) or  die("socket_accept() failed: reason: " . socket_strerror(socket_last_error()) . "/n");
  32. //读取客户端数据
  33. echo "Read client data \n";
  34. //socket_read函数会一直读取客户端数据,直到遇见\n,\t或者\0字符.PHP脚本把这写字符看做是输入的结束符.
  35. $buf = socket_read($msgsock, 8192);
  36. echo "Received msg: $buf   \n";
  37. //数据传送 向客户端写入返回结果
  38. $msg = "welcome \n";
  39. socket_write($msgsock, $msg, strlen($msg)) or die("socket_write() failed: reason: " . socket_strerror(socket_last_error()) ."/n");
  40. //一旦输出被返回到客户端,父/子socket都应通过socket_close($msgsock)函数来终止
  41. socket_close($msgsock);
  42. } while (true);
  43. socket_close($sock);
<?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. <?php
  2. /**
  3. * File name:client.php
  4. * 客户端代码
  5. *
  6. * @author guisu.huang
  7. * @since 2012-04-11
  8. */
  9. set_time_limit(0);
  10. $host = "127.0.0.1";
  11. $port = 2046;
  12. $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)or die("Could not create  socket\n"); // 创建一个Socket
  13. $connection = socket_connect($socket, $host, $port) or die("Could not connet server\n");    //  连接
  14. socket_write($socket, "hello socket") or die("Write failed\n"); // 数据传送 向服务器发送消息
  15. while ($buff = socket_read($socket, 1024, PHP_NORMAL_READ)) {
  16. echo("Response was:" . $buff . "\n");
  17. }
  18. socket_close($socket);
<?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. /* {{{ proto resource socket_create(int domain, int type, int protocol) U
  2. Creates an endpoint for communication in the domain specified by domain, of type specified by type */
  3. PHP_FUNCTION(socket_create)
  4. {
  5. long            arg1, arg2, arg3;
  6. php_socket      *php_sock = (php_socket*)emalloc(sizeof(php_socket));
  7. if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "lll", &arg1, &arg2, &arg3) == FAILURE) {
  8. efree(php_sock);
  9. return;
  10. }
  11. if (arg1 != AF_UNIX
  12. #if HAVE_IPV6
  13. && arg1 != AF_INET6
  14. #endif
  15. && arg1 != AF_INET) {
  16. php_error_docref(NULL TSRMLS_CC, E_WARNING, "invalid socket domain [%ld] specified for argument 1, assuming AF_INET", arg1);
  17. arg1 = AF_INET;
  18. }
  19. if (arg2 > 10) {
  20. php_error_docref(NULL TSRMLS_CC, E_WARNING, "invalid socket type [%ld] specified for argument 2, assuming SOCK_STREAM", arg2);
  21. arg2 = SOCK_STREAM;
  22. }
  23. php_sock->bsd_socket = socket(arg1, arg2, arg3);
  24. php_sock->type = arg1;
  25. if (IS_INVALID_SOCKET(php_sock)) {
  26. SOCKETS_G(last_error) = errno;
  27. php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to create socket [%d]: %s", errno, php_strerror(errno TSRMLS_CC));
  28. efree(php_sock);
  29. RETURN_FALSE;
  30. }
  31. php_sock->error = 0;
  32. php_sock->blocking = 1;
  33. 1257,1-8      61%
  34. ZEND_REGISTER_RESOURCE(return_value, php_sock, le_socket);
  35. }
  36. /* }}} */
/* {{{ 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. //初始化Socket
  2. if( (socket_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){
  3. printf("create socket error: %s(errno: %d)\n",strerror(errno),errno);
  4. exit(0);
  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. * @param $data= array=array('key'=>value)
  4. */
  5. function post_contents($data = array()) {
  6. $post = $data ? http_build_query($data) : '';
  7. $header = "POST /test/ HTTP/1.1" . "\n";
  8. $header .= "User-Agent: Mozilla/4.0+(compatible;+MSIE+6.0;+Windows+NT+5.1;+SV1)" . "\n";
  9. $header .= "Host: localhost" . "\n";
  10. $header .= "Accept: */*" . "\n";
  11. $header .= "Referer: http://localhost/test/" . "\n";
  12. $header .= "Content-Length: ". strlen($post) . "\n";
  13. $header .= "Content-Type: application/x-www-form-urlencoded" . "\n";
  14. $header .= "\r\n";
  15. $ddd = $header . $post;
  16. $fp = stream_socket_client("tcp://localhost:80", $errno, $errstr, 30);
  17. $response = '';
  18. if (!$fp) {
  19. echo "$errstr ($errno)<br />\n";
  20. } else {
  21. fwrite($fp, $ddd);
  22. $i = 1;
  23. while ( !feof($fp) ) {
  24. $r = fgets($fp, 1024);
  25. $response .= $r;
  26. //处理这一行
  27. }
  28. }
  29. fclose($fp);
  30. return $response;
  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. while ( !feof($fp) ) {
  2. $r = fgets($fp, 1024);
  3. $response .= $r;
  4. }
        while ( !feof($fp) ) {
$r = fgets($fp, 1024);
$response .= $r;
}

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

  1. $fp = fopen("myfile.txt", "r");
  2. while (!feof($fp)) {
  3. $current_line = fgets($fp);
  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) 进入死循环

PHP SOCKET编程 .的更多相关文章

  1. Linux下的C Socket编程 -- server端的继续研究

    Linux下的C Socket编程(四) 延长server的生命周期 在前面的一个个例子中,server在处理完一个连接后便会立即结束掉自己,然而这种server并不科学啊,server应该是能够一直 ...

  2. java socket编程(li)

    一.网络编程中两个主要的问题 一个是如何准确的定位网络上一台或多台主机,另一个就是找到主机后如何可靠高效的进行数据传输.在TCP/IP协议中IP层主要负责网络主机的定位,数据传输的路由,由IP地址可以 ...

  3. Python Socket 编程——聊天室示例程序

    上一篇 我们学习了简单的 Python TCP Socket 编程,通过分别写服务端和客户端的代码了解基本的 Python Socket 编程模型.本文再通过一个例子来加强一下对 Socket 编程的 ...

  4. Linux下的C Socket编程 -- server端的简单示例

    Linux下的C Socket编程(三) server端的简单示例 经过前面的client端的学习,我们已经知道了如何创建socket,所以接下来就是去绑定他到具体的一个端口上面去. 绑定socket ...

  5. Linux下的C Socket编程 -- 获取对方IP地址

    Linux下的C Socket编程(二) 获取域名对应的IP地址 经过上面的讨论,如果我们想要连接到远程的服务器,我们需要知道对方的IP地址,系统函数gethostbyname便能够实现这个目的.它能 ...

  6. Linux下的C Socket编程 -- 简介与client端的处理

    Linux下的C Socket编程(一) 介绍 Socket是进程间通信的方式之一,是进程间的通信.这里说的进程并不一定是在同一台机器上也有可能是通过网络连接的不同机器上.只要他们之间建立起了sock ...

  7. python网络编程-socket编程

     一.服务端和客户端 BS架构 (腾讯通软件:server+client) CS架构 (web网站) C/S架构与socket的关系: 我们学习socket就是为了完成C/S架构的开发 二.OSI七层 ...

  8. Socket编程实践(2) Socket API 与 简单例程

    在本篇文章中,先介绍一下Socket编程的一些API,然后利用这些API实现一个客户端-服务器模型的一个简单通信例程.该例子中,服务器接收到客户端的信息后,将信息重新发送给客户端. socket()函 ...

  9. Socket编程实践(1) 基本概念

    1. 什么是socket socket可以看成是用户进程与内核网络协议栈的编程接口.TCP/IP协议的底层部分已经被内核实现了,而应用层是用户需要实现的,这部分程序工作在用户空间.用户空间的程序需要通 ...

  10. [转]C语言SOCKET编程指南

    1.介绍 Socket编程让你沮丧吗?从man pages中很难得到有用的信息吗?你想跟上时代去编Internet相关的程序,但是为你在调用 connect() 前的bind() 的结构而不知所措?等 ...

随机推荐

  1. redis --- lua 脚本实现原子操作

    如题, 楼主的想法很简单, lua 脚本本身支持原子性, 所以把命令写进一个脚本就行, 当然后续还会优化才能放到生产上,例如缓存脚本 ,redis 本身会缓存执行过的脚本 ,这样速度更快, 再优化, ...

  2. http 头信息详解(转)

    HTTP(HyperTextTransferProtocol)是超文本传输协议的缩写,它用于传送WWW方式的数据,关于HTTP协议的详细内容请参考RFC2616.HTTP协议采用了请求/响应模型.客户 ...

  3. iOS isa 浅析

    看见一到面试题讲述一下Objective-C中的isa?完全没听说过,打算小研究一下. 参考:http://blog.sina.com.cn/s/blog_7a2ffd5c01010nme.html ...

  4. RTM CU CTP

    .RTM: Released To Manufacturing,也就是新产品的正式发布版本,比如SQL 2008 RTM.其后的补丁都是基于这个之上的. .CU: Cumulative Update, ...

  5. 【JPA】JPA的注解@MappedSuperclass作用解释【转】

    [代码参考地址]:GitHub地址 [原文地址][http://blog.sina.com.cn/s/blog_7085382f0100uk4p.html] [这里仅用到第三点为止] ======== ...

  6. Go -- 卸载 Go

    直接删除usr下的go文件夹即可 命令行: cd / 1 然后: open usr 1 删除go文件夹

  7. VUE -- 十分钟入门 Less

    这篇文章来自 Danny Markov, 是我最喜欢的博主之一,实际上我最近翻译的一些文章全是出自他手.在查看本文之前你也可以 查看原文. 我们都知道写 CSS 代码是有些枯燥无味的,尤其是面对那些成 ...

  8. 阿里蚂蚁的前端ant-design

    蚂蚁国内镜像: http://ant-design.gitee.io/components/date-picker-cn/ 阿里的设计平台:https://design.alipay.com/deve ...

  9. 读取Properties文件简易代码

    public class Utils { private static Properties props = new Properties(); static { ClassLoader classL ...

  10. 使用终端shell命令批量改动一个文件下的全部文件的读写权限

    之前对openfire安装的目录就遇到过这个问题,今天再次遇到.须要改动一个目录以下的全部子目录以及文件的三个权限:本用户读写.管理员读写.全部人读写,三个都要需改为wr 步骤例如以下:比如我要改动/ ...