在很多业务场景下,需要监视显示屏画面。在实时性要求不高的情况下,可以通过定时对显示屏进行截图及回传实现。

本文通过C#中提供的TCP通信功能,对该功能的实现进行简单描述。

首先,该功能的实现分为客户端和服务端。其中客户端发送显示屏截图请求;服务端接收截图请求后,进行截图并回传;客户端收到服务端回传的截图后,进行显示处理。

1. 客户端

1)建立对目标主机的TCP连接

2)向目标主机上的服务端发送截图指令

3)接收目标主机上的服务端发回的内存流(代表截图),并解析为截图

具体代码如下:

// 目标主机网络端点:hostIP表示目标主机IP,hostPort表示端口号
var hostEndPoint = new IPEndPoint(IPAddress.Parse(hostIP), hostPort);

// 连接目标主机的Socket
Socket clientSocket = null;
try
{
    // 创建Socket
    clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    clientSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);

// 连接目标主机
    clientSocket.Connect(hostEndPoint);
}
catch (Exception ex)
{
    // 错误日志
}

// 建立Socket连接失败
if (clientSocket == null || !clientSocket.Connected)
return null;

// 向目标主机发送截图命令,截图命令为字符串常量"GetScreenSnap"
clientSocket.Send(Encoding.ASCII.GetBytes("GetScreenSnap"), "GetScreenSnap".Length, SocketFlags.None);

// 接收并解析截图
MemoryStream snapMemoryStream = null;
Image screenSnap = null;
try
{

// 每次接收1024字节数据(服务端每次发送1024字节)
    byte[] by = new byte[1024];

// 重试次数
    int retryCount = 0;

// 前1024字节数据代表的字符串
    string s = "";
    while (true)
    {

// 成功接收到前1024字节数据
        if ((clientSocket.Receive(by)) > 0)
        {

// 转化为字符串
            s = Encoding.Unicode.GetString(by);
            by = new byte[1024];

// 结束循环
            break;
        }

// 接收失败后,重试次数加1
        retryCount++;

// 重试次数超过5次
        if (retryCount > 5)
        {

// 放弃接收截图
            break;
        }
    }

// 前1024字节代表的字符串不为空
    if (!s.Equals(""))
    {

// 解析出截图内存流的总字节长度
        s = s.Split('!')[0];
        long len = long.Parse(s);

// 代表截图的内存流

snapMemoryStream = new MemoryStream();

// 依次接收字节流

while (len > 0)
        {
            int number;
            if ((number = clientSocket.Receive(by)) > 0)
            {

// 写入内存流
                snapMemoryStream.Write(by, 0, number);
                len -= number;
            }
            else
            {
                break;
            }
           by = new byte[1024];
        }

// 根据内存流创建图片
        snapMemoryStream.Position = 0;
        screenSnap = Image.FromStream(snapMemoryStream);
     }
}
catch (Exception ex)
{
    // 错误日志
}
finally
{
    // 关闭内存流
    if (snapMemoryStream != null)
    snapMemoryStream.Close();

// 关闭连接
    if (clientSocket != null)
    clientSocket.Close();

}

2. 服务端

1)创建并启动TCP侦听器,监听客户端截图请求

2)进行显示屏截图

3)向客户端发送截图

具体代码如下:

// 创建监听器并开始监听
mSnapRequestListener = new TcpListener(localhostIP, port);
mSnapRequestListener.Start();

// 只要服务端在运行,则一直监听
while (isRunning)
{
    TcpClient tcpClient = null;
    Bitmap screenSnap = null;
    try
    {
        // 获取客户端发送的TcpClient
        tcpClient = mSnapRequestListener.AcceptTcpClient();
        if (tcpClient == null || tcpClient.Client == null)
        {
            return;
        }

// 解析命令文本
        var inBuffer = new Byte["GetScreenSnap".Length];
        tcpClient.Client.Receive(inBuffer, "GetScreenSnap".Length, SocketFlags.None);
        var commandText = Encoding.ASCII.GetString(inBuffer);
        // 不是截图命令
        if (commandText != "GetScreenSnap")
            return;

// 创建一张空白图片
var primaryScreenSize = Screen.PrimaryScreen.Bounds.Size;
screenSnap = new Bitmap(primaryScreenSize.Width, primaryScreenSize.Height, PixelFormat.Format32bppArgb);
// 将屏幕复制到图片上
using (var grfx = Graphics.FromImage(screenSnap))
{
        grfx.CopyFromScreen(new Point(0, 0), new Point(0, 0), primaryScreenSize);
}

using (var snapStream = new MemoryStream())
{
    // 将屏幕截图保存入内存流,并将内存流位置设为0
    screenSnap.Save(snapStream, ImageFormat.Jpeg);
    snapStream.Position = 0;

// 截图内存流长度
    var streamLength = snapStream.Length.ToString();
    // 扩充为512位(Unicode编码中为1024字节),右边以'!'填充
    streamLength = streamLength.PadRight(512, '!');

// 初次发送截图内存流长度
    var snapBytes = Encoding.Unicode.GetBytes(streamLength);

do
    {
        tcpClient.Client.Send(snapBytes);
    }
    // 从流中读取1024个字节,直到读完为止
    while (snapStream.Read(snapBytes, 0, 1024) > 0);
}

}catch (Exception ex)
    {
        // 错误日志
    }
    finally
    {
        // 销毁图片
        if (screenSnap != null)
        {
            screenSnap.Dispose();
        }

// 关闭连接
        if (tcpClient != null && tcpClient.Client != null && tcpClient.Client.Connected)
        {
            tcpClient.Client.Close();
        }
    }
}

