4 ms·
> You write plain, simple, procedural code, no threading or any other cleverness needed Using sockets in a synchronous fashion is one way to block for an indef
by nly 5y ago
> You write plain, simple, procedural code, no threading or any other cleverness needed
Using sockets in a synchronous fashion is one way to block for an indefinite period of time. Once a TCP connection is established, there are failure modes where nothing will notify you that the connection has been lost until you try to write(), and even then after minutes in the worst case. Using sockets without timeouts is nuts. The BSD sockets API doesn't give you timeouts.
>I wasted several months with the wrong approaches on Windows first. I used WinSock2 with IOCP (asynchronous completion ports)
If you'd used Boost ASIO you'd have gotten Windows IOCP under the covers for free.
I honestly don't see an argument here. Defaulting to these low level primitive APIs is an act of hubris. Boost has HTTP, TLS and Websockets as well, all under the same async I/o model. Even HTTP/2 is available under asio via nghttp2
- jstimpfle 5y ago> The BSD sockets API doesn't give you timeouts. Of course you can get timeouts (using select() or any other standard event notification mechanism), and most importantly you can easily get non-blocking socket reads/writes, I did just that. > If you'd used Boost ASIO you'd have gotten Windows IOCP under the covers for free. Well, I got Windows IOCP without the covers. Even better, since now I can integrate all IOCP parts in my application, and don't have to separate the ones that are covered (or might be? hard to see when covered, right?) by library A from those that are covered by library B. But I'd like to see first whether IOCP is strictly needed anyway, synchronous non-blocking reads/writes might give you more than enough performance for most cases. > Boost has HTTP, TLS and Websockets as well I don't use Boost on principle. Maybe some of these libraries are usable, but boost is a community of architecture astronauts. Another reason is that I avoid C++ if possible. > Defaulting to these low level primitive APIs is an act of hubris. BSD sockets is not low level, if anything it is too high-level. As said, it allows you to send and receive packets. What more could you want? Anything else is snakeoil. Update: Yep, this seems to be some overarchitected junk that leads to unmaintainable messes: https://www.boost.org/doc/libs/1_75_0/doc/html/boost_asio/overview/core/basics.html https://www.boost.org/doc/libs/1_75_0/doc/html/boost_asio/ov... The basic primitive, receiving new updates, is not readily available. Instead, you're encouraged to do callback handlers, leading to temporal coupling and ravioli code. All in the name of optimizing for short syntax in toy examples. Look, how much you can do in just 5 lines with automatically inferred types, and pray the RAII! (Nevermind that anything moderately complex will require twice the normal amount of code just to unwrap all the insanity).
- nly 5y ago> Of course you can get timeouts (using select() or any other standard event notification mechanism) That's just it, there's no such thing as a 'standard event notification system'. select() is terrible for performance, and all the best options are different on every single platform. > Instead, you're encouraged to do callback handlers, leading to temporal coupling and ravioli code. Callbacks are the simplest primitive for async code. If you're not comfortable with them then you're not going to go far with async I/O. Not to mention, ASIO also supports futures and coroutines.
- jstimpfle 5y agoBy `standard` I mean a system that works on this platform for any kind of fd or handle. select() is just fine for simple cases, but of course it has some known problems, such as MAX_FD. There are better APIs, and ultimately it was learned that ringbuffers between the user-process and the kernel (that remove the need for system calls) as an implementation of asynchronous I/O are a good idea. I.e. IOCP, io_uring, etc. Often you don't need any of these APIs at all - in a system with a constant ("stochastic") load you don't really need any kind of event waiting system. Instead, you can process all incoming messages every N milliseconds or so. > Callbacks are the simplest primitive for async code. No, the simplest thing is to just use a plain old buffer. See e.g. the IOCP API or just any regular buffer code. One side pushes the message to a buffer. Some (arbitrary) time later, the other side (potentially, but not necessarily a different thread) pulls the message from the buffer and handles it. It's just buffers, buffers are all that is needed, and buffers plainly are the best way to solve all issues related to event handling. No fancy abstract template insanity, no weird generic resource handling systems, no complex scheduling systems, not even a need to declare any kind of event handling function or interface. Just place a few statically allocated buffers at the connection points where threads of execution (OS threads, but also hardware / network etc.) meet. Callbacks are of course theoretically equivalent, since they can be made to do the same thing as buffers. You can trivially write a callback that only pushes the message to a buffer. In practice, the difference is significant because lots of callback boilerplate is created and temporal coupling (i.e. same thread, same code path) between enqueuing a message and handling the message is encouraged. This results in a lot of overly complex code, including custom green thread runtimes. I've seen it, I've tried to do the same, I've seen others try to do the same. It turns out to be a very, very bad idea, resulting in the creation of a whole parallel universe with separate green threads I/O implementations. This is what the term "Callback Hell" was invented for. Look at Windows Fibers API, it's widely recognized to be a dead end. You will find some good post-mortem material on that topic on the internet.
- jstimpfle 5y ago> there are failure modes where nothing will notify you that the connection has been lost until you try to write() you need to either read() or write() on a connection to be informed that the connection was terminated or half-closed. My server application works perfectly, it reacts immediately to any state change. Did not require any special code, just monitor the read and write ends, which is what one does anyway. (Yep, this is API specific behaviour of course, but it's the only sane approach IMO, since the termination event must be sent in a synchronization with the actual channel interaction). Of course, if you're not checking for updates on both directions (read + write) because you're blocked on some blocking interface (either on the same socket or different I/O port or computation), your server won't react. The API is not to fault. The mistake was to write blocking code. That is the difference between dirty batch scripts and systems programming.
- nly 5y agoCalling read() won't tell you anything if someone cut the wire or unplugged an Ethernet cable that wasn't directly connected to your machine. write() won't fail until after a bunch of TCP re-transmit timeouts have passed. TCP keepalives can help but you have to enable them and, as I said before, doing so is different on different platforms. Honestly, if you're doing anything remotely interactive or latency sensitive on the same thread as network I/O you need to go async.
- jstimpfle 5y agoFor the record, I am of course doing async (Or rather non-blocking) I/O. I explicitly said that it's a mistake to write blocking code. (I made an error somewhere though where I wrote "non-blocking" instead of "blocking"). (And as I described, the "green threads" kind of blocking code is a mistake just as well. And the event-driven kind (i.e. callbacks / like in Javascript) leads to messes as well, only way I see it not become a mess is pushing stuff to buffers to process messages later in a separate message processing loop with some better context etc.
- jstimpfle 5y ago> Using sockets in a synchronous fashion is one way to block for an indefinite period of time. I'm not saying to use sockets in a synchronous fashion (i.e. blocking I/O). That would, of course, potentially block the thread indefinitely. "Plain, simple, procedural" does not imply "blocking I/O". What I mean is to use no fancy types, no callbacks, no crazy automatic scheduling magic. Very simply, there is nothing special required to handle events. Just a buffer.