相关函数
- 服务端函数
stream_socket_server( string $local_socket [, int &$errno [, string &$errstr [, int $flags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN [, resource $context ]]]] ) : resource
$flags参数,如果是用udp通信的话,STREAM_SERVER_LISTEN 是不需要的,$context则是上下文,后面有单独的函数来生成此类型,还有需要注意的是,stream_socket_server和socket_create的返回值虽然都是resource,但两个不能通用,socket扩展中有单独的函数来转换这两者。
stream_socket_accept ( resource $server_socket [, float $timeout = ini_get("default_socket_timeout") [, string &$peername ]] ) : resource
接收客户端的连接(udp通信时,不需要用到此函数)
类似socket_accept,需要注意的是第一个参数,只能是stream_socket_server的返回值
- 客户端函数
stream_socket_client ( string $remote_socket [, int &$errno [, string &$errstr [, float $timeout = ini_get("default_socket_timeout") [, int $flags = STREAM_CLIENT_CONNECT [, resource $context ]]]]] ) : resource
创建socket客户端,连接到服务端
- 读写函数
读取数据
fread ( resource $handle , int $length ) : string
写入数据
fwrite ( resource $handle , string $string [, int $length ] ) : int
上面两个函数和我们平常读取文件一样,正如我们说的,操作文件也是操作一种流,所以方法通用
接收数据,最后参数是引用,用于获取远端链接的地址
stream_socket_recvfrom ( resource $socket , int $length [, int $flags = 0 [, string &$address ]] ) : string
发送数据
stream_socket_sendto ( resource $socket , string $data [, int $flags = 0 [, string $address ]] ) : int
上面两个函数和 fread 和 fwrite 基本一致,但功能更多一些,$flags 参数可以设置发送OOB数据,而 $address 参数则多是用于udp通信,由于 udp 通信时不使用 stream_socket_accept,所以无法获取到新的resource,那就无法向指定的客户端中写数据,所以一般先用 stream_socket_recvfrom 获取最后引用参数 $address 即为客户端的地址,然后再用stream_socket_sendto设置$address,来向指定的客户端发送。
利用stream函数做一个日志记录,以及收集日志数据的公共类
<?php
namespace app\common\service;
use think\Config;
class Logger extends Base
{
const IDENTIFICATION = '[XXXX]'; //日志标识
// target,支持file,udp模式
const TargetFile = 'file';
const TargetUdp = 'udp';
// level
const LevelInfo = '[INFO]';
const LevelError = '[ERROR]';
// type
const TypeMySQL = '[MYSQL]';
const TypeCurl = '[CURL]';
// path
const PathBase = '/data/logs/';
public static function error($data)
{
return self::log($data, self::LevelError);
}
public static function log($data, $level = self::LevelInfo)
{
$conf = self::getConf();
$target = empty($conf['target']) ? self::TargetFile : $conf['target'];
switch ($target) {
case self::TargetUdp:
self::logUdp($data, $level);
break;
case self::TargetFile:
default:
self::logFile($data, $level);
break;
}
}
private static function logUdp($data, $level)
{
$conf = self::getConf();
$handle = stream_socket_client("udp://{$conf['host']}:{$conf['port']}", $errno, $errstr);
if (!$handle) {
return false;
}
fwrite($handle, self::formatData($data, $level));
return fclose($handle);
}
private static function logFile($data, $level)
{
return file_put_contents(self::getFile(), self::formatData($data, $level) . PHP_EOL, FILE_APPEND);
}
/**
* @param $data
* @param $level
* @return string 例子解释:402b618f3d[2020-09-11 15:45:53] [INFO] {"type":"[MYSQL]","time":"0.003811","connect":"db connect use time"}
* 402b618f3d 同一个HTTP或者CLI执行ID
* [2021-01-11 15:45:53] 时间
* [INFO] 错误级别 @see LevelError|LevelInfo
* {"type":"[MYSQL]" 日志类型
*/
private static function formatData($data, $level)
{
$data = is_string($data) ? $data : json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$str = self::IDENTIFICATION;
if (!self::isCli()) {
$str .= ' requestID:' . REQUESTID;
}
return $str . ' [' . date('Y-m-d H:i:s') . "] $level " . $data;
}
private static function isCli()
{ //define ('PHP_SAPI', "cli");
return PHP_SAPI == 'cli';
}
public static function run()
{
$conf = self::getConf();
$server = "udp://{$conf['host']}:{$conf['port']}";
$socket = stream_socket_server($server, $errno, $errstr, STREAM_SERVER_BIND);
if (!$socket) {
self::setError("$errstr ($errno)");
return false;
}
while (true) {
$revMsg = stream_socket_recvfrom($socket, 4096, 0, $peer);
file_put_contents(self::getFile(), $revMsg . PHP_EOL, FILE_APPEND);
}
return true;
}
public static function getFile($path = null)
{
if (null === $path) {
$path = self::getConf()['logFile'] ?: self::PathBase;
}
$fileArr = explode('/', $path);
array_pop($fileArr);
$dir = implode('/', $fileArr);
if (!is_dir($dir)) {
mkdir($dir, 0777, true);
chown($dir, 'www');
}
$logFile = $dir . '/' . (Func::isDebug() ? date('md') : date('md-H')) . '.log';
if (!is_file($logFile)) {
touch($logFile);
chmod($logFile, 0777);
}
return $logFile;
}
private static function getConf()
{
return Config::get('logger');
}
}