通过TCP实现显示屏截图请求及回传的更多相关文章

  1. TCP 连接与 HTTP 请求的相关问题

    1.现代浏览器在与服务器建立了一个 TCP 连接后是否会在一个 HTTP 请求完成后断开?什么情况下会断开? 默认情况下建立 TCP 连接不会断开,只有在请求报头中声明 Connection: clo ...

  2. TCP连接与HTTP请求

    一道经典面试题: 从 URL 在浏览器被被输入到页面展现的过程中发生了什么? 相信大多数准备过的同学都能回答出来,但是如果继续问:收到的 HTML 如果包含几十个图片标签,这些图片是以什么方式.什么顺 ...

  3. TCP连接建立系列 — 连接请求块

    连接请求块(request_sock)之于TCP三次握手,就如同网络数据包(sk_buff)之于网络协议栈,都是核心的数据结构. 内核版本:3.6 Author:zhangskd @ csdn blo ...

  4. TCP网络编程-----客户端请求连接服务器、向服务器发数据、从服务器接收数据、关闭连接

    SOCKET m_sockClient; unsigned short portNum; ------------------------------------------------------- ...

  5. jsonp 请求和回传实现

    JSONP最主要的是可以解决跨域问题,不然谁会没事用这种格式. 下面是我用JSONP的一些心得体会: JSONP是JSON with Padding的略称.它是一个非官方的协议,它允许在服务器端集成S ...

  6. IP封包协议头/TCP协议头/TCP3次握手/TCP4次挥手/UDP协议头/ICMP协议头/HTTP协议(请求报文和响应报文)/IP地址/子网掩码(划分子网)/路由概念/MAC封包格式

    IP协议头IP包头格式: 1.版本号:4个bit,用来标识IP版本号.这个4位字段的值设置为二进制的0100表示IPv4,设置为0110表示IPv6.目前使用的IP协议版本号是4. 2.首部长度:4个 ...

  7. 可能会搞砸你的面试:你知道一个TCP连接上能发起多少个HTTP请求吗?

    本文由原作者松若章原创发布,作者主页:zhihu.com/people/hrsonion/posts,感谢原作者的无私分享. 1.引言 一道经典的面试题是:从 URL 在浏览器被被输入到页面展现的过程 ...

  8. 夺命连环问:一个 TCP 连接可以发多少个 HTTP 请求?

    曾经有这么一道面试题:从 URL 在浏览器被被输入到页面展现的过程中发生了什么? 相信大多数准备过的同学都能回答出来,但是如果继续问:收到的 HTML 如果包含几十个图片标签,这些图片是以什么方式.什 ...

  9. 一个 TCP 连接可以发多少个 HTTP 请求

    第一个问题 第二个问题 第三个问题 第四个问题 第五个问题 曾经有这么一道面试题:从 URL 在浏览器被被输入到页面展现的过程中发生了什么? 相信大多数准备过的同学都能回答出来,但是如果继续问:收到的 ...

随机推荐

  1. 【源码分析】你必须知道的string.IsNullOrEmpty && string.IsNullOrWhiteSpace

    写在前面 之前自信撸码时踩了一次小坑,代码如下: private static void AppServer_NewMessageReceived(WebSocketSession session, ...

  2. php和apache工作原理?

    1.CGI和FastCGI是apache处理php脚本的其中两种工作模式,还有ISAPI,SAPI等 2.而php-fpm并不是一种工作模式,而是一个PHP在FastCGI模式运行下的进程管理器,全称 ...

  3. Notepad++中实现Markdown语法高亮与实时预览

    Notepad ++是一个十分强大的编辑器,除了可以用来制作一般的纯文字说明文件,也十分适合编写计算机程序代码.Notepad ++不仅有语法高亮度显示,也有语法折叠功能,并且支持宏以及扩充基本功能的 ...

  4. js通过a链接控制多个DIV只显示其中一个其它隐藏

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  5. oralce数据库常用到的一些sql命令(加字段注释,修改数据之类)

    最近开始接触oralce,整理了一下最近使用 pl/sql 常用到的一些sql命令 1.修改表中的数据 编写查询语句及条件,然后加上"FOR UPDATE","FOR U ...

  6. 转git取消commit

     如果不小心commit了一个不需要commit的文件,可以对其进行撤销. 先使用git log 查看 commit日志 commit 422bc088a7d6c5429f1d0760d008d8 ...

  7. 学号:201621123032 《Java程序设计》第4周学习总结

    1:本周学习总结 1. 写出你认为本周学习中比较重要的知识点关键词 继承,多态,父类object,抽象类 2. 尝试使用思维导图将这些关键词组织起来 2:书面作业 2.1: 面向对象设计 1. 讲故事 ...

  8. prototype 原型链

    // 方法1 var aa=function(){ function bb(){ this.name="1111"; console.log(this.name) }; bb.pr ...

  9. bzoj千题计划244:bzoj3730: 震波

    http://www.lydsy.com/JudgeOnline/problem.php?id=3730 点分树内对每个节点动态维护2颗线段树 线段树以距离为下标,城市的价值为权值 对于节点x的两棵线 ...

  10. 2017 清北济南考前刷题Day 3 morning

    实际得分:100+0+0=100 T1 右上角是必败态,然后推下去 发现同行全是必胜态或全是必败态,不同行必胜必败交叉 列同行 所以n,m 只要有一个是偶数,先手必胜 #include<cstd ...