sk(3)
β¨ NAME β¨
sk β the (done) P Client/Server library, Rev. 03-May-1996, and this whole page is the sk(3) manual bestie π. It's the Library Functions Manual entry for a whole little friend group of routines that let your programs do client/server stuff over sockets. No cap, it's giving "I built my own RPC and it slaps". π
πΈ SYNTAX πΈ
Okay so here's the full roster of function signatures, exactly as they live in the wild. Grouped by vibe. π«Ά
π Server startup
int sock = sk_start(char *service, char * mode);
int sock = sk_astart(char *service, char * mode, char *authorisation_file);
int plug = sk_accept(int sock, int queue_slots);
int status = sk_ack(int plug);
char *caller = sk_caller(int plug);
sk_urgent(int plug);
π Client Connection
int plug = sk_open(char *host, char * service);
int plug = sk_connect(char *host, char * service, char *username, char *password);
int stat = sk_close(int plug);
π¬ Client/Server Dialogue
int bytes = sk_read(int plug, char *buf, int len);
int bytes = sk_get(int plug, char *buf, int len);
int bytes = sk_gets(int plug, char *buf, int len);
long value = sk_getl(int plug);
int bytes = sk_write(int plug, char *buf, int len);
int bytes = sk_put(int plug, char *buf);
int bytes = sk_puts(int plug, char *buf);
int status = sk_putl(int plug, long value);
π¦ Data Exchange on Server Side
int bytes = sk_toclient(int plug, int file_to_send );
int bytes = sk_fromclient(int plug, int file_to_receive);
int status = sk_errsend(int plug, char * message);
int status = sk_perrsend(int plug, char * message);
π Data Exchange on Client Side
int bytes = sk_obeyserver(int plug, int (* digest_routine)(), int (*more_routine)());
int bytes = sk_fromserver(int plug, int file_to_receive, file_to_send);
char *message = sk_error();
sk_kill(int plug, int signo);
π Logging & Debugging
FILE *old_logfile = sk_setlog(FILE *logfile);
FILE *old_debfile = sk_iolog(FILE *debfile);
int lev = sk_log(int level, char *format, ... );
π§° Server Utilities
int status = sk_umatch(char *remote_id, char * template);
βοΈ Socket Configuration
int bytes = sk_setb(int socket_size)
π DESCRIPTION π
So this whole set of functions lives in two libraries, and the split is everything:
/usr/local/lib/libsk.aβ this is where the client routines hang out π«Ά/usr/local/lib/libskserv.aβ and this one is for the server routines π
Together they let you build client/server dialogues using socket(2) facilities in stream mode. The iconic part? A client connected to the Internet can reach a server connected to the Internet independently of the machine architectures β no "wait, are you big-endian or little-endian" drama, it just works β¨. The client/server model here is a way of implementing RPC (Remote Process Call). Periodt.
The service is a character string representing either a number in the range 1024-2047 or a symbolic service name appearing in the file /etc/services(5). It's literally telling you which kind of service we want to connect to, bestie. π
π INCLUDE FILES π
All the function declarations are gathered into two files, one per side of the convo:
/usr/local/include/sk.hβ for client routines π/usr/local/include/skserv.hβ for server routines π
π SERVER STARTUP π
The server can be launched either as a daemon by inetd(8), or as a standalone program in background mode. Which one you get is decided by the mode argument of the sk_start routine: when it contains the character d, the server is started via inetd(8), which requires a specific line in the /etc/inetd.conf(5) file. iykyk.
The possible characters in the mode argument are:
dβ to start in daemon mode πlβ to log the server events in thesyslog(8)πvβ to add debugging messages in the log πvv(two v's) β for very verbose (debugging) log π₯
sk_start returns -1 in case of failure, and prints an error message in the log; otherwise sk_start returns a non-negative socket number. Slay.
sk_astart is a version which uses an authorisation file, which can be the /etc/passwd(5) file, or a file similar to it: the client has to provide a username and a password for identification. This client normally uses the sk_connect routine to identify himself. The authorisation_file parameter may be:
NULL(i.e.(char *)0) β in this case no user authentification is performed π- void string (i.e.
"") β in this case the standard/etc/passwdfile is used π« - a valid file name β which is then used instead of the
/etc/passwdfile. See below the description of the authorisation file π
Once launched, the server will normally listen to the created socket, waiting for a client connection. The sk_accept routine provides this functionnality, i.e. a successful return from sk_accept means that a client asked for a dialogue with the server π¬. The queue_slots argument specifies the size of the queue of clients waiting for a connection with the server: when the queue is full, the unsuccessful client will get an error message. π
After a successful sk_accept, the server has to identify himself, and ask for client's identification. This is achieved with the sk_ack function. π€
In a daemon mode, the function sk_accept is called by the parent process, which forks; the sk_ack function is normally called by the child process, while the parent process waits for another connection. See the example in the Example section below. π«Ά
Once a client has started a dialogue with the server (via sk_accept and sk_ack), the client's name and address can then be retrieved with sk_caller. The syntax of the character string returned by sk_caller is:
Remote_Username[!]@InterNet_Number(Host_Name):
Provided_Username
where the ! indicates a user with root privileges. π
A NULL string ((char *)0) is returned by sk_caller in case of a bad plug number or in case of error. π
The sk_urgent routine allows the server to receive signals sent by the connected client; see below the Signal Communication section. π‘
π CLIENT CONNECTION π
A program can start a dialogue with a server using the sk_open (no identification) or the sk_connect function. service, as for the server, is either a numeric string representing a number in the range 1024-2047, or is a name normally existing in the /etc/services file. The username and password provided by sk_connect, as well as the client's node and username, are checked against what is known in the authorisation_file (see below). β¨
A failure to reach the server specified by its hostname and its service name or number returns a negative value, and an error message is printed or logged (see the sk_setlog function); otherwise sk_open or sk_connect return a non-negative number to be used as the first argument of dialogue routines. π
In case of failure, the returned codes mean the following:
-1β the connection to the Server can't be established due to network problems, like unknown host or service π-2β the Server has been reached, but refused to pursue for invalid username/password or unauthorized machine π«
π¬ CLIENT/SERVER DIALOGUE π¬
When the connection between the client and the server is established, both can read or write on the socket. Which of the client or server has to read or write on the socket is defined only by the calling programs; the dialogue has therefore to be carefully designed to avoid endless waits on an opened socket. β οΈ Other functions described in the next section use a very simple protocol for exchange of data.
Four functions are provided available for reading on the socket, and four other are provided for writing on the socket. All these 8 functions return -1 in case of error. π₯
int bytes = sk_read(int plug, char *buf, int len)is the simplest reading interface, and reads up to len bytes. The returned number of bytes is generally smaller than len: no attempt is made to fill buf. πint bytes = sk_get(int plug, char *buf, int len)fills buf from what comes on the socket. The returned number of bytes is therefore identical to len, unless an error occured or the partner closed the connection. π«Άint bytes = sk_gets(int plug, char *buf, int len)fills buf up to len bytes, or until a newline is found. The returned length includes the newline. Notice however that buf is not terminated by the NULL character. πlong value = sk_getl(int plug)reads a long integer (4-byte integer) on the socket (normally issued by the partner usingsk_putl). Byte swapping is performed if the local machine architecture differs from the network one. πint bytes = sk_write(int plug, char *buf, int len)writes len bytes onto the socket. Its returned number of bytes is identical to len, unless an error occured or the partner closed the connection. β¨int bytes = sk_put(int plug, char *buf)writes a null-terminated string on the socket. π¬int bytes = sk_puts(int plug, char *buf)writes a null-terminated string as a line on the socket, i.e. a newline is appended to the string before being sent to the partner. πint status = sk_putl(int plug, long value)sends a4-byte long integer to the partner; byte swapping is performed if necessary. π
Note that plug is a file handle, and it is therefore possible to use standard i/o routines after the association of a FILE structure generated by the fdopen(2) routine. π
πΎ DATA EXCHANGE ON SERVER SIDE πΎ
okay so bestie, four control characters are used for the data exchange functions. When the server sends to the client the character:
Dβ It means: βI've finished to talk. It's now up to youβ π€Bβ It means: βI'll send Counted Buffers, i.e. data prefixed with its length expressed as a 4-byte integer. I'll continue to send Counted Buffers until the prefix specifies a zero lengthβ. This convention is used bysk_toclient. π¦Cβ It means: βI'll send a single counted buffer, i.e. data prefixed with its 4-byte length. It contains normally an error message. π¨Fβ It means: βPlease send me a Counted Bufferβ. This convention is used bysk_fromclient. πββοΈ
The detailed functionnalities are:
=> int bytes = sk_toclient(int plug, int file_to_send )β is used by the Server to send a file to the connected client, using theBconvention:- Server sends
B - Client acknowledges with
F - Server sends counted buffers (buffers preceded by their 4-byte length). The last counted buffer has a length of zero.
- Server sends
Upon return from sk_toclient, the Server has still to send a D to the client to tell him that it's up to him to talk. file_to_send must have been opened in read mode by open (2).
=> int bytes = sk_fromclient(int plug, int file_to_receive)β is used by the Server to get a data set from the connected client, using theFconvention:- Server sends
F - Client returns a counted buffer (4-byte integer expressing the buffer length, followed by the actual buffer)
- Server asks for the next buffer with
Fif the length of the received counted buffer is not zero.
- Server sends
sk_fromclient stops just after the client sent a zero length data buffer; Upon return from sk_fromclient, the Server has still to send a D to the client to tell him that it's up to him to talk. file_to_receive must have been opened in write mode by open(2).
=> int status = sk_errsend(int plug, char * message)β allows to send a single counted buffer to the client, which generally represents an error message. If the client uses thesk_obeyserveror thesk_fromserverroutine, the server has to send aDto the client to tell him to return from thesk_obeyserverfunction.=> int status = sk_perrsend(int plug, char * message)β is similar tosk_errsend, butmessageis followed by the system error message as inperror(2).
π¦ DATA EXCHANGE ON CLIENT SIDE π¦
sk_fromserverβ is used by the Client to follow the above conventions: it reads what comes over the plug socket and writes it ontofile_to_receivewhich must have been opened in write mode byopen(2); when the Server asks to send a Counted Buffer, it reads it fromfile_to_send. The return fromsk_fromservertherfore occurs
=> int bytes = sk_fromserver(int plug, int file_to_receive,
file_to_send)
=> either when the server sends a D in non-buffered mode;=> or when an error occurs.
sk_obeyserverβ is similar tosk_fromserver, but routines are used instead of files:
=> int bytes = sk_obeyserver(int plug, int (* digest_routine)(), int
(*more_routine)())
=> digest_routine(char *buf, int length)collects what's sent by the Server=> more_routine(char *buf, int length)is called when the Server asks for more data.
Both digest and more routines must return the number of bytes processed, 0 for end-of-file, and -1 for error . As for sk_fromserver, the client has normally to send something to the Server, since he got the D telling he has to talk.
=> char *message = sk_error()β returns the last encountered error message. This function is normally to use when one of the sk routines returns -1. Note that errors are also written to stderr by default; another file - or no file at all - may be chosen as a logfile.
The sk_kill routine allows the client to send signals to the sever; see below the Signal Communication section.
π LOGGING & DEBUGGING π
The server normally logs the occuring events, either to a log file (which can be the stdout terminal), or in the system log. The usage of the syslog facility (in file /var/log/syslog) is recommended when the server is launched in daemon mode by inetd(8).
At any time, the server can log details in the syslog with the sk_log function, which will direct the message to the currently opened logfile or to the syslog.
The first argument of the sk_log(int level, char * format, ...) function is a number defined in the syslog.h file, or -1 to close the log file; this level argument is returned in case of success. The other arguments of the sk_log are similar to those of the printf(3) function.
The sk_setlog function allows to switch the log file at any time, and returns the previously active logfile. Notice that the syslog is indicated by a NULL value: to use the syslog as a log file, use the call
old_logfile = sk_setlog((FILE *)0))
A very verbose debugging is activated with the sk_iolog function: whatever is read or written on the socket is printed on the supplied log file. As sk_setlog, sk_iolog returns the old logfile. A NULL logfile argument asks to stop this debugging feature.
π SIGNAL COMMUNICATION π
The client can send a signal to the server via the sk_kill(int plug, int signo) routine; signo must be a valid signal (see signal(3)). However, the signals can be received by the server only after the server has specified that he would accept signal interruptions. The acceptance of signal interrupts is specified once with the call to sk_urgent(int plug).
π SERVER UTILITIES π
char *sk_caller(plug)β allows to retrieve a client identification; it is described above.int sk_umatch(char *remote_id, char *template )β is a routine which returns0ifremote_identificationas returned bysk_callerdoes not match the template of authorizedRemote_Username@Internetseparated by commas; the wild chars*and?can be used in this field. Remember also the ending!in the username which indicates root privileges. Some examples of template:=> root!@130.79.*.*matches the root user from any of the machines which Internet number starts with130.79=> *@130.79.128.5,root!@130.79.*.*matches anybody having an account on the130.79.128.5machine, or the root user on other machines with Internet number starting with130.79
π AUTHORISATION FILE π
An Authorisation File can be used to restrict access to authorized users via a Username and a Password. The Authorisation File can be identical to the /etc/passwd(5) file when the third parameter of the sk_start routine is a blank string ""
The three fields which are used are:
- the Username the client has to provide (default is
guest) - the Password (encrypted)
- the 5th field (also called
gecoswhich may contain a list of allowedRemote_Username@Internet_Number, templates separated by commas or blanks; the wild chars*and?can be used in this field
The password in the /etc/passwd file can be modified with the passwd(1) utility, as well as the gecos field with the -f option. An alternative to the passwd -f is the chfn (1) utility.
π SOCKET CONFIGURATION π
The size of blocks transferred onto the network can be changed with the sk_setb function. The standard size is generally 8 blocks (4K). sk_setb returns the current configuration.
A negative or null value of socket_size does not modify the size of socket blocks; sk_setb(0) can therefore be used to know the current socket size.
π§ EXAMPLE OF A SERVER π§
The following shows the basic writing of a server which creates a child via fork(2) who has to deal with the client. The server is assumed to receive the name of a file and to send its contents to the Client.
#include <skserv.h>
#include <signal.h>
#include <sys/wait.h>
void on_death() /* Read Zombie status */
{ while(wait3(NULL, WNOHANG, NULL) > 0) ; }
static int theplug;
void on_intr() /* When the Serveur in Interrupted by the Client
*/
{ sk_puts(theplug, "Bye-Bye"); exit(1); }
/* A routine to send a file to the Client */
display_file(plug, filename) int plug; char *filename;
{ int file;
file = open(filename, 0);
if (file < 0) perrsend(filename);
else {
sk_toclient(plug, file);
close(file);
}
}
main()
{
int sock, plug;
char buffer[133];
sock = sk_start("service", "v"); /* Verbose option */
if (sock < 0) exit(1);
signal(SIGCHLD, on_death); /* Handler for Zombies
*/
while(1) { /* Loop on incoming connections
*/
plug = sk_accept(sock, 1);
if (plug < 0) exit(1);
if (fork()) { /* Father Here. He doesn't need plug */
close(plug);
continue;
}
/* ===Child Here. sock is only for new connections;
therefore close it */
close(sock);
sk_ack(plug); /* Acknowledge the Client */
sk_log("%s just called\n", sk_caller(plug));
sk_urgent(plug); /* Allow client to send Signal */
signal(SIGINT, on_intr);
theplug = plug; /* To communicate with on_intr */
sk_gets(plug, buffer, sizeof(buffer)); /* Get question
*/
display_file(plug, buffer);
sk_put(plug, "\04"); /* Tell the Client: I've finished to
talk */
exit(0);
}
}
The corresponding client can be aclient(1), or the following code if the program is assumed to have three parameters which are the host, the service, and the filename to list.
main(argc, argv) int argc; char *argv[];
{
int plug;
plug = sk_open(argv[1], argv[2]);
if (plug < 0 ) { perror(argv[1]); exit(1); }
if (sk_puts(plug, argv[3]) < 0) exit(1);
sk_obeyserver(plug, 1, 0); /* Digest = file#1 (stdout) */
}
π SEE ALSO π
aclient(1) aserver(1) chfn(1) socket(2) open(2) fdopen(2) fork(2) perror(2) printf(3) passwd(1) passwd(5) services(5) inetd(8) signal(3) syslog(8) wait3(2)
π QUESTIONS & PROBLEMES π
a Fox (francois@simbad.u-strasbg.fr)
That's the whole vibe, bestie. This was the sk(3) manual page β source/version and date live in the footer, but we're out. π
β¨
π¦ where this page comes from
- fedora 43: cdsclient 4.07-5.fc43 Β· GPL-3.0-only
- fedora 44: cdsclient 4.07-7.fc44 Β· GPL-3.0-only
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms