(Almost-)Zero-Additional-Latency UDP-over-TCP

 
Author:  Follow: TwitterFacebook
Job Title:Sarcastic Architect
Hobbies:Thinking Aloud, Arguing with Managers, Annoying HRs,
Calling a Spade a Spade, Keeping Tongue in Cheek
 
 
UDP over TCP
#DDMoG, Vol. IV

[[This is Chapter 13(j) from “beta” Volume IV of the upcoming book "Development&Deployment of Multiplayer Online Games", which is currently being beta-tested. Beta-testing is intended to improve the quality of the book, and provides free e-copy of the "release" book to those who help with improving; for further details see "Book Beta Testing". All the content published during Beta Testing, is subject to change before the book is published.

To navigate through the "1st beta" of the book, you may want to use Development&Deployment of MOG: Table of Contents.]]

It is rather well-known that TCP latency kinda suxx – and we have discussed in details why it is the case, above (in particular, head-of-line blocking is a Big Enemy of the latency).

However, it is really important to understand what exactly is meant under “TCP latency is bad”. Actually, on closer examination we will see that at least two different things can be understood under this umbrella statement. The first one is that

We cannot use convenient TCP-like reliable-streaming API without incurring a latency penalty.

Let’s note, however, that this effect can be at least partially mitigated by using Reliable UDP (RUDP). For the time being, I won’t concentrate on RUDP (and on whatever-we-could-get-from-it) – and will just say that if we use reliable-and-ordered RUDP, it will still suffer from Head-of-Line blocking (that’s by definition) – and as a result, any gains from using reliable-and-ordered RUDP will be rather mild compared to TCP1 (and if it is some different flavor of RUDP – we’re not really using TCP-like reliable-streaming APIs anymore).

The second potential – and quite wrong at that – statement says that

<wrong>Whenever we don’t have UDP (for whatever reason) and are forced to use TCP – we’ll inevitably get a latency penalty.</wrong>

While the statement above can look obvious and what-I-am-about-to-say may sound as an obvious heresy – I am about to demonstrate

How to carry UDP-style packets over TCP without incurring (much of) additional latencies.

1 Sure, exponential back-off can be turned off in RUDP, and more aggressive RTO strategy can be used too – but this is still peanuts compared to what can be gained by using, say, state sync algorithms.

 

Why would we want it?

Why we would want such a thing – it is simple. Let’s consider the following scenario:

  • We write our app in UDP style (using anything we like – including state sync algorithm such as the one described in Vol. I’s chapter on Communications and in [Fiedler])
  • Then we try to run it on real-world Clients – and we realize that:
    • Wtf hare:we realize that for some of the Clients – UDP just doesn’t work because of some weird firewall between Client and ServerFor some of the Clients – UDP just doesn’t work because of some weird firewall between Client and Server (hotel and airport connections are particularly guilty of having such firewalls; some of mobile connections are reported to have this problem too). Overall, according to [QUIC] about 6 to 9% of all the Google users cannot reach Google servers via UDP.
    • For some of the Client-platforms-we-need – UDP is not supported at all.
      • In particular, as of now, browsers have this problem. NB: I am not sure whether the approach I am describing will work over WebSockets – it at least depends on the question “whether different WebSockets use the same TCP or different ones” (and we need different TCP connections for the algorithm below to work). While reportedly ‘force new connection’:true should do the trick – I have no idea how universal this solution is (and no idea whether there are some peculiarities of WebSockets which prevents the approach below from working). [[BTW – if you find out whether algorithm below can be made workable with WebSockets – please let me know.]]

Whether it is worth the trouble – depends on your needs, but IMO it is still nice to know that whenever you need it – such an option of tunneling UDP-like requests over TCP (and without incurring latency penalty) does exist.

 

The Algorithm

Actually, the idea is dead simple:

  • We have N TCP connections between Client and Server
    • For each of these TCP connections – there is a “packet en route” flag. This flag is set to false after TCP connection is established.
  • On the sending side, whenever an UDP packet comes in – we’re sending it over one of the TCP connections which does not have the packet-en-route flagAlso, we set the packet-en-route flag for the TCP connection where we sent the packet.
    • Of course, we still need to wrap the packet to send it over TCP (to denote the packet size and/or boundary)
  • On the receiving side – whenever we get an incoming UDP-over-TCP packet – we send an app-level acknowledgement (as small as 1 byte) back to the sender, over the same TCP connection where we got the incoming message.
  • On the sending side, when we get this app-level acknowledgement – we reset the packet-en-route flag

The description above is sufficient to understand how the algorithm works – though of course, to become practical, it will need other stuff (such as detecting connection being “hung” – for example, using app-level keep-alives or just having to wait for the app-level ack too long, creating new connections instead of “hung” ones, using the same N TCP connections for bidirectional connection, auto-detecting number N, and so on). Still, all the other stuff seems to be perfectly viable.

Now, let’s see why this thing will work (almost) about incurring additional latencies for TCP. The answer is simple –

That’s because we always send a packet only to those connections of which we’re sure that there are no outstanding packets.

It means that whenever we’re sending the packet, several properties are guaranteed to stand:

  • Hare thumb up:there is nothing to block us (so head-of-line blocking doesn’t apply)there is nothing to block us (so head-of-line blocking doesn’t apply).
  • there are no outstanding retransmits (i.e. everything which went to the other side – was already acknowledged and retransmitted). This means that exponential back-off doesn’t apply either. 2
  • there are no outstanding bytes. This has an important consequence:
    • As “slow start” and most3 congestion avoidance algorithms work in terms of “congestion window” – which in turn is expressed in terms of outstanding bytes – it seems that we won’t run into problems with slow-start/congestion-avoidance either (at least as long as our sends() are small enough to fit into MSS – and most of the time MSS is at least 1200-1400 bytes – once again making it a direct analogy to typical UDP packet limits).
  • At least in theory, even Nagle algorithm shouldn’t hurt us under these circumstances (though I’d still probably disable it just in case).

Bingo! We’ve ate our cake got our UDP-over-TCP – and have it got no additional latencies too.


2 Even if there is “residual” RTO (i.e. if RTO has been previously doubled per RFC2988 section 5.5) – the first packet will be sent right away anyway, and we don’t care much about subsequent retransmits
3 if not “all”

 

Calculating N

However, before we can claim it as a win, there is still a very important consideration here. In particular: this algorithm is realistically usable only if the number N is small enough (in other words, if we’d need 100 TCP connections per user – it will probably start causing problems).

Let’s make some back-of-the-envelope calculations. For example, for a game with 20 “network ticks” per second (which is quite typical), and for a Client with 100ms RTT (it is more than enough for intra-continent stuff) – then under ideal circumstances we’ll need only 0.1/(1/20)=2 TCP connections for the algorithm above to work. In fact – we’ll certainly need to add at least other 2 connections to account for packet loss (which will result in some connections having packet-en-route flag longer than usual). Still, 4 or 5 TCP connections seem as a reasonably good starting point for such 20 network-ticks/second, and 100 ms RTT.

Other scenarios and likely-to-be-reasonable values of N:

  • 20 network ticks/second, 200ms RTT (this is enough to have global RTT). Reasonable starting N for this case is probably around 6-7 (4 in ideal case, 2-3 reserve).
  • 60 network ticks/second, 100ms RTT. Reasonable starting N: 9-10 (6 for ideal case, 3-4 reserve).

Hare pointing out:As we can see, even in more stressful scenarios N is expected to be relatively smallAs we can see, even in more stressful scenarios N is expected to be relatively small. Now, let’s see where having multiple connections will hurt us:

  • more memory used on the Server-Side per player. This one can be partially mitigated by reducing the size of the TCP buffers. With the schema above – we’re not likely to need more than 4K / TCP connection, so to run 1’000 players/2S-2Userver (which is a kind of “industry standard” number for 2017), we’d need around 4K/TCP*10TCP/player*10000players/server = 40MBytes of RAM, which is pretty much nothing by today’s standards. Even if speaking about a “really communications-heavy” server with 10K players – it is still mere 400MBytes.
  • More TCP ports used. However – on the Server-Side we won’t see these additional ports (they will be still terminated by the same Server-Side TCP port), so I don’t expect it to be a real problem.

As we can see, analysis and the numbers above seem to indicate that UDP-over-TCP using the schema above, is usable (no warranties of any kind, batteries not included). Moreover, as discussed above – we do NOT intend to use this thing for all the players – but just for those platforms (and/or firewalled users) which don’t have UDP at the moment. As a result – the overall impact of using additional resources for multiple connections will be most likely scaled down even further.

Disclaimer and Conclusion

Beware of bugs in the above code; I have only proved it correct, not tried it.

— Donald Knuth —

Now, a necessary disclaimer:

