So, why networking? Well the answer is simple, with the increase in bandwidth because of ever-growing fiber-optic installations games gain a unique ( since '00 ) ability; ingame-interaction with other players.
No matter how advanced AI's become, the difference will always be noticable. Single-player games don't have the human touch that multiplayer ones do.
Note to self: Microsoft dropped Xbox one's - check in every 24h or ban - policy. So yay ~
If you have read Beej's Guide to Network Programming great! Now forget everything you've learned, my guide my rules.
Code: Select all
/**************************************************************\
/* Table of contents *\
/**************************************************************\
| |
| - Sugar cones |
| - Fresh eggs |
| - One litre of milk |
| - 5 pounds of cinnamon |
| - Decaf |
| - That hawaian t-shirt I saw last time |
| |
| |
| |
| |
| Just kidding :P |
| |
| |
| |
| >>> 1. UDP vs TCP vs S.Korea |
| -- 1.1 When to use TCP (A) |
| -- 1.2 When to use UDP (A) |
| -- 1.3 When to use both (A) |
| -- 1.4 What did Koreans taught us? (A) |
| |
| |
| >>> 2. Fixating over design patterns |
| -- 2.1 Why should you care? (A) |
| -- 2.2 Why do others ignore design patterns (A) |
| -- 2.3 Proactor vs Reactor (A) |
| |
| |
| >>> 3. A story of forbidden love and The Great Attack |
| -- 3.1 Episode 3: Settling old scores |
| -- 3.2 Episode 2: NBIO and the Empire strike back! |
| -- 3.3 Episode 1: A new hope. |
| |
| |
| >>> 4. Introduction to `polling` |
| -- 4.1 select, epoll, kqueue, /dev/poll (A) |
| -- 4.2 So, there are only 2 ways to poll per OS, right? |
| -- 4.3 What about non-blocking sockets? |
| |
| |
| >>> 5. Staying out of sync |
| -- 5.1 Introduction to IOCP and friends (W) |
| -- 5.2 Asynchronous I/O on unix (U) |
| -- 5.3 Asynchronous I/O on bsd (B) |
| |
| |
| >>> 6. Sending and receiving data |
| -- 6.1 How should I do that? (A) |
| -- 6.2 Managing your data (A) |
| |
| |
| >>> 7. When there's something strange in the neighbourhood |
| Who you gonna call? |
| -- 7.1 fcntl, ioctl (A) |
| -- 7.2 inet_xtoy and portability ghosts (A) |
| |
| |
Disclaimer: I'm not against external libraries in general, but I'm against most (libev,libuv,libiv,libov ... asio and poco). Only because they force certain things down your throat.
- Boost::ASIO
>> Forces you to install most of boost, which is a waste of time and space. The standalone version of ASIO isn't standalone, it still requires boost so no thanks. - POCO,libev,libuv
>> They use `select` on windows. 'nuff said. - libev,libuv
>> C syntax - 0mq
>> Trying to abstract everything away is not always a good feature. - ACE Framework
>> Severely outdated code.
1. UDP vs TCP vs S.Korea
- 1.1 When to use TCP
TCP or Transmission Control Protocol is itself, you guessed it, a "Protocol" or "A way of communicating".
The word "control" isn't there by accident. TCP is working to "control" the flow of your data. Let's see how it works.
The client A sends a connection message to client B called (SYNchronize). Client B then has to respond that he got the
message and asks client A to be friends (SYN-ACKnowledgement). Then client A accepts the friend invitation sended by B.
Here's a more compact version.
Client A = 1
Client B = 2
Then for each message called (DAT) the other end must respond with the "I got your message" message (ACK)1 ----(SYN)----> 2 // Hello!
1 <--(SYN-ACK)-- 2 // Hi there, lets be friends!
1 ----(ACK)----> 2 // Okay!
The same rules that apply to SYN, apply to FINish/FINalize.1 ----(DAT)---> 2 // Wanna go to the movies?
1 <---(ACK)---- 2 // I've got your message!
1 <---(DAT)---- 2 // I'd love to join you
1 ----(ACK)---> 2 // I've got your message! Awesome!
Note that if one end doesn't respond with (ACK) the message will be sent again! Also notice that for each
packet we send, we're essentially waiting in case we have to send another, or another. The truth is that
we're also waiting for other stuff and all of these combined, result to poor performance if you're firing alot
of packets per second. Fortunately these problems can be circumvented but I'll cover that later.
Note: With TCP, packets are guaranteed to arrive in the order they were sent (LIFO)
- Thanks to CuteAlien for reminding me; i took it for granted that people actually read beej's guide
So, when should I use TCP? Well ,normally , to time-critical applications such as
- Chatting
- Transactions
- Fights that are based on spell-chains as in WoW.
- RTS (RealTimeStrategy) games
In other words, if you want control, you must be willing to sacrifice performance. Though later on i'll teach you
how to minimize that performance loss ~
- 1.2 When to use UDP
do they mean by that? Shouldn't I care about the control flow?
The answer, my lads, is yes. You must care about the control flow. Because the attenuation of Player's 1 line
might be above average and the "one or two" dropped packets might become 100 or 200.
But that doesn't mean we should copy TCP right? Of course .
Let's see how UDP works.
Client A = 1
Client B = 2
What is TTL? TTL my lads is TimeToLive. It's a value specified by the OS (most of the times) and it tells how long one should keep1 (Sending a message) ------(MSG)-----> 2 (Reading a book) // Message lost because of TTL
// 2 hours later Client A didn't receive an answer and is pissed.
1 (Typing angrily) ------(MSG)-----> 2 (Waiting for a new message) // 1 says *&@$%#!$@#$ing #(*$%^*$@ ... insert Perl code here
1 (Waiting for a new message) <-----(MSG)------ 2 (Sending a message) // Why're you so mad bro?
an unread packet before it's discarded.
So, applications like FPS shooters should use UDP! Yeah, kindof, keep reading!
- 1.3 When to use both
You could also use UDP for p/r/world updates and TCP for a custom "control" protocol that will be used to verify critical UDP packets.
>> Note that the last one doesn't make sense to me, none at all but I threw it in just because I've seen it implemented. <<
- 1.4 What did Koreans taught us
Well they didn't taught you anything (or not) explicitely but they taught me how the control flow works and a bunch of gotchas.
And as I pass the knowledge to you, they essentially taught us both something .
To be more specific I learned how to manage my structs from a Korean game that had it's server code leaked. ( I'm Mr.Illegal and that's how I roll )
Plus in the server code there were comments noting 2 things.
( That was back in 2004 there were was almost no MMORPG code to learn from )
So what were those comments about? It was a barely translateable message from the developers to system administrators telling them 2 things.
- Since they were using UDP, the lines attenuation should be kept as low as possible. Below 15dB.
- They should watch the servers SNR.
The line attenuation or noise margin is a value that tells us how much signal is lost because of background noise!
Here's a table. (Line Attenuation)
Code: Select all
/*
+====================================+
| dB | Quality |
+==========+=========================+
| 0 - 15 | Excellent |
+----------+-------------------- ----+
| 15 - 20 | Very Good |
+----------+-------------------------+
| 20 - 30 | Works |
+----------+-------------------------+
| 40 - inf | Terrible for a server |
+----------+-------------------------+
*/
SNR or SIGNAL to NOISE ratio or Noise margin is this (SIGNAL/NOISE). It's also measured in deciBel and the optimal values are.
Code: Select all
/*
+==============================================+
| dB | Quality |
+==========+===================================+
| 0 - 6 | High speeds connection problems |
+----------+-----------------------------------+
| 6 - 12 | Medium speeds fine connection |
+----------+-----------------------------------+
| 12 - inf | Slow speeds fine connection |
+----------+-----------------------------------+
*/
2. Fixating over design patterns
- 2.1 Why should you care?
than anti-smoking habits or other stuff like that.
The main arguement was "I don't care about design patterns, I just send / receive data." (1)
The first time I saw that arguement, I thought the guy had gone nuts. But then I saw it again and again, it's like an ever growing conspiracy ~
Alright, but why should I care? Let's see, most people like bullets, i'll give 'em bullets.
- Your whole server WILL be based around the design pattern you implement. After you choose, there's no going back!
- 2.2 Why do others ignore design patterns?
Chosing a pattern is a step well ignored since the following concept has been stuck to peoples minds.
- The server is a reactor (waits for client messages)
- The client is a proactor (works in sync with the server)
Of course both can use whatever pattern you want. Anyway, let's dive into the whole acting scheme abit further ...
- 2.3 Proactor vs Reactor
For those who're not good with prefixes, Proacting is a mechanism that acts before something; in this case, an event such as ( read, write )
And reacting is the opposite. It's a mechanism that acts after something!
Let's visualize them
A proactor
A reactorDo stuff
Read data
Do stuff
Read data
...
For the sharp-eyed, the difference between the two is that their out of "phase".Read data
Do stuff
Read data
Do stuff
...
In fact the phase difference is T/2. (Where T is the time required for a pair to complete their actions).
Alright, so they're essentially the same thing right? Yeah, pretty much!
So, when should I use them?
Proactor examples
- Add Nodes for each visible star in our sky and calculate the closest quad to the moon using gravity centers; for whatever reason
- A client noted that one node isn't a star but a plane
- Remove that node, recalculate, keep listening for client feedback
- Wait for client action
- The client issued a "hit" command; damage the bosses health
- Wait for client action
So, what did part 2 taught us? Pick a pattern; stick with it.
Did I mention that you shouldn't pick the Reactor pattern on Windows. Oh wait; just don't pick Windows at all!
3. A story of forbidden love and The Great Attack
- 3.1 Episode 3: Settling old scores
In order to do that, we'll need to see how they both work. Even though I already explained what AIO stands for, I'll try once more
just to make sure you've understood everything correctly.
AIO is a notification on completion model and that means:
Hence the "notification on completion".- Issue a command.
- Wait 5 years / until the command has finished executing. The OS will inform you when that happens with a "notification"
Now let's see how Non-Blocking I/O works
Which means, do not pause for whatever reason.- Issue a command.
- If it can execute without stalling so be it, if not continue anyway.
Alright, now we can move on to the next part!
- 3.2 Episode 2: NBIO and the Empire strike back!
Code: Select all
// My 2 penny worth non-nuclear reactor
Socket client = accept(...); // Blocking socket
for(;;)
{
Data d = Recv(client);
// create c based on d
Send(client,c);
}
The answer is `nothing special`, it would work as expected and it would be faster than the NBIO equivalent of this code.
What?? NBIO < SIO?? When talking about SIO, net gurus are like: Don't use SIO, SIO's bad, it slows you down, gives you cancer, burns your treehouse, eats your food, uses your teethbrush.
Wow, slow down fellow! Well, It's true that SIO is introducing alot of delays that are transparent to the code and hurt performance but, remember this:
In a 1 thread per connection scenario, no matter how appealing NBIO sounds, SIO will always outperform it.
Why?
1 word: Overhead.
In any other case (except AIO but I'll cover that later) go for NBIO.
- 3.3 Episode 1: A new hope.
What if we could do all of this stuff in the background and instead of having custom functions, move the buffer and try again?
Fortunately for us, this is where Asynchronous I/O comes into play.
A well implemented AIO server would look like this:
Pseudocode; demonstration of AIO mechanism.
Code: Select all
enum{ RECV, SEND };
void AcceptThread()
{
for(;;)
{
Socket client = accept(...);
Assign(client);
AsyncRead(client); // Issue the first Read
}
}
void WorkerThread()
{
Socket c;
int io_type;
char* data;
size_t bytesTransferred, bytesLeft, bytesDone;
for(;;)
{
GetQueuedCompletionStatus(c,io_type,data,bytesTransferred,bytesLeft,bytesDone);
switch(io_type)
{
case SEND:
{
bytesDone += BytesTransferred;
if( bytesLeft > bytesDone ) // move the buffer, issue AsyncSend again
continue;
}
case RECV:
{
ProcessData(data,bytesTransferred); // Server logic goes here
AsyncRead(c);
continue;
}
default: continue;
}
}
}
So, let's say you spawn 10 workers; everytime an Async operation completes one of them will catch that here: `GetQueuedCompletionStatus`.
The rest are pretty self explanatory.
Also, do NOT mix NBIO with AIO! Think about it, it doesn't make any sense! Not that you'd see any difference, Async calls return immediately by default!
Read more regarding AIO in section 5.
Some parts are not in the table of contents yet.
Part 1: Done!
Part 2: Done!
Part 3: Under revision!
Part 4: Halfway there!
Part 5:
Part 6:
Part 7:
Part 8:
Part 9:
Parts 1,2,3 are mostly theory. 4,5,6 will be code-oriented. 7,8,9 theory + code!