JamLib::Net::Socket

From Onda Technology Institute Wiki
Jump to: navigation, search

Overview

Based on the standard POSIX implementation of network Sockets, JamLib::Net::Sockets has a collection of methods available to be used in applications/programs that require Inter-Process Communication features. This module has support for address-resolving (through dependencies on JamLib::Net::AAddress and JamLib::TString) as well as targeted I/O operations.

Include paths and compiler dependencies

Upon the production of your code, it is mandatory that you include the appropriate header files for usage with the Sockets module, being them: JamLib/Net/Socket.h, JamLib/Net/AAddress_IP.h, JamLib/JamLib.h and optionally JamLib/TString.h (if said header file isn't yet included by your project's source).

To appropriately link to the resources mentioned above, use the flags:

   -I/usr/include/Onda -L/usr/lib/Onda -lJamLib -lJamLib_Net

Along with your compiler of choice.

Creating and binding a new socket

In order to instantiate an object of type Socket, in namespace JamLib::Net::Socket, one must provide parameters to the class constructor that unify with the flags in the file Definitions.h. Each object of class Socket has the constructor:

  1. Socket(E_SocketFamily p_family, E_SocketType p_type, E_SocketProtocol p_protocol);

Where E_SocketFamily, E_SocketType and E_SocketProtocol are the enums with the same name in your system's include paths that contain JamLib::Net header file Definitions.h.

Usage example

For simplicity's sake below is an example where a server receives messages from a single client, printing them to the standard output stream, until the string "EOF" is intercepted and both processes (client and server) terminate execution.

The source code below (for the server process) creates a new socket of IPv4 family, of type Stream and with default protocols, to which will then be assigned a port and an address, using JamLib::Net::AAddress_IP method resolveAddress() and lastly, setting the socket to listening-mode.

  1. JamLib::Net::Socket l_srvSocket(JamLib::Net::E_SocketFamily::IP,
  2.                                 JamLib::Net::E_SocketType::Stream,
  3.                                 JamLib::Net::E_SocketProtocol::Default);
  4.  
  5. JamLib::TString l_address("0.0.0.0");
  6. JamLib::TString l_port("8080");
  7.  
  8. auto * l_addresses = JamLib::Net::AAddress_IP::resolveAddress(l_address,
  9.                                                               l_port,
  10.                                                               JamLib::Net::E_SocketFamily::IP,
  11.                                                               JamLib::Net::E_SocketType::Stream,
  12.                                                               JamLib::Net::E_SocketProtocol::Default);
  13.  
  14. l_srvSocket.Bind(*(*l_addresses)[0]);
  15. l_srvSocket.Listen(1);

POSIX equivalent

In order to create and bind a socket, we ought to use a data structure for the network address (as opposed to simply resolving it) as well as an explicit socket descriptor, requiring the usage of external address conversion routines.

  1. int l_socketDescriptor;
  2. struct sockaddr_in l_address;
  3. int l_addrlen = sizeof(l_address);
  4.  
  5. // Creating new socket with protocol for IPv4 and type SOCK_STREAM
  6. if((l_socketDescriptor = socket(AF_INET, SOCK_STREAM, 0)) == 0)
  7. {
  8.   exit(EXIT_FAILURE);
  9. }
  10.  
  11. l_address.sin_family = AF_INET;
  12. l_address.sin_addr.s_addr = INADDR_ANY;
  13. l_address.sin_port = htons(PORT);
  14.  
  15. // Assigning an IPv4 address to the socket
  16. if (bind(l_socketDescriptor, (struct sockaddr *)&l_address, sizeof(l_address)) < 0)
  17. {
  18.   exit(EXIT_FAILURE);
  19. }
  20.  
  21. // Toggling the socket to passive-mode (i.e: waiting for incoming connections)
  22. if (listen(l_socketDescriptor, 1) < 0)
  23. {
  24.   exit(EXIT_FAILURE);
  25. }

Accepting connections and receiving messages

Still on the server side, we are required to implement a procedure for accepting connections and handling the messages received from the client(s) connected. In our example we accept the first connection in queue.

Using JamLib::Net::Sockets module, we write:

  1. auto * l_clSocket = l_srvSocket.Accept();
  2.  
  3. char l_buffer[256];
  4. int l_tokenSize;
  5.  
  6. while (true)
  7. {
  8.  
  9.   memset(l_buffer, 0, sizeof(l_buffer));
  10.  
  11.   l_tokenSize = l_clSocket->Receive(l_buffer, sizeof(l_buffer), 0);
  12.  
  13.   if (strcmp(l_buffer, "EOF") == 0)
  14.   {
  15.     l_clSocket->Close();
  16.     break;	
  17.   }
  18.   if (l_tokenSize > 0) printf("%s\n",l_buffer);
  19. }

The code snippet above is the application of the specification in the example and provides a memory reference for execution of functions in the client thread that just connected to our server.

Note: Processing of messages only when the application-specific buffer is fully populated is implemented through calling ReceiveFull() method.

POSIX equivalent