Beware of problems with the algorithm above. I have only kinda-demonstrated its correctness, not tried it.

However, assuming that the analysis above stands – it opens the door to the following approach for writing games:

  • Write all your time-critical protocols using UDP; it includes both different flavours of RUDP and state sync algorithm
  • However, for those players who cannot use UDP for whatever reason – your code can use UDP-over-TCP as an (almost-)no-added-latencies backup.
    • An additional disclaimer: at least in case of firewalls, some of them will do weird things with your TCP stream (in particular, re-assembling it and forwarding the stream further only after re-assembling) – and the algorithm above will not be able to deal with them. Still, from my experience I feel that such firewalls are not that frequent (though as always, YMMV, and batteries are not included).

Phew, I think it is enough for today. And BTW, if you see any mistakes in the reasoning above (or have tried it in reality and got either positive or negative results) – please LMK.

[[To Be Continued…

Tired hare:This concludes beta Chapter 13(j) from the upcoming book “Development and Deployment of Multiplayer Online Games (from social games to MMOFPS, with social games in between)”.

Stay tuned for beta Chapter 22, where we’ll start discussing all the boring stuff about debugging, tracing, logging – and not-so-boring post-factum beta/production debugging]]

 

Don't like this post? Comment↯ below. You do?! Please share: ...on LinkedIn...on Reddit...on Twitter...on Facebook

Acknowledgement

Cartoons by Sergey GordeevIRL from Gordeev Animation Graphics, Prague.

Join our mailing list:

