65.9K
CodeProject 正在变化。 阅读更多。
Home

创建实时加密货币 WebSocket API [第 1 部分]

starIconstarIconstarIconstarIconstarIcon

5.00/5 (5投票s)

2017年12月3日

CPOL

6分钟阅读

viewsIcon

29940

downloadIcon

769

我们不想一直去请求信息。让我们创建一个 API 来发送关于当前加密货币股市的推送消息。

引言

世界正在变化。我们希望尽快获得信息,而无需一直去请求。Websockets 允许我们做到这一点。

在本指南中,您将学习如何创建一个 API,该 API 监听 websocket 并将该信息推送到订阅者。

我将实现 Coinigy 的 API,它提供实时的交易历史、订单簿数据和区块链警报。在本示例中,我们将只查看交易信息。

本文代码可在此处下载:此处

本文是该系列的第一部分。

第二部分请访问:使用 Owin 和 SignalR 实现 Websocket API [第二部分]

必备组件

  • Visual Studio 2015 或 2017 配合 Framework 4.6.1
  • 一个 Coinigy API 密钥和密钥

目录

  1. 获取您的 API 密钥
  2. 使用代码
    • 步骤 01 - 创建一个类库
    • 步骤 02 - 安装 Nuget 包
    • 步骤 03 - 创建一个 Websocket
    • 步骤 04 - 身份验证
    • 步骤 05 - 订阅交易频道
    • 步骤 06 - 解析消息
    • 步骤 07 - 反序列化交易响应
    • 步骤 08 - 连接到套接字
    • 步骤 09 - 测试
  3. 最后的寄语

获取您的 API 密钥

请注意,本文可以轻松改编以使用任何其他 API。

登录 Coinigy 后,转到 Settings > My Account > Coinigy API,然后点击“Generate New Key”按钮。这将创建一个您将在 API 中使用的新的密钥和密钥。

请妥善保管您的凭证!

Using the Code

步骤 01 - 创建一个类库

打开 Visual Studio,转到 File > New > Project,然后选择 Class Library。

请确保您的框架目标为 .NET Framework 4.6.1。

为您的项目命名(例如:Coinigy.API),然后点击“OK”。

步骤 02 - 安装 Nuget 包

在程序包管理器控制台中,使用以下命令安装以下程序包

Install-Package PureSocketCluster

步骤 03 - 创建一个 Websocket 类

我们需要能够通过套接字进行身份验证。

在您的项目中添加一个“Models”文件夹(右键单击您的项目,选择 Add > New Folder)。

此文件夹将包含我们所有的模型。我们现在可以在此文件夹下添加一个 ApiCredentials 模型

    public class ApiCredentials
    {
        [JsonProperty("apiKey")]
        public string ApiKey { get; set; }
    
        [JsonProperty("apiSecret")]
        public string ApiSecret { get; set; }
    }

将您的“Class1.cs”文件重命名为“Websocket.cs”。

向您的 Websocket 类添加以下两个字段

    private readonly PureSocketClusterSocket socket;
    private readonly ApiCredentials credentials;

Websocket 类添加一个构造函数

    public Websocket(ApiCredentials credentials)
    {
        this.credentials = credentials;

        this.socket = new PureSocketClusterSocket("wss://sc-02.coinigy.com/socketcluster/");
    }

构造函数允许用户传入其凭证,并建立到 Coingiy 的 websocket URI 的连接。

步骤 04 - 身份验证

websocket 会将信息发送给我们,但我们首先需要订阅这些事件。我们首先需要通过套接字进行身份验证。在身份验证成功后,我们将开始从套接字接收信息。要通过 webservice 进行身份验证,我们需要发送“auth”命令以及我们的凭证。“auth”命令只能在套接字打开后调用。因此,我们需要订阅“OnOpened”事件。以下是我们订阅事件的方式

    this.socket.OnOpened += On_Opened;

该函数看起来是这样的

    private void On_Opened()
    {
        socket.Emit("auth", this.credentials, ack: (string name, object error, object data) =>
        {
            // We can now start listening to trade information
        });
    }

步骤 05 - 订阅交易频道

要订阅交易频道,我们可以向我们的 Websocket 类添加以下函数

    public bool SubscribeToTradeChannel(string exchange, string primaryCurrency, 
    string secondaryCurrency) => this.socket.Subscribe
    ($"TRADE-{exchange}--{primaryCurrency}--{secondaryCurrency}");

此函数将在我们进行身份验证后被调用。我们需要让用户知道身份验证何时完成,所以让我们添加一个事件

    public event ClientIsReady OnClientReady;
    
    public delegate void ClientIsReady();

并在我们的 Emit 回调中调用它,以在身份验证成功时触发

    private void On_Opened()
    {
        socket.Emit("auth", this.credentials, ack: (string name, object error, object data) =>
        {
            OnClientReady?.Invoke();
        });
    }

现在用户可以订阅 OnClientReady 事件,并在该事件被调用后开始订阅交易频道。

注意:Coinigy 的频道名称需要遵循以下格式:METHOD-EXCHANGECODE--PRIMARYCURRENCY--SECONDARYCURRENCY

