Open Systems describes the networking protocol using the seven layers of the Open Systems Interconnection basic reference model, or the OSI model. Each layer has its specific properties:
In the remainder of this document we will only focus on TCP. This transport protocol is most widely used.
UDP is non-guaranteed, and might be delivered out of order (first sent packets may arrive last); but it has less overhead than TCP and is therefore faster. UDP has its uses, of which we name three:
(If you want a look at a UDP client and server, you may want
to look at the package logfwd on
http://public.e-tunity.com.)
double value is sent correctly,
despite of Endianness), and SSL (which has phases like key
negotiation and exchange, encrypted data transmission,
end-of-session warnings).
The advantage of separate OSI layers is that one can substitute alternative mechanisms for one or more layers, without affecting standards of other layers. E.g., UMTS (wireless phone transfer) replaces the first three layers. IP doesn't exist under UMTS. However, above that, all protocols still work, up until HTTP which brings your favorite site to your mobile phone.
Another example is SSL in layer 6. Whether enabled or not, the above application protocol remains the same. If you have a mail reader that doesn't know how to talk to secure POP3 mailboxes (spop3), then you can simply use a program like stunnel to set up an SSL link to your spop3 provider. Next you have your mail agent talk to the non-SSL part of the tunnel and you're up and running.
In the following sections we will talk about layer 5 (the session layer). Other layers will go largely unnoticed. Regarding sockets, we will handle server side and client side separately -- preparations and actions on these are quite different depending on your point of view.
In the sections on client-side sockets we'll discuss all that is necessary to behave as a client that connects to some server. In the sections on server-side sockets we'll discuss all that is necessary to behhave as a server that presents a networked service.
PF_INET), and the type is a sequenced
two-way stream (SOCK_STREAM):
int sock = socket (PF_INET, SOCK_STREAM, 0);
The final 0 argument is a protocol specifier, which is unused at this time.
gethostbyname(),
which returns a struct hostent *. The returned
pointer contains information on the IP address of the host,
its aliases, and so on. This step may fail, in which case
gethostbyname() returns 0, and the hostname cannot be resolved.
struct sockaddr_in with
relevant fields.
In order to neatly prevent endless connection waits, one should set up an acceptable timeout period that is an acceptable wait time. When exceeded, the program would stop.
Waiting for a timeout of the connect() call in most cases consists
of the following steps:
SIGALRM
event. This signal handler 'does nothing' except raise a
flag, called e.g. timed_out.
connect() system call.
connect() returns a non-zero value, then
something's afoot. The call may have been interrupted
due to a signal; which might actually be our alarm
call. We check the flag variable timed_out to see
whether a timeout condition has occurred.
So, having sorted out the timeout problem, here's a demo client
connector. You can run it as ./client smtp.gmail.com 25. It will
connect to smtp.gmail.com, port 25, and show the first transmission (up
to 255 characters) what the server is sending. In this case this would
be the greeting line of a mail server. You can also try out
./client smtp.gmail.com 26 to see the timeout code in action. It
should be pointed out that the timeout applies only to the connecting
of a socket; not to the reading of data. If you direct the client at
e.g. www.google.com port 80, then the client will sit there
indefinitely, waiting for the server's message. A webserver however
doesn't start the chat; it waits for the client to request something,
which is not what this client does.
The relevant part of creating a client socket and connecting it, is
coded in a function client_socket(). This function uses a
hardcoded timeout value of 5 seconds, and exits the program upon
failure. It may however be a starting point for your own code.
1 /* Sample program: client.c */ 2 3 #include <errno.h> 4 #include <netdb.h> 5 #include <stdio.h> 6 #include <stdlib.h> 7 #include <string.h> 8 #include <unistd.h> 9 #include <sys/types.h> 10 #include <sys/socket.h> 11 12 static int client_socket (char const *server, int port); 13 static void timeout_handler (int sig); 14 15 static int timed_out = 0; 16 17 int main (int argc, char const **argv) { 18 int fd; 19 char buf[256]; 20 ssize_t nread; 21 22 if (argc != 3) { 23 fprintf (stderr, 24 "Usage: client server port\n" 25 "Reads the first block of data that the server sends,\n" 26 "up to 255 bytes, and shows it.\n"); 27 exit (1); 28 } 29 30 fd = client_socket (argv[1], atoi(argv[2])); 31 if ( (nread = read (fd, buf, 255)) < 0 ) 32 printf ("Read error\n"); 33 else if (!nread) 34 printf ("No data\n"); 35 else { 36 buf[nread] = 0; 37 printf ("%s:%d says:\n%s\n", argv[1], atoi(argv[2]), buf); 38 } 39 close (fd); 40 return (0); 41 } 42 43 static int client_socket (char const *server, int port) { 44 int sock; 45 struct hostent *hent; 46 struct sockaddr_in sname; 47 48 /* Create the socket. */ 49 if ( (sock = socket (PF_INET, SOCK_STREAM, 0)) < 0 ) { 50 fprintf (stderr, "Failed to create socket: %s\n", strerror (errno)); 51 exit (1); 52 } 53 54 /* Look up the host. */ 55 if (! (hent = gethostbyname (server)) ) { 56 fprintf (stderr, "Failed to resolve host %s\n", server); 57 exit (2); 58 } 59 60 /* Prepare the server name binding. */ 61 sname.sin_family = AF_INET; 62 sname.sin_port = htons (port); 63 sname.sin_addr = *(struct in_addr *)hent->h_addr; 64 65 /* Set up the timeout mechanism. We allow 5 seconds for trying. */ 66 signal (SIGALRM, timeout_handler); 67 alarm (5); 68 69 /* Connect. */ 70 if (connect (sock, (struct sockaddr *) &sname, sizeof(sname))) { 71 close (sock); 72 alarm (0); 73 signal (SIGALRM, SIG_DFL); 74 if (timed_out) 75 fprintf (stderr, "Cannot connect to %s:%d: timed out\n", 76 server, port); 77 else 78 fprintf (stderr, "Cannot connect to %s:%d: %s\n", 79 server, port, strerror(errno)); 80 exit (3); 81 } 82 /* Connect call succeeded */ 83 alarm (0); 84 signal (SIGALRM, SIG_DFL); 85 return (sock); 86 } 87 88 void timeout_handler (int sig) { 89 timed_out++; 90 } 91 The approach shown above in section 3.1.1 is applicable only to non-threaded programs; ie., in the shown sample client program, or in forked daemons. The approach is not thread-safe:
timed_out is used;
The approach for multi-threaded daemons is somewhat different. It will not be fully described here, but the outline is shown below. A full example can be found in section 1.
sockaddr_in is filled.
fcntl().
connect() is attempted. Since the socket is
non-blocking, the connect() won't block either, but will
almost immediately return a non-zero value. The variable
errno will be EINPROGRESS.
As a global variable errno is used, this piece of code
must be protected by mutex locks.
connect(), the socket can be placed
back in blocking mode.
select() call is executed, with the
socket in the writable set. The required timeout is
stated. The select() call will try to wait until a next
write() to the socket is possible; which might time out.
select() call will apparently succeed
in situations where a connection was refused. In such cases,
the next read() or write() will yield errno
ECONNREFUSED.
read() or write() is
a system call in itself that the kernel has to handle. In
contrast, fprintf() or cout are buffered; you use call
the function or use the object, and the underlying library
flushes from time to time. If you want buffering with sockets,
then you have to implement it yourself.
read()'s or write()'s. Each atomic
read() or write() may handle only a part of its
assignment. You have to check the return value to see how many
bytes were actually handled, and optionally you may need to
re-issue the system call for next parts of the buffer you're
handling.
The following sample code shows how to write() to a socket. The
sample program just uses file descriptor 0 (stdout), but the same
methodology applies to network sockets. The trick is in function
fd_write(): it loops until the total number of written bytes
equals the total number of bytes we want to write. Inside the loop
atomic write()'s send the information. The return value of each
write() is checked to verify that at least some information could
be sent.
1 /* Sample program: socketwrite.c */ 2 3 #include <stdio.h> 4 #include <stdlib.h> 5 #include <string.h> 6 #include <unistd.h> 7 8 static int fd_write (int fd, char const *buf, int len); 9 10 int main () { 11 char *msg = "Hello World!\n"; 12 int len = strlen (msg); 13 int res = fd_write (0, msg, len); 14 15 if (res != len) { 16 fprintf (stderr, "Only %d bytes written out of %d\n", res, len); 17 exit (1); 18 } 19 20 return (0); 21 } 22 23 static int fd_write (int fd, char const *buf, int len) { 24 int totwritten = 0, nwritten; 25 26 while (totwritten < len) { 27 nwritten = write (fd, buf + totwritten, len - totwritten); 28 if (nwritten < 1) 29 return (totwritten); 30 totwritten += nwritten; 31 } 32 return (totwritten); 33 } 34
The same mechanism applies to the reading from a socket. If you expect
to read e.g. 1000 bytes, then you may have to issue several
read()'s before all information is collected. Each read()
provides a signal for the next course:
read() returns 0, then there is no more
information to be read. Usually this means that the other
network party has hung up.
read() returns -1, then there has been an
error. Usually this means that the network connection failed.We've seen in section 3.1 that connecting to a network server may result in a timeout, and how to handle it. However, reading from a network socket or writing to one, may cause timeouts too.
The basic principle here is that the system calls read() and
write() are blocking calls: they will stop execution of the
program until they can do something. In normal situations that is just
what you want. For example, tail -f /var/log/messages will monitor
/var/log/messages and show text as it is appended to that file. In
this case, you'd simply use a blocking read(), and once it signals
that information has arrived, display that information.
In contrast, you'll most often want to use some timeout value when
handling network sockets -- especially if you're writing a daemon
program. The trick here is to see whether a socket can be read or
written before the actual read() or write(). The system
call select() allows you to do exactly that: peek at a file
descriptor to see if a read() or write() would block.
The select() call works as follows:
struct
timeval. This specifier is set to the timeout length (in
seconds and microseconds).
fd_set variables. Each fd_set
is cleared, and then the file descriptor you want to watch is
added. One of the fd_set's is used as either the read or
write watcher; the other fd_set is used as a watcher for
exeptions (errors).
select() call is issued, with the
timeout specifier and the file descriptor sets. select()
will return the number of available descriptors, or 0 when a
timeout occurred.
read() or write() can take
place.
The above setup is illustrated in the below listing. The program is
identical to the client socket initializer of section
3.1.2, except that main()'s read is replaced by a
function socketread(). This function is implements the above
mechanism. Note also the checking of exceptions on the socket.
In order to test this yourself, try reading from e.g. an HTTP server. The server won't initiate chatting and will patiently wait for the client. This client will time out, while attempting to read from the server.
1 /* Sample program: readtimeout.c */ 2 3 #include <errno.h> 4 #include <netdb.h> 5 #include <stdio.h> 6 #include <stdlib.h> 7 #include <string.h> 8 #include <unistd.h> 9 #include <sys/types.h> 10 #include <sys/select.h> 11 #include <sys/socket.h> 12 #include <sys/time.h> 13 14 static int timed_out; 15 static int client_socket (char const *server, int port); 16 static void timeout_handler (int sig); 17 static int socketread (int sock, char *buf, unsigned bufsz); 18 19 int main (int argc, char const **argv) { 20 int fd; 21 char buf[256]; 22 ssize_t nread; 23 24 if (argc != 3) { 25 fprintf (stderr, 26 "Usage: readtimeout server port\n" 27 "Reads the first block of data that the server sends,\n" 28 "up to 255 bytes, and shows it.\n"); 29 exit (1); 30 } 31 32 fd = client_socket (argv[1], atoi(argv[2])); 33 nread = socketread (fd, buf, 255); 34 buf[nread] = 0; 35 printf ("%s:%d says:\n%s\n", argv[1], atoi(argv[2]), buf); 36 close (fd); 37 return (0); 38 } 39 40 static int socketread (int sock, char *buf, unsigned bufsz) { 41 struct timeval tv; 42 fd_set readset, exceptset; 43 int res; 44 45 /* Prepare the timeout (10 sec) */ 46 tv.tv_sec = 10; 47 tv.tv_usec = 0; 48 49 /* Prepare the file descriptors sets */ 50 FD_ZERO (&readset); 51 FD_SET (sock, &readset); 52 FD_ZERO (&exceptset); 53 FD_SET (sock, &exceptset); 54 55 /* Block until something happens. The arguments are: 56 * - nr of file descriptors, FD_SETSIZE is the system value 57 * - ptr to the set with read-fd's (or 0 if none to watch) 58 * - ptr to the set with write-fd's (or 0 if none to watch) 59 * - ptr to the set with exception-fd's (or 0 if none to watch) 60 * - ptr to the timeout specifier */ 61 if ( (res = select(FD_SETSIZE, &readset, 0, &exceptset, &tv)) < 0 ) { 62 /* select() returns -1: error */ 63 fprintf (stderr, "Select failed: %s\n", strerror(errno)); 64 exit (1); 65 } else if (! res) { 66 /* select returns 0: no file descriptor ready. Must be a timeout. */ 67 fprintf (stderr, "Timeout occurred\n"); 68 exit (2); 69 } 70 71 /* Now our socket is either in readset or in exceptset. */ 72 if (FD_ISSET (sock, &exceptset)) { 73 fprintf (stderr, "Exception on socket\n"); 74 exit (3); 75 } 76 77 /* Our socket must be in the readset. */ 78 return (read (sock, buf, bufsz)); 79 } 80 81 static int client_socket (char const *server, int port) { 82 int sock; 83 struct hostent *hent; 84 struct sockaddr_in sname; 85 86 /* Create the socket. */ 87 if ( (sock = socket (PF_INET, SOCK_STREAM, 0)) < 0 ) { 88 fprintf (stderr, "Failed to create socket: %s\n", strerror (errno)); 89 exit (1); 90 } 91 92 /* Look up the host. */ 93 if (! (hent = gethostbyname (server)) ) { 94 fprintf (stderr, "Failed to resolve host %s\n", server); 95 exit (2); 96 } 97 98 /* Prepare the server name binding. */ 99 sname.sin_family = AF_INET; 100 sname.sin_port = htons (port); 101 sname.sin_addr = *(struct in_addr *)hent->h_addr; 102 103 /* Set up the timeout mechanism. We allow 5 seconds for trying. */ 104 signal (SIGALRM, timeout_handler); 105 alarm (5); 106 107 /* Connect. */ 108 if (connect (sock, (struct sockaddr *) &sname, sizeof(sname))) { 109 close (sock); 110 alarm (0); 111 signal (SIGALRM, SIG_DFL); 112 if (timed_out) 113 fprintf (stderr, "Cannot connect to %s:%d: timed out\n", 114 server, port); 115 else 116 fprintf (stderr, "Cannot connect to %s:%d: %s\n", 117 server, port, strerror(errno)); 118 exit (3); 119 } 120 /* Connect call succeeded */ 121 alarm (0); 122 signal (SIGALRM, SIG_DFL); 123 return (sock); 124 } 125 126 void timeout_handler (int sig) { 127 timed_out++; 128 } 129
socket().
setsockopt(), using option SO_REUSEADDR. If this step
is skipped, then restarting the server process would require
waiting for the old network socket to free up. This usually
takes a few seconds.
localhost, but the IP address can
be specified separately. This feature is e.g. used in the SSH
daemon sshd which allows you to bind the daemon to a
specific IP address; e.g., only to the address of one network
card.
bind().
listen()
call. Contrary to what the name of this system call suggests,
listen() is non-blocking.
syslog() to the system log.Having established a server network socket, the program waits for client activity. The server enters an endless loop to serve clients. The loop usually consists of the following:
select() call, the server waits for
activity on the socket.
accept(). In this step the kernel can give us
information on the client, such as their IP address.
SIGCHLD must be set up, so that stopped
children are seen and their status is collected.
Without further ado, let's dive into some code. The following program
is started as ./server {port}, where port is a numeric TCP
port. The server listens to all network interfaces; it doesn't bind to
a specific server address. Once the server establishes its service, it
says hello to the client, also stating the client's IP address, and
then hangs up.
The following is a rather lengthy listing, which is however self-explanatory in its comments. You can try out the program with the following steps:
tail -f /var/log/messages (or
whatever your syslogd output file is);
./server 10000. Unix will allow a user-space process to
start a networked process on an unprivileged port (greater
than 1024). If you want to run the server on say port 80, then
you have to start it as root.
./client 10000.
killall server.
1 /* Sample program: server.c */ 2 3 #include <errno.h> 4 #include <fcntl.h> 5 #include <signal.h> 6 #include <stdio.h> 7 #include <stdlib.h> 8 #include <string.h> 9 #include <syslog.h> 10 #include <unistd.h> 11 #include <arpa/inet.h> 12 #include <sys/socket.h> 13 #include <sys/types.h> 14 15 static int daemonize (void); 16 static void fd_writestr (int sock, char const *msg); 17 static int prepare_service (int port); 18 static void run_service (int serversock); 19 static void grim_reaper (int sig); 20 21 int main (int argc, char **argv) { 22 pid_t pid; 23 int sock, port; 24 25 /* Arguments check, scan the port number */ 26 if (argc != 2 || sscanf (argv[1], "%d", &port) < 1) { 27 fprintf (stderr, 28 "Usage: server PORT\n" 29 "The port must be a numerical TCP port number.\n"); 30 exit (1); 31 } 32 33 /* At the parent process level, we create a network socket. */ 34 sock = prepare_service (port); 35 36 /* This will be our reporter on child processes. */ 37 signal (SIGCHLD, grim_reaper); 38 39 /* Next we can become a daemon. */ 40 if ( (pid = daemonize()) > 0 ) { 41 /* Parent process. */ 42 printf ("Daemon started as process ID %u.\n" 43 "We're open for business at port %d.\n", pid, port); 44 close (sock); 45 } else { 46 /* Child process. We're a true daemon now, so no more 47 * fprintf(). We have to use syslog() for messaging. */ 48 openlog ("demo-server", LOG_PID, LOG_DAEMON); 49 syslog (LOG_NOTICE, "Server started!"); 50 run_service (sock); 51 } 52 53 return (0); 54 } 55 56 static int prepare_service (int port) { 57 int sock, val = 1; 58 struct sockaddr_in servername; 59 60 /* Create the socket. */ 61 if ( (sock = socket (PF_INET, SOCK_STREAM, 0)) < 0 ) { 62 fprintf (stderr, "Failed to create socket: %s\n", strerror (errno)); 63 exit (2); 64 } 65 66 /* Make sure it's re-usable upon restarts. */ 67 if (setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val))) { 68 fprintf (stderr, "Failed to set socket options: %s\n", 69 strerror(errno)); 70 close (sock); 71 exit (3); 72 } 73 74 /* Prepare the naming of the socket, for all IP addresses of this host. */ 75 servername.sin_family = AF_INET; 76 servername.sin_port = htons (port); 77 servername.sin_addr.s_addr = htonl (INADDR_ANY); 78 79 /* Bind the socket to the prepared address. */ 80 if (bind (sock, (struct sockaddr *) &servername, sizeof(servername)) < 0) { 81 fprintf (stderr, "Failed to bind socket to port %d: %s\n", 82 port, strerror(errno)); 83 exit (4); 84 } 85 86 /* Tell the kernel that we're listening to it. */ 87 if (listen (sock, 5) < 0) { 88 fprintf (stderr, "Error while listening to server socket %d: %s", 89 sock, strerror(errno)); 90 exit (5); 91 } 92 return (sock); 93 } 94 95 static void grim_reaper (int sig) { 96 pid_t child; 97 int status; 98 99 /* Clean up any statuses of child processes. */ 100 while( (child = wait4 (-1, &status, WNOHANG, 0)) > 0 ) { 101 syslog (LOG_NOTICE, "Child %u terminated", child); 102 if (WIFEXITED(status)) 103 syslog (LOG_NOTICE, "Exit status: %d", WEXITSTATUS(status)); 104 if (WIFSIGNALED(status)) 105 syslog (LOG_NOTICE, "Child got signal %d", WTERMSIG(status)); 106 if (WIFSIGNALED(status) && WCOREDUMP(status)) 107 syslog (LOG_NOTICE, "Child dumped core"); 108 if (WIFSTOPPED(status)) 109 syslog (LOG_NOTICE,"Child stopped due to signal %d", 110 WSTOPSIG(status)); 111 } 112 } 113 114 int daemonize () { 115 pid_t pid; 116 117 /* Try to fork. */ 118 if ( (pid = fork()) < 0 ) { 119 /* Failed */ 120 fprintf (stderr, "Fork failure: %s\n", strerror(errno)); 121 exit (1); 122 } else if (pid > 0) { 123 /* Parent branch */ 124 return (pid); 125 } 126 127 /* We now must be the child branch. Go to the root dir. */ 128 if (chdir ("/")) { 129 fprintf (stderr, "Cannot chdir to /: %s\n", strerror(errno)); 130 exit (1); 131 } 132 133 /* Become session leader of a new process group. */ 134 if (setsid() == -1) { 135 fprintf (stderr, "Failed to create process group. Already crated?\n"); 136 exit (1); 137 } 138 139 /* Close FD's 0,1,2 and reopen them on /dev/null. Note that we 140 * cannot report on failures of the open() below -- stderr is 141 * very likely already gone. */ 142 close (0); 143 close (1); 144 close (2); 145 open ("/dev/null", O_RDONLY); 146 open ("/dev/null", O_WRONLY); 147 open ("/dev/null", O_WRONLY); 148 149 /* We're a daemon now! */ 150 return (0); 151 } 152 153 static void run_service (int serversock) { 154 int clientsock, size, pid; 155 struct sockaddr_in clientname; 156 char *client_ip; 157 fd_set readset, exceptset; 158 159 syslog (LOG_NOTICE, "Awaiting activity on server socket %d", serversock); 160 161 /* Note that we're a server now. We never return, and never exit -- 162 * this would stop the service! */ 163 164 while (1) { 165 /* Wait for network activity; basically a select() with an 166 * indefinite timeout specifier. */ 167 FD_ZERO (&readset); 168 FD_SET (serversock, &readset); 169 FD_ZERO (&exceptset); 170 FD_SET (serversock, &exceptset); 171 172 if (select (FD_SETSIZE, &readset, 0, &exceptset, 0) < 0) 173 continue; 174 if (FD_ISSET (serversock, &exceptset)) { 175 syslog (LOG_WARNING, "Exception on network socket"); 176 continue; 177 } 178 179 /* We got action! Accept the network connection. */ 180 size = sizeof(clientname); 181 if ( (clientsock = accept (serversock, (struct sockaddr *) &clientname, 182 (socklen_t *) &size)) < 0) { 183 syslog (LOG_WARNING, "Failed to accept network connection: %s", 184 strerror(errno)); 185 continue; 186 } 187 client_ip = inet_ntoa (clientname.sin_addr); 188 syslog (LOG_NOTICE, "Accepted network connection from %s on fd %d", 189 client_ip, clientsock); 190 191 /* Try to fork off a handler for this client, so that the 192 * daemon can serve new requests. */ 193 if ((pid = fork()) < 0) { 194 /* Fork failed */ 195 syslog (LOG_WARNING, "Failed to fork: %s", strerror(errno)); 196 close (clientsock); 197 } else if (pid) { 198 /* Parent branch of this fork */ 199 syslog (LOG_NOTICE, "New connection from %s, handled by pid %d", 200 client_ip, pid); 201 close (clientsock); 202 } else { 203 /* Child branch of this fork, the grandchild overall. 204 * Serve the request, and don't forget to exit! */ 205 syslog (LOG_NOTICE, "Servicing client %s", client_ip); 206 fd_writestr (clientsock, "Hello there, "); 207 fd_writestr (clientsock, client_ip); 208 fd_writestr (clientsock, "!\n"); 209 close (clientsock); 210 exit (0); 211 } 212 } 213 } 214 215 static void fd_writestr (int sock, char const *msg) { 216 fd_set writeset, exceptset; 217 int len = strlen (msg), totwritten = 0, nwritten, res; 218 struct timeval tv; 219 220 /* Loop until we've written all bytes. */ 221 while (totwritten < len) { 222 /* Wait until the socket is writable (or time out after 5 secs). */ 223 FD_ZERO (&writeset); 224 FD_SET (sock, &writeset); 225 FD_ZERO (&exceptset); 226 FD_SET (sock, &exceptset); 227 228 tv.tv_sec = 5; 229 tv.tv_usec = 0; 230 231 if ((res = select (FD_SETSIZE, 0, &writeset, &exceptset, &tv)) < 0) { 232 syslog (LOG_WARNING, "Select failed"); 233 exit (1); 234 } 235 if (!res) { 236 syslog (LOG_NOTICE, "Timeout while servicing client"); 237 exit (1); 238 } 239 if (FD_ISSET (sock, &exceptset)) { 240 syslog (LOG_WARNING, "Exception on client connection"); 241 exit (2); 242 } 243 244 /* Send some bytes. At least 1 byte must be sent. */ 245 if ((nwritten = write (sock, msg + totwritten, 246 len - totwritten)) < 1) { 247 syslog (LOG_WARNING, "Write error on network socket"); 248 exit (3); 249 } 250 totwritten += nwritten; 251 } 252 } 253