Time Service: Set up the Listener
Using APR, I can put this restaurant analogy to work. In addition to being portable, APR's networking routines are more convenient to use than the UNIX-style builtins. Both use data structures to represent connections and address info, but APR includes routines to abstract the developer from the raw bit-twiddling.
The sample code is a simple network-based time service. I leave the client side as a reader exercise. For now, you can telnet to the service port to simulate a client. If the service listens on port 8123 of the localhost interface, for example, run:
telnet localhost 8123
to connect.
The first version of the code is in the file step1.cc. I've broken the important part of the code into two functions: main() and handleClient(). Granted, this would be unpleasant from a support perspective, but it's easier to describe here.
step1's main() sets up the listener and prepares for client connections; see Listing One. Following some basic APR setup, main() creates a blank socket structure, type apr_socket_t (1) and sets some options. APR_SO_REUSEADDR lets you rebind to an address:port pair that has just been released, instead of having to wait for a timeout. The TRUE value is an APR constant. It doesn't use the Boolean True because Booleans don't (technically) exist in C.
int main( const int argc , const char** argv ){ const char* listenString = argv[1] ; // ... initialize APR // ... setup memory pool "mainMemPool" ... apr_socket_t* serverSocket ; // create socket apr_socket_create( /* 1 */ &serverSocket , APR_INET , SOCK_STREAM , APR_PROTO_TCP , mainMemPool ) ; // set options apr_socket_opt_set( serverSocket , APR_SO_REUSEADDR , TRUE // "TRUE" --> APR constant ) ; // create sockaddr_t apr_sockaddr_t* sockAddr = NULL ; char* listenHost ; char* scopeID ; // unused apr_port_t listenPort ; apr_parse_addr_port( /* 2 */ &listenHost , &scopeID , &listenPort , listenString , mainMemPool ) ; apr_sockaddr_info_get( /* 3 */ &sockAddr , listenHost , APR_UNSPEC , // let system decide listenPort , 0 , mainMemPool ) ; // bind aprResult = apr_socket_bind( /* 4 */ serverSocket , sockAddr ) ; // listen aprResult = apr_socket_listen( /* 5 */ serverSocket , 15 ) ; apr_socket_t* clientSocket = NULL ; while( true ){ std::cout << "accepting ..." << std::endl ; aprResult = apr_socket_accept( /* 6 */ &clientSocket , serverSocket , mainMemPool ) ; try{ handleClient( clientSocket , interpreter ) ; }catch( const std::runtime_error& swallowed ){ // ... handle exception ... } apr_socket_shutdown( clientSocket , APR_SHUTDOWN_READWRITE ) ; apr_socket_close( clientSocket ) ; std::cout << "closed" << std::endl ; clientSocket = NULL ; } // cleanup aprResult = apr_socket_close( serverSocket ) ; } // main()
Next, main() calls the apr_parse_addr_port() convenience routine (2) to parse the program's command-line argument into a host/address and port pair. The call to apr_sockaddr_info_get() (3) configures the socket object to listen on the specified address and port.
Per its name, apr_socket_bind() attempts to bind to the specified address and port (4). Barring a failure, step1 calls apr_socket_listen() to open the door for client connections (5). Finally, apr_socket_accept() (6) blocks waiting for a client connection. When this function returns, it creates a new socket that's used for the conversation with the client.
Returning to the restaurant analogy, these steps put the "open" sign in the window. Now, you wait for a customer.