【问题标题】:Live Streaming in asp.net在 asp.net 中进行实时流式传输
【发布时间】:2021-05-26 18:48:36
【问题描述】:

我想将我的屏幕实时流式传输到网页,我有一个发送屏幕截图的 win 表单,然后我有一个接收 img 并将其放入 asp:Image 标记的 asp.net 网站。 起初我在两个 winforms 应用程序上编写了这段代码,它可以工作,但是当我尝试将其传输到 asp.net 时,它不再工作了。

//this is the win forms app
BinaryFormatter binFormatter = new BinaryFormatter();
    MemoryStream ms = new MemoryStream();
    while (connected)
    {
        stream = client.GetStream();
        System.Drawing.Image bmp = (System.Drawing.Bitmap)binFormatter.Deserialize(stream);
        bmp.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
        img.ImageUrl = "data:image/gif;base64," + Convert.ToBase64String(ms.ToArray());
        ms.FlushAsync();
    }
}







//this is the web page
private void ReciveImg()
{
    BinaryFormatter binFormatter = new BinaryFormatter();
    MemoryStream ms = new MemoryStream();
    while (connected)
    {
        stream = client.GetStream();
        System.Drawing.Image bmp = (System.Drawing.Bitmap)binFormatter.Deserialize(stream);
        bmp.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
        img.ImageUrl = "data:image/gif;base64," + Convert.ToBase64String(ms.ToArray());
        ms.FlushAsync();
    }
}

【问题讨论】:

  • 这似乎经常调用 ms.ToArray() 会将一堆东西转储到无法正确清理的大对象堆中,并最终由于缺少地址而导致 OOM 情况空间清理。
  • 另外,你能澄清一下“不再工作”的意思吗?
  • Joel Coehoorn,抱歉并不意味着不再工作。我的意思是,当我从两个单独的 winforms 应用程序(一个共享屏幕和一个显示屏幕)翻译它时,此代码有效(但我没有在 中显示 img,而是在图片框中显示了 img,我也没有使用内存流,因为将 bmp 转换为 base 64 字符串没有用)希望这更有意义,英语不是我的第一语言谢谢
  • 另外,Joel Coehoorn,经常调用 ms.ToArray 有什么替代方法吗? ms.FlushAsync() 不应该清除数据吗?
  • 如果您阅读 MemoryStream.FlushAsync() 的文档,您会发现它说该方法是多余的:也就是说,包含它是为了与其他 API 兼容,但它没有做任何事情垃圾收集器还没有做。具体来说,垃圾收集器清理内存,但如果这些数组是 85,000 字节或更大(对于 jpeg 和 bmp 对象非常常见),它们将继续在大对象堆上,垃圾收集器将回收进程地址空间之前占用的内存,或者不管你有多少内存,你通常限制为2GB

标签: c# asp.net


【解决方案1】:

Windows 窗体和 Web 窗体具有两种不同的体系结构。

在 Windows 窗体中,当您修改控件上的图像时,这会发生在内存中,并且框架会自动负责重新绘制窗体中的图像。

在 Web 表单中,您有一个客户端执行一个 http 请求,服务器接收该请求并根据请求的参数执行代码。执行完所有代码并返回包含代码输出数据的 http 响应。

在您的情况下,浏览器将发送 http 请求,服务器将执行 ReciveImg 方法,这取决于 connected 的实现方式可能会永远运行,甚至不会向浏览器返回响应。即使它确实返回了响应,它也只会返回你写的最后一张图片。

要完成你正在尝试的事情,你需要一种不同的方法。

  1. 您可以有一个方法为每次调用返回一个图像。然后在浏览器中,您可以重复调用该方法,可能使用配置为在加载图像后立即刷新的旧版 AjaxControlToolkit Asp:UpdatePanel,或者使用 JavaScript 循环重复调用您的方法。这种方法可能很复杂,而且效率不高。

  2. 更好的选择是在您的客户端和服务器之间设置websocket。一旦客户端连接到 websocket,服务器就可以开始写入图像,客户端将在收到的每个消息事件中接收这些图像。然后在 javascript 中,您可以将图像设置为从服务器接收到的图像。这种方法更简单、更高效。

