Read SslStream continuously in C# Web MVC 5 project

我只是一个虾纸丫 提交于 2021-02-18 18:43:56

问题


tl;dr: "I would like to read an SslStream continuously in C# but I can't figure out the best way."

Some background: I'm getting stock data from a stream that I would like to present in a web interface. The stream sends a heartbeat every 5 seconds and you can also subscribe/unsubscribe to stock prices and news etc.

https://api.test.nordnet.se/next/2/api-docs/docs/feeds

Currently I'm using SslTcpClient from MSDN example to read and write to the stream and it works OK.

https://msdn.microsoft.com/en-us/library/system.net.security.sslstream.aspx

My problem is that the stream continuously sends data and I'm not really sure how to consume this and present it in the best way.

My solution now is to make a ajax call every five seconds to "clear" the stream and get the new data. This does not feel like a robust solution and I'm quite often getting the error "The Read method cannot be called when another read operation is pending.".

Update:

@phuzi: Difference below between ajax and SignalR call. Unfortunately this does not help me to read the stream continuously since I still need to call the method every five seconds. A solution might be Hangfire in combination with SignalR although I do not know how to best implement this.

$.connection.hub.start().done(function () {
    setInterval(function () {
        stockHub.server.getHeartBeat();
    }, 5000);
});

setInterval(function () {
    $.ajax({
        url: '@Url.Action("getHeartBeat")',
        type: 'POST',
        data: {
        },
        success: function (message) {
            console.log(message);
        },
        error: function () {
        }
    );
}, 5000);

@usr: Here is some example data:

I subscribe to stock depth at a specific market using this string

"{"cmd":"subscribe", "args":{"t":"depth", "i":"5110", "m":11}}\n"

Then I read the feed and add a expected value to my read method that the response should contain to be the string I'm looking for, for example:

response.Contains("{"type":"depth","data":{"i":"5110","m":11")

And then I get a string that looks something like this in return:

"{"type":"depth","data":{"i":"5110","m":11,"tick_timestamp":1439894747189,"bid1":1.02,"bid_volume1":734827,"bid_orders1":30,"ask1":1.03,"ask_volume1":393598,"ask_orders1":8,"bid2":1.01,"bid_volume2":805705,"bid_orders2":35,"ask2":1.04,"ask_volume2":404815,"ask_orders2":15,"bid3":1.00,"bid_volume3":1387177,"bid_orders3":62,"ask3":1.05,"ask_volume3":601579,"ask_orders3":29,"bid4":0.995,"bid_volume4":123610,"bid_orders4":9,"ask4":1.06,"ask_volume4":313060,"ask_orders4":15,"bid5":0.990,"bid_volume5":386543,"bid_orders5":31,"ask5":1.07,"ask_volume5":741100,"ask_orders5":11}}"

Solved the error "The Read method cannot be called when another read operation is pending." using a lock.

private static readonly object _readBytesLock = new object();

private static volatile bool _readingBytes = false;

public static string ReadMessage(SslStream sslStream, string expectedValue = "heartbeat")
{
    // Read the  message sent by the server. 
    // The end of the message is signaled using the 
    // "<EOF>" marker.
    string message;
    byte[] buffer = new byte[2048];
    StringBuilder messageData = new StringBuilder();
    int bytes = -1;
    do
    {
        //bytes = sslStream.Read(buffer, 0, buffer.Length);
        bytes = GetBytes(sslStream, buffer);
        // Use Decoder class to convert from bytes to UTF8 
        // in case a character spans two buffers.
        Decoder decoder = Encoding.UTF8.GetDecoder();
        char[] chars = new char[decoder.GetCharCount(buffer, 0, bytes)];
        decoder.GetChars(buffer, 0, bytes, chars, 0);
        message = messageData.Append(chars).ToString();
        Debug.WriteLine(messageData);
        if (message.Contains(expectedValue))
        {
            return message;
        }
        if (Regex.Matches(messageData.ToString(), "heartbeat").Count >= 3)
        {
            Debug.WriteLine("Something went wrong, 3 heartbeats received and nothing with the expected value: " + expectedValue);
            return message;
        }
    } while (bytes != 0);

    return message;
}

public static int GetBytes(SslStream sslStream, byte[] buffer)
{
    try
    {
        if (buffer == null)
            return -1;
        var bytes = -1;
        lock (_readBytesLock)
        {
            if (!_readingBytes)
            {
                _readingBytes = true;
                bytes = sslStream.Read(buffer, 0, buffer.Length);
            }
            _readingBytes = false;
            return bytes;
        }
    }
    catch (Exception ex)
    {
        var methodName = System.Reflection.MethodBase.GetCurrentMethod().Name;
        Debug.WriteLine("Method " + methodName + " failed " + ex.Message);
        return 0;
        //throw;
    }
}

回答1:


Final code that works really well. A big thanks to @usr and everyone else helping.

private Task _feedTask;
private SslStream _sslStream;
private StreamReader _streamReader;
private static readonly log4net.ILog _logger
        = log4net.LogManager.GetLogger(
                System.Reflection.MethodBase.GetCurrentMethod()
                 .DeclaringType);

public void CreateNewTaskAndStartReadingFeed()
{
    _feedTask = new Task(() => StartReadingFeed());
    _feedTask.Start();
}

public void StartReadingFeed()
{
   try
   {
       while (true)
       {
           var message = GetStreamMessage();

           if (message != null)
           {
               Debug.WriteLine("StartReadingFeed message: " + message);
               OnReceivedSomething(message);
           }
           else
           {
               Debug.WriteLine("StartReadingFeed message was null");
           }
       }
   }
   catch (Exception ex)
   {
       var methodName = System.Reflection.MethodBase.GetCurrentMethod().Name;
       Debug.WriteLine($"Method {methodName} failed " + ex.Message);
       Debug.WriteLine("Creating a new task and starts to reed feed again");
       _logger.Error("StartReadingFeed failed", ex);
       CreateNewTaskAndStartReadingFeed();
   }
}

public string GetStreamMessage()
{
    try
    {
        return _sslStream.CanRead ? _streamReader.ReadLine() : null;
    }
    catch (Exception ex)
    {
        var methodName = System.Reflection.MethodBase.GetCurrentMethod().Name;
        Debug.WriteLine($"Method {methodName} failed " + ex.Message);
        _logger.Error($"{methodName} failed.", ex);
        return null;
    }
}


来源:https://stackoverflow.com/questions/32012026/read-sslstream-continuously-in-c-sharp-web-mvc-5-project

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!