步骤 06 - 解析消息

现在我们已经订阅了,但我们仍然需要创建功能来接收套接字发送给我们的消息。我们需要订阅 OnMessage 事件

    this.socket.OnMessage += On_Message;

该函数看起来是这样的

    private void On_Message(string message)
    {
        // Do something with the message received
    }

这里会触发我们从套接字接收到的每条消息。我们仍然需要确定服务器发送给我们的消息类型,因为它可能返回不同类型的消息。为了确定类型,我们可以添加以下函数

        private static string GetRequestType(JObject jObj)
        {
            string requestType = string.Empty;
            var channelName = jObj["data"]["channel"].ToString();

            Guid guid;
            if (!Guid.TryParse(channelName, out guid))
                return channelName.Substring(0, channelName.IndexOf('-'));

            Guid channelGuid;
            requestType = channelName;
            if (Guid.TryParse(channelName, out channelGuid))
                if (channelGuid.ToString().ToLower() == channelName.ToLower())
                    requestType = jObj["data"]["data"]["MessageType"].ToString();

            return requestType;
        }

当我们收到消息时,我们首先需要检查它是否为“publish”消息。在消息验证后,我们可以将其解析为 JObject 并将其发送到我们的 GetRequestType 函数以确定请求类型。将以下逻辑添加到 On_Message 函数中

    string PUBLISH_REGEX = @"^{""event""*.:*.""#publish""";
    
    // Determine if message is a publish message using regex
    var m = Regex.Match(message, PUBLISH_REGEX);
    if (!m.Success) return;
    
    // If so, parse the string
    var jObj = JObject.Parse(message);
    
    // Retrieve the channel's name
    string channelName = jObj["data"]["channel"].ToString();
    
    // Determine request type
    string requestType = GetRequestType(jObj);
    if (string.IsNullOrEmpty(requestType))
        return;

一旦我们知道请求类型,我们就需要将消息解析为该类型并将其发送给用户。有不同的消息类型,所以让我们为此添加一个 enum

   public enum MessageType
    {
        TradeData,
        OrderData,
        NewsData,
        BlockData,
        FavoriteData,
        NewMarket,
        NotificationData,
        Unknown
    } 

以及一个根据请求类型确定消息类型的函数

    private static MessageType GetMessageType(string requestType)
    {
        switch (requestType.ToUpper())
        {
            case "ORDER":
                return MessageType.OrderData;
            case "TRADE":
                return MessageType.TradeData;
            case "BLOCK":
                return MessageType.BlockData;
            case "FAVORITE":
                return MessageType.FavoriteData;
            case "NOTIFICATION":
                return MessageType.NotificationData;
            case "NEWS":
                return MessageType.NewsData;
            case "NEWMARKET":
                return MessageType.NewMarket;
            default:
                return MessageType.Unknown;
        }
    }

此函数将请求类型作为参数并返回我们的消息类型。一旦我们确定了消息类型,我们就可以使用 switch 语句来解析和返回数据。更新 OnMessage 函数

    private void On_Message(string message)
    {
        // Previous code that determines the request type and channel name...
        
        InvokeMessageReceived(channelName, requestType, message);
    }

并添加 InvokeMessageReceived 函数

    private void InvokeMessageReceived(string channelName, string requestType, string message)
    {
        // Determine the message type using the function we previously created
        MessageType messageType = GetMessageType(requestType);
        switch (messageType)
        {
            case MessageType.TradeData:
                // Parse the channel name
                var tradeMarketInfo = MarketInfo.ParseMarketInfo(channelName);
                
                // Deserialize the string to a TradeResponse-entity
                var trade = Helper.ToEntity<TradeResponse>(message);

                // Invoke an event to let the subscribers know we have received trade information
                OnTradeMessage?.Invoke(tradeMarketInfo.Exchange, tradeMarketInfo.PrimaryCurrency, 
                tradeMarketInfo.SecondaryCurrency, trade.TradeData.Trade);
                break;
                
                // Other cases for each MessageType...
        }
    }

我们仍然需要添加一些函数来将消息解析为交易数据实体。让我们创建一个类,其中包含一个以频道名称作为参数并返回 MarketInfo 实体的函数(我们可以将此类添加到我们的 Models 文件夹中)。

    internal class MarketInfo
    {
        internal string Exchange { get; set; }
        internal string PrimaryCurrency { get; set; }
        internal string SecondaryCurrency { get; set; }

        // A function to parse a string and return our MarketInfo
        internal static MarketInfo ParseMarketInfo(string data)
        {
            var str = data.Replace("--", "-");
            var strArr = str.Split('-');
            return new MarketInfo() { Exchange = strArr[1], PrimaryCurrency = strArr[2], 
                                      SecondaryCurrency = strArr[3] };
        }
    }