The accept() function POSIX implements, requires keeping track of a few objects and data structures, as opposed to JL's parameterless implementation of Accept that simply returns an new object of type Socket.

  1. char l_buffer[256];
  2. int l_tokenSize;
  3. int l_clSocket;
  4.  
  5. // Accepting first connection request on queue.
  6. if ((l_clSocket = accept(l_socketDescriptor, (struct sockaddr *)&l_address, (socklen_t *)&l_addrlen)) < 0)
  7. {
  8.   exit(EXIT_FAILURE);
  9. }
  10.  
  11. // Developer-defined behavior for processing strings and aborting server's execution.
  12. while (true)
  13. {
  14.  
  15.   memset(l_buffer, 0, sizeof(l_buffer));
  16.  
  17.   // Receiving contents from client to buffer char *
  18.   l_tokenSize = recv(l_clSocket, l_buffer, 256, 0);
  19.  
  20.   // Shutting server down upon recieving the EOF string
  21.   if (strcmp(l_buffer, "EOF") == 0)
  22.   {
  23.     shutdown(l_socketDescriptor, SHUT_RDWR);
  24.     close(l_socketDescriptor);
  25.     break;
  26.   }
  27.  
  28.   if (l_tokenSize > 0) printf("%s\n",l_buffer);
  29.  
  30. }

Closing a socket

On the server side, closing a socket through JamLib::Net::Socket is fairly similar to the way it is usually done in UNIX, minus the file-descriptor specification.

In standard POSIX we call:

  1. close(l_socketDescriptor);

Through JamLib:

  1. l_clSocket->Close();

Client-side communication

Typically a client socket gets instantiated, connects to a server and sends messages though the usual APIs implemented, whereas using JamLib also requires the same relatively similar procedural approach.

JamLib client socket example

Uses the same address-resolving procedure as in the server and calls Send() method of the Socket class.

  1. char l_message[256];
  2. memset(l_message, 0, sizeof(l_message));
  3.  
  4. JamLib::Net::Socket l_clSocket(JamLib::Net::E_SocketFamily::IP, JamLib::Net::E_SocketType::Stream, JamLib::Net::E_SocketProtocol::Default);
  5.  
  6. JamLib::TString l_address("127.0.0.1");
  7. JamLib::TString l_port("8080");
  8.  
  9. auto * l_addresses = JamLib::Net::AAddress_IP::resolveAddress(l_address,
  10.                                                               l_port,
  11.                                                               JamLib::Net::E_SocketFamily::IP,
  12.                                                               JamLib::Net::E_SocketType::Stream,
  13.                                                               JamLib::Net::E_SocketProtocol::Default);
  14.  
  15. l_clSocket.Connect(*(*l_addresses)[0]);
  16.  
  17. while (true)
  18. {
  19.  
  20.   fgets(l_message, sizeof(l_message), stdin);
  21.  
  22.   l_clSocket.Send(l_message, strlen(l_message)-1, 0);
  23.  
  24.   if (strcmp(l_message, "EOF\n") != 0)
  25.   {
  26.     memset(l_message, 0, strlen(l_message));
  27.   }
  28.   else if (strcmp(l_message, "EOF\n") == 0) break;
  29. }

Like in the server program example presented before, we are to use a struct for the network address and perform the appropriate casting as required by the connect routine.

POSIX equivalent implementation

Similarly to the server program, we manipulate the address

  1. struct sockaddr_in l_address;
  2. int l_socketDescriptor, l_valread;
  3. struct sockaddr_in l_serv_addr;
  4.  
  5. char l_message[256];
  6.  
  7. if ((l_socketDescriptor = socket(AF_INET, SOCK_STREAM, 0)) < 0)
  8. {
  9.   exit(EXIT_FAILURE);
  10. }
  11.  
  12. memset(&l_serv_addr, '0', sizeof(l_serv_addr));
  13.  
  14. l_serv_addr.sin_family = AF_INET;
  15. l_serv_addr.sin_port = htons(PORT);
  16.  
  17. if (inet_pton(AF_INET, "127.0.0.1", &l_serv_addr.sin_addr)<=0)
  18. {
  19.   exit(EXIT_FAILURE);
  20. }
  21.  
  22. if (connect(l_socketDescriptor, (struct sockaddr *)&l_serv_addr, sizeof(l_serv_addr)) < 0)
  23. {
  24.   exit(EXIT_FAILURE);
  25. }
  26.  
  27. while (true)
  28. {
  29.  
  30.   fgets(l_message, sizeof(l_message), stdin);
  31.  
  32.   send(l_socketDescriptor, l_message, strlen(l_message)-1, 0);
  33.  
  34.   if (strcmp(l_message, "EOF\n") != 0)
  35.   {
  36.     memset(l_message, 0, strlen(l_message));
  37.   }
  38.   else if (strcmp(l_message, "EOF\n") == 0) break;
  39. }

TODO

  • Look into documenting the ReceiveFrom() and SendTo() methods, not yet implemented;
  • Evaluate the need to implement socket shutdown() routine featured in POSIX systems, allowing the conditional restriction of IO operations - refer to: Advanced Programming in UNIX Environment ch. 16 pp. 548,549.