2

I'm developing a multithreaded game server (C/TCP). I need to send a list of 50-100 available games to a console client.

Option A: Send 50-100 separate messages, using send() for every single formatted line (e.g., ID: 1 - Game by Alice\n). Very simple client (just printf()).

Option B: Build a single compact buffer (e.g., Alice:1,Bob:2,...) and send it with a single send(). Maximize Server I/O efficiency.

In a Multithreaded Server architecture, is I/O efficiency (Option B), which reduces costly System Calls, the better practice, even if it requires parsing on the Client?

1
  • I don't think you can avoid parsing on the client. Can you elaborate on what difficulty you see there? Commented Nov 18 at 21:36

4 Answers 4

1

For every message sent, it's not just the client-app / server-app which has to parse things, but also everything in between, inter alia the complete network stack on both systems.

Add to that the necessary per-message overhead of message encapsulation (TCP+IP+Ethernet+whatever), message processing (routing, packing, unpacking), transfer overhead (gaps, getting a slot).

All in all, putting it into a single message on the server and parsing that on the client is a negligible amount of work to minimize much more work.

As an aside, your network stack probably buffers until a timeout or a full package is assembled, so the cost stays fully on the senders side.

1

When designing any API it is often a good idea to allow some kind of batching to reduce per item overhead. This applies in all many contexts, networking, graphics, databases, whatever. If you expect the number of items to be large you may also want to support something like pagination or streaming. The downside is that it can make the API more complicated. So you need to consider if the benefits are worth the cost.

I'm not familiar with C/TCP, but TCP is a stream based protocol. If you want to send multiple messages over the same connection you need to use some kind of message framing. Higher level protocols, like http, gRPC, AMQP etc takes care of this for you as well as many of the difficult details of TCP. So I would typically recommend using a library for some suitable existing protocol rather than invent your own.

I would also recommend using existing libraries for serialization/deserialization. In most situations these will provide better value and security for your effort than anything you would build yourself. This should make it almost as easy to send/receive multiple values as a single one. Note that some protocols, like gRPC, includes serialization.

While sending a list of games does not sound very performance critical, some types of games can be very latency sensitive. If that is the case a custom protocol optimized for your specific case may be warranted. But then you should do some benchmarking to make sure you can measure any differences accurately.

If you are worried about parsing performance, keep in mind that CPUs are many order of magnitude faster than the network. Deserializing 100 short strings takes about 4us for me using protobuf, while the round trip latency within a single data center is about 500us.

1
  • I took C/TCP to mean a TCP implementation written in C Commented Nov 19 at 14:22
0

Let's back up a step. At a high level, we have multiple ways to accomplish "same thing", and we've not yet written down any section criteria to rank them. We should take a moment to do that.

First and foremost, for a good UX we want to minimize "time to display first game", or perhaps first few games that are visible without scrolling. That's how the user knows the server is responsive and is doing what was requested.

Second goal is to minimize "time to get all game titles", so the UI widget is fully interactive and scrollable, showing its complete scroll bar size.

A minor goal could be to minimize battery energy consumed on the client to deserialize / parse the game titles, but that seems such a minor consideration in this Use Case that we can simply ignore it.

So I recommend sending a "small" query for maybe six titles or just one, and then one "big" query that fetches the rest. If development time for two queries is longer than you'd like, then sending a single big query would be fine. Notice that this week's "hundred titles" could become a thousand titles next month, more than a user would usually want to interact with, so you may need to paginate perhaps a hundred at a time anyway. Or put a "more" button at the bottom so we get 100, then 200, then 300 titles to scroll through.


Some or all titles may have "expensive" metadata to display, such as a screen shot or animation. Feel free to let the browser lazily load those assets in the usual way, so displaying a dozen titles may lead to a dozen GET requests for image assets which eventually are displayed.

I assume we're doing https over HTTP/2 transport where the connection is kept alive for a few seconds of idle, just in case. We suffered a couple of RTT round trip times at the beginning, for TCP 3-way handshake and for crypto setup. But thereafter each GET will be pipelined within a multiplexed channel, and we can take good advantage of the available bandwidth.

One way to think about the tradeoffs is in terms of overhead, bearing in mind that often a client will have more bandwidth in the download direction than it has for uploads. Depending on the cookies that you've set, each GET request might be about a kilobyte long. Sending a few dozen titles, or sending an image, will be a few KiB, more than the size of a GET request, so that's a sensible ratio. But replying with just a couple dozen bytes for a single title turns the ratio on its head, making it seem like much work for little gain. And recall that we're going to have a separate log entry for each request.

1
  • An alternative to a small query possibly followed by a big query is to have only one query that tells, immediately, the total number of elements and starts sending them to the client, checking regularly if the connection wasn't aborted. It belongs then to the client to decide when to stop. Commented Nov 19 at 11:48
0

Short answer: Send everything in one call for the best throughput, but your total amount of data seems to be so low that it doesn't matter much.

I agree to the other answers that the processing time of messages is neglectable compared to the time required to send and receive messages.
So whether an application works multithreaded or not does not matter, the bottleneck is the message transport.

At that level it also doesn't matter what kind of data you have, all that matters is the total amount of data to send.
What also matters is the overhead to send messages:

  • Does every message require a handshake to establish a connection or is an already established connection used?
  • On a low level also the size of package headers could matter. Every sent package comes with a protocol specific header information. Usually you want to have have a bigger portion of user data than required for the header.

So for the best throughput it is preferrable to send fewer but bigger messages than a lot of small messages.

Your use case sounds like you have something below 5 KB of data, which in case of TCP/IP can be put into one or few TCP/IP packages.
So I recommend to send everything in one call (and let TCP/IP care how to put this into one or multiple packages).

It is not worth to think about how to split data into multiple messages unless you want to send many MB of total data.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.