文章851
标签121
分类10

PHP 连接币安,okex,火币,websoket的示例代码

看到有同学在 Webscocket pong 包 php 踩坑实例 文章中留言 要我给出Demo
满足这位同学的要求:

下面Demo使用的 Saber 人性化的协程HTTP客户端封装库
运行环境 swoole4.2+ php7.1+ 需要使用协程 go();

demo 并不全 但坑都是踩过的 getSendMessage 创建订阅数据json格式这个就不提供了, 很简单没坑, 但最好控制好订阅交易对数量.

币安的webscocket 主要坑在于 ping包 非常正规的使用opcode是9, 并且收包中的data是空包, 回pongopcode传10.

/**
 * 币安现货ws 订阅
 * @param array $symbolList 订阅交易对列表
 * @param $subType 订阅类型
 * @return bool
 */
public function binanceSubscribe(array $symbolList, $subType = self::SUB_SPOT_DEPTH_TYPE)
{
    // 订阅地址
    $this->stream = 'wss://stream.binance.com:443/stream?streams=';

    $startTime = time();

    try {
        // 判断连接是否为空
        if (!empty($this->websocket)) {
            // 关闭连接
            @$this->websocket->close();
        }

        if (empty($this->websocket)) {

            $this->websocket = SaberGM::websocket($this->stream);

            // 创建订阅数据 json 数据
            $sendMessage = $this->getSendMessage($symbolList, $subType);

            // 推送订阅内容
            $this->websocket->push($sendMessage);
        }

        // 容错
        if ($this->websocket == null) {
            return false;
        }

        $errCount = 0;
        do {
            // 诶币安比较乖 莫名其妙就断包了 , 还是10分钟一重连比较好
            if (($startTime + 600) < time()) {

                Logger::wss(['超时10分钟' => $startTime, 'send' => $sendMessage]);
                return false;
            }

            // 超时 1秒抛出空包
            $data = $this->websocket->recv(1);  // timeout

            // 收包数据结构对象类型 想要存储仅能通过 serialize
            Logger::recv(['s' => serialize($data)]);

            $errCount++;

            // 空包容错 5次 (空包或超时过多最好重新连接)
            if ($errCount > 5) return false;

            // 币安对于opcode 还是有要求的
            if (empty($data->opcode)) {

                Logger::wss(['超时' => serialize($data)]);

                continue;
            } else if ($data->opcode == 9) {

                // pong 币安的ping包 opcode=9 且是data是空 我们需要 回 pong 且opcode=10
                Logger::pong(['s' => serialize($data)]);
                $this->websocket->push('pong', 10, true);

                continue;
            }

            // 空包
            if (empty($data->data)) continue;

            $responseData = $data->data;
            $finish = $data->finish;

            if (!$finish || !$responseData) continue;

            Logger::wsDepthJson(['opcode' => $data->opcode, 'fd' => $data->fd, 's' => serialize($data)]);

            //同步处理
            $responseData = json_decode($responseData, true);
            // todo  ... 代码逻辑 根据订阅类型分别存储于不同redis 有序集合Key中 , 获取时永远提取最新1秒数据 取不到在通过 api 拿, 币安api延迟太狠了.

            //  计数器归零
            $errCount = 0;

        } while (true);

    } catch (\Swlib\Http\Exception\ConnectException $e) {

        echo 'ConnectException ', $e->getMessage(),PHP_EOL;
        return false;
    } catch (\Throwable $e) {

        echo 'Throwable ', $e->getMessage(),PHP_EOL;

        return false;
    }

    return true;
}

OKEX 的webscocket订阅重要在于 解包 使用 gzinflate 函数实现解包

/**
 * okex现货ws 订阅
 * @param array $symbolList
 * @param $subType
 * @return bool
 */