有关如何实现此功能的示例,请参阅my repo here,它会截取桌面屏幕截图并将其写入服务器端的 websocket。

服务器代码:

class Program
    {
        static bool running = false;
        static void Main(string[] args)
        {
            Console.WriteLine("Press any key to start the WebSocketServer!");

            Console.ReadKey();
            Console.WriteLine();

            var appServer = new WebSocketServer();

            //Setup the appServer
            if (!appServer.Setup(2012)) //Setup with listening port
            {
                Console.WriteLine("Failed to setup!");
                Console.ReadKey();
                return;
            }

            appServer.NewMessageReceived += new SessionHandler<WebSocketSession, string>(appServer_NewMessageReceived);
            appServer.NewSessionConnected += AppServer_NewSessionConnected;
            Console.WriteLine();

            //Try to start the appServer
            if (!appServer.Start())
            {
                Console.WriteLine("Failed to start!");
                Console.ReadKey();
                return;
            }
            running = true;
            Console.WriteLine("The server started successfully, press key 'q' to stop it!");

            while (Console.ReadKey().KeyChar != 'q')
            {
                Console.WriteLine();
                continue;
            }

            //Stop the appServer

            running = false;
            Console.WriteLine();
            Console.WriteLine("The server was stopped!");
            Console.ReadKey();
        }

        private static void AppServer_NewSessionConnected(WebSocketSession session)
        {

            byte[] bytes = null;
            var fps = 1000 / 12.0;
            while (running && session.Connected)
            {
                DateTime start = DateTime.Now;
                bytes = ScreenShotUtility.TakeScreenshot();
                var segment = new ArraySegment<byte>(bytes);
                session.Send(segment);
                var diff = DateTime.Now.Subtract(start).TotalMilliseconds;
                var sleep = Math.Max(0, fps - diff);
                System.Threading.Thread.Sleep((int)sleep);
            }

        }

        static void appServer_NewMessageReceived(WebSocketSession session, string message)
        {
            //Send the received message back
            session.Send("Server: " + message);
        }
    }

