LIBPKG(3)
Okay bestie, this is LIBPKG(3) from the Library Functions Manual β the manual page for libpkg β and it's part 1 of 3, so buckle up π
π NAME π
libpkg β a message passing abstraction library that supports communication between cooperating processes over TCP connections β¨
πΈ BACKGROUND πΈ
So the lore is this: the description of the libpkg routines was kindly provided by Peter B. Shames on 26 Sept 1986, and then updated on 14 Nov 1986 by PC Dykstra to reflect the BRL extensions that had landed since the first writing π«Ά. The plan was apparently to publish an RFC at some point, but at this point this document (plus the source code) is literally all we have π. In the CAD distribution, the remote framebuffer interface (libfb/if_remote.c) and the server (fbserv) communicate using this protocol β iykyk π
.
β¨ OVERVIEW β¨
A prototype interface has been developed by Mike Muuss, Charles Kennedy, and Phillip Dykstra at BRL that implements a fairly simple message passing abstraction between cooperating processes running over TCP connections π. Some experiments have been run at STScI to implement this interface on a DECnet link with good success β slay β¨. A few changes have been suggested to improve network throughput for data flows that consist of short message traffic or byte-stream traffic π.
This note describes the motivation for developing such an interface and describes the subroutine interfaces and functionality π. The interface routine descriptions and portions of the textual descriptions have been taken directly from the source code provided by Muuss and Kennedy, which has been the only available description to date β no cap, that's the whole archive vibe π«Ά.
π¦ WHY A MESSAGE INTERFACE? π¦
The interface β PKG β provides a simple interface for managing client/server connections in a distributed environment π. A server task is initialized to wait for incoming requests from clients. Clients open a connection to the server task, which executes on some host processor waiting for incoming requests. Messages with identifying type codes are passed between processes, with optional appended data blocks. The interface is non-blocking and accepts arbitrary data messages of arbitrary length β literally arbitrary length, bestie π .
Here's an example: interaction with a remote data archive may require a request for services, user authentication, permission granting, index hierarchy queries β with manipulation of results, archive request processing, delivery of output products β electronically or otherwise π. The archive is a server that responds to service requests from clients; how many may be served at one time and whether public access and private services are supported is an implementation issue. Since no direct interactive support is required (if smart client agents are in use), the message paradigm can support the requirements β¨.
A [counter] example: Athena's XWindow uses a client-server model to provide screen/window management services for distributed processes πΈ. XWindow is very permissive of different window management policies and provides hooks for implementing various user interfaces. At the lowest level, XWindow requires a reliable duplex byte-stream, which can be implemented in many operating environments. Because XWindow wants to manage all process-window communications directly, it builds its own block stream protocol to manage the connection. This permits the interactive update required by some processes, but basically is the same paradigm as PKG, defined within the window system π.
π PKG INTERFACE DESCRIPTION π
The PKG interface implements a client-server network connection that multiplexes synchronous and asynchronous messages across stream connections π«. Connection control is provided by open/close routines, where the client authentication is handled prior to establishing the connection π. Server side actions include initialization, client connection, and client request servicing. Client side actions include server request, reply processing, possible connection negotiation, and data transfer processing. So both sides have stuff to do β it's giving full-stack but make it 1986 π
.
A connection is established when a client issues an open to a server, running on some host. Which hosts provide required services must be determined prior to connection requests, using some service directory β yes, you gotta know where to knock before you knock π«Ά. Default connection processing is stream oriented, where a process may get data as it is available on the connection. A blocking read may also be issued, waiting for any complete message, or just for messages of a specified type. The connection remains open until explicitly closed by either client or server. No ghosting this connection, okay? π
Once the connection is established it may be used to process a full-duplex asynchronous byte stream, or a buffered, synchronous query/reply exchange π. Message type multiplexing supports user actions based upon message type, where the multiplexing is handled in the interface. Different message type streams may operate using different flow control over the same connection. Multiplexing is handled in the interface by pre-pending a header block to the message before transmitting it to the reader. Data is transmitted as received, but the header is transmitted in Internet network byte/bit order β that detail matters, bestie β.
A package connection may incur significant overhead if many short messages are passed between processes, so a buffered stream connection may be requested π. The application calls the pkg_stream interface rather than the usual pkg_send request, and PKG builds a message buffer and passes it to the client as it gets filled. A stream buffer may be flushed with an explicit call to the pkg_flush routine β flush it, slay π
.
π PKG INTERFACE DEFINITION π
okay bestie this is the actual menu of everything the interface gives you. Three flavor groups: control, I/O, and the handler stuff. Buckle up π
β¨ Control Interfaces β¨
like, the "who are you and are we connected" girlies π¦
PKG_OPEN β Open a connection to a host
pkg_open( host, service, protocol, uname, passwd, pkg_switch, errlog )
returns ptr_to_connection
so we are the client here β we're the one reaching out, making a connection to a server running on some host. the return value is literally the connection handle, that's your new bestie for the rest of the convo π«Ά
protocol, username, and password are all optional, no pressure.
PKG_CLOSE β Close a connection
pkg_close( ptr_to_connection )
returns VOID
this is when either side β client or server β wants to gracefully close a connection. no drama, just a clean goodbye π
PKG_TRANSERVER β Become a one-time network server on the already accepted connection
pkg_transerver( pkg_switch, errlog )
returns ptr_to_connection
okay so picture this: you already have an established connection, and now you want to turn it into a server situation. this routine creates a pkg_conn structure for it and initializes it to use the given pkg_switch and error logging function. π§
it's needed by servers that get created by some other process after the connection is already established. the classic example? processes started by the UNIX "inetd". iconic honestly.
PKG_PERMSERVER β Create a network server and listen for connections
pkg_permserver( service, protocol, backlog )
returns listen_file_desc
the service port gets determined, and a listen is hung on the port that's bound to the socket. the return value is the file descriptor of the socket. β
for this kind of server, you use pkg_getclient to accept connections from clients β that's the next one, keep reading bestie.
PKG_GETCLIENT β Used by a server to accept a client connection
pkg_getclient( listen_file_desc, pkg_switch, errlog, nodelay_flag )
returns ptr_to_connection or NULL or ERROR
if possible, the connection request from the client gets accepted, a pkg_connection block is created, and a ptr to it is handed back to the server. π
the server can request non-blocked service by setting the nodelay_flag to TRUE. slay, no waiting around.
πΈ I/O Interfaces πΈ
the actual "sending and receiving bytes" part, aka the main character energy of this whole page π
PKG_GET β Read bytes from connection, assembling message
pkg_get( ptr_to_connection ) returns
returns message_arrived, more_data_coming or ERROR.
get waits until a message header is received, and then calls the user-specified message handler for that message type. π¦
your program should call this routine whenever there's data waiting on a connection and the program isn't otherwise waiting on a specific message type. it returns directly to the caller if no header is available, or if only a partial message came in β otherwise it calls the user-specified message type handler. ngl that's kind of the whole vibe.
PKG_SEND β Send a message on the connection
pkg_send( message_type, data_buff, buff_len, ptr_to_connection )
returns bytes_sent or ERROR
send constructs a message header for the data buffer and transmits it on the connection. if only part of the message can be sent, the actual number of bytes transmitted is what comes back to you. lowkey important, don't ignore it.
any data in the stream buffer gets flushed first. π«§ and here's the cute trick: if the stream buffer needs flushing, and the message is less than MAXQLEN (currently 512) bytes long, and there's sufficient room left in the stream buffer β this message gets "piggybacked" on by copying it to the buffer before flushing. efficient queen.
and if available, "writev" is used to send the header and data with one syscall. π₯
PKG_2SEND β Send a two part message on the connection
pkg_2send( message_type, data_buff1, buff1_len, data_buff2, buff2_len, ptr_to_connection )
returns bytes_sent or ERROR
2send does the exact same job as pkg_send except the data arrives in two parts. this happens a lot when it's, like, a command followed by user data. β¨
the point? user data copies can be avoided while still allowing a single trip in and out of the kernel on the receiving end. we love a shortcut that's also correct π
PKG_STREAM β Send a buffered data stream on the connection
pkg_stream( message_type, data_buff, buff_len, ptr_to_connection )
returns bytes_sent or ERROR
Pkg_stream connections give you a low network overhead connection β the tradeoff being that interactive responsiveness isn't required. think "batch mode, not chat mode" π
connections have a PKG_STREAMLEN (currently 4096) byte stream buffer. if the current message is less than MAXQLEN (currently 512) bytes in length, and space remains for it, it gets appended onto the end of that buffer. otherwise the buffer is implicitly flushed by sending this message via PKG_SEND. π
PKG_FLUSH β Flush the stream buffer out on the connection
pkg_flush( ptr_to_connection )
returns bytes_sent or ERROR
flush takes the current contents of the stream buffer that stream is building up and flushes it out to the connection. this gives programs explicit control over the stream interface where it's required. π
PKG_BLOCK β Wait for a complete message of any type
pkg_block( ptr_to_connection )
returns message_arrived or ERROR
this routine blocks, waiting for a complete message of any type to arrive on the connection. then the user message handler gets called to process it. π
you can call this in a loop when you're waiting for asynchronous messages, or for the arrival of messages whose type you're not sure about. iykyk.
PKG_WAITFOR β Wait for a message of specific type, return in user buffer
pkg_waitfor( message_type, user_buff, buff_len, ptr_to_connection )
returns buff_len or ERROR
this one does a blocking read on the connection until a message of message_type is received. while it waits, asynchronous messages and messages of other types get processed. the message itself comes back in the user's buffer. π«Ά
PKG_BWAITFOR β Wait for a message of specific type, return in allocated buffer
pkg_bwaitfor( message_type, ptr_to_connection )
returns ptr_to_buffer or ERROR
same blocking-read deal as before β it waits on the connection until a message of message_type shows up, processing asynchronous messages and other types in the meantime. πΈ
the difference? the message comes back in a newly allocated buffer that the caller must free. yes, that's on you. don't forget or it's a leak bestie π
π§ User Message Handler Interface π§
the part where you get to define what happens when a message lands π
PKG_SWITCH β Table of User Message Handler Pointers
struct pkg_switch {
unsigned short pks_type; /* Type code */
int (*pks_handler)(); /* Message Handler */
char *pks_title; /* Description */
};
pks_handler( ptr_to_connection, message_buff )
returns ignored int
the user may specify handlers for one or more message types. when these routines get called, they're passed a pointer to the connection and a pointer to a buffer containing the message. π
heads up: the user message handler routine is responsible for freeing the message buffer after processing it. if there's no message handler for that message type, the message is discarded and the buffer is freed. π
the switch routine uses an external message handler array, created by the caller, that defines message types, handler entry points, and a message descriptor field.
and the pkg_switch list that gets passed to some of the functions above? it's a NULL terminated array of structures containing the message type, a pointer to a function which handles messages of that type, and a descriptive string. β
a separate pkg_switch array is kept for each connection, so a single application can carry out several entirely different PKG conversations at the same time. multitalented behavior, we love to see it π
PKG_CONN β Connection block structure
struct pkg_conn {
pkg_file_desc, pkg_magic, mess_len, ptr_to_buffer,
curr_buffer_pos, pkg_switch, errlog, stream_buff,
curr_stream_pos
};
connection block structures get created by pkg_open or pkg_getclient when a connection is established, and they're freed when the connection is closed. π¦
this connection block gets used during all I/O requests, and it contains the pointer to the start of the current buffer and to the current position in the buffer for incomplete reads or writes. the mess_len field specifies the number of bytes expected by get. the pkg_switch array, plus a pointer to an error logging function for this connection, is included too. and the stream buffer lives in the structure as well. π
see pkg.h for the actual names of the structure elements β don't come for me, that's just where they live π
π IMPLEMENTATION NOTES π
at least two implementations of the PKG interface have been done β one based on TCP/IP connections, the other based on DECnet connections. future releases of PKG will be layered on top of a virtual stream library known as NET. π§
the implementation running on 4.2BSD UNIX takes advantage of the "writev" syscall, which can output the header and data in a single syscall/TCP_PUSH cycle. π₯
ngl though β the advantage of the short data copy is realized only when writev can not be used, or when a pkg_flush is not required (a la PKG_STREAM). read that twice, it matters β¨
π FUTURE DEVELOPMENTS π
further development of the lower levels of the interface will be provided so that this message passing abstraction can be provided on top of both DECnet and TCP/IP network layers, with other implementations provided as required. π
the program interface layer is to be as described in the body of this note, and the lower layers will interface to the network layers using whatever subroutine interfaces are most appropriate for the system hosting the interface. a simple read/write/open/close abstraction may be provided at a lower layer to further isolate the network layers from the message handling layer. π
where it's necessary to cross network protocol boundaries, message relay processes will be created to reside on a Janus host that straddles the two domains. domain routing and relay point information will be maintained above the network layer within the extended PKG interface. β
clients and servers will need to determine server host id information from some available database. server names must either carry domain information, or such information must be available as part of the server database. within the extended PKG interface, routing information for domain boundary transits must be maintained, and appropriate Janus host systems connected to provide relay services. π¦
and here's the honest part: it is not anticipated that full asynchronous I/O implementation will work particularly well across domain boundaries π β but all other forms of message transfer should work quite well. as already noted, buffered stream I/O may be perfectly satisfactory for many applications where full interaction isn't required. where applicable, this mode of interface is to be preferred over full asynchronous I/O. π
further development of this interface layer on other protocol families is possible, perhaps even on low level networks like BITNET, and certainly on connection oriented X.25 or OSI networks. the range, honestly π₯
π AUTHOR π
Peter B. Shames
π BUG REPORTS (yes, this is your moment) π
okay so bestie, if you spot anything in here that's wrong β like straight-up inaccurate, not just "hmm I'd have phrased it cuter" β you gotta report it. And the way you do that is electronic mail, sent to:
devs@brlcad.org
that's it. that's the whole vibe. reports of inaccuracies should be submitted via electronic mail to devs@brlcad.org π
ngl, accuracy matters, so if something in this page lied to you, the devs genuinely wanna know. don't just suffer in silence with a wrong manpage, that's not the main character energy we're going for π«Ά
and that's a wrap on LIBPKG(3)! β¨ you made it to the end, icon behavior honestly π
just so we're keeping it π― to the very last line β this page is from BRL-CAD (that's the source/version situation, periodt) and its manual page reference is LIBPKG(3), section 3 and all π
now go forth and write some iconic messages, bestie π πΈπ¦
π¦ where this page comes from
- nixpkgs unstable: brlcad 7.44.0
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms