Haiku BNetEndpoint: TCP Streams, UDP Datagrams, and Timeouts
Use Haiku's BNetEndpoint with explicit TCP or UDP semantics, checked status, partial I/O loops, bounded waits, and clear socket ownership.
BNetEndpoint is Haiku’s Network Kit wrapper around an endpoint-oriented socket API. It can represent stream or datagram communication and exposes operations such as bind, connect, listen, accept, send, receive, timeout configuration, and close. The class is useful when a native Haiku application wants a C++ wrapper integrated with Haiku’s network-kit types, but it does not remove the core transport distinction: TCP is a byte stream; UDP is a sequence of datagrams.
The most common bugs come from assuming a call maps one-to-one to an application message. TCP does not preserve sender write boundaries: one Send() may be observed by multiple Receive() calls, and one receive may contain bytes from several writes. UDP preserves datagram boundaries but can lose, duplicate, or reorder datagrams. The application protocol must define framing, timeouts, retries, and idempotency explicitly.
Initialize the endpoint and inspect status
The current public header provides a constructor with endpoint type selection and an InitCheck() method. Check initialization immediately and stop if it fails. A successful object allocation is not evidence that the socket was created. Keep ownership simple: one component owns the endpoint, closes it exactly once, and communicates shutdown to any worker blocked on I/O.
The Network Kit BNetAddress type represents address information, while convenience overloads accept a hostname string and port. Name resolution can block or fail; do not call blocking resolution or network setup on a window looper. Resolve/connect on a worker and send a result message back to the UI. Use explicit error reporting: log the operation, destination class, return value, and Error() without leaking credentials or full sensitive payloads.
An endpoint’s state matters. A client stream connects to a peer; a server stream binds to a local address, listens, then accepts a separate endpoint for each connection. The listening endpoint remains responsible for accepting new clients. A UDP endpoint can bind for local reception and use send-to/receive-from operations for datagrams; it does not create a TCP-style session through Listen() and Accept().
TCP requires framing and complete-write logic
A stream reader must know where one protocol message ends. Common designs include a fixed-size header containing a bounded payload length, a delimiter with a defined escaping rule, or a fixed-length record. Parse incrementally: the header itself may arrive partially, the peer can close mid-record, and the declared length must be validated before allocating memory.
Likewise, do not assume one Send() transmits the full buffer. The API returns an integer byte count, so callers must handle short writes and errors according to the current endpoint behavior. A robust send loop advances only by the number of bytes actually written and stops on failure. A robust receive loop collects bytes until one complete protocol frame exists, rather than treating each call as a complete request.
For a client, a typical flow is Connect(), write a framed request with a complete-write loop, read and validate a framed response, then Close(). If the connection fails halfway through, do not blindly resend a non-idempotent request; the peer may have completed the operation even though the response was lost. Include request IDs and deduplication or a queryable operation state when retries can cause side effects.
For a server, bind the intended address rather than accidentally exposing a service on every interface. Call Listen() only for stream endpoints. Each accepted endpoint needs its own lifetime, timeout, and worker policy. Limit simultaneous clients, bound request size, and stop accepting gracefully during shutdown. A client that never sends a complete frame must not retain an unbounded thread or memory buffer.
UDP requires datagram-aware behavior
Use SendTo() and ReceiveFrom() when a datagram exchange does not establish a stream connection. A UDP receive gives one datagram at a time, subject to the maximum buffer size; if the receive buffer is too small, datagram truncation behavior must be handled according to the current socket API. Validate sender address, datagram size, protocol version, and message type before acting.
Do not use UDP for large arbitrary payloads without an application protocol that handles fragmentation, loss, ordering, and reassembly safely. IP fragmentation is fragile across paths and cannot be used as a substitute for application-level flow control. For small discovery or telemetry packets, include a version, bounded payload, and enough identifiers to reject stale or duplicate data.
UDP’s lack of connection does not make it inherently secure or reliable. A source address can be spoofed in some network situations, and no delivery acknowledgment exists unless the application adds one. Do not send secrets or privileged commands in unauthenticated datagrams.
Timeouts and shutdown are part of the protocol
SetTimeout(bigtime_t usec) is expressed in microseconds in the current header. The legacy Accept(int32 timeout) parameter has a different signature; do not assume its unit is the same as SetTimeout() without checking current implementation and docs. In current source, accept timeout handling converts its argument separately. This distinction is easy to miss when code passes a bigtime_t directly to Accept().
Choose finite deadlines for interactive clients and servers. A timeout is not proof that the remote operation did not occur. Keep the request ID and resulting connection state so a retry is safe. Report timeout separately from DNS failure, refused connection, orderly close, and protocol parse error.
Shutdown should wake blocked work. Do not destroy an endpoint while a worker is concurrently in Receive() unless the API contract and your synchronization explicitly support that pattern. Coordinate a stop flag, close/shutdown through the supported mechanism, join the worker, then release the endpoint. Avoid holding a global or window lock over blocking I/O.
Minimal API-shaped example
The following illustrates a bounded client setup; payload framing and exact error policy are intentionally application-specific:
BNetEndpoint endpoint;
status_t status = endpoint.InitCheck();
if (status != B_OK)
return status;
endpoint.SetTimeout(5LL * 1000 * 1000); // five seconds in microseconds
status = endpoint.Connect("service.example", 443);
if (status != B_OK)
return status;
// Send/Receive must be used with stream framing and short-I/O handling.
// Use TLS through an appropriate supported layer; BNetEndpoint is not TLS.
endpoint.Close();
The constructor’s default type and overloads are confirmed in the current public header. This snippet deliberately does not claim that a raw connection to port 443 is encrypted: BNetEndpoint itself is a socket wrapper, not a TLS implementation. Production HTTPS should use the appropriate TLS-capable API or library and verify certificates.
Diagnostics and verification
Test a local server and client first, then the real network. Record the Haiku revision, endpoint type, address family, destination, timeout, and exact result. If a connection times out, verify name resolution, route, firewall, listening address, and service readiness separately. A successful ping does not prove the application port accepts a connection.
Test partial TCP reads/writes by deliberately splitting a frame across writes. Test peer close mid-header and mid-payload. For UDP, test oversized, malformed, duplicate, and reordered datagrams. Test server shutdown while clients are idle and while one is sending an incomplete frame. Confirm all workers exit and the app does not hang.
BNetEndpoint provides endpoint operations, not application-level guarantees. Correct code makes transport semantics visible: frame TCP streams, bound UDP messages, check byte counts and status, use finite timeouts, and define ownership during shutdown. Keeping those responsibilities explicit is more reliable than treating sockets as a message queue.
Related:
- Inside Haiku’s Network Stack: Interfaces, Protocol Modules, and Userland Services
- Haiku’s Network Kit HTTP Client: Requests, Listeners, Cookies, and TLS Boundaries
Sources: