conexus logo

Quick Start Guide to Conexus

This is intended to be a very succinct introduction to conexus, and is not intended as a general tutorial.

This quick start guide will use IPv6/UDP sockets to demonstrate basic conexus concepts.

Sections:

  1. Concepts
  2. Quick start example IPv6/UDP client and server
    1. An IPv6/UDP client
      1. IPv6/UDP client code
      2. IPv6/UDP client code discussion
    2. An IPv6/UDP server
      1. IPv6/UDP server code
      2. IPv6/UDP server code discussion

Concepts

Concept #1: Endpoints

Endpoints are the points where the I and O in I/O (Input/Output) occur. If you perform a write operation on an endpoint then data will be sent via the endpoint's communication mechanism (which is dependent upon the specific endpoint type). If you perform a read operation then data will be received via the endpoint's communication mechanism (again, dependent upon the specific endpoint type, but only if data is available).
Endpoints derived from Conexus::Endpoint include endpoints that employ serial communications, IPv4/TCP, IPv4/UDP, IPv6/TCP, IPv6/UDP, kernel message queues, pipes, et. al.

Concept #2: Data

The Conexus::Data class is essentially an encapsulation of a data buffer ( a pointer to a block of bytes) and the buffer size (in bytes). This class is analogous to the buf and count parameters in the POSIX write() function:
 ssize_t write(int fd, const void *buf, size_t count);
In addition to storing the buffer and buffer size Conexus::Data provides a mechanism for storing a priority value (useful when creating priority queues) and a single time value as well as several other utility methods.

Concept #3: Starting and stopping endpoints (or moving to an event driven model)

Data can be read from a Conexus::Endpoint with the read() method which is similar in concept to the POSIX read() operation. However, a Conexus::Endpoint also provides start() and stop() methods. When a Conexus::Endpoint is started it will create a separate service thread. This thread will watch for input data and emit signal_data when data is received by the endpoint.
To receive the data as it is received by an endpoint you need to connect signal_data to the function or class method you want to receive the data. Since data is delivered via a sigc++ signal you can connect multiple functions and/or class methods and all connected callbacks will receive the endpoint's data.

Concept #4: sigc++ signal and slot library

As mentioned in Concept #3: Starting and stopping endpoints a running endpoint delivers received data to one or more of your functions or classes via the signal_data signal.
To receive data from an endpoint you need to:
  1. Create the function or class method that will receive the data from the endpoint.
    • Your function or method must have the following signature:
      1. void function_or_method_name (const Conexus::Data)
      • This means that the return type of the function or method must be void and the first parameter must be const Conexus::Data
      • If you want other parameters you must use sigc::bind , which is discussed in the Gtkmm tutorial and the sigc++ documentation.
  2. Create the sigc++ slot that will receive the data from the endpoint.
    • Generally, sigc::mem_fun and sigc::ptr_fun will suffice
    • The required signature of the sigc::slot is:
      1. sigc::slot<void, Conexus::Data>
  3. Connect the slot to the endpoint using the signal_data() method to access the data signal. You will use the signal's connect() method to accomplish this.

Concepts #5, #6 and #7: Smart Pointers, pointer and create()

An early design decision was made to focus on the use of smart pointers as defined in the ANSI C++ committee's tr1 (Technical Report 1), which as of this writing has been accepted by the committee and recommended to ISO for incorporation as the first revision to the C++ standard. A reference implementation can be found in the boost library or in the std::tr1 namespace of gcc 4.0 or greater.
Two key concepts are the pointer typedef and the create method defined in each conexus class.
The pointer typedef is defined within each class and is typedefed to a smart pointer to that specific class. Thus, Conexus::IPv6::UDP::pointer is a smart pointer to an IPv6/UDP endpoint, and Conexus::TTY::pointer is a smart pointer to a TTY endpoint.
Each class also has static create() methods with parameters that are identical to the class' constructors. The create() method dynamically creates an object and returns a smart pointer to that object (the typedefed pointer ), similar to the way the new operator dynamically creates an object and returns a raw pointer to the newly created object. Since the create() method is static, you do not need an instance of the class to call it; Classname::create() is sufficient.
Naturally, the smart pointer returned from create() can be assigned to the class' pointer type, which allows you to use a syntax such as:
 Classname::pointer my_variable = Classname::create(); 

Quick start example IPv6/UDP client and server

This example will demonstrate the creation of an IPv6 UDP client and server pair. The client will send a simple data pattern to the server on port 1500.

An IPv6/UDP client

IPv6/UDP client code

#include <conexus.h>
 
 int main(int argc, char* argv[]) {
  const char* data = "0123456789";
  Conexus::init();
  Conexus::IPv6::UDP::pointer udp = Conexus::IPv6::UDP::create();
  udp->set_remote_address("::1", 1500);
  udp->write( data, 11 );
  return 0;
 }
 

IPv6/UDP client code discussion