Comments

  1. David Turner says

    Seems a nice idea.

    What do you do if all your connections have the packet-en-route flag set? Do you open another connection? How about speculatively opening more connections when you start to run out, in order to ensure you have some spare ones? Or send it down an in-use connection and take the latency hit?

    • "No Bugs" Hare says

      > What do you do if all your connections have the packet-en-route flag set?

      This is a tough one: ideally, it shouldn’t normally happen.

      > Do you open another connection? How about speculatively opening more connections when you start to run out, in order to ensure you have some spare ones? Or send it down an in-use connection and take the latency hit?

      Sure, we need to have some spare ones (if we need 2 just to cover ideal case – I suggested to have another 2 in reserve). OTOH, opening new ones forever-and-ever is rarely a good idea (when all connections, including reserve, are being stuck – we’re probably dealing with a temporary “black hole”); I’d rather play around the “if we have stuck connection – try ‘opportunistic reconnect'” (described, for example, here: http://ithare.com/tcp-and-websockets-for-games/ ) – without increasing overall number of connections too much. Still, it is a field for further research :-).

      • Andrew Armstrong says

        Why not just discard the packet? UDP is potentially lossy, so dropping packets during periods of poor network performance shouldn’t impact the protocols built on top.

        • "No Bugs" Hare says

          > Why not just discard the packet?

          EDIT: at this point, I misunderstood the question – please see another reply below.

          • Jesper Nielsen says

            I think what he’s trying to say is, that if all of the connections are busy then just “drop” the packet before even sending it.

          • "No Bugs" Hare says

            Yes, this could happen to be a good option (as long as other mechanisms are present to detect and restore “hung” connections).

  2. David Turner says

    One other cute observation is that this way you can send ‘packets’ that are much larger than you would be able to over UDP. Of course they’d be fragmented, but even with fragmentation you can’t send a UDP datagram larger than 64k.

    • Jesper Nielsen says

      The maximum ‘packet’ size you would be able to send like this would be the size of your output buffer if I understand this correctly.

      • "No Bugs" Hare says

        Yes, but if fighting for latencies – I’d rather not try sending stuff which is larger than MSS (usually within 1.4K).

  3. Jesper Nielsen says

    >However – on the Server-Side we won’t see these additional ports (they will be still terminated by the same Server-Side TCP port), so I don’t expect it to be a real problem.

    I learned something new there for sure:) I wasn’t aware that the server port of an accepted connection didn’t change to a dynamic port.

    >An additional disclaimer: at least in case of firewalls, some of them will do weird things with your TCP stream (in particular, re-assembling it and forwarding the stream further only after re-assembling) – and the algorithm above will not be able to deal with them. Still, from my experience I feel that such firewalls are not that frequent (though as always, YMMV, and batteries are not included).

    I’m not sure I understand exactly what is meant by this? Could this be mitigated by listening on a number of server ports instead of only 1, and avoid using the same server port for more than one connection to the same client? I suppose if there are many clients behind the same NAT IP this will require too many server ports to be feasible?

    One thing that has me a little worried was that a nonblocking Send()/BeginSend() is not guaranteed to send all of the bytes. I suppose if there are no outstanding packets then the outgoing buffer must be empty and as long as your “package” is smaller than the buffer then you should be able to Send() and you could in fact even perform a blocking Send(), knowing that it wouldn’t block?

    • "No Bugs" Hare says

      > I wasn’t aware that the server port of an accepted connection didn’t change to a dynamic port.

      Well, I wouldn’t bet my life on it, but – last time I’ve look in Wireshark, it looked so :-).

      > I’m not sure I understand exactly what is meant by this?

      This means that “there are ugly TCP firewalls out there which cause crazy delays which we cannot do much about” 🙁

      > I suppose if there are no outstanding packets then the outgoing buffer must be empty and as long as your “package” is smaller than the buffer then you should be able to Send() and you could in fact even perform a blocking Send(), knowing that it wouldn’t block?

      Yes – though I’d still suggest to test it ;-).

  4. Michael Shuen says

    With regards to the suitability of the algorithm in web browsers, I would say that it could be done. The Mozilla Developer Network documentation on WebSockets (available at https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) implies that with default settings, each opened WebSocket object uses a different TCP connection). I’ll do some more testing shortly. Even so, depending on the firewall/NAT situation, we might be able to use a narrow range of ports (N ports, one for each connection) to ensure each connection is separate. As for the rest of the algorithm, it seems quite workable. On the client-side, the browser gives us access to see the number of untransmitted bytes, so we can pick a connection that is ready to go. On the server-side, we can use the “packet en route” flag. The only minor problem would be to find a UDP stack for JavaScript. If that is a problem, the client can just use faux-UDP of a sort. I’ll do my best to post a demo of the algorithm shortly.

    • "No Bugs" Hare says

      > I’ll do my best to post a demo of the algorithm shortly.

      Would be great! I am really eager to see whether it works in real world (we’re going to include it to our product – but the context is different and it is going to take a while…). Oh, and if there are any questions/problems – feel free to ask (ideally – with Wireshark dumps, otherwise any kind of analysis is pretty much hopeless).

      • Michael Shuen says

        Progress is quite good so far, stay tuned!
        Things I’ve learned so far:
        Wireshark doesn’t lie.
        GDB and Wireshark are my friends.

        As I had hoped, the browser APIs are actually pretty good. Watching the communication reveals that each websocket goes over a separate TCP connection by its nature. There is only one round-trip to set up websocket before it is ready – not sure if that will influence the possibility of speculatvely opening new connections when bad things happen.

        I heard somewhere on StackOverflow that Chrome has a global limit of 255 websockets open at any time, but I don’t think that will be a problem.

        The only bug at the moment is the fact that the server mysteriously crashes when the demo page is refreshed (it works fine if you open another copy).

        I’ll keep you posted on my progress.

        • "No Bugs" Hare says

          Sounds very promising… Please keep posting the updates.

          > Wireshark doesn’t lie.

          Yep – this is one of the very very few things out there which doesn’t lie :-).

          • Michael Shuen says

            Quick semi-update:
            As before, coming along very well.

            Server side scalability seems pretty good. I’m using the uWebSockets server (https://github.com/uWebSockets/uWebSockets) and it works pretty good. Frontend servers just need to route/forward all traffic from a single session to the same WebSocket server.

            The server does need to hold maps of sockets to sessions, though, and needs to do multiple lookups whenever sending. I’ll have some performance numbers later when I test with more clients connected.

            The channels can definitely be bidirectional – can you advise on whether this would be wise? The browser *is* able to track if it has any unsent packets, and client code can be modified to count channels as busy if an ACK has not been received. However, if something happens to the packet going to the server and the server doesn’t know about it, we could lose the channel for a short time.

            Finally, I’ve traced the crash bug from earlier down to a threading race between the sending code and the code that handles disconnects. There shouldn’t be a performance penalty, as only a single user’s data needs to be locked at a time.

            I’ll keep you posted on my progress!

          • "No Bugs" Hare says

            Thanks a lot for the update! 🙂

            The first most important question is IMO – ‘whether there the latency improvement is observed with real-world TCP stacks in presence of packet loss and delays?’ If I had time – I would run an experiment with packet loss simulation (based on intermediate linux box/VM with netem, netem adding both like 10% packet loss and 50-100ms delay in each direction to simulate real-world RTT of 100-200ms) – and would try to observe the difference in latencies on the receiving side depending on the number of channels in use. Sure, in theory the algorithm is solid – but I’d rather see experimental data to be 100% sure (it doesn’t make sense to go into fine details unless the effect is not only predicted theoretically, but is also observed in the experiment which somewhat-simulates real-world). Also – such a test bench is IMO a very important tool to test subtle modifications (and to see that at least they’re not making things worse).

            > The channels can definitely be bidirectional – can you advise on whether this would be wise? The browser *is* able to track if it has any unsent packets, and client code can be modified to count channels as busy if an ACK has not been received. However, if something happens to the packet going to the server and the server doesn’t know about it, we could lose the channel for a short time.

            I am not 100% sure what is the exact processing model you have in mind – could you elaborate (a.what exactly you’re using now – you mentioned ‘number of untransmitted bytes’, which can be tricky, and b. what you’re planning to add)? OTOH – those bidirectional systems I can think of, will cause additional latencies (due to interplay between app-level ACK and ‘UDP packet’ tunneled in the opposite direction but going over the same TCP), and TBH I have no idea how much this additional latency will be. My suggestion in this regard would be – (a) to make a test bench along the lines outlined above (with simulated packet loss and delays), and (b) to test it while using sockets only as unidirectional – and while using sockets as bidirectional. Trying to estimate delays theoretically would lead to pretty complicated math – with quite a bit of room to make a mistake, and quite a bit of things which are very difficult to account for; on the other hand, such an experiment will show behaviour under a more-or-less reasonable approximation of the reality.

            > I’ve traced the crash bug from earlier down to a threading race between the sending code and the code that handles disconnects

            I _hate_ multithreading… :-((.

    • Jesper Nielsen says

      >The only minor problem would be to find a UDP stack for JavaScript.

      My guess is if you’re already having to resort to websockets vs. straight TCP then UDP is definitely not an option:)

  5. Dahrkael says

    So if I understand correctly, this somewhat mitigates the head-of-line issue, creating a TCP version of what reliable UDP libraries usually call channels.
    Lets say we have 200ms of RTT and 5 channels, and we are synchronizing states 20 times per second. This gives us exactly 20 packets every second in best case scenario, right? theres no way we can go up without more channels, even worse if a packet delays more than expected.
    So we need an additional channel everytime we want to cut latency (between packets) by half. This can stack pretty quickly with RTTs above 200 (mobile games), specially if as you say, channels are not bidirectional (this doubles the amount of channels). Also bidirectional channels can incur on extra delay if the other side needs to resend a lost packet before sending the acknowledgement.
    For sure this is only for special cases, I see it a bit fragile, but anyway an interesting approach.

    • "No Bugs" Hare says

      Yes, however – the whole point is that channels are cheap. There is very little cost in terms of ports (they’re consumed on the Client-Side only), and we should be able to limit TCP buffers to ~4K, which makes memory usage on server pretty much negligible (80M for 1000 players is pretty much nothing by today’s standards).

      > this is only for special cases

      We’re going to try using it as a backup for UDP (in cases when UDP doesn’t work – because of firewalls); plus, it _might_ also work for those platforms where UDP is not supported (such as browsers).

  6. Angela Collier says

    A network newbie here. I was told that TCP and UDP don’t play nice together and to just use one or the other. Was it a big fat fib or is there some truth to it?

    • "No Bugs" Hare says

      Short answer: They don’t _always_ play nice when used alongside each other (in other words – you need to be REALLY careful when doing it). However, in some limited well-controlled cases it can fly.

      A bit longer answer: the most common problem when using TCP alongside UDP is that TCP can easily eat up all the “last mile” capacity, hurting UDP (and overall game experience). However, if you know for sure what’s going on within TCP (and use your own flow control, significantly more aggressive than built-in TCP one) – it can be avoided.

  7. Michael Shuen says

    Sorry for the lack of news yesterday. So far:

    Server stability seems to be much better. Unfortunately, that came at the cost of only allowing one thread to send() at a time. I’m pretty sure it’s the library’s fault, but no matter.

    So far, preliminary testing with netem indicates that more WebSocket connections will be necessary than anticipated. Somewhere on this site you mentioned tcp_thin_dupack and tcp_thin_linear_timeouts; I turned those on but saw little effect. I’ll get better statistics and data before saying whether they have an effect, however.

    Using 100ms latency and ~30ms jitter with 10% packet loss, I got occasional events where the server had no “ready” sockets when there were 6 sockets, but little to none with 10 sockets. I’ll give more concrete numbers soon.

    Stay tuned!

    • "No Bugs" Hare says

      > So far, preliminary testing with netem indicates that more WebSocket connections will be necessary than anticipated.

      > Using 100ms latency and ~30ms jitter with 10% packet loss, I got occasional events where the server had no “ready” sockets when there were 6 sockets, but little to none with 10 sockets. I’ll give more concrete numbers soon.

      Thanks (BTW, what is your ‘network tick’?)! TBH, I am not _too_ surprised – and IMO it is still a pretty good result :-). BTW, do you try to handle “hung” connections?

      Two thoughts on “how we might _try_ to reduce number of TCP connections”, which might (or might not) help.

      I. Now, we have two states of the connection – “good” (ACK’ed), and “bad” (non-ACK’ed). However, when we run out of “good” ones – there are quite a few connections which are actually “probably good” – but we just don’t know about it yet. I.e. when we run out of “good” connections – we could try to find out these “probably good” connections (for example – those which aren’t ACK’d yet – but which have time-since-last-post about RTT – though not much more, as being delayed too long means likely problems with specific TCP).

      II. Or – we may delay a packet-which-is-about-to-be-sent, for a few ms – waiting for a channel to be ACK’d; this can be still better than dropping our outgoing packet completely.

      IF using any of these two schemas – the criteria to be measured, should be not “number of times we’ve found a ‘free’ socket”, but rather “histogram of packet losses/delays on receiving side”.

      Just thinking aloud… 🙂

  8. Michael Shuen says

    Many apologies for not responding sooner, I’ve been somewhat busy lately. I’m currently working on refitting the server to collect performance metrics, and should have an update soon.

    • "No Bugs" Hare says

      Absolutely no problem (to start with, this whole project is just your goodwill).

      Of course, it is indeed a _very_ interesting project – and I am really eager to hear about the results :-). In other words – I am all ears (which is not that difficult for a rabbit ;-)).

  9. Michael Shuen says

    So, I finally found the time to revisit this, and I have some good results! Using “tc qdisc replace dev lo root netem delay 100ms 30ms drop 10%”, 10 WebSocket connections, and 20 messages per second, over one minute, all sockets were busy only 5 times. With tcp_thin_linear_timeouts and tcp_thin_dupack, it only happened twice.

    I’ll probably do some more scientific testing later. I’ve put the code up as a Gist at https://gist.github.com/Zirientis/16c60215c13880ada43014a533bf8e9e and I used the uWebSockets library at https://github.com/uWebSockets/uWebSockets. To run it, download the code, compile uWebSockets, and build with ‘g++ server.cpp -g -std=c++14 -luWS -lssl -lz -pthread -Wall -Wextra -fsanitize=address -o server’. Connect to localhost on port 3000.

    I look forward to hearing from you!

    • "No Bugs" Hare says

      Wow! Actually, even 10 sockets/player doesn’t look prohibitively expensive. It means that we’ll be able to handle about 6000 players/single IPv4 address – and an “industry standard” number for simulation-based games being ~1000 players/server-box means that it isn’t likely to cause any exhaustion of available TCP ports. To be 100% sure, I’d still check TCP memory usage (and to limit the size of TCP buffers if necessary) – but honestly, I don’t really expect problems on this way (as our channels are always-empty, even as little as 4K/connection should work fine).

      > I’ll probably do some more scientific testing later.

      Would be great! And BTW, if you ever have a problem “where to publish this research” (it can be tough these days) – I would be Really Happy(tm) to post it as a guest post here (and if not – please LMK when you publish it).

    • MS says

      I am also following this development closely. Tested Michael Shuen’s code on CentOS and it worked as advertised – opened 10 browser clients and left them running for 3 hours on unmodified project & settings.. All clients had all sockets busy only 5-6 times in a 3 hour nonstop run. I would like to see load capacity performance benchmarks a uWebSockets server.

      I am looking into using this method as an alternative to TURN servers for my WebRTC multiplayer game.

  10. Michael Shuen says

    Sorry – got really caught up in other things! I’ll definitely interested in perusing this, so I’ll try and make some time soonish. Thanks for the interest!

Leave a Reply

Your email address will not be published. Required fields are marked *