tungstenite-rs: Sharing socket between threads / non-blocking read
Hello, I’m building a service which demands minimal latency.
I need to be able to send a message as fast as possible (latency wise), but still engage in potential ping/pong interactions.
My problem is that read_message seems to block until a message arrives, which won’t do.
I was thinking I could access the underlying stream, split it, then have two threads, one which blocks while waiting for new messages and then handles them, and the other which writes whenever it needs to according to its own independent logic.
Is this possible? I’ve heard about using mio to make some of the components async, I saw a set_nonblocking method mentioned in another issue regarding the blocking nature of read_message. I’m overall a bit confused, and can’t find an example of how I would achieve an async read (or something equivalent) using mio.
Thanks so much!
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Reactions: 7
- Comments: 20 (7 by maintainers)
Hi, what you’re doing now is probably the least efficient way ever. Using multiple network connections does neither simplify things nor improve latency since the communication between threads is not easier than the WebSocket itself. It just has exactly the same problem. In your case I’d rather use no threads at all. Use
tokioandtokio-tungstenite. Create the WebSocket and callsplit()on it. You’ll get separate sender and receiver parts, both asynchronous, and then you’ll be able to spawn two separate tasks. A good example of doing so is available here: https://github.com/snapview/tokio-tungstenite/blob/master/examples/interval-server.rsHi,
first, what
set_nonblocking()does. It preventsread_message()from blocking. Instead it will returnErrimmediately if there are no messages available. If your software has some CPU-intensive computations all the time without pauses and with 100% CPU load, then calling non-blockingread_messagesomewhere in the computation would do the trick. But this approach always causes 100% CPU load and thus should not be used directly. Instead, you generally combine non-blockingread_messagewith your own blocking waiting. This differs from blockingread_messagein that you have full control over blocking waiting and are able to interrupt it for i.e. sending.That’s what
miodoes. Usingmiois nothing else than the standard “poll/select” approach known from UNIX textbooks.miois a wrapper around poll/select. It waits for one or multiple sockets and tells when one or more of them has data for reading. By using it you would be able to send and receive within the same thread. That’s how we work with Tungstenite in our company.Probably the best way nowadays is
tokiowithtokio-tungstenite. It wrapsmioandtungstenitetogether usingFuturetrait. Then you can useasync/awaitjust like in JavaScript or start background tasks (not threads!) that do some work in parallel. If unsure, trytokio. But if for some reason you need fine-grained control over low-level sockets and threads, then usemiodirectly. This is generally harder to do and I don’t recommend this for general use.Hi @agalakhov, sorry to resurrect this thread, but I’m trying to use
tungstenitewithmioas well.Right now the issue I have is with the handshake when using the
clientfunction. For some reason I keep gettingHandshakeError::Interrupted(...).Any chance you have a simple example using
mioas a client? I can post a simplified version of what I’m trying to do if that helpsMuch thanks!
You can also set
TcpStreamto non-blocking mode. In this case it will returnWouldBlockimmediately. You can then use third-party library (i.e.mio) to check if it will returnWouldBlockbefore you call and thus manage your connections and tasks. Or you can just callreadperiodically, but this is not so nice since your CPU will be always active.Hello
You can achieve this by using multi-producer-single-consumer channels.
So basically when you create a WS connection.
You create these channels; pass them to the shared server object and that owns them.
Together with the WsConnection start point you spawn a new task and listen for the receiving messages and then write to the tcp stream on the WsConnection.
The ws connection should have the sender part of the server (which you can clone multiple times)
If you want to read from multiple places you can instead use a multi-producer-multiple-consumer channel.
I put together a naive ascii art for you:
I have created an example create to show you how you can achieve this with full working code. Though it’s not fully compatible with the rfc spec yet and can probably be improved a lot. But it’s focused towards explaining the simpler concepts of multi users in a server which handles connections etc.
Hope that makes sense in some way 😮 Please let me know if you have any further questions