Help C++ - Reading and writing packets
#1
So I am trying to make a basic mc server using winsock and send and every time I try to send a packet minecraft does not respond with the right respone and my server console spams a ton of numbers
What I need:
 A way to read packets from the client
 A way to send packets to the client
I have been stuck on this for a month an a haft mostly because of poor documentation and not knowing what to do
Why I am doing it? To improve my skills with server development

Please don't link https://wiki.vg/ I have read it
I just need some actually code and a explanation on how it works
Reply
Thanks given by:
#2
There's very little we can help without actually seeing any code or messages or anything, really.
Reply
Thanks given by:
#3
(09-30-2019, 04:40 PM)xoft Wrote: There's very little we can help without actually seeing any code or messages or anything, really.
I did not really want to show the code bc its messy as hell but here you go
https://pastebin.com/ui4t64N2
Reply
Thanks given by:
#4
You're right, that's a mess, but there is some advice I can give.

Use a library that provides event-driven networking. The Minecraft protocol isn't a classical dialog-driven protocol, either side may send any data at any time, so you really need to be processing the data as two separate streams. You can't really expect to call recv() and have all the data you ask for.

If you do decide to stay with raw winsock, you'll at least need two threads, one for reading, another for writing (or a more sophisticated machinery, such as select(), which is btw. a bad idea altogether).

Then you need to organize your code. You have one giant function called start that does basically everything. You should instead have a separate function for sending each packet, so that your main function (in the writer thread) looks something like:
void writerMain()
{
  sendHandshake(1);  // Status = 1 in that handshake
  sendRequest();
  waitForResponse();  // waits for the Reader thread to finish reading a response packet
  sendPing();
  // ...
}

On the other side, the reader function needs to read individual packets and react to them. In pseudo-code:
void readerMain()
{
  Buffer byteBuffer;
  while (true)
  {
    receiveToBuffer(byteBuffer);
    while (true)
    {
      if (!extractAndHandlePacket(byteBuffer))
      {
        break;
      }
    }
  }
}

void receiveToBuffer(Buffer aBuffer)
{
  // Append incoming network data to aBuffer
  char recvBuffer[1024];
  auto numRecvd = recv(socket, recvBuffer, sizeof(recvBuffer);
  aBuffer.append(recvBuffer, numRecvd);
}

/** Returns true if a whole packet was extracted, false if incomplete. */
bool extractAndHandlePacket(Buffer aBuffer)
{
  auto packetType = aBuffer.read(...);
  auto packetLen = aBuffer.read(...);
  if (packetLen > aBuffer.numUsedBytes() - headerSize)
  {
    // Incomplete packet
    return false;
  }
  Packet pkt(aBuffer.data() + headerSize, packetLen);
  handlePacket(pkt);
  aBuffer.erase(packetLen + headerSize);
}
Reply
Thanks given by:
#5
(10-01-2019, 12:40 AM)xoft Wrote: You're right, that's a mess, but there is some advice I can give.

Use a library that provides event-driven networking. The Minecraft protocol isn't a classical dialog-driven protocol, either side may send any data at any time, so you really need to be processing the data as two separate streams. You can't really expect to call recv() and have all the data you ask for.

If you do decide to stay with raw winsock, you'll at least need two threads, one for reading, another for writing (or a more sophisticated machinery, such as select(), which is btw. a bad idea altogether).

Then you need to organize your code. You have one giant function called start that does basically everything. You should instead have a separate function for sending each packet, so that your main function (in the writer thread) looks something like:
void writerMain()
{
  sendHandshake(1);  // Status = 1 in that handshake
  sendRequest();
  waitForResponse();  // waits for the Reader thread to finish reading a response packet
  sendPing();
  // ...
}

On the other side, the reader function needs to read individual packets and react to them. In pseudo-code:
void readerMain()
{
  Buffer byteBuffer;
  while (true)
  {
    receiveToBuffer(byteBuffer);
    while (true)
    {
      if (!extractAndHandlePacket(byteBuffer))
      {
        break;
      }
    }
  }
}

void receiveToBuffer(Buffer aBuffer)
{
  // Append incoming network data to aBuffer
  char recvBuffer[1024];
  auto numRecvd = recv(socket, recvBuffer, sizeof(recvBuffer);
  aBuffer.append(recvBuffer, numRecvd);
}

/** Returns true if a whole packet was extracted, false if incomplete. */
bool extractAndHandlePacket(Buffer aBuffer)
{
  auto packetType = aBuffer.read(...);
  auto packetLen = aBuffer.read(...);
  if (packetLen > aBuffer.numUsedBytes() - headerSize)
  {
    // Incomplete packet
    return false;
  }
  Packet pkt(aBuffer.data() + headerSize, packetLen);
  handlePacket(pkt);
  aBuffer.erase(packetLen + headerSize);
}
This is very helpful but I dont want any API or premade stuff I dont know how the bytebuffer class works thats the main part I have been struggling with
Reply
Thanks given by:
#6
You need to write your own bytebuffer class. Basically it needs the operations already mentioned - append data to the end of the buffer, read from the beginning, rewind the read to the beginning again, and erase from the beginning. It's fairly simple to do this.
Reply
Thanks given by:
#7
As for the API / library - networking IS HARD, there's no shame in using a library that already shields you from the nasty low-level problems you'd encounter otherwise. For example, the select() API you might be tempted to use works alright on Windows, but is hopelessly broken on Linux / Unix; the function signatures are the same on both platforms, but each platform has its quirks that make writing multiplatform code a nightmare. Threading, which I advised as the minimal helping API, is heavy - two threads per client is unacceptable for high-throughput / high-availability servers.

People have been saying good stuff about boost::asio, it's modern, it's c++; if I were to start a new server project, I'd seriously consider learning it.
Reply
Thanks given by:
#8
(10-01-2019, 06:15 AM)Alpha Mode Wrote:
(10-01-2019, 05:19 AM)xoft Wrote: As for the API / library - networking IS HARD, there's no shame in using a library that already shields you from the nasty low-level problems you'd encounter otherwise. For example, the select() API you might be tempted to use works alright on Windows, but is hopelessly broken on Linux / Unix; the function signatures are the same on both platforms, but each platform has its quirks that make writing multiplatform code a nightmare. Threading, which I advised as the minimal helping API, is heavy - two threads per client is unacceptable for high-throughput / high-availability servers.

People have been saying good stuff about boost::asio, it's modern, it's c++; if I were to start a new server project, I'd seriously consider learning it.
I mean I am thinking about using a API at this point but a also want to do the low level work to learn more

The last thing I want to know is how does the apend function work
and I also heard people saying you have to send the PacketID and others for sending packets
I am just trying to ping the server list for the client to show the MOTD and online players

Also my Discord: Alpha Mode#0001
Reply
Thanks given by:
#9
Sorry for the late reply, only now I noticed your post had another question in it.

The Buffer class can be pretty much substituted by a std::string - it has the append and erase operations, you just need to add a "read position" and possibly some helper functions to read individual data types. So something like this:
class Buffer
{
public:

  Buffer():
    mReadPos(0)
  {
  }

  void restartRead()
  {
    mReadPos = 0;
  }

  void append(const char * aData, size_t aSize)
  {
    mData.append(aData, aSize);
  }

  void erase(size_t aNumBytes)
  {
    mData.erase(0, aNumBytes);
    if (mReadPos > aNumBytes)
    {
      mReadPos -= aNumBytes;
    }
    else
    {
      mReadPos = 0;
    }
  }

  UInt8 readByte()
  {
    if (mReadPos + 1 >= mData.size())
    {
      // TODO: Not enough bytes in the buffer, throw exception?
    }
    return static_cast<UInt8>(mData[mReadPos++]);
  }

  UInt16 readShort()
  {
    if (mReadPos + 2 >= mData.size()
    {
      // TODO: Not enough bytes in the buffer, throw exception?
    }
    auto oldReadPos = mReadPos;
    mReadPos += 2;
    return static_cast<UInt16>((mData[oldReadPos] << 8) | mData[oldReadPos + 1];
  }
  ...

private:

  /** The bytes in the buffer, including those already read for the current packet. */
  std::string mData;

  /** The position in mData where the next Read operation will start. */
  size_t mReadPos;
}
Reply
Thanks given by:




Users browsing this thread: 1 Guest(s)