Передача скриншота рабочего стола с удаленного ПК по TCP - C#
Формулировка задачи:
Доброго дня всем! Пытаюсь написать для себя программу для удаленного доступа к ПК, используя протокол TCP. Застрял уже на первом этапе - пересылки скриншотов рабочего стола с удаленного ПК. Для этого клиентская часть в бесконечном цикле каждые 40 мс (25 кадров в секунду) отправляет размер пересылаемого сжатого скриншота, а потом сам скриншот:
Серверная часть принимает также в бесконечном цикле сначала размер скриншота и затем сам скриншот:
Ошибка возникает на этапе получения размера очередного скриншота (размер сжатого скриншота колеблется от 60 000 байт до 110 000 байт) - он почему-то через раз на несколько тысяч байт меньше реального. Причем, если я каждый раз пересылаю один и тот же заранее созданный скриншот, то размер всегда корректный.
Также оговорюсь, что:
1. Это лишь заготовка, поэтому подключение пока создается на localhost;
2. Предполагается множественное подключение, поэтому используется класс ClientObject (в будущем планирую список всех активных подключений держать в List'е).
Ниже прилагаю исходный код клиентской (которым будут управлять) и серверной (который будет управлять) частей.
Сервер:
Клиент:
using (FileStream fs = new FileStream("screenshot.gz", FileMode.Open)) //создаем файловый поток fs для чтения сжатого скриншота
{
using (BinaryReader br = new BinaryReader(fs)) //считываем в двоичный поток br из потока fs
{
int ReadedBytes; //количество считанных байт
int len = (Int32)fs.Length; //размер скриншота
byte[] length = Encoding.Unicode.GetBytes(len.ToString()); //переводим в байтовый массив размер скриншота
stream.Write(length, 0, length.Length); //отправляем сначала размер скриншота
do
{
ReadedBytes = br.Read(buf, 0, buf.Length); //считываем максимально возможно количество байт файла в массив buf и записываем количество считанных байт в переменную ReadedBytes
stream.Write(buf, 0, ReadedBytes); //отправляем массив buf
len -= ReadedBytes; //вычитаем из общего размера файла количество отправленных байт
} while (len != 0); //повторяем до тех пор, пока не отправим весь файл "кусок" за "куском"
}
} int ReceivedBytes = Stream.Read(ScreenLengthB, 0, ScreenLengthB.Length); //считываем из потока в массив ScreenLengthB размер очередного скриншота и записываем в переменную ReceivedBytes количество прочитанных байт
int ScrLength = int.Parse(Encoding.Unicode.GetString(ScreenLengthB, 0, ReceivedBytes)); //преобразуем массив ScreenLengthB в число
//далее принимаем скриншот
...using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Runtime.Serialization.Formatters.Binary;
using LibraryTV;
namespace Server
{
public partial class ServerTV : Form
{
TcpListener tcpListener;
ClientObject clientObject;
Thread Srv;
public ServerTV()
{
InitializeComponent();
Screen.Width = this.ClientSize.Width; //устанавливаем размер pictureBox (имя Screen)
Screen.Height = this.ClientSize.Height; //устанавливаем размер pictureBox (имя Screen)
Screen.SizeMode = PictureBoxSizeMode.StretchImage; //устанавливаем свойство pictureBox для увеличения/уменьшения изображения в нем по мере необходимости
Srv = new Thread(new ThreadStart(Connect)); //создаем отдельный поток для метода Connect
Srv.Start(); //запускаем созданный поток
}
private void Connect()
{
try
{
tcpListener = new TcpListener(IPAddress.Any, 50000); //будем слушать 50000 порт и обрабатывать запросы с любого IP-адреса
tcpListener.Start();
while (true)
{
TcpClient client = tcpListener.AcceptTcpClient(); //получаем входящее подключение
clientObject = new ClientObject(client, ref Screen); //создаем объект класса clientObject и передаем ему по ссылке pictureBox и наш TcpClient
Thread clientThread = new Thread(new ThreadStart(clientObject.Process)); //создаем отдельный поток для метода Process созданного выше объекта
clientThread.Start(); //запускаем созданный поток
}
}
catch (Exception ex)
{ MessageBox.Show(ex.Message); }
finally
{ if (tcpListener != null) tcpListener.Stop(); }
}
private void ServerTV_FormClosing(object sender, FormClosingEventArgs e)
{
tcpListener.Stop();
Srv.Abort();
}
}
public class ClientObject
{
public TcpClient client;
public PrintScreen Screen;
private PictureBox PB;
protected internal NetworkStream Stream { get; private set; }
public ClientObject(TcpClient tcpClient, ref PictureBox PictB) //конструктор класса
{
client = tcpClient;
PB = PictB;
Screen = new PrintScreen(); //объект класса, который будет создавать скриншоты и сжимать их
}
public void Process()
{
try
{
Stream = client.GetStream(); //получаем сетевой поток для чтения
while (true) //в бесконечном цикле выполняем процедуру GetScreen получения данных
{
try
{ GetScreen(); }
catch
{ MessageBox.Show("Не удалось получить скриншот экрана"); }
}
}
catch (Exception ex)
{ MessageBox.Show(ex.Message); }
finally
{
if (Stream != null)
Stream.Close();
if (client != null)
client.Close();
}
}
private void GetScreen()
{
try
{
byte[] ScreenLengthB = new byte[10]; //создаем байтовый массив, в который будем записывать размер очередного сжатого скриншота
do
{
int ReceivedBytes = Stream.Read(ScreenLengthB, 0, ScreenLengthB.Length); //считываем из потока в массив ScreenLengthB размер очередного скриншота и записываем в переменную ReceivedBytes количество прочитанных байт
int ScrLength = int.Parse(Encoding.Unicode.GetString(ScreenLengthB, 0, ReceivedBytes)); //преобразуем массив ScreenLengthB в число
using (MemoryStream FileR = new MemoryStream()) //будем записывать сжатый скриншот сначала в память
{
byte[] FileScr = new byte[ScrLength]; //создаем байтовый массив по размеру скриншота, полученного ранее
do
{
ReceivedBytes = Stream.Read(FileScr, 0, FileScr.Length); //считываем из потока в массив FileScr очередной "кусочек" скриншота (по возможности весь скриншот, поэтому указывается размер оставшейся части скриншота) и записываем в переменную ReceivedBytes количество прочитанных байт
FileR.Write(FileScr, 0, ReceivedBytes); //записываем в память полученный "кусок" скриншота
ScrLength -= ReceivedBytes; //вычитаем из размера скриншота размер принятого "куска"
FileScr = new byte[ScrLength]; //создаем массив по новому размеру
} while (ScrLength > 0); //выполняем всю последовательность, пока не примем весь скриншот
using (FileStream File = new FileStream("screenshot.gz", FileMode.Create)) //создаем файл с именем screenshot.gz
{ File.Write(FileR.ToArray(), 0, FileR.ToArray().Length); } //записываем в созданный файл скриншот из памяти
}
Screen.Decompress(); //выполняем декомпрессию принятого сжатого скриншота
PB.LoadAsync(@"screenshot"); //асинхронно подгружаем в pictureBox скриншот
} while (Stream.DataAvailable); //читаем пока в потоке есть данные, доступные для чтения
}
catch (Exception ex)
{ MessageBox.Show(ex.Message); }
}
}
}using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO;
using System.Net.Sockets;
using LibraryTV;
namespace Client
{
public partial class Client : Form
{
TcpClient tcpClient;
NetworkStream stream;
private const string addr = "127.0.0.1";
private const int port = 50000;
PrintScreen screenshot;
public Client()
{
InitializeComponent();
Location = new Point(Screen.PrimaryScreen.WorkingArea.Width - this.Width, Screen.PrimaryScreen.WorkingArea.Height - this.Height); //задаем местоположение окна приложеня в правом нижнем углу экрана
screenshot = new PrintScreen(); //создаем объект класса PrintScreen, с помощью которого будем создавать скриншоты и сжимать их
Thread SendThread = new Thread(new ThreadStart(Connect)); //создаем отдельный поток для метода Connect
SendThread.Start(); //запускаем созданный поток
}
public void Connect()
{
try
{
tcpClient = new TcpClient(addr, port); //пытаемся подключиться к серверу на заданный IP-адрес и порт
stream = tcpClient.GetStream(); //получаем сетевой поток для записи
SendDisplay();
}
catch (Exception ex)
{ MessageBox.Show(ex.Message); }
finally
{ Disconnect(); }
}
public void SendDisplay()
{
while (true)
{
try
{
byte[] buf = new byte[55000]; //создаем байтовый массив в 55000 байт
screenshot.makeScreenshot(); //создаем скриншот
screenshot.Compress(); //сжимаем созданный скриншот
using (FileStream fs = new FileStream("screenshot.gz", FileMode.Open)) //создаем файловый поток fs для чтения сжатого скриншота
{
using (BinaryReader br = new BinaryReader(fs)) //считываем в двоичный поток br из потока fs
{
int ReadedBytes; //количество считанных байт
int len = (Int32)fs.Length; //размер скриншота
byte[] length = Encoding.Unicode.GetBytes(len.ToString()); //переводим в байтовый массив размер скриншота
stream.Write(length, 0, length.Length); //отправляем сначала размер скриншота
do
{
ReadedBytes = br.Read(buf, 0, buf.Length); //считываем максимально возможно количество байт файла в массив buf и записываем количество считанных байт в переменную ReadedBytes
stream.Write(buf, 0, ReadedBytes); //отправляем массив buf
len -= ReadedBytes; //вычитаем из общего размера файла количество отправленных байт
} while (len != 0); //повторяем до тех пор, пока не отправим весь файл "кусок" за "куском"
}
}
Thread.Sleep(40); //останавливаем потом на 40 мс (25 кадров в секунду)
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
Disconnect();
}
}
}
public void Disconnect()
{
if (stream != null)
stream.Close(); //отключение потока
if (tcpClient != null)
tcpClient.Close(); //отключение клиента
Environment.Exit(0); //завершение процесса
}
}
}Решение задачи: «Передача скриншота рабочего стола с удаленного ПК по TCP»
textual
Листинг программы
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Net.Sockets;
using System.Threading;
using System.Windows.Forms;
namespace WindowsFormsApplication344
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
//создаем PictureBox для отображения скриншотов
var pb = new PictureBox { Parent = this, Dock = DockStyle.Fill, SizeMode = PictureBoxSizeMode.Zoom };
//создаем поток для клиента (на самом деле это должно запускаться на машине клиента, но для теста, клиент запускается здесь)
ThreadPool.QueueUserWorkItem(delegate { new Client().Start(); });
//создаем поток для сервера
ThreadPool.QueueUserWorkItem(
delegate
{
//получаем в цикле скриншоты с клиента
foreach (var bmp in new Server().GetScreenshots())
{
//уничтожаем предыдущее изображение
if (pb.Image != null) pb.Image.Dispose();
//заносим скриншот в PictureBox
pb.Image = bmp;
}
});
}
}
/// <summary>
/// Сервер
/// </summary>
class Server
{
public IEnumerable<Image> GetScreenshots(int port = 24432)
{
var list = new TcpListener(port);
list.Start();
using (var tcp = list.AcceptTcpClient())//принимаем конект
using (var stream = tcp.GetStream())//создаем сетевой поток
using (var br = new BinaryReader(stream)) //создаем BinaryReader
while (true)//делаем бесконечно
{
//принимаем длину массива
var len = br.ReadInt32();
//принимаем массив
var arr = br.ReadBytes(len);
using(var ms = new MemoryStream(arr))//создаем временный поток для сжатого изображения
{
//создаем изображение
yield return Bitmap.FromStream(ms);
}
}
}
}
/// <summary>
/// Клиент
/// </summary>
class Client
{
public void Start(string host = "localhost", int port = 24432)
{
//размеры экрана
var rect = Screen.PrimaryScreen.Bounds;
//конкетимся к серверу, получаем поток
using (var tcp = new TcpClient(host, port)) //создаем TcpClient
using (var stream = tcp.GetStream()) //получаем сетевой поток
using (var bw = new BinaryWriter(stream)) //создаем BinaryWriter
using (var bmp = new Bitmap(rect.Width, rect.Height)) //создаем битмап для отправки
using (var gr = Graphics.FromImage(bmp)) //создаем канву
using (var ms = new MemoryStream()) //создаем временный поток для сжатого изображения
while (true) //делаем бесконечно
{
//захватываем изображение экрана
gr.CopyFromScreen(rect.Left, rect.Top, 0, 0, rect.Size);
//конвертируем изображение в массив байт в формате jpeg
ms.Position = 0;
bmp.Save(ms, ImageFormat.Jpeg);
var arr = ms.ToArray(); //получаем массив байт
//отправляем длину массива данных
bw.Write(arr.Length);
//отправляем массив
bw.Write(arr);
//точно, отправялем
bw.Flush();
}
}
}
}