Lew Pitcher <
lew.p...@digitalfreehold.ca> writes:
> [..]
>> Lew Pitcher <
lew.p...@digitalfreehold.ca> writes:
>>> On Tue, 13 Feb 2024 12:54:23 +0000, Ben Bacarisse wrote:
[...]
>>>> (The only advice I'd question is that of using non-block I/O by
>>>> default. I would be surprised if that helps you in this project,
>>>> and it might make things more complicated.)
>>>
>>> I've done less programming with non-blocking I/O than I've done
>>> with sockets. I have heard the advice to use non-blocking I/O,
>>> and will consider it, but I don't want to take on too much in one
>>> shot.
>> [...]
>
> For this mini-project, both client and server live on the same
> machine, and communicate through the loopback ("localhost").
> There won't be any significant latency issues or other
> communications interference.
>
> I'm only concerned with writing the client, and it will only
> interact over one channel in a simple request/reply type protocol.
> There doesn't seem to be a pressing need for nonblocking I/O.
>
> But, as it has been suggested here, I will look into nonblocking
> I/O. Perhaps I'm missing something that others have seen.
I guess I should expand on the idea that using non-blocking I/O
is a good idea.
First, my comment was offered as pragmatic advice following many
years of experience both with networking and with socket-level
programming. It is generic advice, given without taking into
account any of the specifics of your project. There is no question
that using non-blocking I/O means more work up front, and I expect
there is a good chance you could get something working without doing
any of the non-blocking stuff.
That said, here are some things to think about.
Although there is only one channel, there are two communication
paths: one for reading and one for writing. If there were really
only one path (e.g., reading a file and writing to a socket) then
using blocking I/O is probably good enough. But any more than
just a single path makes things more complicated.
There can be problems at the protocol level even if the transport
layer is working perfectly. Network programming has a lot in
common with inter-process synchronization, and is just as tricky
except in the cases where it's even trickier. How is the remote
process (which is Asterisk in this case) going to behave? That
can (and often does) matter.
(Incidentally, speaking of Asterisk, Asterisk is great! I've
been running my own home phone system on Asterisk for more than a
decade now, and I'd never give it up.)
As far as the transport layer goes, TCP is very reliable once it
gets going, but there is a little dance that happens at the start
to get things set up, and sometimes one of the partners steps on
the other's foot. I see this happen sporadically with programs
that have the same basic outline as your application (one socket
used for both reading and writing). Probably there is some sort
of interaction with what's going on at the protocol level, but
it's hard to know whether the problem is a protocol issue or
something in one of the network layers.
Assuming you do go ahead with using blocking I/O, and get things
working, there is still a fair chance that at some point down the
road (probably after the application has been deployed) the program
will be the victim of a network hiccup and go catatonic. If and
when that happens: one, it will be frustrating; two, the problem
will be non-reproducible; and three, you won't have any tools to
help diagnose what's going on to cause the problem, because all the
significant events are happening inside one of the blocking system
calls. Conversely, if all the socket I/O is non-blocking, the code
can be instrumented (whether before the fact or after) with various
sorts of logging, and the log information can be examined to see
what's going on.
Final point: probably the most important lesson here is that we
may expect that network system calls are going to fail, and should
program accordingly. When working with files it's easy to get
into the habit of thinking the calls will never fail, because they
almost never do. Network system calls are different. They might
not fail all the time, but they *are* going to fail, and it's
better to anticipate that, and program accordingly. Using
non-blocking I/O makes that easier, partly because "failures"
happen more often in the form of "operation would block", so one
gets into the habit of writing code that handles different kinds
of error returns. This means a little more work at the outset,
but ultimately less work (we hope!) overall.
I guess I should add that I'm not trying to persuade anyone, and
please go ahead as you think best. I thought it might be helpful
to explain some of the rationale underlying my suggestion, and
hence this response.