现在我们知道了交易所名称以及我们的主要/次要货币。我们仍然需要使用消息解析我们的交易信息。让我们创建一个 TradeResponse 实体,它将包含所有这些信息

    public class TradeResponse
    {
        [JsonProperty("data")]
        public TradeData TradeData { get; set; }

        [JsonProperty("event")]
        public string Event { get; set; }
    }
    
    public class TradeData
    {
        [JsonProperty("channel")]
        public string Channel { get; set; }

        [JsonProperty("data")]
        public TradeItem Trade { get; set; }
    }
    
    public class TradeItem
    {
        [JsonProperty("label")]
        public string Label { get; set; }

        [JsonProperty("quantity")]
        public decimal Quantity { get; set; }

        [JsonProperty("exchId")]
        public long ExchId { get; set; }

        [JsonProperty("channel")]
        public string Channel { get; set; }

        [JsonProperty("exchange")]
        public string Exchange { get; set; }

        [JsonProperty("marketid")]
        public long Marketid { get; set; }

        [JsonProperty("market_history_id")]
        public long MarketHistoryId { get; set; }

        [JsonProperty("price")]
        public decimal Price { get; set; }

        [JsonProperty("time_local")]
        public string TimeLocal { get; set; }

        [JsonProperty("total")]
        public decimal Total { get; set; }

        [JsonProperty("time")]
        public string Time { get; set; }

        [JsonProperty("timestamp")]
        public string Timestamp { get; set; }

        [JsonProperty("tradeid")]
        public string TradeId { get; set; }

        [JsonProperty("type")]
        public string Type { get; set; }
    }

步骤 07 - 反序列化交易响应

我们仍然需要将消息反序列化为 TradeResponse。让我们添加一个 Helper 类,其中包含一个通用的“ToEntity”函数,该函数以 string 作为参数并返回一个实体

    internal static class Helper
    {
        internal static T ToEntity<T>(string data)
        {
            return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(data);
        }
    }

现在我们已经反序列化了我们的交易市场数据,并且需要告知用户。让我们添加一个用户可以订阅的事件

    public event TradeMessage OnTradeMessage;
    
    public delegate void TradeMessage(string exchange, string primaryCurrency, 
                                      string secondaryCurreny, TradeItem trade);

步骤 08 - 连接到套接字

我们需要添加的最后一个函数是允许用户使用以下代码连接到套接字

    public bool Connect()
    {
        return this.socket.Connect();
    }

步骤 09 - 测试

现在我们准备好接收交易消息了。让我们用一个单元测试项目来测试我们的实现。右键单击解决方案,选择 Add > New Project > Test > Unit Test Project,并为其命名(例如:Coinigy.API.Tests)。

我们需要创建一个类来测试我们的交易消息功能。在测试项目中添加一个新的“WebsocketTests”类

    [TestClass]
    public class WebsocketTests
    {
        private static readonly ManualResetEvent resetEvent = new ManualResetEvent(false);
        private static Websocket socket;

        [TestMethod]
        public void Subscribe_And_Listen()
        {
            // Initialize an instance of our socket
            socket = new Websocket(new ApiCredentials
            {
                ApiKey = "[YOUR-API-KEY]",
                ApiSecret = "[YOUR-API-SECRET]"
            });
            
            // Subscribe to OnClientReady-event so we know when we can subscribe to trade channels
            socket.OnClientReady += Socket_OnClientReady;
            
            // Subscribe to the OnTradeMessage-event so we can receive trade messages
            socket.OnTradeMessage += Socket_OnTradeMessage;

            // Finally we can connect to our socket and wait for incoming messages
            socket.Connect();

            // Forces the methods not to exit
            resetEvent.WaitOne();
        }

        private void WriteLog(string message)
        {
            Debug.WriteLine($"{DateTime.UtcNow}: {message}");
        }

        private void Socket_OnTradeMessage(string exchange, string primaryCurrency, 
                                           string secondaryCurrency, Models.TradeItem trade)
        {
            WriteLog($"Received new trade for {exchange} market 
                     {primaryCurrency}/{secondaryCurrency} price {trade.Price}");
        }

        private void Socket_OnClientReady()
        {
            // Subscribe to a new trade channel
            socket.SubscribeToTradeChannel("BMEX", "XBT", "USD");
        }
    }

我们需要添加对我们 API 的引用。右键单击单元测试项目下的 References,然后选择“Add Reference”>“Project”,然后选择 API 项目。最后,我们需要在我们的单元测试项目中安装最后一个程序包。运行以下命令

"Install-Package PureSocketCluster"

当我们运行我们的测试时(Ctrl+R, Ctrl+A),我们应该开始在输出窗口中看到交易消息

02-Dec-17 8:04:09 PM: Received new trade for BMEX market XBT/USD price 10939.5
02-Dec-17 8:04:09 PM: Received new trade for BMEX market XBT/USD price 10939.5
02-Dec-17 8:04:09 PM: Received new trade for BMEX market XBT/USD price 10940
02-Dec-17 8:04:10 PM: Received new trade for BMEX market XBT/USD price 10940

结束语

我们现在可以接收来自 Coinigy API 的实时交易数据。我们甚至可以更进一步,将此 API 实现到一个 Web 应用程序中,以显示一些统计信息。这将是我们的下一个项目。感谢您的阅读!

© . All rights reserved.