In the previous article I presented a simple socket-based server and client. The beauty of sockets is that they’re comparatively simple, especially for the client. A program wanting to connect to a server does the equivalent of picking up a phone and dialing a number, and then transfers data back and forth across that open connection.
The server’s job is a little harder, since they have to wait for a call from a client, and potentially take connections from many clients at once. This example doesn’t demonstrate handling multiple connections, but it does show how to take a call. I’ll go over the interesting points inline in the server source code, but the part I always found hardest to wrap my head around is that the server actually has two different types of sockets. The first, which I’ve called the listener below, is the one that gets notified when a client tries to contact the server. This is the one which has the port number assigned to it. No data is transferred over this socket though. Instead, you have to pull another socket from it for every client connection. This, which I call the transfer socket, is the one that the conversation with the client actually happens on, and is the one which handles passing data back and forth. You get a new one of these for every client connection on the server side, though to the client it just appears that they’re talking to the single socket they opened.
The actual file transfer is handled using file-like calls to read and write. There are plenty of more complex ways to transfer data over sockets, but for most purposes this sort of TCP/IP/Streaming style of connection is both simplest and sufficient.
If you want the raw source code, you can download the example here.
/* Pete Warden - A simple example demonstrating a socket-based server */
/* Adapted from code at http://www.cs.rpi.edu/courses/sysprog/sockets/sock.html */
/* See http://petewarden.typepad.com/ for more details */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
void error(char *msg)
{
fprintf(stderr,"%s\n",msg);
exit(1);
}
int main(int argc, char *argv[])
{
if (argc < 2)
error("ERROR, no port provided\n");
This port number allows multiple server programs to listen for different clients on the same machine. Each service has to pick a number that’s like an internal phone extension in a company. Unfortunately there’s no good way to figure out if another service has already picked that extension other than trying to use it. Most well-known services like http and smtp have numbers below 2000, so the usual advice is to pick a random-looking number between that and 9999, and provide a user-configuration option to change that if the administrator of the machine you’re using discovers a conflict. The IANA maintains a list of registered ports here, but there’s no guarantee it’s complete.
const int listenPortNumber = atoi(argv[1]);
const int listenSocketFile = socket(AF_INET, SOCK_STREAM, 0);
if (listenSocketFile < 0)
error("ERROR opening server socket");
struct sockaddr_in listenAddress;
bzero((char *) &listenAddress, sizeof(listenAddress));
listenAddress.sin_family = AF_INET;
listenAddress.sin_addr.s_addr = INADDR_ANY;
listenAddress.sin_port = htons(listenPortNumber);
const int bindResult = bind(listenSocketFile,
(struct sockaddr *) &listenAddress,
sizeof(listenAddress));
if (bindResult<0)
error("ERROR on binding");
listen(listenSocketFile,5);
The code above sets up the socket that’s going to sit on the port and listen for any incoming client connections. The sin_family = AF_INET tells the system we want a network socket, rather than one that’s only available to processes on the same file system. We don’t specify an address since it will be listening on the current machine rather than trying to connect remotely, and we pass in the user-specified port number. The bind() passes the request to the system and tries to set up the socket, which may fail if the port is already in use. Finally the listen() activates the socket so it can start accepting requests for client connections. The second argument is the number of connections to queue up for the server to deal with before new connections are rejected. On most systems the maximum is 5, so this is the usual setting.
A consequence of this is that you have to deal with each client very quickly or you’ll start rejecting connections if you have a lot trying to connect at once. I’ll be covering strategies to handle this later.
struct sockaddr_in transferAddress;
socklen_t sizeofTransferAddress = sizeof(transferAddress);
const int transferSocketFile = accept(listenSocketFile,
(struct sockaddr *) &transferAddress,
&sizeofTransferAddress);
This call to accept() will not return until a client has tried to connect to the server. It’s possible to create non-blocking sockets that let accept return immediately with an error if there’s no clients waiting, but blocking is often a lot simpler.
The call returns a new socket file descriptor, which I’ve called the transfer socket. This is the actual connection to the client. You can think of the listener socket like an old-style switchboard operator, whose job it is to set up a direct call between the client and server, returning a socket that’s a private connection between the two.
The server can then treat this transfer socket as a file, though there are more advanced options for accessing the data that I won’t cover.
if (transferSocketFile<0)
error("ERROR on accept");
const int bufferLength = 256;
char buffer[bufferLength];
const int bytesRead = read(transferSocketFile,buffer,(bufferLength-1));
if (bytesRead<0)
error("ERROR reading from socket");
// Pete- On some systems the buffer past the read bytes may be altered, so make sure the string is zero terminated
buffer[bytesRead] = '';
printf("Here is the message: %s\n",buffer);
Getting data from the client is simply a matter of doing a read() call like you would do with a local file. I’m not demonstrating how to handle anything beyond a short input message here, but I will be covering that in a future post. One wrinkle I discovered that some examples don’t handle is that Linux (though not OS X) doesn’t define what will be in the buffer past the bytes that were read, even if you cleared the whole buffer to zero before the call, so I had to manually add a terminator.
const char* outputMessage = "I got your message";
const int outputMessageLength = strlen(outputMessage);
const int bytesWritten = write(transferSocketFile, outputMessage, outputMessageLength);
if (bytesWritten<0)
error("ERROR writing to socket");
Just like reading, you call write() to send data back to the client.
close(transferSocketFile);
close(listenSocketFile);
Make sure you close both socket files before you exit. Some OS’s will close them automatically, but Red Hat Linux at least will leave them open as zombies even after the process has exited.
return 0;
}