public function okexSubscribe(array $symbolList, $subType)
{
    $this->stream = 'wss://real.okex.com:8443/ws/v3';

    try {

        if (!empty($this->websocket))
        {
            // 关闭连接
            @$this->websocket->close();
        }

        if (empty($this->websocket))
        {
            // Saber\WebSocket
            $this->websocket = SaberGM::websocket($this->stream);
            $sendMessage = self::getSendMessage($symbolList, $subType);

            $this->websocket->push($sendMessage);
        }

        if ($this->websocket == null)
        {
            Logger::errmsg(['platform' => "ok", 'stream' => $this->stream]);
            return false;
        }

        $errCount = 0;
        do {
            $data = $this->websocket->recv(1);// timeout
            $errCount++;

            if(empty($data->data)) return false;
            $responseData = gzinflate($data->data);

            if ($errCount > 5) return false;
            if (empty($responseData)) return false;

            $finish = $data->finish;
            if (!$finish || !$responseData) return false;

            //同步处理
            $responseData = json_decode($responseData, true);
           
           // todo ... code

            //  计数器归零
            $errCount = 0;

        } while (true);

    } catch (\Swlib\Http\Exception\ConnectException $e) {

        $this->websocket = null;
    } catch (\Exception $e) {

        $this->websocket = null;
    }

    return true;
}

火币坑位也是在于解包 gzip解压, 感谢开发同学的钻研, 为我大PHP在币圈量化提供轮子.

/**
 * 火币现货ws 订阅
 * @param array $symbolList
 * @param string $subType 订阅类型
 * @return bool
 */
public function huobiSubscribe(array $symbolList, $subType)
{
    $this->stream = 'wss://api.huobi.pro:443/ws';

    try {

        if (!empty($this->websocket)) {
            // 关闭连接
            @$this->websocket->close();
        }

        if (empty($this->websocket)) {
            // Saber\WebSocket
            $this->websocket = SaberGM::websocket($this->stream);

            foreach ($symbolList as $sym) {
                $huoBiSymbol = strtolower(implode('', explode('_', $sym)));
                $sendMessage = $this->getSendMessage($huoBiSymbol, $subType, $sym);
                $this->websocket->push($sendMessage);
            }
        }

        if ($this->websocket == null) {
            Logger::errmsg(['platform' => "huobi", 'stream' => $this->stream]);
            return false;
        }

        $errCount = 0;
        do {

            $data = $this->websocket->recv(1);// timeout'

            $errCount++;

            //容错5次
            if ($errCount > 5) return false;

            if (empty($data->finish)) continue;

            $responseData = json_decode($this->gzdecode($data), true);

            //接受到ping 恢复pong  {"ping": 1492420473027}   {"pong": 1492420473027}
            if (isset($responseData['ping'])) {
                $this->websocket->push($this->getPong($responseData['ping']));
            }

            if (!$responseData) return false;

            // todo ...code

            // 计数器归零
            $errCount = 0;

        } while (true);

    } catch (\Swlib\Http\Exception\ConnectException $e) {

        $this->websocket = null;
    } catch (\Throwable $e) {

        $this->websocket = null;
    }

    return true;
}

/**
 * gzip解压
 * @param $data
 * @return false|string
 */
public function gzdecode($data)
{
    $flags = ord(substr($data, 3, 1));
    $headerlen = 10;
    if ($flags & 4) {
        $extralen = unpack('v', substr($data, 10, 2));
        $extralen = $extralen[1];
        $headerlen += 2 + $extralen;
    }
    
    if ($flags & 8) // Filename
        $headerlen = strpos($data, chr(0), $headerlen) + 1;
    if ($flags & 16) // Comment
        $headerlen = strpos($data, chr(0), $headerlen) + 1;
    if ($flags & 2) // CRC at end of file
        $headerlen += 2;
    $unpacked = @gzinflate(substr($data, $headerlen));
    if ($unpacked === FALSE) $unpacked = $data;
    
    return $unpacked;
}

/**
 * 发送pong信息
 * @param $pingInt
 * @return false|string
 */
public static function getPong($pingInt)
{
    return json_encode(["pong" => $pingInt], JSON_UNESCAPED_UNICODE);
}

好了 这篇文章 demo 就写着点, 部分内容没全写进去, 如果你不是自己踩坑就想直接使用,我建议你还是不要用我的demo了.
伸手党的同学也不要发评论要demo. 本文仅帮助广大PHP同行 不重复入坑.

解决git文件名大小写无法修改的问题

解决 Git 文件名大小写无法修改的问题

简介

在使用 Git 进行版本控制时,有时会遇到一种问题:文件名大小写发生变化后,Git 无法正确识别和跟踪这些变化。本篇博客将介绍导致文件名大小写问题的原因,并提供解决方案和代码示例。

原因

