Overview of the Socket API (1/2) Sockets are the most common network programming API available on operating system platforms •Originally developed in BSD Unix as a C language API to TCP/IP protocol suite •The Socket API has approximately two dozen functions classified in five categories •Socket is a handle created by the OS that associates it with an end point of a communication channel 1
•A socket can be bound to a local or remote address •In Unix, socket handles & I/O handles can be used interchangeably in most cases, but this is not the case for Windows
Overview of the Socket API (2/2) Local context management
Connection establishment & termination
Data transfer mechanisms
Options management Network addressing 2
Taxonomy of Socket Dimensions The Socket API can be decomposed into the following dimensions: • Type of communication service • e.g., streams versus datagrams versus connected datagrams
• Communication & connection role • e.g., clients often initiate connections actively, whereas servers often accept them passively
• Communication domain
3
• e.g., local host only versus local or remote host
Limitations with the Socket APIs (1/2) Poorly structured, non-uniform, & non-portable • API is linear rather than hierarchical • i.e., the API is not structured according to the different phases of connection lifecycle management and the roles played by the participants
• No consistency among the names • Non-portable & error-prone • Function names: read() & write() used for any I/O handle on Unix but Windows needs ReadFile() & WriteFile() • Function semantics: different behavior of same function on different OS e.g., accept () can take NULL client address parameter on Unix/Windows, but will crash on some operating systems, such as VxWorks • Socket handle representations: different platforms represent sockets differently e.g., Unix uses unsigned integers whereas Windows uses pointers • Header files: Different platforms use different names for header files for the socket API 4
Limitations with the Socket APIs (2/2) Lack of type safety • I/O handles are not amenable to strong type checking at compile time • e.g., no type distinction between a socket used for passive listening & a socket used for data transfer
Steep learning curve due to complex semantics • Multiple protocol families & address families • Options for infrequently used features such as broadcasting, async I/O, non blocking I/O, urgent data delivery • Communication optimizations such as scatter-read & gather-write • Different communication and connection roles, such as active & passive connection establishment, & data transfer
Too many low-level details • Forgetting to use the network byte order before data transfer • Possibility of missing a function, such as listen() • Possibility of mismatch between protocol & address families • Forgetting to initialize underlying C structures e.g., sockaddr • Using a wrong socket for a given role 5
Example of Socket API Limitations (1/3) 1 #include <sys/types.h> 2 #include <sys/socket.h>
Possible differences in header file names
3 4 const int PORT_NUM = 10000; 5 6 int echo_server () 7 { 8 9
6
struct sockaddr_in addr; int addr_len;
Forgot to initialize to sizeof (sockaddr_in)
10
char buf[BUFSIZ];
11
int n_handle;
12
// Create the local endpoint.
Use of non-portable handle type
Example of Socket API Limitations (2/3) 13
int s_handle = socket (PF_UNIX, SOCK_DGRAM, 0);
14
if (s_handle == -1) return -1;
15 16
Use of non-portable return value
17
// Set up address information where server listens. Protocol and address family addr.sin_family = AF_INET; mismatch
18
addr.sin_port = PORT_NUM;
19 20
addr.sin_addr.addr = INADDR_ANY; Unused structure members not zeroed out
21
if (bind (s_handle, (struct sockaddr *) &addr,
22 23 24 7
Wrong byte order
sizeof addr) == -1) return -1; Missed call to listen()
Example of Socket API Limitations (3/3) 25
// Create a new communication endpoint.
26
if (n_handle = accept (s_handle, (struct sockaddr *) &addr,
27
&addr_len) != -1) { int n;
29
while ((n = read (s_handle, buf, sizeof buf)) > 0)
30
write (n_handle, buf, n);
31
close (n_handle);
33
}
34
return 0;
35 }
Reading from wrong handle
No guarantee that “n” bytes will be written
32
8
SOCK_DGRAM handle illegal here
28
ACE Socket Wrapper Façade Classes ACE defines a set of C++ classes that address the limitations with the Socket API • Enhance type-safety • Ensure portability • Simplify common use cases • Building blocks for higher-level abstractions These classes are designed in accordance with the Wrapper Facade design pattern
9
The Wrapper Façade Pattern (1/2) Context • Networked applications must manage a variety of OS services, including processes, threads, socket connections, virtual memory, & files
Applications
• OS platforms provide lowlevel APIs written in C to access these services
Problem • The diversity of hardware & operating systems makes it hard to build portable & robust networked application software • Programming directly to low-level OS APIs is tedious, error-prone, & non10 portable
Solaris
Win2K
VxWorks
Linux
LynxOS
The Wrapper Façade Pattern (2/2) Solution • Apply the Wrapper Facade design pattern (P2) to avoid accessing low-level operating system APIs directly Wrapper Facade
calls
data
calls
method1() … methodN()
calls
API FunctionA()
calls methods
Application
This pattern encapsulates data & functions provided by existing non-OO APIs within more concise, robust, portable, maintainable, & cohesive OO class interfaces
void method1(){ functionA(); functionB(); }
: Application
API FunctionB() API FunctionC()
void methodN(){ functionA(); }
: Wrapper Facade
: APIFunctionA
: APIFunctionB
method() functionA() functionB()
11
ACE Socket Wrapper Façades Taxonomy •The structure of the ACE Socket wrapper facades reflects the domain of networked IPC properties •The ACE Socket wrapper façade classes provide the following capabilities: •ACE_SOCK_* classes encapsulate Internet-domain Socket API functionality •ACE_LSOCK_* classes encapsulate UNIX-domain Socket API functionality 12
•ACE also has wrapper facades for datagrams •e.g., unicast, multicast, broadcast
Roles in the ACE Socket Wrapper Facade
13
•The active connection role (ACE_SOCK_Connector) is played by a peer application that initiates a connection to a remote peer •The passive connection role (ACE_SOCK_Acceptor) is played by a peer application that accepts a connection from a remote peer & •The communication role (ACE_SOCK_Stream) is played by both peer applications to exchange data after they are connected
ACE Socket Addressing Classes (1/2) Motivation • Network addressing is a trouble spot in the Socket API • To minimize the complexity of these low-level details, ACE defines a hierarchy of classes that provide a uniform interface for all ACE network addressing objects
14
ACE Socket Addressing Classes (2/2)
Class Capabilities •The ACE_Addr class is the root of the ACE network addressing hierarchy •The ACE_INET_Addr class represents TCP/IP & UDP/IP addressing information •This class eliminates many subtle sources of accidental complexity
15
ACE I/O Handle Classes (1/2) Motivation • Low-level C I/O handle types are tedious, error-prone, & non-portable • Even the ACE_HANDLE typedef is still not sufficiently object-oriented & typesafe int buggy_echo_server (u_short port_num) { sockaddr_in s_addr; int acceptor = socket (PF_UNIX, SOCK_DGRAM, 0); int is not portable to Windows s_addr.sin_family = AF_INET; s_addr.sin_port = port_num; s_addr.sin_addr.s_addr = INADDR_ANY; bind (acceptor, (sockaddr *) &s_addr, sizeof s_addr); int handle = accept (acceptor, 0, 0); for (;;) { char buf[BUFSIZ]; ssize_t n = read (acceptor, buf, sizeof buf); if (n <= 0) break; Reading from wrong handle write (handle, buf, n); } } 16
ACE I/O Handle Classes (2/2) Class Capabilities •ACE_IPC_SAP is the root of the ACE hierarchy of IPC wrapper facades •It provides basic I/O handle manipulation capabilities to other ACE IPC wrapper facades •ACE_SOCK is the root of the ACE Socket wrapper facades & it provides methods to •Create & destroy socket handles •Obtain the network addresses of local & remote peers •Set/get socket options, such as socket queue sizes, •Enable broadcast/multicast communication 17
•Disable Nagle‘s algorithm
The ACE_SOCK_Connector Class Motivation •There is a confusing asymmetry in the Socket API between (1) connection roles & (2) socket modes •e.g., an application may accidentally call recv() or send() on a data-mode socket handle before it's connected •This problem can't be detected until run time since C socket handles are weakly-typed int buggy_echo_client (u_short port_num, const char *s) { int handle = socket (PF_UNIX, SOCK_DGRAM, 0); write (handle, s, strlen (s) + 1); sockaddr_in s_addr; Operations called in memset (&s_addr, 0, sizeof s_addr); wrong order s_addr.sin_family = AF_INET; s_addr.sin_port = htons (port_num); connect (handle, (sockaddr *) &s_addr, sizeof s_addr); } 18
The ACE_SOCK_Connector Class Class Capabilities •ACE_SOCK_Connector is factory that establishes a new endpoint of communication actively & provides capabilities to •Initiate a connection with a peer acceptor & then to initialize an ACE_SOCK_Stream object after the connection is established •Initiate connections in either a blocking, nonblocking, or timed manner •Use C++ traits to support generic programming techniques that enable wholesale replacement of IPC functionality
19
Sidebar: Traits for ACE Wrapper Facades (1/2) •ACE uses the C++ generic programming idiom to define & combine a set of characteristics to alter the behavior of a template class •In C++, the typedef & typename language feature is used to define a trait •A trait provides a convenient way to associate related types, values, & functions with template parameter type without requiring that they be defined as members of the type •Traits are used extensively in the C++ Standard Template Library (STL)
20
Sidebar: Traits for ACE Wrapper Facades (2/2) •ACE Socket wrapper facades use traits to define the following associations •PEER_ADDR – this trait defines the ACE_INET_Addr class associated with the ACE Socket Wrapper Façade •PEER_STREAM – this trait defines the ACE_SOCK_Stream data transfer class associated with the ACE_SOCK_Acceptor & ACE_SOCK_Connector factories class ACE_SOCK_Connector { public: typedef ACE_INET_Addr PEER_ADDR; typedef ACE_SOCK_Stream PEER_STREAM; // ...
21
class ACE_TLI_Connector { public: typedef ACE_INET_Addr PEER_ADDR; typedef ACE_TLI_Stream PEER_STREAM; // ...
Using the ACE_SOCK_Connector (1/3) •This example shows how the ACE_SOCK_Connector can be used to connect a client application to a Web server int main (int argc, char *argv[]) { const char *pathname = argc > 1 ? argv[1] : “/index.html"; const char *server_hostname = • Instantiate the connector, argc > 2 data transfer, & address ? argv[2] : “www.dre.vanderbilt.edu"; objects typedef ACE_SOCK_Connector CONNECTOR; CONNECTOR connector; CONNECTOR::PEER_STREAM peer; CONNECTOR::PEER_ADDR peer_addr; if (peer_addr.set (80, server_hostname) == -1) • Block until connection return 1; established or else if (connector.connect (peer, peer_addr) == -1) connection request failure return 1; 22
Using the ACE_SOCK_Connector (2/3) // Designate a nonblocking connect. • Perform a non-blocking if (connector.connect (peer, connect peer_addr, &ACE_Time_Value::zero) == -1) { if (errno == EWOULDBLOCK) { // Do some other work ... // Now, try to complete connection establishment, // but don't block if it isn't complete yet. if (connector.complete (peer, 0, • If connection not &ACE_Time_Value::zero) == -1) established, do other work & try again without blocking // Designate a timed connect. ACE_Time_Value timeout (10); // 10 second timeout. if (connector.connect (peer, • Perform a timed connect peer_addr, e.g., 10 seconds in this &timeout) == -1) { case if (errno == ETIME) { // Timeout, do something else 23
Using the ACE_SOCK_Connector (3/3) •The ACE_SOCK_Connector can be passed the following values to control its timeout behavior
24
The ACE_SOCK_Stream Class (1/2) Motivation •Developers can misuse sockets in ways that can't be detected during compilation •An ACE_SOCK_Stream object can't be used in any role other than data transfer without violating its (statically type-checked) interface int buggy_echo_server (u_short port_num) { sockaddr_in s_addr; int acceptor = socket (PF_UNIX, SOCK_DGRAM, 0); s_addr.sin_family = AF_INET; s_addr.sin_port = port_num; s_addr.sin_addr.s_addr = INADDR_ANY; bind (acceptor, (sockaddr *) &s_addr, sizeof s_addr); int handle = accept (acceptor, 0, 0); for (;;) { char buf[BUFSIZ]; ssize_t n = read (acceptor, buf, sizeof buf); if (n <= 0) break; Reading from wrong handle write (handle, buf, n); } } 25
The ACE_SOCK_Stream Class (2/2) Class Capabilities •Encapsulates data transfer mechanisms supported by data-mode sockets to provide the following capabilities: •Support for sending & receiving up to n bytes or exactly n bytes •Support for “scatter-read,” which populate multiple callersupplied buffers instead of a single contiguous buffer •Support for ``gather-write'' operations, which transmit the contents of multiple noncontiguous data buffers in a single operation •Support for blocking, nonblocking, & timed I/O operations •Support for generic programming techniques that enable the wholesale replacement of functionality via C++ parameterized types 26
Using the ACE_SOCK_Stream (1/2) •This example shows how an ACE_SOCK_Stream can be used to send & receive data to & from a Web server // ...Connection code from example in Section 3.5 omitted... char buf[BUFSIZ]; • Initialize the iovec iovec iov[3]; vector for scatter-read & gather-write I/O iov[0].iov_base = (char *) "GET "; iov[0].iov_len = 4; // Length of "GET ". iov[1].iov_base = (char *) pathname; iov[1].iov_len = strlen (pathname); iov[2].iov_base = (char *) " HTTP/1.0\r\n\r\n"; iov[2].iov_len = 13; // Length of " HTTP/1.0\r\n\r\n"; if (peer.sendv_n (iov, 3) == -1) return 1;
• Perform
blocking gatherwrite on ACE_SOCK_Stream
for (ssize_t n; (n = peer.recv (buf, sizeof buf)) > 0; ) ACE::write_n (ACE_STDOUT, buf, n); return peer.close () == -1 ? 1 : 0; } 27
• Perform
blocking read on ACE_SOCK_Stream
Using the ACE_SOCK_Stream (2/2) •Blocking & non-blocking I/O semantics can be controlled via the ACE_SOCK_STREAM enable() & disable() methods, e.g., •peer.enable (ACE_NONBLOCK); // enables non blocking peer.disable (ACE_NONBLOCK); // disable non blocking • If the I/O operation blocks, it returns a -1 & errno is set to EWOULDBLOCK •I/O operations can involve timeouts, e.g., ACE_Time_Value timeout (10); // 10 second timeout If (peer.sendv_n (iov, 3, &timeout) == -1) { // check if errno is set to ETIME, // which indicates a timeout } // similarly use timeout for receiving data 28
Sidebar: Working with (& Around) Nagle’s Algorithm Nagle’s Algorithm • Problem: Need to tackle the send-side silly window syndrome, where small data payloads, such as a keystroke, result in transmissions of large packets & causing unnecessary waste of network resources & congestion • Solution: The OS kernel buffers a # of small-sized application messages & concatenates them into a larger size packet that can then be transmitted • Consequences: Although network congestion is minimized, it can lead to higher & unpredictable latencies, as well as lower throughput Controlling Nagle’s Algorithm via ACE • Use the set_option() method of the ACE_SOCK class e.g., int nodelay = 1; // Disable Nagle’s algorithm ACE_SOCK_Stream option_setter (handle); if (-1 == option_setter.set_option (ACE_IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof (nodelay))) ... 29
The ACE_SOCK_Acceptor Class (1/2) Motivation •The C functions in the Socket API are weakly typed, which makes it easy to apply them incorrectly in ways that can’t be detected until run-time •The ACE_SOCK_Acceptor class ensures type errors are detected at compile-time
30
int buggy_echo_server (u_short port_num) { sockaddr_in s_addr; int acceptor = socket (PF_UNIX, SOCK_DGRAM, 0); s_addr.sin_family = AF_INET; s_addr.sin_port = port_num; s_addr.sin_addr.s_addr = INADDR_ANY; bind (acceptor, (sockaddr *) &s_addr, sizeof s_addr); int handle = accept (acceptor, 0, 0); for (;;) { char buf[BUFSIZ]; ssize_t n = read (acceptor, buf, sizeof buf); if (n <= 0) break; Reading from wrong handle write (handle, buf, n); } }
The ACE_SOCK_Acceptor Class (2/2) Class Capabilities •This class is a factory that establishes a new endpoint of communication passively & provides the following capabilities: •It accepts a connection from a peer connector & then initializes an ACE_SOCK_Stream object after the connection is established •Connections can be accepted in either a blocking, nonblocking, or timed manner •C++ traits are used to support generic programming techniques that enable the wholesale replacement of functionality via C++ parameterized types 31
Using the ACE_SOCK_Acceptor • This example shows how an ACE_SOCK_Acceptor & ACE_SOCK_Stream can be used to accept connections & send/receive data to/from a web client extern char *get_url_pathname (ACE_SOCK_Stream *); int main () • Instantiate the acceptor, data transfer, & address objects { ACE_INET_Addr server_addr; • Initialize a passive ACE_SOCK_Acceptor acceptor; mode endpoint to ACE_SOCK_Stream peer; listen for connections on port 80 if (server_addr.set (80) == -1) return 1; if (acceptor.open (server_addr) == -1) return 1; • Accept a new connection for (;;) { if (acceptor.accept (peer) == -1) return 1; peer.disable (ACE_NONBLOCK); // Ensure blocking <send_n>. ACE_Auto_Array_Ptr pathname (get_url_pathname (peer)); ACE_Mem_Map mapped_file (pathname.get ()); • Send the
}32
requested data if (peer.send_n (mapped_file.addr (), mapped_file.size ()) == -1) return 1; peer.close (); • Close the connection to the sender } • Stop receiving any return acceptor.close () == -1 ? 1 : 0; connections
Sidebar: The ACE_Mem_Map Class Memory Mapped Files
ACE_Mem_Map Class
•Many modern operating systems provide a mechanism for mapping a file’s contents directly into a process’s virtual address space
•A wrapper façade that encapsulates the memory mapped file system mechanisms on different operating systems
•This memory-mapped file mechanism can be read from or written to directly by referencing the virtual memory •e.g., via pointers instead of using less efficient I/O functions •The file manager defers all read/write operations to the virtual memory manager •Contents of memory mapped files can be shared by multiple processes on the same machine •It can also be used to provide a persistent backing store 33
•Relieves application developers from having to manually perform bookkeeping tasks •e.g., explicitly opening files or determining their lengths •The ACE_Mem_Map class offers multiple constructors with several signature variants