随着开发深入,socket的应用还是比较常见的。毕竟自己是业余出身,水平到不了自己开发php扩展的程度。所以利用php本身的功能,实现socket的一些应用还算是比较靠谱的选择。
如果在linux平台,可以考虑Swoole扩展,功能还是很强大的。至于windows,扩展的编译实在不方便。还是进入我们的主角吧,php的socket编程。内容多是基于众多网上资料拼凑而成。
一.需要了解的基本概念
1.阻塞/非阻塞 阻塞IO是指调用结果返回之前,当前线程会被挂起;相反,非阻塞指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。
2.多路复用 对于 Socket 来说,应该说能同时处理多个连接的模型都应该被称为多路复用,目前比较常用的有 select/poll/epoll/kqueue。在这些多路复用的模式中,异步阻塞/非阻塞模式的扩展性和性能最好。
3.同步/异步 所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回;相反,当一个异步过程调用发出后,调用者不能立刻得到结果,实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。
使用 select/poll 的同步模型:属于同步非阻塞 IO 模型,代码如下:
/**
* 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);