Git 在默认配置下对文件名的大小写是不敏感的。这意味着在某些操作系统(例如 Windows)上,修改文件名的大小写只是一个表面上的修改,而 Git 无法正确跟踪这些变化。这是因为 Git 将文件名存储在索引(index)中,而索引中的文件名是根据操作系统的文件系统来存储的。

解决方案

要解决 Git 文件名大小写无法修改的问题,可以采取以下步骤:

  1. 修改 Git 配置:首先,需要将 Git 配置中的 core.ignorecase 选项设置为 false,以使 Git 对文件名大小写敏感。

    git config core.ignorecase false
  2. 重命名文件:接下来,将文件名变更为一个临时名称,然后再将其修改回所需的大小写。这样可以使 Git 正确地识别和跟踪文件名的大小写变化。

    git mv old_filename temp_filename
    git mv temp_filename new_filename

    请确保在重命名文件时保留正确的文件路径。

  3. 提交更改:完成文件名的修改后,将更改提交到 Git 仓库。

    git commit -m "Rename file with correct casing"

    这将记录文件名大小写变化的提交历史。

现在,Git 应该能够正确地识别和跟踪文件名的大小写变化。

示例

假设我们有一个名为 myFile.txt 的文件,但我们想将其改为 MyFile.txt。以下是解决该问题的示例代码:

# 修改 Git 配置
git config core.ignorecase false

# 重命名文件
git mv myFile.txt tempFile.txt
git mv tempFile.txt MyFile.txt

# 提交更改
git commit -m "Rename file with correct casing"

在上述示例中,我们首先修改了 Git 配置,使其对文件名大小写敏感。然后,通过使用 git mv 命令将文件重命名为临时文件名 tempFile.txt,然后再将其修改为所需的文件名 MyFile.txt。最后,我们使用 git commit 命令提交更改。

结论

当在 Git 中遇到文件名大小写无法修改的问题时,通常是由于 Git 对文件名大小写不敏感所导致的。本篇博客介绍了解决该问题的步骤,包括修改 Git 配置、重命名文件和提交更改。根据您的具体情况,按照上述步骤进行操作,即可解决 Git 文件名大小写无法修改的问题。

参考文献

解决 npm WARN registry Unexpected warning...

解决 npm WARN registry Unexpected warning...

在使用 npm 进行包管理时,有时可能会遇到 npm WARN registry Unexpected warning... 这样的警告信息。这种警告通常表明 npm 在访问注册表(registry)时遇到了意外的情况。本文将介绍如何解决这个警告以及可能导致它出现的一些常见原因。

原因分析

出现 npm WARN registry Unexpected warning... 警告的原因可能有多种,包括但不限于:

  1. 网络问题:npm 在访问注册表时可能由于网络问题而出现异常,例如网络连接不稳定、DNS解析问题等。
  2. npm 配置问题:npm 的配置文件(.npmrc)可能存在问题,例如代理配置、镜像源配置等。
  3. npm 版本问题:可能是由于 npm 的版本过旧或者过新导致的兼容性问题。
  4. 注册表问题:可能是注册表本身出现了问题,例如服务端故障、维护等。

解决方法

针对 npm WARN registry Unexpected warning... 警告,我们可以尝试以下解决方法:

  1. 检查网络连接:首先确保网络连接正常,尝试使用其他网络或者重新连接网络。
  2. 检查 npm 配置:检查 .npmrc 文件中的配置是否正确,特别是代理和镜像源的配置,可以尝试注释掉这些配置并重新尝试。
  3. 更新 npm:使用 npm update -g npm 命令来更新 npm 到最新版本,有时这可以解决一些已知的问题。
  4. 清理 npm 缓存:使用 npm cache clean --force 命令清理 npm 缓存,有时缓存的问题会导致意外的警告出现。
  5. 切换注册表:尝试切换 npm 的注册表源,例如从官方源切换到淘宝源等。

文献引用

在解决 npm WARN registry Unexpected warning... 警告时,以下资源可能会提供帮助:

  1. npm 官方文档:Registry
  2. npm Community Forums:Unexpected warning...

这些资源提供了关于 npm 注册表的使用和解决意外警告的一些建议和实践经验。

通过合理利用这些解决方法,我们可以有效地解决 npm WARN registry Unexpected warning... 警告,提高包管理过程中的顺利性和稳定性。


通过分析和解决 npm WARN registry Unexpected warning... 警告,我们可以更好地应对 npm 包管理过程中遇到的问题,提高开发效率和代码质量。

">