-
Notifications
You must be signed in to change notification settings - Fork 221
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support reading into user provided buffers #209
Comments
This would be fine, but I'm in doubt it will be much faster due to the fragmentation of WebSocket messages. They are copied while assembling. Yes, one memory allocation can be spared but I don't see how zero-copy could be implemented here. I like zero-copy but I wasn't able to implement this in the very first version of Tungstenite. Any ideas how to write this internally? |
Thanks @agalakhov One way to do zero-copy might be for .Net people do something similar as you can see in their send and receive APIs. This design means that WS message assembly becomes the user's job instead of the library's. In some cases, such as my tunnel example above, assembly is not needed or even desirable because you want to minimize latency (for the tunnel you want to simply shovel whatever you received off the websocket immediately to the TCP socket and vice-versa). So it would be nice to have that flexibility. Will it be faster? I do think so as it avoids an extra copy and an allocation. In the tunnel-like use-cases not much else is going on in the program other than the data copying operation and hence it cost dominates. Therefore such use-cases should benefit significantly. That said, I could be completely wrong and we'd have to measure it to know for sure. |
I see. This is in fact how Tungstenigte works internally, and this could even be achieved if using Maybe the best way would be providing a pool of bytes and returning a slice of actually read data, what do you think? |
You're right about ping and close. We may not be able to avoid assembly for them, but they are generally very small, so they'd rarely fragment and therefore it should be okay. In the second paragraph I presume you're referring to the read API. I don't think we'd need to return a slice out of a pool of bytes provided by the user. The read API can simply take a buffer of type
For example: enum MesageType {
Text,
Binary
// No need for close and ping since they are automatically handled
}
struct ReadResult {
bytes_read: usize,
message_type: MessageType,
end_of_mesage: bool
}
// This is the signature of the read API
fn read(buf: &mut [u8]) -> Result<ReadResult, Error> {
// Read data from the underlying websocket by directly passing the user-provided buf to it (which basically means zero-copy)
// Create the ReadResult object and return it
} Inside the read API body we don't have to maintain our own buffers. We read from the underlying websocket directly into the user provided buffer and return a ReadResult with the fields appropriately set. I may be oversimplifying all of this since I don't know the internals of |
Just as a note: there was recently a very similar discussion here. |
Is there any example of what this would look like? I'm needing to handle multi-gigabyte messages in my app and it sounds like I need a solution or workaround for this issue. |
Currently
read_message()
andwrite_message()
work with aMessage
which carries data in aVec<u8>
or aString
. This means that an allocation will happen every time a message is read or written. It is a problem for uses cases that require high performance such as tunnelling a TCP stream over a WebSockets connection. To address such needs, do you think it would be a good idea to have additional methods for reading and writing that take a caller-owned buffer like this:Then these methods can be used to copy to and from other sources and destinations like a TcpStream without any allocations, roughly like this:
The text was updated successfully, but these errors were encountered: