🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

How to handle socket buffer quickly filling in during fast write and slow read

Started by
8 comments, last by hplus0603 1 year, 10 months ago

say for example if I have a server and many clients connected to this server,

clients will publish message in 1 second or less to the server, and there are times that the server reads the data written to it slower than how the clients publish it, so there might be possibility that the servers socket buffer will ran out or something and will cause unexpected behaviour, sometimes sockets disconnects or something.

How to handle such situations?

Advertisement

cebugdev2 said:

How to handle such situations?

I'd say: “Welcome to the exciting world of networking programming!”

Answering those questions is part of what network programmers do every day at work. People say “we need to communicate X”, and we find a way to make it happen. For each thing we're given, we figure out: What do you transmit, and what doesn't need to be sent? How do you transmit it efficiently? What do you do when there are errors? Every object, every communication pattern, every need presents itself with potentially different answers. If you're lucky you've got a good framework that you can use to implement your decisions around each question easily.

If the server falls behind the clients, the game will likely fail.

The right way to handle this, is to make it so that the server drains all the sockets for each iteration. If the server can't drain the socket (there's always data there when it reads) or if running one game step takes longer than one game step's worth of time, engage “server overload” and tell all the clients about it.

What your server does at “server overload” varies by whatever your design is – from just stopping the game, to engaging slow motion, to kicking some players, are all reasonable choices in various situations.

enum Bool { True, False, FileNotFound };

@cebugdev2 What language are you using to write the web server? If you able to use C/C++ in Linux then your best option will be to build your server based on epoll. That is the best performing solution out right now. I've also had good results with liburing, especially with high performance file I/O. Those are going to easily handle many thousands of requests per second, but they are low level and you'll have to build a web server on top of them.

If you need a ready made framework to get up and running quickly, then you have frameworks like Drogon. I think that is the easiest one to get going with in C++ and it's one of the best performing web frameworks out right now (I've also had success with this one). It also has some built-in database connectors.

Also you need to think about using WebSockets. There are frameworks like Oat++ that can handle tons of WebSocket connections (haven't used this one, but it's on my list).

Those are just frameworks though. The code you put inside the frameworks will determine the real performance, so obviously you need to do benchmarks. I personally use wrk2.

Using more efficient I/O mechanisms might move the bar for where a server finds itself “falling behind,” but it doesn't change the fact that you have to deal with “falling behind” when it happens.

Also, almost all game servers will be bound on CPU updates simulating the game, or perhaps available bandwidth sending data about all players to all players, rather than the raw overhead of I/O. uring and kpoll and IOCP and friends are great for high-throughput LAN systems like file servers, and can reduce load on other systems where network I/O dominates (e g, saturating a 100 Gbps Ethernet interface,) but that's not generally where the bottleneck is for game servers. (Game DOWNLOAD servers is another story, though – but there, you can generally use a server already written, and serve over HTTP, and let the server do the heavy I/O.)

enum Bool { True, False, FileNotFound };

@hplus0603 I understand that, but OP is asking about socket handling.

Hi guys, thank you for all comments
Im using both Posix and win sockets in our codes and its TCP.

Is there a way to see check if the socket is full before writing or check something or somewhere about socket buffer to prevent or wait writing for more?
can non-blocking sockets work?
or maybe there is a way to drain everything from a socket read in one shot, like can we check what is total available size to read in a buffer or something?

cebugdev2 said:
Is there a way to see check if the socket is full before writing or check something or somewhere about socket buffer to prevent or wait writing for more?

Unix has select(2), https://www.man7.org/linux/man-pages/man2/select.2.html​ that can give you notifications when it is useful to access a socket. It has been many years, but afaik this just gives a boolean flag “it has use”, so combine with non-blocking IO if you do IO multi-plexing in a single thread.

For one thread for a single socket, there is of course no need to for these things, just block and the OS will re-schedule the CPU time to something else.

EDIT: There is also poll(2), epoll(2), and devpoll at Solaris, and kqueue at BSDs.

@cebugdev2 You should carefully read the manual page for recv() and recvfrom()

Specifically, these functions will read whatever is available from the socket, up to the given size, and immediately return if there is any data to return. This means that they may return less than you asked for, or up to the amount you asked for.

The function of the select() call is to return “readable” if there is at least one byte to read, meaning that recv() or recvfrom() will not block.

From this, you can draw the conclusion, that if you make the buffer passed into recv() as big as the kernel buffer for the socket (which you can set with SO_RCVBUFSIZ) then you are guaranteed to drain whatever is in the buffer.

This, in turn, means that there are two “plain” ways to poll a number of sockets:

  1. Set all the sockets to non-blocking mode, and rip through the list of sockets you have open, and call recv() for each of them, once per main loop. The drawback here is that you won't have any good way to block until there is more data available, and you will also have to call into the kernel to ask for data even for sockets that don't have data. For “chat” servers and “web” servers and such, the traditional kind of server, this ends up being inefficient, but for game servers that need to simulate 60 times a second and where all clients will typically send data most of the time, it might be a fine way to do it.
  2. Provide all sockets into the select() call, and then poll the sockets that select claims have data. This lets you wait until there is data, with a timeout, and it tells you which sockets to read from, so you don't waste CPU cycles entering the kernel to try to receive from sockets that don't have any data. The draw-back is that the select() system interface is somewhat annoying to work with (it mutates its socket lists/bitmasks) and on Windows, there's an upper limit of 64 sockets that can be polled with select at once. It also doesn't scale to tens of thousands of sockets.

The third way, is to use one of many possible asynchronous mechanisms. Linux got this wrong several times (various flavors of poll) before they managed to get it right with uring. Windows did better with their I/O completion port system, which is still pretty good, but they also have IoRing these days. These APIs matter when your sockets count in the tens of thousands, or when you're trying to saturate a top-of-the-line network interface like 40 or 100 Gbps Ethernet or Fiber Channel or somesuch.

At this point, it's useful to understand the distinction between “user mode” and “kernel mode,” and the difference between “a library call” and “a system call,” and the various costs involved in those mechanisms – the various trade-offs won't make a lot of sense without a solid understanding of these systems programming fundamentals. If you're not there yet, I recommend just going with option 1 or 2, or perhaps using an existing wrapper library (like ASIO) which will be plenty good enough for most indie game systems.

enum Bool { True, False, FileNotFound };

This topic is closed to new replies.

Advertisement