For example, a network write() for 50 bytes may only write 30 bytes; Sio would continue writing until all 50 bytes have been sent.
Allow operations that would normally block can be set to timeout after a customizable period of time.
Catch the SIGPIPE signal which would cause an unexpecting process to exit.
A frequent source of problems for beginning programmers is the situation where their process is suddenly no longer running because a write to a socket caused a broken pipe. This problem can be difficult to diagnose if the process is running as a daemon process in the background.
Resume operation when system calls are interrupted (operations errout with EINTR).
Cater to the internet socket interface (i.e. struct sockaddr_in).
Simplified interface to name service.
Library routines can take a textual address specification instead of you having to prepare a struct sockaddr_in.
For connectionless exchanges, sio uses the UDP/IP protocol's datagram messages. There is an implicit definition of a record boundary, because each message is treated as a record. So, three writes results in three separate messages.
For example, let's say a sender writes three 50-byte messages. If a receiver does a 20-byte read followed by 100-byte read followed by a 50-byte read, the receiver would get the first 20 bytes of the first message, 50 of 50 bytes of the second message, and 50 of 50 bytes of the third message. It's important to understand that in the first read that since UDP datagrams are message oriented that the remaining 30 bytes of the first message are lost, and that although the second read is for 100 bytes, the read immediately returns as finished even though only 50 bytes were actually read.
With UDP datagrams, there are also other important implications that
affect message ordering, duplication, and overall reliability.
A stream server socket is created using SNewStreamServer(), which returns a socket file descriptor ready to accept new client connections. After a socket descriptor is obtained, your server program can then use SAccept() to establish a connection with a client.
A stream client socket is created using SNewStreamClient(). After a socket descriptor is obtained, your client program can use SConnectByName() or SConnect() to initiate a connection to a server.
A datagram server socket is created using SNewDatagramServer(). After a socket descriptor is obtained, it is ready to receive messages from clients using SRecvfrom() and reply back with SSendto().
A datagram client socket is created using SNewDatagramClient(). After a socket descriptor is obtained, it is ready to communicate with servers using SSendto() and SRecvfrom().
All socket descriptors are disposed of with SClose()
when communication is finished.
Now go to the "sio" directory you just made. There is a script you must run which will checks your system for certain features, so that the library can be compiled on a variety of UNIX systems. Run this script by typing "sh ./configure" in that directory. After that, you can look at the Makefile it made if you like, and then you run "make" to create the "libsio.a" library file.
Finally, install the library and headers. You can manually copy the
files, or you can run "make install" to copy the files for you.
If you choose to "make install" you may want to edit the Makefile
if you do not want to install to the /usr/local tree.
This takes a textual internet address specification and converts it to a struct sockaddr_in. An address string may be any of the following forms:
If the string contains a hostname but a port number does not appear to be present and the defaultPort parameter is greater than zero, then the address structure will use defaultPort as the port number.
The function returns a negative number if the string could not be converted, such as the case where the hostname was not found in the name service. Name service is not used unless there is a name instead of an IP address, so if you don't want to use name service, only use IP addresses.
Example:
struct sockaddr_in addr; int result; result = AddrStrToAddr("ftp.ncftp.com", &addr, 21); result = AddrStrToAddr("ftp.ncftp.com:21", &addr, 21); result = AddrStrToAddr("206.28.166.234:21", &addr, 21); result = AddrStrToAddr("ftp://ftp.ncftp.com", &addr, 0); result = AddrStrToAddr("[email protected]", &addr, 0);
This function takes a struct sockaddr_in and converts into a readable internet textual address.
The dns parameter specifies if name service should be used to lookup the symbolic hostname from the raw address. If zero, it expresses the raw IP address in the standard dotted-decimal format, like 192.168.1.13.
The fmt parameter is a magic cookie string, with the following cookies:
Example:
char str[128]; fputs(AddrToAddrStr(str, sizeof(str), &sin, 1, "%h"), stdout); fputs(AddrToAddrStr(str, sizeof(str), &sin, 1, "%h:%p"), stdout); fputs(AddrToAddrStr(str, sizeof(str), &sin, 1, "%s://%h"), stdout);
This function is used to dispose of a SReadlineInfo structure that was created using InitSReadlineInfo(). You're required to use this to free dynamically allocated buffers it creates unless you specified that you wanted to use your own buffer, in which case it's optional (but recommended for clarity).
This rarely used function is used to reset and clear the buffer used by SReadline(). It acts similarly to a fflush(stdin).
This utility routine returns the size of the socket's internal buffers, as maintained by the kernel (or socket library). It does this by calling getsockopt() with the SO_RCVBUF and SO_SNDBUF options, if they are defined. In the event they aren't defined, it returns a negative result code.
Example:
size_t rsize, ssize; if (GetSocketBufSize(sockfd, &rsize, &ssize) == 0) ...
This utility routine returns whether linger mode has been turned on, and also sets the amount of time in the lingertime parameter.
Example:
int lingertime; if (GetSocketLinger(sockfd, &lingtime) > 0) /* linger is on... */ ...;
This utility routine returns whether the Nagle Algorithm is in effect (TCP_NODELAY mode not on).
This function is used to prepare a SReadlineInfo structure for use with the SReadline() function. The sockfd parameter specifies the socket that will be used for line buffering. The sio library does not open or close this socket.
The buf parameter is the area of memory to use for buffering; it should be large enough to hold several lines of data. If buf is NULL, the function will malloc() one of bsize bytes, otherwise it is assumed that buf is maintained by you and is of size bsize bytes. If you let sio malloc() this buffer, you must also use DisposeSReadlineInfo() to free it when you're finished with it.
The tlen parameter is the timeout value (in seconds) to use for each call of SReadline().
The requireEOLN parameter specifies how to handle a situation where the remote line is longer than the line you asked for (buf of bsize characters). In any case, when this happens, you will receive a full buf back from SReadline(), and the buffer will not have a newline character ('\n') as the last character.
If you set requireEOLN to 0, then SReadline() will stop at the filled buf and return. This will allow you to get the next chunk of the remote line by calling SReadline() again.
If you set requireEOLN to 1, then SReadline() will continue reading the remote input line and discard the extraneous characters until a newline is reached. The main benefit to this is that the next call of SReadline() will be ready to read a new remote line, and not be stuck in the middle of a long previous line.
Example:
SReadlineInfo sri; char localbuf[2048]; if (InitSReadlineInfo(&sri, sockfd, NULL, 512, 10, 0) < 0) perror("malloc 512 bytes failed"); ...or... (void) InitSReadlineInfo(&sri, sockfd, localbuf, sizeof(localbuf), 10, 0);
This is does an accept(), with a timeout of tlen seconds (tlen may be zero to mean block indefinitely). If no new connection is accepted, kTimeoutErr is returned, otherwise a new socket or (-1) is returned. The socket is still usable if a timeout occurred, so you can call SAccept() again.
This calls bind(), to the wildcard address with the specified port (not in network byte order). The nTries parameter tells how many attempts it tries before giving up (which is useful if other processes are grabbing the same port number). If the reuseFlag parameter is non-zero, SBind() tries to turn on the SO_REUSEADDR (and SO_REUSEPORT, if available) socket options before binding.
Normally you will not call this function directly, since SNewStreamServer() and SNewDatagramServer() do this for you.
This is close() with a timeout of tlen seconds. Normally you don't need to worry about close() blocking, but if you have turned on linger mode, close() could block. SClose() calls close() for up to tlen seconds, and if the timeout expires, it calls shutdown() on the socket.
This is connect() with a timeout of tlen seconds (tlen may be zero to mean block indefinitely). If it returns (-1) or kTimeoutErr, the socket is no longer usable and must be closed.
This is connect() with a timeout of tlen seconds (tlen may be zero to mean block indefinitely). If it returns (-1) or kTimeoutErr, the socket is no longer usable and must be closed. The difference between SConnect() is that this function takes a textual address string instead of a struct sockaddr_in.
Example:
if (SConnectByName(sockfd, "http://www.ncftp.com", 15) == 0) ...
This isn't too useful at present, since it just does listen(sfd, backlog). And, you will not call this function directly, since SNewStreamServer() and SNewDatagramServer() do this for you.
This returns a new datagram socket, which is ready to send (and then receive) datagrams. This function is just socket(AF_INET, SOCK_DGRAM, 0). If successful, it returns a non-negative socket descriptor.
Example:
int sockfd; sockfd = SNewDatagramClient();
This function creates a new socket and binds it to the specified port (not in network byte order) on the wildcard address. The nTries and reuseFlag are used when it calls SBind(). If successful, it returns a non-negative socket descriptor.
Example:
int sockfd; sockfd = SNewDatagramServer(13, 3, 0); if (sockfd >= 0) /* ready to receive requests on the daytime port (13) */
This returns a new stream socket, which is ready to SConnect() to a server. This function is just socket(AF_INET, SOCK_STREAM, 0). If successful, it returns a non-negative socket descriptor.
Example:
int sockfd; sockfd = SNewStreamClient();
This function creates a new socket, binds it to the specified port (not in network byte order) on the wildcard address, and turns it on for listening. The nTries and reuseFlag are used when it calls SBind(). The listenQueueSize is for the listen() call. If successful, it returns a non-negative socket descriptor.
Example:
int sockfd; sockfd = SNewStreamServer(80, 3, 0); if (sockfd >= 0) /* ready to accept HTTP connections */
This is read() on a socket descriptor, with a timeout of tlen seconds (tlen must be greater than zero). Like read(), it can return 0, (-1), or the number of bytes read, but in addition, it can also return kTimeoutErr.
If retry is set to kFullBufferRequired, SRead() does not return until size bytes has been read or EOF is encountered. This is useful if you expect fixed-length records, and it doesn't do much good until a complete record has been read. However, it is still possible that an EOF is encountered after some bytes have been read, and with retry set to kFullBufferRequired, you get EOF instead of that partial record. If you set retry to kFullBufferRequiredExceptLast, you get the partial record (and EOF on the next SRead()). Otherwise, if you should set retry to kFullBufferNotRequired and SRead() will return when there is some data read.
Example:
int nread; char buf[256]; while (1) { nread = SRead(sockfd, buf, sizeof(buf), 15, kFullBufferNotRequired); if (nread <= 0) { if (nread == 0) break; /* okay, EOF */ else if (nread == kTimeoutErr) { fprintf(stderr, "timed-out\n"); break; } else { perror("read"); } } (void) write(1, buf, nread); }
It is often desirable to process data from sockets line by line, however this is cumbersome to do on a socket descriptor. The SReadline() function allows you to do this, so you can do one call of the function and get back a line of input. It accomplishes this much the same way the standard library's I/O routines do this, using a buffer. However, it is usually not possible to determine if the standard library takes the same special I/O measures on socket descriptors that sio does, so using the standard library function fdopen() with a socket descriptor may or may not work the way you want.
SReadline() needs to maintain state information for each call, so a data structure is required. You must first call InitSReadlineInfo() to initialize a SReadlineInfo structure. After that, you may call SReadline() repeatedly until it returns 0 to indicate EOF. The function returns the number of characters in the input line, including a newline (however, carriage return characters are omitted) if a whole line was read. You must call DisposeSReadlineInfo() if you chose to let InitSReadlineInfo() use malloc() to allocate your buffer.
Example:
SReadlineInfo sri; char line[80]; int nread; if (InitSReadlineInfo(&sri, sockfd, NULL, 512, 10, 0) < 0) { perror("malloc"); exit(1); } while (1) { nread = SReadline(&sri, line, sizeof(line)); if (nread <= 0) { if (nread == kTimeoutErr) fprintf(stderr, "readline timed-out.\n"); else if (nread < 0) perror("readline"); break; } if (line[nread - 1] == '\n') { /* A complete remote line of input was read. */ /* For this example, omit the trailing newline. */ line[nread - 1] = '\0'; fprintf(stdout, "read complete line: [%s]\n", line); } else { /* A partial remote line of input was read. */ /* The next call of SReadline() will give us */ /* the next chunk of that remote line. */ fprintf(stdout, "read partial line: [%s]\n", line); } } DisposeSReadlineInfo(&sri); (void) SClose(sockfd, 3);
This is the corresponding wrapper for recv(), as SRead() is for read(). You will never need this function, unless you need the special receiving flags that recv() gives you.
This is recvfrom() with a timeout of tlen seconds (tlen must be greater than zero). Like recvfrom(), it can return 0, (-1), or the number of bytes read, but in addition, it can also return kTimeoutErr. Upon a timeout, the socket is still valid for additional I/O.
Example:
int nread; char buf[80]; struct sockaddr_in remoteClientAddr; nread = SRecvfrom(sockfd, buf, sizeof(buf), 0, &remoteClientAddr, 15); if (nread <= 0) { if (nread == kTimeoutErr) fprintf(stderr, "recvfrom timed-out.\n"); else if (nread < 0) perror("recvfrom"); }
This is the corresponding wrapper for recvmsg(), as SRead() is for read().
This is the corresponding wrapper for send(), as SWrite() is for write(). You will never need this function, unless you need the special receiving flags that send() gives you.
This is sendto() with a timeout of tlen seconds (tlen must be greater than zero). Like sendto(), it can return 0, (-1), or the number of bytes sent, but in addition, it can also return kTimeoutErr. Upon a timeout, the socket is still valid for additional I/O.
Since sendto() rarely blocks (only if the outgoing queue is full), you probably do not want to use a timeout or bother with its associated overhead; therefore Sendto() would be a better choice.
This is SSendto(), only you can use a textual internet address string instead of a struct sockaddr_in.
This is write() on a network socket with a timeout of tlen seconds (tlen must be greater than zero). Like write(), it can return 0, (-1), or the number of bytes sent, but in addition, it can also return kTimeoutErr.
The swopts parameter can be either 0 (almost always), or the special value kNoFirstSelect for a special case where you want to instruct SWrite() to assume that the socket has already been select()ed for writing.
This does a select() for reading with the file descriptors in the specified SelectSet. Using a SelectSet ensures that the first argument to select is always correct (the smallest correct value, for speed) and SelectR() does not destroy the original fd_set and timeval (it copies it to resultssp before using select()).
Example:
SelectSet ss, selected; SelectSetInit(&ss, 10.0); SelectSetAdd(&ss, sockfd1); SelectSetAdd(&ss, sockfd2); rc = SelectR(&ss, &selected); if ((rc > 0) && (FD_ISSET(sockfd2, &selected.fds))) ...
This does a select() for writing with the file descriptors in the specified SelectSet. Using a SelectSet ensures that the first argument to select is always correct (the smallest correct value, for speed) and SelectW() does not destroy the original fd_set and timeval (it copies it to resultssp before using select()).
Example:
SelectSet ss, selected; SelectSetInit(&ss, 10.0); SelectSetAdd(&ss, sockfd1); SelectSetAdd(&ss, sockfd2); rc = SelectW(&ss, &selected); if ((rc > 0) && (FD_ISSET(sockfd2, &selected.fds))) ...
This adds a descriptor to the set to select on.
Example:
SelectSet ss; SelectSetInit(&ss, 10.0); SelectSetAdd(&ss, sockfd);
Before adding members to the SelectSet structure, it must be initialized with this function. The timeout parameter initializes the timeout to use for future Selects.
This removes a descriptor from the set to select on. You will need to do this just before you close a descriptor that was in the set.
This is a simple wrapper for sendto(), which only handles EINTR for you. It does not worry about SIGPIPEs.
This is a simple wrapper for sendto(), which only handles EINTR for you. In addition, you can use a textual internet address string instead of a struct sockaddr_in.
Example:
if (SendtoByName(sockfd, msg, sizeof(msg), "elwood.ncftp.com:13") > 0) ...
This utility routine changes the size of the socket's internal buffers, as maintained by the kernel (or socket library). It does this by calling setsockopt() with the SO_RCVBUF and SO_SNDBUF options, if they are defined. In the event they aren't defined, it returns a negative result code. The operation is only performed if the size is greater than zero, so if you only wanted to change the receive buffer you could set rsize to greater than zero and ssize to 0.
This is an interface to the SO_LINGER socket option. The l_onoff parameter is a boolean specifying whether linger is on, and the l_linger parameter specifies the length of the linger time if enabled.
Example:
if (SetSocketLinger(sockfd, 1, 90) == 0) ...
This utility routine enables or disables the Nagle Algorithm (TCP_NODELAY mode is off or on). Generally you won't care about this, unless you're writing an interactive application like telnet, talk, or rlogin, where response time is more important than throughput.
Example:
if (SetSocketNagleAlgorithm(sockfd, 0) == 0) ...
/* ucase_s.c */ #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/wait.h> #include <netinet/in.h> #include <arpa/inet.h> #include <errno.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <time.h> #include <sio.h> static void ServeOneClient(int sockfd, struct sockaddr_in *cliAddr) { char buf[32], cliAddrStr[64]; int nread, nwrote, i; printf("subserver[%d]: started, connected to %s.\n", (int) getpid(), AddrToAddrStr(cliAddrStr, sizeof(cliAddrStr), cliAddr, 1, "<%h:%p>") ); for (;;) { nread = SRead(sockfd, buf, sizeof(buf), 15, kFullBufferNotRequired); if (nread == 0) { break; } else if (nread < 0) { fprintf(stderr, "subserver[%d]: read error: %s\n", (int) getpid(), strerror(errno)); break; } for (i=0; i<nread; i++) if (islower(buf[i])) buf[i] = toupper(buf[i]); nwrote = SWrite(sockfd, buf, nread, 15, 0); if (nwrote < 0) { fprintf(stderr, "subserver[%d]: write error: %s\n", (int) getpid(), strerror(errno)); break; } } (void) SClose(sockfd, 10); printf("subserver[%d]: done.\n", (int) getpid()); exit(0); } /* ServeOneClient */ static void Server(int port) { int sockfd, newsockfd; struct sockaddr_in cliAddr; int pid; sockfd = SNewStreamServer(port, 3, kReUseAddrYes, 3); if (sockfd < 0) { perror("Server setup failed"); exit(1); } printf("server[%d]: started.\n", (int) getpid()); for(;;) { while (waitpid(-1, NULL, WNOHANG) > 0) ; newsockfd = SAccept(sockfd, &cliAddr, 5); if (newsockfd < 0) { if (newsockfd == kTimeoutErr) printf("server[%d]: idle\n", (int) getpid()); else fprintf(stderr, "server[%d]: accept error: %s\n", (int) getpid(), strerror(errno)); } else if ((pid = fork()) < 0) { fprintf(stderr, "server[%d]: fork error: %s\n", (int) getpid(), strerror(errno)); exit(1); } else if (pid == 0) { ServeOneClient(newsockfd, &cliAddr); exit(0); } else { /* Parent doesn't need it now. */ (void) close(newsockfd); } } } /* Server */ void main(int argc, char **argv) { int port; if (argc < 2) { fprintf(stderr, "Usage: %s <port>\n", argv[0]); exit(2); } port = atoi(argv[1]); Server(port); exit(0); } /* main */
/* ucase_c.c */ #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/wait.h> #include <netinet/in.h> #include <arpa/inet.h> #include <errno.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <time.h> #include <sio.h> static void Client(char *serverAddrStr) { char buf[256]; int nread, nwrote, sockfd; sockfd = SNewStreamClient(); if (sockfd < 0) { fprintf(stderr, "client[%d]: socket error: %s\n", (int) getpid(), strerror(errno)); exit(1); } if (SConnectByName(sockfd, serverAddrStr, 15) < 0) { fprintf(stderr, "client[%d]: could not connect to <%s>: %s\n", (int) getpid(), serverAddrStr, strerror(errno)); exit(1); } printf("client[%d]: connected to <%s>.\n", (int) getpid(), serverAddrStr); for (buf[sizeof(buf) - 1] = '\0';;) { printf("client[%d]: Enter message to send -> ", (int) getpid()); if (fgets(buf, sizeof(buf) - 1, stdin) == NULL) break; buf[strlen(buf) - 1] = '\0'; /* Delete newline. */ if (buf[0] == '\0') continue; /* Blank line. */ /* Send the request line to the server. */ nwrote = SWrite(sockfd, buf, strlen(buf), 15, 0); if (nwrote < 0) { fprintf(stderr, "client[%d]: write error: %s\n", (int) getpid(), strerror(errno)); break; } /* Wait for complete reply line */ nread = SRead(sockfd, buf, nwrote, 15, kFullBufferRequired); if (nread == 0) { fprintf(stderr, "client[%d]: no reply received (EOF).\n", (int) getpid()); break; } else if (nread < 0) { fprintf(stderr, "client[%d]: read error: %s\n", (int) getpid(), strerror(errno)); break; } buf[nread] = '\0'; fprintf(stdout, "client[%d]: received: %s\n", (int) getpid(), buf); } (void) SClose(sockfd, 10); printf("\nclient[%d]: done.\n", (int) getpid()); exit(0); } /* Client */ void main(int argc, char **argv) { int port; if (argc < 2) { fprintf(stderr, "Usage: %s <host:port>\n", argv[0]); exit(2); } Client(argv[1]); exit(0); } /* main */