Недавно пытался решить проблему с передачей данных по сети и попутно прочитал пару статей (и несколько видосов) про то, как работают сокеты. Заодно решил возродить свой очень древний проект клиент-сервера (который ещё с Delphi остался). Тогда я очень плохо понимал, что я делаю и почему оно не работает. Сейчас кажется, что стало понятно всё (по сравнению с тем, что было раньше).
А вопрос в следующем.
Вот допустим, надо передать через сокет структуру данных. Допустим, что структура небольшая. На C++
я делаю так:
enum PacketType
{
ChatMessage
};
struct Packet
{
PacketType type;
int len;
};
int SendPacket(SOCKET* s, Packet packet)
{
int packetSize = sizeof(Packet);
char* buffer = (char*)_malloca(packetSize);
if (!buffer)
{
return 0;
}
memcpy(buffer, &packet, packetSize);
int ret = send(*s, buffer, packetSize, 0);
_freea(buffer);
return ret;
}
//отправка
std::string msg = "Chat you!";
int len = msg.length();
Packet packet{ ChatMessage, len };
if (SendPacket(&clientSocket, packet) == SOCKET_ERROR)
{
std::cerr << "Packet sending failed!" << std::endl;
break;
}
if (send(clientSocket, msg.c_str(), len, 0) == SOCKET_ERROR)
{
std::cerr << "Message sending failed!" << std::endl;
break;
}
а на сервере принимаем похожим способом:
int ReceivePacket(SOCKET* s, Packet& packet)
{
int packetSize = sizeof(Packet);
char* buf = (char*)_malloca(packetSize);
if (!buf)
{
return 0;
}
int ret = recv(*s, buf, packetSize, 0);
if (ret != 0 && ret != SOCKET_ERROR)
{
memcpy(&packet, buf, packetSize);
}
_freea(buf);
return ret;
}
const int BUFFER_SIZE = 1024;
char* buffer = (char*)_malloca(sizeof(char) * BUFFER_SIZE);
if (!buffer)
{
std::cerr << "Can't allocate a buffer!" << std::endl;
active = false;
break;
}
while (true)
{
Packet packet;
int bytesReceived = ReceivePacket(&clientSocket, packet);
if (bytesReceived == SOCKET_ERROR || bytesReceived == 0)
{
std::cerr << "Packet receiving failed!" << std::endl;
break;
}
memset(buffer, 0, BUFFER_SIZE);
bytesReceived = recv(clientSocket, buffer, packet.len, 0);
if (bytesReceived == SOCKET_ERROR || bytesReceived == 0)
{
std::cerr << "recv() failed!" << std::endl;
break;
}
if (packet.type == ChatMessage)
{
if (bytesReceived != packet.len)
{
std::cerr << "Message receiving failed! Disconnecting the client..." << std::endl;
closesocket(clientSocket);
continue;
}
std::cout << "Received: " << buffer << std::endl;
}
}
closesocket(clientSocket);
_freea(buffer);
Тут есть что допиливать, но общий принцип рабочий.
Теперь к самой сути. На C++
можно делать так:
int SendPacket(SOCKET* s, Packet packet)
{
int packetSize = sizeof(Packet);
char* buffer = (char*)_malloca(packetSize);
if (!buffer)
{
return 0;
}
memcpy(buffer, &packet, packetSize);
int ret = send(*s, buffer, packetSize, 0);
_freea(buffer);
return ret;
}
То есть, просто взять указатель на структуру и через memcpy()
скопировать все данные в массив байт (char
). Всё просто и понятно. Потом этот массив можно либо записать в файл, либо передать через сокет. Потом вернуть эти байты обратно в структуру.
А на C#
это предполагается делать через BinaryFormatter
и MemoryStream
:
namespace ConsoleApp2
{
internal class Program
{
static void Main(string[] args)
{
MyClass myClass = new MyClass();
BinaryFormatter binaryFormatter = new BinaryFormatter();
MemoryStream memoryStream = new MemoryStream();
binaryFormatter.Serialize(memoryStream, myClass);
byte[] buffer = memoryStream.ToArray(); //вот тут MyClass
memoryStream.Dispose();
}
}
public enum MyEnum { Something }
[Serializable]
public class MyClass
{
public MyEnum MyEnum = MyEnum.Something;
public string MyString = "something";
public int MyInt = 666;
}
}
А как это сделать вручную? Как узнать, сколько байт будет занимать класс и как преобразовать каждую переменную/поле в byte[]
? Нельзя же просто (byte[])myInt
Просто в целях повышения образованности