March 2008 Socket Programming
Intro |
Copyright (C) 2008 by Steve Litt. All rights reserved. Materials from guest authors copyrighted by them and licensed for perpetual use to Linux Productivity Magazine. All rights reserved to the copyright holder, except for items specifically marked otherwise (certain free software source code, GNU/GPL, etc.). All material herein provided "As-Is". User assumes all risk and responsibility for any outcome.
Twenty Eight Tales of Troubleshooting Troubleshooting: Just the Facts Manager's Guide to Technical Troubleshooting Troubleshooting Techniques of the Successful Technologist |
[ Troubleshooters.Com
| Back Issues |Troubleshooting
Professional Magazine
]
|
CONTENTS
Term | Definition | |||||||||||||||||||||||||||||||
Socket | An endpoint in network communications between programs. Generally speaking, two programs communicate. One is called the client, and one is called the server. Client and server programs will be defined later in this glossary. | |||||||||||||||||||||||||||||||
Two types of sockets | Two types of sockets are active and passive. | |||||||||||||||||||||||||||||||
Active socket | A socket used to trade data between two programs, using a connection. | |||||||||||||||||||||||||||||||
Passive socket | A socket used by the server program to listen for clients attempting to connect. When the attempt is detected, the accept() function is used to create a new active socket to service the incoming client. The server needs an active socket for every client with which it's communicating, but it needs only one passive socket to listen for new clients attempting to connect. | |||||||||||||||||||||||||||||||
Protocol | An agreement between programs as to what the data should look like. | |||||||||||||||||||||||||||||||
TCP | A protocol in which first a line of communication (a connection) is established between two programs, and then the two programs begin trading data. TCP guarantees you'll have a connection, or else you'll know you don't have it. It guarantees data is received in the same order it's transmitted, and as long as the connection stays up, it will be received. TCP data is sent as a stream of bytes. | |||||||||||||||||||||||||||||||
UDP | A protocol in which data is traded without first establishing a line of communication. The programs throw data at each other on the assumption that it will be received. If it's necessary to ascertain whether sent data was received, the receiving program must be programmed to send an acknowledgement. UDP data is sent in datagrams instead of a byte stream, and there is no guarantee that the datagrams will be received in the same order as they were sent. UDP does a lot less for the programmer than TCP does, but if the communicating programs are programmed right, it can communicate across unreliable networks that would break TCP connections. | |||||||||||||||||||||||||||||||
Client/Server | An interapplication structure in which one program (the server) serves up data to one or more anonymous other programs (the clients). In socket programming, the server is the program that waits for anonymous clients to connect. The client is the anonymous program that initiates a connection with the known server program. | |||||||||||||||||||||||||||||||
Server | Within the sockets context, this is a program, at a known IP address with a known port number, that waits for client programs to connect to it. The way it waits is by listening to its port, via a passive socket created with the listen() function. The listening and waiting itself is done by the accept() function. | |||||||||||||||||||||||||||||||
Client | Within the sockets context, this is a program that initiates a connection with a server, using the known IP address and port of that server. Once the connection is established, the client and server can trade data. | |||||||||||||||||||||||||||||||
Blocking | A
function is said to be blocking
if it "hangs" until receiving a response. The advantage of blocking is
a guaranteed synchronization between the function call, the response,
and whatever comes after the function call. The disadvantage is that
the blocking function can hang the entire program while waiting for a
response. There are various programming techniques, including use of
the select()
function and forking off communication processes with the fork()
function, to limit the harm from this disadvantage. Some of the major
blocking functions in the sockets world are:
|
|||||||||||||||||||||||||||||||
Non-blocking | A
function is said to be non-blocking
if it does not wait for a response before continuing. This eliminates
delays, but requires the programmer to track state and synchronization
much more tightly. Non-blocking functions in the sockets world
include:
|
|||||||||||||||||||||||||||||||
Basic server function sequence |
|
#include <stdio.h> |
[slitt@mydesk sockets]$ ./a.out |
#include <netinet/in.h> |
[slitt@mydesk sockets]$ ./a.out |
The first argument is the socket handle, the next is the a pointer to sockaddr, which itself is just a superset of one of the following:int bind(int socket, const struct sockaddr *address, socklen_t address_len);
Mode | struct sockaddr subtype |
TCP sockets | struct sockaddr_in |
UDP sockets | struct sockaddr_in |
Unix domain sockets | struct sockaddr_un |
#include <stdio.h> |
WARNING
The following code won't work if you run it too soon after the last run. You might need to wait a minute or so to get it to run. There is a way to make it able to rerun instantly. To do so, put the following code right after the call to socket() and its associated error handling and informational messages: // MAKE IT RERUN INSTANTLYI didn't put it into the earlier examples in order to keep everything simple, but if you find yourself doing extensive troubleshooting, the preceding code can prevent the need to wait up to a minute or two between server runs. |
[slitt@mydesk sockets]$ ./a.out |
#include <stdio.h> |
[slitt@mydesk sockets]$ ./a.out |
#include <stdio.h> |
[slitt@mydesk sockets]$ ./a.out |
telnet ip_address port_numberOK, let's do it:
[slitt@mydesk sockets]$ telnet 127.0.0.1 43210 |
[slitt@mydesk sockets]$ ./a.out |
#include <stdio.h> |
Server session | Client (telnet) session | |||
|
|
telnet 192.168.100.2 43210The following is the result:
[slitt@mydesk ~]$ telnet 192.168.100.2 43210 |
[slitt@mylap2 ~]$ telnet 192.168.100.2 43210 |
#include <stdio.h> |
[slitt@mydesk ~]$ telnet 192.168.100.2 43210 |
[slitt@mydesk ~]$ telnet -e~ 192.168.100.2 43210 |
#include <stdio.h> |
[slitt@mydesk ~]$ telnet -e~ 192.168.100.2 43210 |
signal(SIGCHLD, SIG_IGN);There are better, more reliable and more versatile methods of preventing zombies, but the preceding is good enough for a demonstration program.
#include <stdio.h> |
[slitt@mydesk ~]$ telnet -e~ 192.168.100.2 43210 |
[slitt@mydesk ~]$ telnet -e~ 192.168.100.2 43210 |
[slitt@mydesk ~]$ telnet -e~ 192.168.100.2 43210 |
int dataSocketArray[MAXDATASOCKETS];New clients are added to the array like this:
int firstZeroDataSocket;When a socket logs off, its place in the array is zeroed like this:
for(firstZeroDataSocket=0; firstZeroDataSocket < MAXDATASOCKETS; firstZeroDataSocket++)
if(dataSocketArray[firstZeroDataSocket] == 0)
break;
if(firstZeroDataSocket == MAXDATASOCKETS)
abortt("Maximum data sockets reached!");
dataSocketArray[firstZeroDataSocket] = data_socket;
if(!respond_to_data(i))All this sequential lookup is very inefficient, but given the usual low number of file and socket handles, it's lightning fast. On loaded systems with large numbers of handles, you could make an array of 65535 elements and have the subscript represent the handle number, in which case lookup would be almost instantaneous.
for(j=0; j < MAXDATASOCKETS; j++)
if(dataSocketArray[j] == i)
dataSocketArray[j] = 0;
int select(int nfds, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict errorfds, struct timeval *restrict timeout);
The
integer it returns is the number of sockets ready to yield
information, so if it's zero there's no socket reading to be done. The
arguments are as follows:
int nfds |
This should be set to the highest fd to check plus one. So if the highest data socket descriptor is 14, this should be set to 15. You can also think of it this way: If the highest fd is 14, you need to check from 0 through 14, so the number of fds (nfds) is 15. | |
fd_set *restrict readfds |
The set of socket fds to be checked for pending reads. If it's NULL, no check is made. | |
fd_set *restrict writefds |
The set of socket fds to be checked for readiness to write. If it's NULL, as it is in this server, no check is made. | |
fd_set *restrict errorfds |
The set of socket fds to be checked for pending error conditions. If it's NULL, as it is in this server, no check is made. | |
struct timeval *restrict timeout |
The select()
function returns immediately if there are sockets needing service. If
there are no sockets needing service upon entry to select(), select() waits
the timeout period to see if a socket will require service. The instant
any socket requires service, select()
returns. The struct
timeval structure looks like this:struct timeval{It's set to 5 seconds like this: struct timeval timeout = {5,0}; |
Macro | What it does | |
FD_ZERO(fd_set * set) |
This "instantiates" an fd_set variable to the NULL set. Do this before calling select(). | |
FD_SET(int fd, fd_set * set) |
This sets the slot for the integer fd to 1 within the fd_set variable. Because select() works by setting any fd of a working socket back to 0 if it is not pending, before calling select() all fds of interest must be set to 1 with FD_SET. However, if you set an fd that is not a valid socket or file, the select() will fail and return -1, which you should test for with perror(), because it's a hard to find error. | |
FD_ISSET(int fd, fd_set * set) |
This tests whether the fd fd is set. Because select() sets any blocked fds to 0, fd must have been set to 1 with FD_SET() before each invocation of select(). If FD_ISSET() returns 1 for a given fd, that fd can be read without blocking. | |
FD_CLR(int fd, fd_set * set) |
This removes the fd from the set under consideration. Things seem to work OK without it, but if a data socket disconnects, it's probably a good idea to run FD_CLR() on that data socket. |
#include <stdio.h> |
#include <stdio.h> |
[slitt@mydesk sockets]$ ./a.out |
|
As you can see,
text numbers one
through ten
were pasted in. ten
was terminated with three tildes, translating into a newline. There was
also a line with three tildes after the ten. The entire
paste showed up before the first return from the server. Worse, two,
six and seven showed some timing anomolies. The results would have been much worse if the server had tens of clients constantly sending information. In that case all clients would get back extremely unexpected forms of echos. Assuming the desire was to echo a line only after a newline was received, the real way to do it would have been to scan the buffer for newlines after each recv(), and if one were found, place a null byte in its place, shoot the buffer back with send(), and then slide the remainder of the data to the beginning of the data. With TCP sockets, it's important to understand that the data is a stream of bytes, nothing more and nothing less. It doesn't get sent as "strings", and if you want it recognized as units like "strings" or "records", delimiter bytes must be used. However, such delimiters on the client end and parsing on the server end would have obfuscated the important socket concepts and thus would have been overkill for the purpose of an exercise. |
TCP Socket | Unix Domain Socket | |
Binds to | An IPaddress/Port combo | A filename |
sockaddr subtype | struct sockaddr_in | struct sockaddr_un |
length arg in connect | sizeof(struct sockaddr_in) | strlen(local.sun_path) + sizeof(local.sun_family)** |
access to server | sockaddr_in.sin_addr.s_addr, sockaddr_in.sin_port, firewall rules |
write permissions on bound filename +++ |
Reach | Anywhere on network | Only on localhost |
struct sockaddr_un {After copying the filename to sun_path, the portion of sun_path not containing garbage is its strlen(). Add to that the length of sun_family. Notice also the magic number 108. This could bite you if you construct filenames on the fly in various directories.
short int sun_family;
char sun_path[108];
}
Server procedure | Client procedure | |
|
|
#include<stdio.h> |
#include<stdio.h> |
CLIENT SESSION | SERVER SESSION | |
[slitt@mydesk sockets]$ gcc -Wall udomain_client.c |
[slitt@mydesk sockets]$ ./udomain.exe |
Starling | Sockets |
Message producer | Socket server |
Message consumer | Socket client |
Message queue | Queue in front of socket server |
"GNU/Linux" is probably the most accurate moniker one can give to this operating system. Please be aware that in all of Troubleshooters.Com, when I say "Linux" I really mean "GNU/Linux". I completely believe that without the GNU project, without the GNU Manifesto and the GNU/GPL license it spawned, the operating system the press calls "Linux" never would have happened.
I'm part of the press and there are times when it's easier to say "Linux" than explain to certain audiences that "GNU/Linux" is the same as what the press calls "Linux". So I abbreviate. Additionally, I abbreviate in the same way one might abbreviate the name of a multi-partner law firm. But make no mistake about it. In any article in Troubleshooting Professional Magazine, in the whole of Troubleshooters.Com, and even in the technical books I write, when I say "Linux", I mean "GNU/Linux".
There are those who think FSF is making too big a deal of this. Nothing could be farther from the truth. The GNU General Public License, combined with Richard Stallman's GNU Manifesto and the resulting GNU-GPL License, are the only reason we can enjoy this wonderful alternative to proprietary operating systems, and the only reason proprietary operating systems aren't even more flaky than they are now.Any article submitted to Linux Productivity Magazine must be licensed with the Open Publication License, which you can view at http://opencontent.org/openpub/. At your option you may elect the option to prohibit substantive modifications. However, in order to publish your article in Linux Productivity Magazine, you must decline the option to prohibit commercial use, because Linux Productivity Magazine is a commercial publication.
Obviously, you must be the copyright holder and must be legally able to so license the article. We do not currently pay for articles.
Troubleshooters.Com reserves the right to edit any submission for clarity or brevity, within the scope of the Open Publication License. If you elect to prohibit substantive modifications, we may elect to place editors notes outside of your material, or reject the submission, or send it back for modification. Any published article will include a two sentence description of the author, a hypertext link to his or her email, and a phone number if desired. Upon request, we will include a hypertext link, at the end of the magazine issue, to the author's website, providing that website meets the Troubleshooters.Com criteria for links and that the author's website first links to Troubleshooters.Com. Authors: please understand we can't place hyperlinks inside articles. If we did, only the first article would be read, and we can't place every article first.
Submissions should be emailed to Steve Litt's email address, with subject line Article Submission. The first paragraph of your message should read as follows (unless other arrangements are previously made in writing):
Copyright (c) 2003 by <your name>. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, version Draft v1.0, 8 June 1999 (Available at http://www.troubleshooters.com/openpub04.txt/ (wordwrapped for readability at http://www.troubleshooters.com/openpub04_wrapped.txt). The latest version is presently available at http://www.opencontent.org/openpub/).
Open Publication License Option A [ is | is not] elected, so this document [may | may not] be modified. Option B is not elected, so this material may be published for commercial purposes.
After that paragraph, write the title, text of the article, and a two sentence description of the author.