• Socket 深度探究 4 PHP (三)


    转自 http://blog.csdn.net/shagoo/article/details/6647961

    看过前两篇文章《Socket深度探究4PHP(一)》和《Socket深度探究4PHP(二)》,大家应该对目前 Socket 技术的底层有了一定的了解。本文我们会对 PHP-5.3.6 的源码中的 Socket 模块进行一定的分析,然后再简单介绍一下目前比较热门的一些相关技术,比如 Node.js 等。

    自 PHP4 之后,越来越多的模块都被作为扩展提取出来(可单独编译),都在 PHP 源码的 ext 目录下面,因此我们我需要先进入 ext/sockets/ 目录,做过 PHP 扩展的同学应该都很熟悉下面的一些文件了,这次我们主要分析的是 php_sockets.h 和 sockets.c 这两个 C 源码文件。

    ext/sockets/php_sockets.h

    这个头文件很简单,我们主要看一下下面列出的几个重点:

    32 行:
    [cpp] view plain copy
    1. #ifdef PHP_WIN32  
    2. #include <winsock.h>  
    3. #else  
    4. #if HAVE_SYS_SOCKET_H  
    5. #include <sys/socket.h>  
    6. #endif  
    7. #endif  

    以上就是 PHP 对于不同环境 Socket 底层调用的定义了,我们可以看到不管是 Unix 还是 Windows 环境,PHP均调用的是系统标准的 BSD Socket 库。然后我们看下面这个重要的结构体定义:

    82 行:
    [cpp] view plain copy
    1. typedef struct {  
    2.     PHP_SOCKET bsd_socket;  
    3.     int        type;  
    4.     int        error;  
    5.     int        blocking;  
    6. } php_socket;  

    这个就是 php socket 的存储结构了,此结构体在以下的代码阅读中将会大量出现,里面的几个字段很容易理解:bsd_socket 就是标准的 socket 类型,type 表示 socket 类型(PF_UNIX/AF_UNIX),error 是错误代码,blocking 则表示是否阻塞。

    ext/sockets/sockets.c

    这个文件比较长,为了直接切入重点,我们会按照《Socket 深度探索 4 PHP (一) 》中 select_server.php 部分代码来按顺序分析一下在最经典的 select 模式中我们用到的主要方法:

    >socket_create_listen

    859 行:PHP_FUNCTION(socket_create_listen)
    这个函数很简单,初始化 php_sock 并获取 socket 需要监听的端口,然后传入下面的 php_open_listen_sock 函数进行加工,最后调用 ZEND_REGISTER_RESOURCE 宏返回 php_sock。

    347行:static int php_open_listen_sock(php_socket **php_sock, int port, int backlog TSRMLS_DC)
    此函数基本上就是 socket 的标准初始化过程:socket(...) -> bind(...) -> listen(...)(详见 368 行至 391 行)。
    [cpp] view plain copy
    1. sock->bsd_socket = socket(PF_INET, SOCK_STREAM, 0);  
    2. sock->blocking = 1;  
    3. ...  
    4. sock->type = PF_INET;  
    5. ...  
    6. if (bind(sock->bsd_socket, (struct sockaddr *)&la, sizeof(la)) != 0) {  
    7. ...  
    8. }  
    9. if (listen(sock->bsd_socket, backlog) != 0) {  
    10. ...  
    11. }  

    >socket_set_nonblock

    906 行:PHP_FUNCTION(socket_set_nonblock)
    这个函数也很简单,从 ZEND_FETCH_RESOURCE 取出 runtime 中的 php_sock 然后调用 php_set_sock_blocking 函数来设置 sockfd 的阻塞或者非阻塞(此函数可以参考 main/network.c 第 1069 行,我们可以看到 PHP 是使用 fcntl 函数来设置的)。

    >socket_select

    785 行:PHP_FUNCTION(socket_select)
    也是标准的 select 函数调用,过程如下:FD_ZERO(...) -> php_sock_array_to_fd_set(...) -> select(...) -> php_sock_array_from_fd_set(...),可能比较特殊的就是 php_sock_array_from_fd_set() 和 php_sock_array_from_fd_set() 两个函数,这是由于我们要先把 PHP 的 fd 数组转换成原生 fd 集合,才能调用原生的 select 函数,而最后系统还把 fd 集合重新转回到 PHP 的 fd 数组(具体代码参考 799 行至 851 行)。

    >socket_accept

    881 行:PHP_FUNCTION(socket_accept)
    此函数基本上也就是 socket 原生 accept 函数的包装,具体代码可参考 397 行:php_accept_connect 函数中的逻辑,最后调用 ZEND_REGISTER_RESOURCE 宏返回 new_sock,若失败程序会清理使用的 out_socket 资源。

    >socket_write

    986 行:PHP_FUNCTION(socket_write)
    按照以上的思路看这个函数也非常简单,详见 986 行,唯一值得注意的是对于不同操作系统调用的函数有点不同,代码(见 1004 行)如下:
    [cpp] view plain copy
    1. #ifndef PHP_WIN32  
    2.     retval = write(php_sock->bsd_socket, str, MIN(length, str_len));  
    3. #else  
    4.     retval = send(php_sock->bsd_socket, str, min(length, str_len), 0);  
    5. #endif  

    >socket_read

    1021 行:PHP_FUNCTION(socket_read)
    此函数是用于接受 socket 的数据,调用的原生函数是 recv(),不过这里需要注意的是 PHP 为我们提供两种获取方式:
    1、PHP_NORMAL_READ
    按行读取,具体代码见 419 行:php_read 函数的逻辑,我们注意到此函数在非阻塞模式下会立即返回,否则将会读取直至遇到 或者 字符。
    2、PHP_BINARY_READ
    代码见 1045 行:retval = recv(php_sock->bsd_socket, tmpbuf, length, 0); 相当原生和“环保”。
    最后,如果返回值为 -1 则会进行一些错误记录和系统清理工作。

    >socket_close

    970 行:PHP_FUNCTION(socket_close)
    清理 socket 运行时所用的资源。

    >socket_shutdown

    1968 行:PHP_FUNCTION(socket_shutdown)
    调用原生 shutdown 函数来关闭 socket。

    分析下来,PHP 的 socket 模块中绝大部分的代码还是使用的是系统标准的原生 socket 库,其中唯一有可能造成性能隐患的就是 select 中 PHP 的 fd 数组与原生 fd 集合转换,至于其他的一些简单的数据拷贝基本对效率不会有什么影响。总的来说,PHP 的 socket 模块应该效率还是比较高的,但是在使用的时候还是需要注意到一些资源的及时释放,因为毕竟是 Daemon 程序,需要不断运行的,而且 PHP 的数据结构是很占内存(是原生 C 的 4 倍左右)的。

    node.js

    最后,我们看看现在很流行的 Node.js(http://nodejs.org/),它采用了 JavaScript 的语言引擎,语法非常的简洁,对闭包的完美支持让它特别适合做异步 IO 的代码编写,下面是一个最简单的 HTTP Server,只用仅仅六行代码:
    [javascript] view plain copy
    1. var http = require('http');  
    2. http.createServer(function (req, res) {  
    3.   res.writeHead(200, {'Content-Type': 'text/plain'});  
    4.   res.end('Hello World ');  
    5. }).listen(8000, "127.0.0.1");  
    6. console.log('Server running at http://127.0.0.1:8000/');  

    运行起来感受一下,有没有惊艳的感觉啊?事实上用它来写一些简单的服务确实很不错,有兴趣的朋友可以多研究研究(中文社区:http://cnodejs.org/),它有 8000 行 C++ 代码,2000 行 javascript 代码,使用 Google 的 V8 引擎(和 Mongodb 一样),相当的很小巧精悍。下面是我在使用过程总结出中几个要点,大家可以参考:

    1、使用 V8 引擎(和 Mongodb 一样),内置 JSON,代码简洁,使用方便。
    2、使用单线程非阻塞 I/O 中的 select 方式,比较稳定(但是对于超高并发有点力不从心)。
    3、一些第三方应用接口不是很稳定,比如 Mongodb 的接口,并发 200 出现卡死现象,Mysql 接口也比 fast-cgi 差很多。
    4、注意使用 try{...}catch{...} 来捕获错误;使用 process.on('uncaughtException', function(err){...}); 来处理未捕获的错误,否则出错会导致整个服务退出。

    当然,Node.js 还在不断的更新发展中,虽然目前我在公司的服务架构中还不敢使用它,我还是很希望它能够迅速成长起来,这样子我们开发服务中间件的时候,就会多出一个很棒的选项啦~
     
     
  • 相关阅读:
    使用Eclipse搭建Struts2框架
    老王学jsp之response
    老王学jsp之request对象
    老王学jsp之四种属性范围
    老王学jsp之包含指令
    老王学jsp之page指令
    老王学jsp之jdom解析
    老王学jsp之sax解析xml
    老王学jsp之dom解析xml
    python文本分类
  • 原文地址:https://www.cnblogs.com/yuanlipu/p/6427297.html
Copyright © 2020-2023  润新知