Sockets are interprocess communication interfaces that can also be used for network communication. For example, client-server applications can be written using sockets.
On maemo, you can use the Glib library to make the sockets implementation easier. Using the Glib::IOChannel class, you can integrate your sockets' I/O operations to the main event loop. This way your application doesn't need to keep polling the socket descriptor to be aware when a message is ready to be sent or read.
This section explains how to create a simple TCP (streamed) socket connection between two applications, client and server. These applications communicate with each other using sockets and use I/O channels to manage the incoming and outgoing messages.
Note: I/O channels can be used with other file descriptors besides sockets, such as files and pipes. The main goal of using I/O channels is that they do not block your application waiting for I/O operations because they are well integrated in the Glib main loop.
The server initializes with a given port specified by the user:
The client starts and asks the user about the server IP address and port number of the server:
After this, the client tries to connect to the server and if everything is correct, the server waits until a new message from the client arrives:
After connecting, the client shows the user an entry box for writing a message to be sent to the server:
The server gets the message sent by the client and displays
it on the screen:
Next, the source code of both applications is discussed. Client source code is explained first, since it is simpler than server source code.
This section explains some parts of the source code related to sockets and I/O channels operation. Below is the header for the Connection class. It contains the following new attributes:
/* Connection class */ class Connection { public: Connection(); virtual ~Connection(); bool create(Gtk::Window *window, Glib::ustring address, Glib::ustring port); bool finish(Gtk::Window *window); bool write_message(Gtk::Window *window, Glib::ustring message); /* I/O Channel */ Glib::RefPtr<Glib::IOChannel> c_channel; protected: /* Socket descriptor */ int c_socket; /* Exception error */ std::auto_ptr<Glib::Error> c_ex; };
Note that this class has some special methods. They receive Gtk::Window from the application window in order to generate error messages (if any). They also return true if everything went OK and false if an error occurred.
bool Connection::create(Gtk::Window *window, Glib::ustring address, Glib::ustring port) { struct sockaddr_in server_address; // Server address structure struct hostent *host_info; // Host info structure /* Transform address string into network address */ host_info = gethostbyname(address.c_str()); if (host_info == NULL) { show_error(window, _("Incorrect server address!")); return false; } else { /* Create socket descriptor */ c_socket = socket(AF_INET, SOCK_STREAM, 0); if (c_socket < 0) { show_error(window, _("Cannot create socket!")); return false; } else { /* Set up server address properties (family, address, port) */ server_address.sin_family = host_info->h_addrtype; memcpy((char *) &server_address.sin_addr.s_addr, host_info->h_addr_list[0], host_info->h_length); server_address.sin_port = htons(atoi(port.c_str())); /* Connect socket to server */ if (connect(c_socket, (struct sockaddr *) &server_address, sizeof(server_address)) < 0) { show_error(window, _("Cannot connect to server address!")); return false; } else { /* Create an IOChannel from socket descriptor */ c_channel = Glib::IOChannel::create_from_fd(c_socket); /* Set channel to be non-blocking */ c_channel->set_flags((c_channel->get_flags() | Glib::IO_FLAG_NONBLOCK), c_ex); if (c_ex.get()) { show_exception(window, c_ex); return false; } else return true; } } } }
bool Connection::finish(Gtk::Window *window) { /* Close I/O channel */ c_channel->close(true, c_ex); if (c_ex.get()) { show_exception(window, c_ex); return false; } else { /* Close socket descriptor */ close(c_socket); return true; } }
bool Connection::write_message(Gtk::Window *window, Glib::ustring message) { /* Write message to I/O channel */ c_channel->write(message, c_ex); if (c_ex.get()) { show_exception(window, c_ex); return false; } else { /* Flush I/O channel */ c_channel->flush(c_ex); if (c_ex.get()) { show_exception(window, c_ex); return false; } else return true; } }
This section examines the server code related to sockets and I/O operations. Gtk::TextView widget is also explained here. The server application has two sockets and an I/O channel associated for each one. The first socket is the listener for new connections, and the other is activated when a new connection is accepted from a client.
Below is the Server class responsible for dealing with new connections:
/* Server listener class */ class Server { public: Server(); virtual ~Server(); bool create(Gtk::Window *window, int port); bool finish(Gtk::Window *window); /* Listen socket */ int s_listen_socket; /* Socket address */ sockaddr_in s_address; /* Listen I/O channel */ Glib::RefPtr<Glib::IOChannel> s_listen_channel; protected: /* Exception error */ std::auto_ptr<Glib::Error> s_ex; };
This class contains two special methods:
create: Create a socket descriptor, bind it with the server address and listen for new connections, associating an I/O channel for this socket.
bool Server::create(Gtk::Window *window, int port) { /* Create TCP (streamed) network socket */ s_listen_socket = socket(AF_INET, SOCK_STREAM, 0); if (s_listen_socket < 0) { show_error(window, _("Can't create listen socket!")); return false; } else { /* Set socket address properties */ s_address.sin_family = AF_INET; s_address.sin_addr.s_addr = htonl(INADDR_ANY); s_address.sin_port = htons(port); /* Bind socket with server address */ if (bind(s_listen_socket, (struct sockaddr *) &s_address, sizeof(s_address)) < 0) { show_error(window, _("Can't bind socket!")); return false; } else { /* Listen for only 1 connection */ if (listen(s_listen_socket, 1) != 0) { show_error(window, _("Can't listen from socket!")); return false; } else { /* Create an IOChannel from socket descriptor */ s_listen_channel = Glib::IOChannel::create_from_fd(s_listen_socket); /* Set listen channel to be non-blocking */ s_listen_channel->set_flags((s_listen_channel->get_flags() | Glib::IO_FLAG_NONBLOCK), s_ex); if (s_ex.get()) { show_exception(window, s_ex); return false; } else return true; } } } }
finish: Close the I/O channel and the socket descriptor.
bool Server::finish(Gtk::Window *window) { /* Close listen I/O channel */ s_listen_channel->close(true, s_ex); if (s_ex.get()) { show_exception(window, s_ex); return false; } else { /* Close listen socket descriptor */ close(s_listen_socket); return true; } }
Now let's see the Connection class which is used when a new connection is accepted by the server. Is also has a special attribute called buffer that is used to store the message read by the read_message method:
/* Client connection class */ class Connection { public: Connection(); virtual ~Connection(); bool create(Gtk::Window *window, Server *server); bool finish(Gtk::Window *window); bool read_message(Gtk::Window *window); /* Message buffer */ Glib::ustring buffer; /* Client Connection I/O channel */ Glib::RefPtr<Glib::IOChannel> c_conn_channel; protected: /* Client connection socket */ int c_conn_socket; /* Exception error */ std::auto_ptr<Glib::Error> c_ex; };
This class contains three special methods:
bool Connection::create(Gtk::Window *window, Server *server) { /* Pick up server's socket length */ socklen_t socket_length = sizeof(server->s_listen_socket); /* Accept a socket connection from client */ c_conn_socket = accept(server->s_listen_socket, (struct sockaddr *) &server->s_address, >socket_length); if (c_conn_socket < 0) { show_error(window, "Can't accept from socket!"); return false; } else { /* Create an IOChannel from socket descriptor */ c_conn_channel = Glib::IOChannel::create_from_fd(c_conn_socket); /* Set connection channel to be non-blocking */ c_conn_channel->set_flags((c_conn_channel->get_flags() | Glib::IO_FLAG_NONBLOCK), c_ex); if (c_ex.get()) { show_exception(window, c_ex); return false; } else return true; } }
bool Connection::finish(Gtk::Window *window) { /* Close client connection I/O channel */ c_conn_channel->close(true, c_ex); if (c_ex.get()) { show_exception(window, c_ex); return false; } else { /* Close client connection socket descriptor */ close(c_conn_socket); return true; } }
bool Connection::read_message(Gtk::Window *window) { if (c_conn_channel->read(buffer, 100, c_ex) != Glib::IO_STATUS_NORMAL) { return false; } else { if (c_ex.get()) { show_exception(window, c_ex); return false; } else return true; } }
As you can see, these classes are very similar to each other in some parts, but the creation process differs since each one has a specific purpose. The full source code of this example is available here.
Links