因此,我想制作一个带有客户端和服务器的小型 RDP(没有操纵器)。有一个用于创建、压缩、发送和接收图像的小代码。看起来一切都很好,但是即使在局域网上也有野物(尽管正常接收到一半的图像),我还没有通过网络尝试过,我认为它会是一样的。
代码:发送 SendStream.cs
using System;
using System.Drawing;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Forms;
namespace Client.Sender
{
public class SendStream
{
private IPEndPoint ipEndPoint;
private UdpClient _udpClient;
private int width;
private int height;
private const UInt16 UdpSize = 65507;
private const UInt16 ControlBlockSize = 5;
private Random random = new Random();
private byte lastA = 0; // Последний код цепочки
private byte lastB = 0; // Последний код цепочки
private float FPS = 60;
public SendStream()
{
// Загружаем номер порта, на которой надо встать
using (StreamReader streamReader = new StreamReader("ip.txt"))
{
string ip = streamReader.ReadLine();
int port = Convert.ToInt32(streamReader.ReadLine());
if (ip != null) ipEndPoint = new IPEndPoint(IPAddress.Parse(ip), port);
}
width = Screen.PrimaryScreen.Bounds.Width;
height = Screen.PrimaryScreen.Bounds.Height;
}
public async Task Run()
{
_udpClient = new UdpClient();
Bitmap backGround = new Bitmap(width, height);
Graphics graphics = Graphics.FromImage(backGround);
while (true)
{
await Task.Delay((int) (1000 / FPS));
graphics.CopyFromScreen(0, 0, 0, 0, new System.Drawing.Size(width, height)); // Получаем снимок экрана
byte[] bytes = ConvertToByte(backGround); // Получаем изображение в виде массива байтов
List<byte[]> data = Package(bytes); // Упаковка изображения в протокол
foreach (var block in data)
{
await _udpClient.SendAsync(block, block.Length, ipEndPoint);
}
}
}
/*
* Конвертируем изображение в массив байтов со сжатием
* Jpeg - качество средние, скорость средние, потери малые (полупрозрачные, 40-60%)
* Gif - качество плохое, скорость максимальное, потери огромные (тв, 90%)
* Png - качество выше-среднего, скорость выше-среднего, потери средние (серый цвет, 40-60%)
*
*/
private byte[] ConvertToByte(Bitmap bmp)
{
MemoryStream memoryStream = new MemoryStream();
bmp.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Gif);
return memoryStream.ToArray();
}
// Пакеты для передачи UDP
private List<byte[]> Package(byte[] bt)
{
int countMsg = (int) Math.Ceiling(bt.Length / (double) UdpSize); // Количество сообщений
List<byte[]> chain = new List<byte[]>(); // Цепочка сообщений
if (countMsg > 65536)
throw new Exception(
"Вы пытаетесь передать сообщение больше 4 ГБ - протокол не подерживает передачу свыше 4 ГБ");
byte[] controlBlock = new byte[ControlBlockSize]; // Формируем контрольный блок
controlBlock[0] = 1;
byte a, b;
do
{
a = (byte) random.Next(0, 256);
b = (byte) random.Next(0, 256);
} while (a == lastA && b == lastB);
// Цикл нужен, чтобы случайно не совпал код сообщений из другой цепочки,
// в одной из параллельных вселенной этот цикл выполняется бесконечно
controlBlock[1] = a;
controlBlock[2] = b;
controlBlock[3] = BitConverter.GetBytes(countMsg)[0];
controlBlock[4] = BitConverter.GetBytes(countMsg)[1];
chain.Add(controlBlock);
int offset = 0;
for (int i = 0; i < countMsg; i++)
{
byte[] msgBlock = new byte[UdpSize]; // Формируем блок сообщения
msgBlock[0] = 0;
msgBlock[1] = a;
msgBlock[2] = b;
msgBlock[3] = BitConverter.GetBytes(i)[0];
msgBlock[4] = BitConverter.GetBytes(i)[1];
int msgBlockLength;
if (bt.Length - offset <= UdpSize)
{
msgBlockLength = bt.Length - offset;
}
else
{
msgBlockLength = UdpSize - 7; // 5 - в данном случае первые 5 байтов
}
msgBlock[5] = BitConverter.GetBytes(msgBlockLength)[0];
msgBlock[6] = BitConverter.GetBytes(msgBlockLength)[1];
if (i == countMsg - 1)
{
Array.Copy(bt, offset, msgBlock, 7, bt.Length - offset);
}
else
{
Array.Copy(bt, offset, msgBlock, 7, UdpSize - 7);
}
chain.Add(msgBlock);
offset += UdpSize;
if (offset > bt.Length)
{
offset = bt.Length;
}
}
return chain;
}
/* Описание протокола передачи LO поверх UDP
1 байт - контрольный пакет (1 если контрольный и 0 если не контрольный)
Расположение байтов для контрольного пакета
2 - 3 байт кодовый номер цепочки пакетов
4 - 5 байт количество пакетов
Расположение байтов для неконтрольного пакета
2 - 3 байт кодовый номер пакета
4 - 5 байт номер пакета
5 - 6 байт размер пакета, а именно данных без первых 6 байт (включая нулевой байт).
*/
}
}
代码:接受 TakeStream.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media.Imaging;
using System.IO;
namespace Server.Receiving
{
class TakeStream
{
private int TTL = 70; // Время жизни пакетов
private List<Packet> turn = new List<Packet>(); // Очередь сообщений
private List<Packet> controls = new List<Packet>(); // Контрольные пакеты
public delegate void EventReady(BitmapImage img);
public event EventReady FrameReady;
private struct Packet
{
public bool isControl; // Это контрольный пакет?
public UInt16 id; // ID цепочки
public byte[] date; // Данные пакета
public UInt16 count; // Количество пакетов
public UInt16 number; // Порядковый номер пакета
public int TTL; // Длительность жызни пакета или контрола
}
public TakeStream()
{
}
public void addPacked(byte[] data)
{
Packet packet = decodeMsg(data);
if (packet.isControl)
{
controls.Add(packet);
}
else
{
turn.Add(packet);
for (int i = 0; i < controls.Count; i++)
{
if (controls[i].id == packet.id && controls[i].count - 1 == packet.number)
{
MemoryStream ms = new MemoryStream(Compare(packet.id));
controls.RemoveAt(i);
i--;
BitmapImage bitmapImg = new BitmapImage();
bitmapImg.BeginInit();
bitmapImg.CacheOption = BitmapCacheOption.OnLoad;
bitmapImg.StreamSource = ms;
bitmapImg.EndInit();
bitmapImg.Freeze();
FrameReady(bitmapImg);
}
}
}
}
// Сообщение
private Packet decodeMsg(byte[] data)
{
Packet packet = new Packet();
packet.TTL = 0;
packet.isControl = data[0] == 1;
packet.id = BitConverter.ToUInt16(data, 1);
if (packet.isControl)
{
packet.count = BitConverter.ToUInt16(data, 3);
}
else
{
packet.number = BitConverter.ToUInt16(data, 3);
packet.date = new byte[BitConverter.ToUInt16(data, 5)];
try
{
Array.Copy(data, 7, packet.date, 0, packet.date.Length);
}
catch (Exception ex)
{
}
}
return packet;
}
// Исходные данные
private byte[] Compare(UInt16 id)
{
List<byte> data = new List<byte>();
for (int i = 0; i < turn.Count; i++)
{
if (turn[i].id == id)
{
data.AddRange(turn[i].date);
turn.RemoveAt(i);
i--;
}
}
return data.ToArray();
}
private void RemoveChain(UInt16 id)
{
for (int i = 0; i < turn.Count; i++)
{
if (turn[i].id == id)
{
turn.RemoveAt(i);
}
}
}
/* Описание протокола передачи LO поверх UDP
1 байт - контрольный пакет (1 если контрольный и 0 если не контрольный)
Расположение байтов для контрольного пакета
2 - 3 байт кодовый номер цепочки пакетов
4 - 5 байт количество пакетов
Расположение байтов для неконтрольного пакета
2 - 3 байт кодовый номер пакета
4 - 5 байт номер пакета
5 - 6 байт размер пакета, а именно данных без первых 6 байт (включая нулевой байт).
*/
}
}
代码:接受具有给定大小的空 UserControlScreenStream.cs PS 表单
using System;
using System.Text;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Net;
using System.Net.Sockets;
using System.IO;
using PRP_Server.Receiving;
namespace Server.UserControls
{
public partial class UserControlScreenStream : UserControl
{
private UdpClient udpClient;
private const UInt16 UdpSize = 65507;
private const UInt16 ControlBlockSize = 5;
private delegate void AsynkWorker();
private delegate void DrawEvent(BitmapImage bitmapImage);
private delegate void Invoke(byte[] data);
private TakeStream takeStream = new TakeStream();
private UInt16 _port;
public UserControlScreenStream()
{
InitializeComponent();
takeStream.FrameReady += new TakeStream.EventReady(decoder_FrameReady);
new AsynkWorker(Run).BeginInvoke(null, null);
}
void decoder_FrameReady(BitmapImage img)
{
this.Background = new ImageBrush(img);
}
void MainWindow_Invoke(byte[] data)
{
try
{
takeStream.addPacked(data);
}
catch (Exception ex)
{
// ignored
}
}
private void Run()
{
using (StreamReader sr = new StreamReader("port.txt"))
{
_port = UInt16.Parse(sr.ReadLine());
}
UdpClient udp = new UdpClient(new IPEndPoint(IPAddress.Any, _port));
IPEndPoint ep = new IPEndPoint(IPAddress.None, 0);
while (true)
{
byte[] mass = udp.Receive(ref ep);
Dispatcher.Invoke(new Invoke(MainWindow_Invoke), mass);
}
/* byte[] test = new byte[5];
test[0] = 1;
test[1] = 2;
test[2] = 3;
test[3] = 4;
test[4] = 5;
decoder.addPacked(test); */
}
}
}
优化代码并不是那么容易,尤其是别人的。有几种流行的方法。
“凝视的方法”。
Вы внимательно читаете код и пытаетесь угадать, где же в нём наиболее часто выполняющийся кусок, или кусок, который создаёт наибольшую нагрузку на память/процессор/и. т. п. В этом куске имеет смысл подумать, можно ли его как-нибудь улучшить. Например, накопление строки в цикле имеет смысл заменять на использование
StringBuilder
. Возможно, кое-где в критических по производительности частях программы придётся отказаться от использования мощных средств наподобие LINQ. После каждого проведённого изменения обязательно перепроверяйте, является ли оно реально оптимизацией, или, наоборот, лишь ухудшает дело.Не старайтесь улучшить всю программу, это отнимет у вас слишком много времени и не окупится вообще никак. Избегайте ухудшения читаемости программы.
Недостаток этого метода в том, что даже опытные программисты не всегда могут «на глазок» установить реальную причину просадки производительности, и много времени зачастую тратится на бессмысленные не-оптимизации наподобие «переиспользовать переменную цикла» или «развернуть LINQ в процедурный код после ввода с клавиатуры».
Алгоритмические оптимизации и выбор структур данных.
Нередко производительность фрагмента программы можно улучшить в разы, выбрав подходящие алгоритмы и/или структуры данных. Очень часто в качестве универсальной структуры данных используется
List<T>
, в котором поиск и добавление может быть очень медленным. Имеет смысл задать себе вопрос: какие операции с моей структурой данных нужны, и как часто они будут выполняться? В зависимости от этого нужно подбирать подходящую структуру данных.Аналогично предыдущему случаю, после каждого проведённого изменения обязательно перепроверяйте, улучшает ли это реально ситуацию.
Недостаток этого метода — он не всегда применим, т. к. в простых программах может и не быть алгоритмических проблем.
Профилирование.
Зачем гадать, где проблема, если можно просто измерить? Запуская программу с профайлером, вы видите, в каких местах происходит реальная задержка. Оптимизируя именно этот код (ускоряя выполнение или просто переструктурировав программу так, чтобы медленный код вызывался реже), вы добьётесь хорошего прироста производительности с достаточно небольшими изменениями. Профайлер позволит не пытаться проводить бессмысленные оптимизации кусков, которые в оптимизации не нуждаются.
这种方法的缺点是您需要学习如何阅读和理解分析器的输出。分析器不会神奇地告诉您要更改源文件中的哪一行,它只会缓慢显示实际发生的情况。如果您是业余程序员,另一个缺点是好的分析器需要花钱。如果你从事商业编程,一定要购买profiler,你离不开它。