First, we need to include the main conexus library header:
 #include <conexus.h>
 
 int main(int argc, char* argv[]) {
Next, we will declare the variable containing the data we will send. For this we will send a simple C string:
 const char* data = "0123456789"; 
Before we do anything in conexus we must call the library's init() method which will take care of various initializations including the threading system.
With the conexus library initialized we will create the IPv6/UDP endpoint that will transmit our character string.
Note:
The statement above declares a smart pointer to the IPv6/UDP endpoint through the pointer typedef in the Conexus::IPv6::UDP class, and also calls upon the static method create() in the same class to actually create the endpoint. We use the pointer typedef since the return value from the create() method is always a smart pointer to that class type.
There are several ways a UDP socket can write to an address. The Conexus::Socket class (derived from Conexus::Endpoint and ancestor of Conexus::IPv6::UDP) introduces the writeto() method. However, for this example we will use the basic write() method present in Conexus::Endpoint (ancestor of our udp class).
While the writeto() method accepts the address as a parameter, the write() method requires the remote address to already be set, so we will do that now.
   udp->set_remote_address("::1", 1500);
Now that we have created the IPv6/UDP endpoint and set the remote address we can simply write the data to the endpoint to send it via IPv6/UDP to the remote address.
   udp->write( data, 11 );
That's it. All that is left of our example program is to provide a return value from main() and the closing brace.
   return 0;
 }

An IPv6/UDP server

This section provides an example of an IPv6/UDP server. The server will listen for all traffic on port 1500 and print the data to stdout as a C string. It will run for 20 seconds then automatically shut-down.

IPv6/UDP server code

#include <conexus.h>
#include <iostream>
#include <unistd.h>

 void print_data(const Conexus::Data d);

 int main(int argc, char* argv[]) {
   Conexus::init();
   Conexus::IPv6::UDP::pointer udp = Conexus::IPv6::UDP::create(1500);
   udp->signal_data().connect(sigc::ptr_fun(&print_data));
   udp->start();
   sleep(20);
   udp->stop();
   return 0;
 }

 void print_data(const Conexus::Data d) {
   std::cout << "Received " << d.size() << " bytes of data: " << (const char*)(d.data()) << "\n";
 }
 

IPv6/UDP server code discussion

Like the client example, we will start by including the main conexus header. Since we will also print the received data to cout we will need to include iostream . Finally, since we call the sleep() function we will also need to include unistd.h .
 #include <conexus.h>
 #include <iostream>
 #include <unistd.h>
We will declare the prototype of the function that will receive all input data. As mentioned above in the Concepts section our function must have a signature with a void return type and the first parameter must be a const Conexus::Data type.
 void print_data(const Conexus::Data d);
As in the client example, before calling any other functions in the conexus library we must ensure that the conexus library is initialized by calling the init() method.
 int main(int argc, char* argv[]) {
   Conexus::init();
With the library initialized we can create an IPv6/UDP endpoint. This will dynamically create an IPv6/UDP endpoint with the port set to 1500.
With the endpoint created we can now connect our callback function print_data() to the endpoint's data signal. We use the signal_data() accessor to get the signal, and then use the signal's connect() method. The connect() method in the sigc++ library takes a sigc++ slot as its parameter. We turn the print_data() function into a slot by using the sigc::ptr_fun() class which creates a slot from a pointer to a function (hence ptr_fun ). Thus, our function name is preceeded by the address operator & .
   udp->signal_data().connect( sigc::ptr_fun(&print_data) );
Now our endpoint is created and connected to our print_data() function. We will start the endpoint using the start() method. This will create a service thread that will call our print_data() function as our endpoint receives data.
   udp->start();
Note:
The callback will occur in the service thread, and not in the main thread.
Since all data will be delivered to print_data() in the service thread we will have this main thread sleep for 20 seconds. When you run the example server and client you should start the server, then run the client several times. You will see that the service thread is responding to your client while the main thread is sleeping at this point.
   sleep(20);
Now that we have run for 20 seconds we will shut down the endpoint and allow the endpoint to perform any necessary cleanup, where the term "necessary cleanup" varies by endpoint type.
   udp->stop();
With the endpoint stopped, all that remains is to return a proper value from main and to include the closing brace.
   return 0;
 }
Now we need to provide the body of our print_data() function. This is a one line function that adds a little helper text and prints out the number of bytes received and the actual data as a C style string.
 void print_data(const Conexus::Data d) {
   std::cout << "Received " << d.size() << " bytes of data: " << (const char*)(d.data()) << "\n";
 }
Note:
The bytes received are available through the Conexus::Data class' size() member and the data is available through the data() member.

The typecast is necessary since the native data type of the Conexus::Data class is an unsigned 8-bit integer (uint8_t ) which is probably (but not necessarily) a C unsigned char type.

Conclusion and more information

That concludes this quick-start tutorial. Additional information may be found in the conexus library documentation as well as the sigc++ library documentation. An additional source of information on how to use the sigc++ classes can be found in the Gtkmm tutorial.

Generated on Wed Jul 8 15:51:14 2009 for conexus by doxygen 1.5.8