客户端代码(使用查询和 websocket):

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
    <title>Test</title>
    <script type="text/javascript" src="jquery.js"></script>
    <script type="text/javascript">
        var noSupportMessage = "Your browser cannot support WebSocket!";
        var ws;
        var canvas;
        var ctx;
        var urlCreator;
        function wsMessage(e) {
            if (typeof e.data === "string") {
                appendMessage("# " + evt.data + "<br />");

            }
            else if (e.data instanceof ArrayBuffer) {

            } else if (e.data instanceof Blob) {
                //showBlob(e.data);
                blob2canvas(e.data);
            }
        }
        function appendMessage(e) {
            $('#lastMessage').html(e);
        }

        function blob2canvas(blob) {
            var img = new Image();
            img.onload = function () {
                if (canvas.width != img.width)
                    canvas.width = img.width;
                if (canvas.height != img.height)
                    canvas.height = img.height;
                ctx.drawImage(img, 0, 0);
            }
            img.src = urlCreator.createObjectURL(blob);// blob;
        }
        function showBlob(blob) {
            //console.log(blob);

            var imageUrl = urlCreator.createObjectURL(blob);
            document.querySelector("#blobImage").src = imageUrl;
            //disconnectWebSocket();
        }
        function connectSocketServer() {
            var support = "MozWebSocket" in window ? 'MozWebSocket' : ("WebSocket" in window ? 'WebSocket' : null);

            if (support == null) {
                appendMessage("* " + noSupportMessage + "<br/>");
                return;
            }

            appendMessage("* Connecting to server ..<br/>");
            // create a new websocket and connect
            var host = "localhost"; //"192.168.1.170"; //"localhost" <-- broke for edge, so is "127.0.0.1";
            ws = new window[support]('ws://'+host +':2012/');

            // when data is comming from the server, this metod is called
            ws.onmessage = function (evt) {
                wsMessage(evt);
            }
            ws.onerror = function (event) {
                console.error("WebSocket error observed:", event);
            };

            // when the connection is established, this method is called
            ws.onopen = function () {
                appendMessage('* Connection open<br/>');
                $('#messageInput').attr("disabled", "");
                $('#sendButton').attr("disabled", "");
                $('#connectButton').attr("disabled", "disabled");
                $('#disconnectButton').attr("disabled", "");
            };

            // when the connection is closed, this method is called
            ws.onclose = function () {
                appendMessage('* Connection closed<br/>');
                $('#messageInput').attr("disabled", "disabled");
                $('#sendButton').attr("disabled", "disabled");
                $('#connectButton').attr("disabled", "");
                $('#disconnectButton').attr("disabled", "disabled");
            }
        }

        function sendMessage() {
            if (ws) {
                var messageBox = document.getElementById('messageInput');
                ws.send(messageBox.value);
                messageBox.value = "";
            }
        }

        function disconnectWebSocket() {
            if (ws) {
                ws.close();
            }
        }

        function connectWebSocket() {
            connectSocketServer();
        }

        window.onload = function () {
            $('#messageInput').attr("disabled", "disabled");
            $('#sendButton').attr("disabled", "disabled");
            $('#disconnectButton').attr("disabled", "disabled");
            canvas = document.getElementById('canvas');
            ctx = canvas.getContext('2d');
            urlCreator = window.URL || window.webkitURL;
        }

    </script>
</head>
<body>
    <input type="button" id="connectButton" value="Connect" onclick="connectWebSocket()" /> <input type="button" id="disconnectButton" value="Disconnect" onclick="disconnectWebSocket()" /> <input type="text" id="messageInput" /> <input type="button" id="sendButton" value="Send" onclick="sendMessage()" /> <br />
    <div id="lastMessage"></div>
    <img id="blobImage" />
    <canvas id="canvas" height="500" width="500"></canvas>
</body>
</html>

【讨论】:

    【解决方案2】:

    ASP:Image 表示一个 Web 表单项目。我需要确保您了解 Web 表单的基础知识,因为它们与 winform 不同:主要是,在 Web 表单中初始渲染完成后表单就会被销毁。表单的任何属性或属性(例如更新图像控件的计时器)在 Web 表单环境中都没有长期生命周期;它被创建,表单被渲染到浏览器,然后它被立即销毁。

    每次你有一个像按钮点击这样的服务器事件时,表单都会从头开始重建,呈现给浏览器,然后立即再次销毁。如果这对您来说听起来既慢又昂贵,那么您是对的,优秀的 Web 表单开发人员会花费大量精力来避免引发服务器事件。

    换句话说,Web 表单并不能让您不必了解 Web 浏览器使用的 html 和 javascript 发生了什么。在这种情况下,您需要编写 javascript 代码来刷新 html 页面中的图像元素。

    【讨论】:

    • 谢谢!但我仍然不明白为什么它不显示任何东西,即使它又慢又贵,难道它仍然应该渲染 img 吗?因为您说每次出现服务器事件时,表单都会从头开始重建,这是否意味着它至少会显示我要显示的 imgs?
    • 在网页中,图像标签有它自己的src 属性,该属性作为单独的http 请求加载到服务器。在发出这个 http 请求时,表单已经消失了。
    • 此外,服务器事件仅从用户/浏览器引发。想想按钮点击。计时器滴答声或经过的事件不会触发,因为负责它的计时器已被销毁。
    猜你喜欢
    • 2017-11-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-16
    相关资源
    最近更新 更多