Jump to content

Cross platform C++ networking (similar to sockets)

I need a client-server model networking library (for a game) using TCP in C++ and it needs to be cross platform (i.e. windows and Linux). I've found many things (the closest I've found is ZeroMQ but it doesn't work for the client-server model I need) but none of them seem suitable. I also need the library to not be huge (i.e. not boost or SDL or Qt or anything else that has way more stuff than just networking). 

 

Thanks in advance.

Link to comment
Share on other sites

Link to post
Share on other sites

i think so

if you want a reply you need to quote or @me 

Link to comment
Share on other sites

Link to post
Share on other sites

15 minutes ago, Hi P said:

 *speaker* Will this be all?

I guess so, not entirely sure what u mean though.

 

Edit: aight I realised. I thought it would be fairly simple to have a cross platform socket library but maybe I just don't know enough. I'm not the most experienced with cpp in the world.

Edited by James Kitching
Link to comment
Share on other sites

Link to post
Share on other sites

1 hour ago, James Kitching said:

I guess so, not entirely sure what u mean though.

 

Edit: aight I realised. I thought it would be fairly simple to have a cross platform socket library but maybe I just don't know enough. I'm not the most experienced with cpp in the world.

Hahaha no dude, I was just messing with you since your post reminded me of a drive thru order :D I apologize

 

Here's a big discord server for programming: https://discord.gg/programming

 

There's a lot of active people over there willing to help in anything related to programming, you might find your answer in a couple minutes or hours :)

 

Link to comment
Share on other sites

Link to post
Share on other sites

20 minutes ago, Hi P said:

Hahaha no dude, I was just messing with you since your post reminded me of a drive thru order :D I apologize

 

Here's a big discord server for programming: https://discord.gg/programming

 

There's a lot of active people over there willing to help in anything related to programming, you might find your answer in a couple minutes or hours :)

 

Oh lol fair enough. I guess I'm quick to assume. Anyway, thanks for that I'll definitely go there and ask.

Link to comment
Share on other sites

Link to post
Share on other sites

1. Thou shalt not use a library.

2. Thou shalt write thine own API which meets only thine own needs and nothing more.

3. The design of that API will be simple: (this is pseudo code, but close pseudo code)

struct NetworkFrame {
  unsigned int seq_id;
  unsigned char opcode;
  unsigned int data_len;
  unsigned char* data;
#ifdef WINDOWS
  //whatever windows uses for a UDP socket handle.
#else
  int fd; //where are we writing (linux file descriptor for the socket)
#endif
  //you'll probably want 1-2 other fields. This struct is not for looks. 
  // This is literally what you'll be sending across the wire 
  // (not a straight 'write struct to socket' operation).
}

int setupConnection(int ip, int port);
int removeConnection(int fd);
int startListening();
int sendToClient(struct NetworkFrame *nf);
int readFromClient(struct NetworkFrame *nf);
//other OS specific functions here.

#ifdef WINDOWS
#include <windows.h>

int setupConnection(int ip, int port)
{
  //Use UDP for your game. No, seriously, use UDP. Don't do what every noob does and try doing it with TCP.
  //Windows specific code to setup connection here
}
//other functions here

#else
#include <sys/types.h> // for socket()
#include <sys/socket.h> // for socket()

int setupConnection(int ip, int port)
{
  //Use UDP for your game. No, seriously, use UDP. Don't do what every noob does and try doing it with TCP.
  //linux specific code to setup connection here
}
//other functions here

#endif

4. The reason that thou shalt do it this way is to learn, and because it's what the APIs you use do for you, except then you have to dig through 10,000 lines of code instead of 1000 you wrote yourself.

5. The reason you use UDP is because you do NOT want your game to block waiting for a packet to arrive. Things WILL arrive out of order, so that's why the NetworkFrame includes a sequence ID field. Every time you send a packet you increment that value by 1. If the client receives sequence ID 99 and then sequence ID 97 comes in after it, it just disregards 97 and expects any data it had to be re-transmitted.

6. Because you are using UDP, you can't let the TCP stack create your packets for you. You need to query the OS for the current MTU and then create the UDP packets yourself. Why? Because if you wanted to write 18000kB of data to the host, and relied on the TCP stack to create the UDP frames for you, then the OS wouldn't know to put your header info (opcode, data_len, etc) at the start of each message (2 packets with MTU 9000, 13 packets with MTU 1500). That would mean that when you read the UDP datagrams on the far side, you might reassemble the data from multiple different frames. Therefore, you have to include your network header in every.single.packet.

 

More on #6: Imagine you have two messages transmitted by the server like this:

 

m1-1, m1-2, m1-3, m2-1, m2-2, m2-3

 

But the host receives them in this order:

 

m1-1,m2-3, m1-3,m1-2, m2-2, m2-1

 

Then your host (if the header was only in the first packet) would re-assemble packet one as:

 

m1-2, m2-3, m1-3

 

