php扩展socket连接,PHP扩展之网络socket扩展导读

我在此仅仅对PHPSocket网络扩展作摘录,如下:

select/poll 的同步模型:属于同步非阻塞 IO 模型,代码如下:

select_server.php

[php] view plaincopy

/**

* SelectSocketServer Class

* By James.Huang

**/

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

/**

* SelectSocket Test Client

* By James.Huang

**/

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

#else

#if HAVE_SYS_SOCKET_H

#include

#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) {

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值