|
CX Framework
Cross-platform C utility framework
|
Data Structures | |
| struct | NetSocket |
Macros | |
| #define | netsocketSendEx(self, data, len, dest, info, flags) NetSocket_sendEx(NetSocket(self), data, len, dest, info, flags) |
| #define | netsocketSetRecvInfo(self, enable) NetSocket_setRecvInfo(NetSocket(self), enable) |
| #define | netsocketSetDontFragment(self, enable) NetSocket_setDontFragment(NetSocket(self), enable) |
| #define | netsocketSetHandlers(self, handlers, ctx) NetSocket_setHandlers(NetSocket(self), handlers, ctx) |
| #define | netsocketSetHandlersObj(self, handlers, ctx) NetSocket_setHandlersObj(NetSocket(self), handlers, ObjInst(ctx)) |
| #define | netsocketAddFilter(self, filter) NetSocket_addFilter(NetSocket(self), NetFilter(filter)) |
| #define | netsocketRemoveFilters(self) NetSocket_removeFilters(NetSocket(self)) |
| #define | netsocketRecv(self, buf, bufsz, src, flags) (self)->_->recv(NetSocket(self), buf, bufsz, src, flags) |
| #define | netsocketRecvMsgs(self, cb, ctx) (self)->_->recvMsgs(NetSocket(self), cb, ctx) |
| #define | netsocketSend(self, data, len, dest, flags) (self)->_->send(NetSocket(self), data, len, dest, flags) |
| #define | netsocketConnect(self, host, port) (self)->_->connect(NetSocket(self), host, port) |
| #define | netsocketClose(self) (self)->_->close(NetSocket(self)) |
Typedefs | |
| typedef struct NetSocket | NetSocket |
| typedef bool(* | socketRecvCB) (NetSocket *sock, NetMessage *msg, void *ctx) |
Class that encapsulates a network socket.
bool netsocketAddFilter(NetSocket* self, NetFilter* filter);
Attach a filter to the socket, applying it to every flow
The filter is appended to the socket's list, so the first one attached is the stage closest to the application and the last is the one nearest the wire. A per-flow NetFlowFilter is created immediately for every flow the socket already has – the single flow of a stream socket, or every peer flow of a datagram socket – and for every flow it opens afterwards.
The socket acquires its own reference, so the caller keeps theirs and should release it when done; the same filter may be attached to any number of sockets, which is how one filter holding a certificate and key serves every accepted connection on a server.
A filter attached to a listening socket is inherited by every connection it accepts, and is installed on the accepted socket before that socket becomes reachable – so no byte can arrive on a new connection ahead of its chain. That is the way to secure a server: attach once to the listener, not per connection from the NET_Accepted handler, which races the worker already servicing the new socket.
Install filters before data starts moving – right after creating the socket, and before connect or listen.
| filter | Filter to attach |
| #define netsocketClose | ( | self | ) | (self)->_->close(NetSocket(self)) |
bool netsocketClose(NetSocket* self);
Close socket
Will also remove from a NetQueue if it is registered to one. The socket cannot be reused once closed.
A connection is shut down at once, but its OS handle – and the local address it is bound to – is only released when the last reference to the socket is. A listening socket is the exception and releases its handle immediately, so its address can be listened on again.
| #define netsocketConnect | ( | self, | |
| host, | |||
| port | |||
| ) | (self)->_->connect(NetSocket(self), host, port) |
bool netsocketConnect(NetSocket* self, strref host, uint16 port);
Open an outbound connection to a host and port
Stream sockets only. This call is asynchronous: it returns immediately after starting the process, and the result arrives later as a NET_Connection event on the socket's flow. The host is resolved on a dedicated resolver thread, never on a net I/O thread, and the socket passes through NS_Resolving and NS_Connecting on the way. Each resolved address is tried in turn with a fresh OS handle of the right family, falling through on failure or timeout, until one connects or the list is exhausted.
The socket must already be registered with a queue (that is where the resolver, backend, and flow live). A literal address is accepted directly without a DNS lookup.
| host | Hostname or literal address to connect to (NULL/empty means loopback) |
| port | Port number, host byte order |
| #define netsocketRecv | ( | self, | |
| buf, | |||
| bufsz, | |||
| src, | |||
| flags | |||
| ) | (self)->_->recv(NetSocket(self), buf, bufsz, src, flags) |
size_t netsocketRecv(NetSocket* self, uint8* buf, size_t bufsz, NetAddr* src, flags_t flags);
Read buffered stream data out of the socket receive buffer
This does not actively try to receive; it only drains bytes already buffered in the socket's receive ring by the queue's ingest thread (or by tick() in polled mode). With a filter attached it drains decoded application bytes from the end of the flow's filter chain instead – the raw wire bytes in the receive ring were already consumed by the decode pass that ran before NET_DataReceived was delivered.
event->recv.msg instead of being buffered here. Calling recv() on a datagram socket returns 0.| buf | Buffer to store received data |
| bufsz | Size of the buffer |
| src | Unused; retained for signature compatibility. Datagram source addresses arrive on the event, not here. |
| flags | Optional flags to control receive behavior |
| #define netsocketRecvMsgs | ( | self, | |
| cb, | |||
| ctx | |||
| ) | (self)->_->recvMsgs(NetSocket(self), cb, ctx) |
bool netsocketRecvMsgs(NetSocket* self, socketRecvCB cb, void* ctx);
Drain buffered stream data as a series of zero-copy messages
Invokes the callback for each chunk of data available in the receive ring, repeatedly, until it returns false or the ring is exhausted. More efficient than recv() when draining a lot of data, since it hands out the ring's own segments instead of copying into a caller-supplied buffer.
The callback receives ownership of the buffer in the NetMessage. It may keep the buffer by setting msg->buf to NULL, otherwise the buffer is automatically destroyed after the callback returns.
| cb | Callback invoked for each message (return false to stop) |
| ctx | User-defined context pointer passed to callback |
| #define netsocketRemoveFilters | ( | self | ) | NetSocket_removeFilters(NetSocket(self)) |
void netsocketRemoveFilters(NetSocket* self);
Detach and release every filter on the socket
Drops the socket's filter list and tears down the corresponding chain on every flow it owns, discarding anything those stages still had buffered. After this the socket's send/recv paths are back on the unfiltered fast path.
| #define netsocketSend | ( | self, | |
| data, | |||
| len, | |||
| dest, | |||
| flags | |||
| ) | (self)->_->send(NetSocket(self), data, len, dest, flags) |
bool netsocketSend(NetSocket* self, const uint8* data, size_t len, const NetAddr* dest, flags_t flags);
Send data on the socket
Data the OS won't accept immediately is queued and flushed later when the socket becomes writable. The same logic is shared by both socket types: a stream socket copies the data into its outbound chain and sends it with scatter/gather; a datagram socket queues whole messages, each with its own destination.
sendHigh bytes are already queued, the call returns false and queues nothing; back off until NET_SendReady fires, which happens once the backlog drains below sendLow. NSO_Immediate never queues – it sends what the OS takes right now and returns true only if the whole payload went out.For connected (stream) sockets, dest is ignored. For connectionless (datagram) sockets, dest must be a valid address.
NSO_Immediate is ignored on a filtered socket – the filter owns the framing, and a payload jumping the chain would land in the middle of whatever the chain is producing.| data | Pointer to data to send (copied; need not outlive the call) |
| len | Length of data in bytes |
| dest | Destination address (required for connectionless, ignored for connected) |
| flags | Optional flags to control send behavior |
| #define netsocketSendEx | ( | self, | |
| data, | |||
| len, | |||
| dest, | |||
| info, | |||
| flags | |||
| ) | NetSocket_sendEx(NetSocket(self), data, len, dest, info, flags) |
Send a datagram, asking the IP layer for a specific local address and ECN mark
Everything send() does, plus the two things a plain send cannot express: which of this machine's addresses the datagram leaves from, and the ECN codepoint it carries. Both are requests – a platform that cannot honour one sends the datagram without it rather than failing, so a caller that needs to know whether the mark survived has to ask the peer.
Datagram sockets only, and unfiltered ones: a filter chain owns what reaches the wire, so there is nothing here for one to attach per-datagram information to.
| data | Pointer to data to send (copied; need not outlive the call) |
| len | Length of data in bytes |
| dest | Destination address |
| info | Local address and ECN mark to ask for, or NULL for neither |
| flags | Optional flags to control send behavior |
| #define netsocketSetDontFragment | ( | self, | |
| enable | |||
| ) | NetSocket_setDontFragment(NetSocket(self), enable) |
bool netsocketSetDontFragment(NetSocket* self, bool enable);
Set the don't-fragment bit on datagrams leaving this socket
A datagram larger than the path allows is then dropped instead of being fragmented, which is what turns an oversized send into a measurement rather than a slow success. Call it after bind(), for the same reason as setRecvInfo().
| enable | true to set the bit, false to leave fragmentation to the OS |
| #define netsocketSetHandlers | ( | self, | |
| handlers, | |||
| ctx | |||
| ) | NetSocket_setHandlers(NetSocket(self), handlers, ctx) |
void netsocketSetHandlers(NetSocket* self, const NetHandlers* handlers, void* ctx);
Register per-socket handler overrides
Fields left NULL fall through to the queue-wide set. The handler struct is not copied – it must outlive the socket, which is why it is usually static const.
| handlers | Handler set, or NULL to clear |
| ctx | Context passed to these handlers on NetEvent.ctx |
| #define netsocketSetHandlersObj | ( | self, | |
| handlers, | |||
| ctx | |||
| ) | NetSocket_setHandlersObj(NetSocket(self), handlers, ObjInst(ctx)) |
void netsocketSetHandlersObj(NetSocket* self, const NetHandlers* handlers, ObjInst* ctx);
Register per-socket handler overrides, with an object as the context
Same as setHandlers(), except ctx is held weakly rather than borrowed.
| handlers | Handler set, or NULL to clear |
| ctx | Object passed to these handlers on NetEvent.ctx, held weakly; NULL to clear |
| #define netsocketSetRecvInfo | ( | self, | |
| enable | |||
| ) | NetSocket_setRecvInfo(NetSocket(self), enable) |
bool netsocketSetRecvInfo(NetSocket* self, bool enable);
Ask the OS to report the local address and ECN mark of each received datagram
While enabled, every NetMessage delivered from this socket carries them on NetMessage::info. Call it after bind(): the options are per address family, and a socket has no family until it is bound.
| enable | true to start reporting, false to stop |
| typedef bool(* socketRecvCB) (NetSocket *sock, NetMessage *msg, void *ctx) |
Callback for receiving messages from a socket.
The callback receives ownership of the buffer in the NetMessage. To keep the buffer, set msg->buf to NULL before returning; otherwise it will be automatically destroyed.
| sock | Socket the message was received from |
| msg | Message containing data buffer and source address (for datagrams) |
| ctx | User-defined context pointer |