It would think m2-3 was part of message 1! It wouldn't have a way to know that was wrong. As for message 2, its up to you whether you do (very) short term buffering to re-assemble slightly out of order data or just drop anything that is out of order. If you use the recommended header I include in my pseudo code, then you'll have the ability to know (if you also add a msgid field) that m2-3 should be copied to the *end* of the data buffer for message 2, and you could go ahead and reassemble m2 without issue.

 

msgid: Say you use a 1 second message timeout (you'll use something smaller, but  meh). If you send no more than 30,000 packets/second to a single host, then you can get away with 2 bytes. Use 3 bytes until you get get up to around 8 million packets/second/host. The reason being, you don't want msgid to wrap around before the message timeout has a chance to pass, that way you don't end up with two different messages that both have the same ID breaking your game by giving it (effectively) corrupt data.

 

I got busy and couldn't finish this, but I had this much written, so this is what you get.

Edited by asquirrel
I forgot some really useful stuff
Link to comment
Share on other sites

Link to post
Share on other sites

On 5/18/2020 at 5:12 PM, asquirrel said:

1. Thou shalt not use a library.

2. Thou shalt write thine own API which meets only thine own needs and nothing more.

3. The design of that API will be simple: (this is pseudo code, but close pseudo code)


struct NetworkFrame {
  unsigned int seq_id;
  unsigned char opcode;
  unsigned int data_len;
  unsigned char* data;
#ifdef WINDOWS
  //whatever windows uses for a UDP socket handle.
#else
  int fd; //where are we writing (linux file descriptor for the socket)
#endif
  //you'll probably want 1-2 other fields. This struct is not for looks. 
  // This is literally what you'll be sending across the wire 
  // (not a straight 'write struct to socket' operation).
}

int setupConnection(int ip, int port);
int removeConnection(int fd);
int startListening();
int sendToClient(struct NetworkFrame *nf);
int readFromClient(struct NetworkFrame *nf);
//other OS specific functions here.

#ifdef WINDOWS
#include <windows.h>

int setupConnection(int ip, int port)
{
  //Use UDP for your game. No, seriously, use UDP. Don't do what every noob does and try doing it with TCP.
  //Windows specific code to setup connection here
}
//other functions here

#else
#include <sys/types.h> // for socket()
#include <sys/socket.h> // for socket()

int setupConnection(int ip, int port)
{
  //Use UDP for your game. No, seriously, use UDP. Don't do what every noob does and try doing it with TCP.
  //linux specific code to setup connection here
}
//other functions here

#endif

4. The reason that thou shalt do it this way is to learn, and because it's what the APIs you use do for you, except then you have to dig through 10,000 lines of code instead of 1000 you wrote yourself.

5. The reason you use UDP is because you do NOT want your game to block waiting for a packet to arrive. Things WILL arrive out of order, so that's why the NetworkFrame includes a sequence ID field. Every time you send a packet you increment that value by 1. If the client receives sequence ID 99 and then sequence ID 97 comes in after it, it just disregards 97 and expects any data it had to be re-transmitted.

6. Because you are using UDP, you can't let the TCP stack create your packets for you. You need to query the OS for the current MTU and then create the UDP packets yourself. Why? Because if you wanted to write 18000kB of data to the host, and relied on the TCP stack to create the UDP frames for you, then the OS wouldn't know to put your header info (opcode, data_len, etc) at the start of each message (2 packets with MTU 9000, 13 packets with MTU 1500). That would mean that when you read the UDP datagrams on the far side, you might reassemble the data from multiple different frames. Therefore, you have to include your network header in every.single.packet.

 

More on #6: Imagine you have two messages transmitted by the server like this:

 

m1-1, m1-2, m1-3, m2-1, m2-2, m2-3

 

But the host receives them in this order:

 

m1-1,m2-3, m1-3,m1-2, m2-2, m2-1

 

Then your host (if the header was only in the first packet) would re-assemble packet one as:

 

m1-2, m2-3, m1-3

 

It would think m2-3 was part of message 1! It wouldn't have a way to know that was wrong. As for message 2, its up to you whether you do (very) short term buffering to re-assemble slightly out of order data or just drop anything that is out of order. If you use the recommended header I include in my pseudo code, then you'll have the ability to know (if you also add a msgid field) that m2-3 should be copied to the *end* of the data buffer for message 2, and you could go ahead and reassemble m2 without issue.

 

msgid: Say you use a 1 second message timeout (you'll use something smaller, but  meh). If you send no more than 30,000 packets/second to a single host, then you can get away with 2 bytes. Use 3 bytes until you get get up to around 8 million packets/second/host. The reason being, you don't want msgid to wrap around before the message timeout has a chance to pass, that way you don't end up with two different messages that both have the same ID breaking your game by giving it (effectively) corrupt data.

 

I got busy and couldn't finish this, but I had this much written, so this is what you get.

hahahahahaahahahhahahahhahahahahaahahhahahahhaa

if you want a reply you need to quote or @me 

Link to comment
Share on other sites

Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×