没想到在CSDN上看到一个兄弟写的文章成连载了,于是细看了下,发现对PHP的Socket的编码及扩展实现写得不错,建议看下其扩展写法,很有学习教育意义,再就是对Epoll模型的分析很到位,觉得写得都不错,特摘录下Socket这块的PHP扩展代码分析结合其PHP的Soket服务器写法,原文来自:
http://blog.csdn.net/shagoo/article/details/6647961
http://blog.csdn.net/shagoo/article/details/6396089
《Socket深度探究4PHP(一)》和《Socket深度探究4PHP(二)》 Socket 深度探究 4 PHP (三) 应该是写得很有体系的难得一见的不错的好文章。
我在此仅仅对PHPSocket网络扩展作摘录,如下:
select/poll 的同步模型:属于同步非阻塞 IO 模型,代码如下:
select_server.php
[php] view plaincopy
<?php
/**
* SelectSocketServer Class
* By James.Huang <shagoo#gmail.com>
**/
set_time_limit(0);
class SelectSocketServer
{
private static $socket;
private static $timeout = 60;
private static $maxconns = 1024;
private static $connections = array();
function SelectSocketServer($port)
{
global $errno, $errstr;
if ($port < 1024) {
die("Port must be a number which bigger than 1024/n");
}
$socket = socket_create_listen($port);
if (!$socket) die("Listen $port failed");
socket_set_nonblock($socket); // 非阻塞
while (true)
{
$readfds = array_merge(self::$connections, array($socket));
$writefds = array();
// 选择一个连接,获取读、写连接通道
if (socket_select($readfds, $writefds, $e = null, $t = self::$timeout))
{
// 如果是当前服务端的监听连接
if (in_array($socket, $readfds)) {
// 接受客户端连接
$newconn = socket_accept($socket);
$i = (int) $newconn;
$reject = '';
if (count(self::$connections) >= self::$maxconns) {
$reject = "Server full, Try again later./n";
}
// 将当前客户端连接放入 socket_select 选择
self::$connections[$i] = $newconn;
// 输入的连接资源缓存容器
$writefds[$i] = $newconn;
// 连接不正常
if ($reject) {
socket_write($writefds[$i], $reject);
unset($writefds[$i]);
self::close($i);
} else {
echo "Client $i come./n";
}
// remove the listening socket from the clients-with-data array
$key = array_search($socket, $readfds);
unset($readfds[$key]);
}
// 轮循读通道
foreach ($readfds as $rfd) {
// 客户端连接
$i = (int) $rfd;
// 从通道读取
$line = @socket_read($rfd, 2048, PHP_NORMAL_READ);
if ($line === false) {
// 读取不到内容,结束连接
echo "Connection closed on socket $i./n";
self::close($i);
continue;
}
$tmp = substr($line, -1);
if ($tmp != "/r" && $tmp != "/n") {
// 等待更多数据
continue;
}
// 处理逻辑
$line = trim($line);
if ($line == "quit") {
echo "Client $i quit./n";
self::close($i);
break;
}
if ($line) {
echo "Client $i >>" . $line . "/n";
}
}
// 轮循写通道
foreach ($writefds as $wfd) {
$i = (int) $wfd;
$w = socket_write($wfd, "Welcome Client $i!/n");
}
}
}
}
function close ($i)
{
socket_shutdown(self::$connections[$i]);
socket_close(self::$connections[$i]);
unset(self::$connections[$i]);
}
}
new SelectSocketServer(2000);
select_client.php
[php] view plaincopy
<?php
/**
* SelectSocket Test Client
* By James.Huang <shagoo#gmail.com>
**/
function debug ($msg)
{
// echo $msg;
error_log($msg, 3, '/tmp/socket.log');
}
if ($argv[1]) {
$socket_client = stream_socket_client('tcp://0.0.0.0:2000', $errno, $errstr, 30);
// stream_set_timeout($socket_client, 0, 100000);
if (!$socket_client) {
die("$errstr ($errno)");
} else {
$msg = trim($argv[1]);
for ($i = 0; $i < 10; $i++) {
$res = fwrite($socket_client, "$msg($i)/n");
usleep(100000);
// debug(fread($socket_client, 1024)); // 将产生死锁,因为 fread 在阻塞模式下未读到数据时将等待
}
fwrite($socket_client, "quit/n"); // add end token
debug(fread($socket_client, 1024));
fclose($socket_client);
}
}
else {
$phArr = array();
for ($i = 0; $i < 10; $i++) {
$phArr[$i] = popen("php ".__FILE__." '{$i}:test'", 'r');
}
foreach ($phArr as $ph) {
pclose($ph);
}
// for ($i = 0; $i < 10; $i++) {
// system("php ".__FILE__." '{$i}:test'");
// }
}
以上代码的逻辑也很简单,select_server.php 实现了一个类似聊天室的功能,你可以使用 telnet 工具登录上去,和其他用户文字聊天,也可以键入“quit”命令离开;而 select_client.php 则模拟了一个登录用户连续发 10 条信息,然后退出。这里也分析两个问题:
A> 这里如果我们执行 php select_client.php 程序将会同时打开 10 个连接,同时进行模拟登录用户操作;观察服务端打印的数据你会发现服务端确实是在同时处理这些连接,这就是多路复用实现的非阻塞 IO 模型,当然这个模型并没有真正的实现异步,因为最终服务端程序还是要去通道里面读取数据,得到结果后同步返回给客户端。如果这次你也使用 telnet 命令同时打开多个客户端,你会发现服务端可以同时处理这些连接,这就是非阻塞 IO,当然比古老的阻塞 IO 效率要高多了,但是这种模式还是有局限的,继续看下去你就会发现了~
B> 我在 select_server.php 中设置了几个参数,大家可以调整试试:
$timeout :表示的是 select 的超时时间,这个一般来说不要太短,否则会导致 CPU 负载过高。
$maxconns :表示的是最大连接数,客户端超过这个数的话,服务器会拒绝接收。这里要提到的一点是,由于 select 是通过句柄来读写的,所以会受到系统默认参数 __FD_SETSIZE 的限制,一般默认值为 1024,修改的话需要重新编译内核;另外通过测试发现 select 模式的性能会随着连接数的增大而线性便差(详情见《Socket深度探究4PHP(二)》),这也就是 select 模式最大的问题所在,所以如果是超高并发服务器建议使用下一种模式。
根据上面这一段来阅读PHP的扩展源代码是相当不错的:
PHP 源码的 ext 目录下面,因此我们我需要先进入 ext/sockets/ 目录,做过 PHP 扩展的同学应该都很熟悉下面的一些文件了,这次我们主要分析的是 php_sockets.h 和 sockets.c 这两个 C 源码文件。
ext/sockets/php_sockets.h
这个头文件很简单,我们主要看一下下面列出的几个重点:
32 行:
[cpp] view plaincopy
#ifdef PHP_WIN32
#include <winsock.h>
#else
#if HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#endif
以上就是 PHP 对于不同环境 Socket 底层调用的定义了,我们可以看到不管是 Unix 还是 Windows 环境,PHP均调用的是系统标准的 BSD Socket 库。然后我们看下面这个重要的结构体定义:
82 行:
[cpp] view plaincopy
typedef struct {
PHP_SOCKET bsd_socket;
int type;
int error;
int blocking;
} 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 plaincopy
sock->bsd_socket = socket(PF_INET, SOCK_STREAM, 0);
sock->blocking = 1;
...
sock->type = PF_INET;
...
if (bind(sock->bsd_socket, (struct sockaddr *)&la, sizeof(la)) != 0) {
...
}
if (listen(sock->bsd_socket, backlog) != 0) {
...
}
>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 plaincopy
#ifndef PHP_WIN32
retval = write(php_sock->bsd_socket, str, MIN(length, str_len));
#else
retval = send(php_sock->bsd_socket, str, min(length, str_len), 0);
#endif
>socket_read
1021 行:PHP_FUNCTION(socket_read)
此函数是用于接受 socket 的数据,调用的原生函数是 recv(),不过这里需要注意的是 PHP 为我们提供两种获取方式:
1、PHP_NORMAL_READ
按行读取,具体代码见 419 行:php_read 函数的逻辑,我们注意到此函数在非阻塞模式下会立即返回,否则将会读取直至遇到 \n 或者 \r 字符。
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 plaincopy
var http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World\n');
}).listen(8000, "127.0.0.1");
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 还在不断的更新发展中,虽然目前我在公司的服务架构中还不敢使用它,我还是很希望它能够迅速成长起来,这样子我们开发服务中间件的时候,就会多出一个很棒的选项啦~
http://blog.csdn.net/shagoo/article/details/6647961
http://blog.csdn.net/shagoo/article/details/6396089
《Socket深度探究4PHP(一)》和《Socket深度探究4PHP(二)》 Socket 深度探究 4 PHP (三) 应该是写得很有体系的难得一见的不错的好文章。
我在此仅仅对PHPSocket网络扩展作摘录,如下:
select/poll 的同步模型:属于同步非阻塞 IO 模型,代码如下:
select_server.php
[php] view plaincopy
<?php
/**
* SelectSocketServer Class
* By James.Huang <shagoo#gmail.com>
**/
set_time_limit(0);
class SelectSocketServer
{
private static $socket;
private static $timeout = 60;
private static $maxconns = 1024;
private static $connections = array();
function SelectSocketServer($port)
{
global $errno, $errstr;
if ($port < 1024) {
die("Port must be a number which bigger than 1024/n");
}
$socket = socket_create_listen($port);
if (!$socket) die("Listen $port failed");
socket_set_nonblock($socket); // 非阻塞
while (true)
{
$readfds = array_merge(self::$connections, array($socket));
$writefds = array();
// 选择一个连接,获取读、写连接通道
if (socket_select($readfds, $writefds, $e = null, $t = self::$timeout))
{
// 如果是当前服务端的监听连接
if (in_array($socket, $readfds)) {
// 接受客户端连接
$newconn = socket_accept($socket);
$i = (int) $newconn;
$reject = '';
if (count(self::$connections) >= self::$maxconns) {
$reject = "Server full, Try again later./n";
}
// 将当前客户端连接放入 socket_select 选择
self::$connections[$i] = $newconn;
// 输入的连接资源缓存容器
$writefds[$i] = $newconn;
// 连接不正常
if ($reject) {
socket_write($writefds[$i], $reject);
unset($writefds[$i]);
self::close($i);
} else {
echo "Client $i come./n";
}
// remove the listening socket from the clients-with-data array
$key = array_search($socket, $readfds);
unset($readfds[$key]);
}
// 轮循读通道
foreach ($readfds as $rfd) {
// 客户端连接
$i = (int) $rfd;
// 从通道读取
$line = @socket_read($rfd, 2048, PHP_NORMAL_READ);
if ($line === false) {
// 读取不到内容,结束连接
echo "Connection closed on socket $i./n";
self::close($i);
continue;
}
$tmp = substr($line, -1);
if ($tmp != "/r" && $tmp != "/n") {
// 等待更多数据
continue;
}
// 处理逻辑
$line = trim($line);
if ($line == "quit") {
echo "Client $i quit./n";
self::close($i);
break;
}
if ($line) {
echo "Client $i >>" . $line . "/n";
}
}
// 轮循写通道
foreach ($writefds as $wfd) {
$i = (int) $wfd;
$w = socket_write($wfd, "Welcome Client $i!/n");
}
}
}
}
function close ($i)
{
socket_shutdown(self::$connections[$i]);
socket_close(self::$connections[$i]);
unset(self::$connections[$i]);
}
}
new SelectSocketServer(2000);
select_client.php
[php] view plaincopy
<?php
/**
* SelectSocket Test Client
* By James.Huang <shagoo#gmail.com>
**/
function debug ($msg)
{
// echo $msg;
error_log($msg, 3, '/tmp/socket.log');
}
if ($argv[1]) {
$socket_client = stream_socket_client('tcp://0.0.0.0:2000', $errno, $errstr, 30);
// stream_set_timeout($socket_client, 0, 100000);
if (!$socket_client) {
die("$errstr ($errno)");
} else {
$msg = trim($argv[1]);
for ($i = 0; $i < 10; $i++) {
$res = fwrite($socket_client, "$msg($i)/n");
usleep(100000);
// debug(fread($socket_client, 1024)); // 将产生死锁,因为 fread 在阻塞模式下未读到数据时将等待
}
fwrite($socket_client, "quit/n"); // add end token
debug(fread($socket_client, 1024));
fclose($socket_client);
}
}
else {
$phArr = array();
for ($i = 0; $i < 10; $i++) {
$phArr[$i] = popen("php ".__FILE__." '{$i}:test'", 'r');
}
foreach ($phArr as $ph) {
pclose($ph);
}
// for ($i = 0; $i < 10; $i++) {
// system("php ".__FILE__." '{$i}:test'");
// }
}
以上代码的逻辑也很简单,select_server.php 实现了一个类似聊天室的功能,你可以使用 telnet 工具登录上去,和其他用户文字聊天,也可以键入“quit”命令离开;而 select_client.php 则模拟了一个登录用户连续发 10 条信息,然后退出。这里也分析两个问题:
A> 这里如果我们执行 php select_client.php 程序将会同时打开 10 个连接,同时进行模拟登录用户操作;观察服务端打印的数据你会发现服务端确实是在同时处理这些连接,这就是多路复用实现的非阻塞 IO 模型,当然这个模型并没有真正的实现异步,因为最终服务端程序还是要去通道里面读取数据,得到结果后同步返回给客户端。如果这次你也使用 telnet 命令同时打开多个客户端,你会发现服务端可以同时处理这些连接,这就是非阻塞 IO,当然比古老的阻塞 IO 效率要高多了,但是这种模式还是有局限的,继续看下去你就会发现了~
B> 我在 select_server.php 中设置了几个参数,大家可以调整试试:
$timeout :表示的是 select 的超时时间,这个一般来说不要太短,否则会导致 CPU 负载过高。
$maxconns :表示的是最大连接数,客户端超过这个数的话,服务器会拒绝接收。这里要提到的一点是,由于 select 是通过句柄来读写的,所以会受到系统默认参数 __FD_SETSIZE 的限制,一般默认值为 1024,修改的话需要重新编译内核;另外通过测试发现 select 模式的性能会随着连接数的增大而线性便差(详情见《Socket深度探究4PHP(二)》),这也就是 select 模式最大的问题所在,所以如果是超高并发服务器建议使用下一种模式。
根据上面这一段来阅读PHP的扩展源代码是相当不错的:
PHP 源码的 ext 目录下面,因此我们我需要先进入 ext/sockets/ 目录,做过 PHP 扩展的同学应该都很熟悉下面的一些文件了,这次我们主要分析的是 php_sockets.h 和 sockets.c 这两个 C 源码文件。
ext/sockets/php_sockets.h
这个头文件很简单,我们主要看一下下面列出的几个重点:
32 行:
[cpp] view plaincopy
#ifdef PHP_WIN32
#include <winsock.h>
#else
#if HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#endif
以上就是 PHP 对于不同环境 Socket 底层调用的定义了,我们可以看到不管是 Unix 还是 Windows 环境,PHP均调用的是系统标准的 BSD Socket 库。然后我们看下面这个重要的结构体定义:
82 行:
[cpp] view plaincopy
typedef struct {
PHP_SOCKET bsd_socket;
int type;
int error;
int blocking;
} 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 plaincopy
sock->bsd_socket = socket(PF_INET, SOCK_STREAM, 0);
sock->blocking = 1;
...
sock->type = PF_INET;
...
if (bind(sock->bsd_socket, (struct sockaddr *)&la, sizeof(la)) != 0) {
...
}
if (listen(sock->bsd_socket, backlog) != 0) {
...
}
>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 plaincopy
#ifndef PHP_WIN32
retval = write(php_sock->bsd_socket, str, MIN(length, str_len));
#else
retval = send(php_sock->bsd_socket, str, min(length, str_len), 0);
#endif
>socket_read
1021 行:PHP_FUNCTION(socket_read)
此函数是用于接受 socket 的数据,调用的原生函数是 recv(),不过这里需要注意的是 PHP 为我们提供两种获取方式:
1、PHP_NORMAL_READ
按行读取,具体代码见 419 行:php_read 函数的逻辑,我们注意到此函数在非阻塞模式下会立即返回,否则将会读取直至遇到 \n 或者 \r 字符。
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 plaincopy
var http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World\n');
}).listen(8000, "127.0.0.1");
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 还在不断的更新发展中,虽然目前我在公司的服务架构中还不敢使用它,我还是很希望它能够迅速成长起来,这样子我们开发服务中间件的时候,就会多出一个很棒的选项啦~
作者:jackxiang@向东博客 专注WEB应用 构架之美 --- 构架之美,在于尽态极妍 | 应用之美,在于药到病除
地址:http://jackxiang.com/post/5560/
版权所有。转载时必须以链接形式注明作者和原始出处及本声明!
评论列表
2014-4-13 02:11 | 小红帽
呵呵,我去年刚好也是看到石头君的文章,照着博主的思路写了一个php socket 服务器框架workerman,采用的libevebt + 非阻塞 + 多路复用。压测了下,性能很强悍,不必c写的差多少,而且比想象中的稳定,在公司用了几个月了,没出现啥大问题。
分页: 1/1 1