Sockets: The Connectionless Paradigm
Sockets The Connectionless Paradigm
The sequence of events for connectionless clientserver communication has some common elements with connection-oriented communication. Both the client and server still generate sockets using the socket call. The server and, most often, the client, bind their sockets to an address. However, in the connection-oriented sequence, only the server performs this step. The client process does not use connect to establish a connection with the server. Instead, both the server and client send and receive datagram packets to and from a specified address. The server process sends its packets to the client address, and the client sends its packets to the server address. These events are shown in Figure 10.14.
Figure 10.14. A connectionless clientserver communication sequence.
In this example, we have used the sendto and recvfrom system calls for data exchange. The sendto call in the client is somewhat similar in function to the connect write sequence we saw in the initial Internet domain connection-oriented example. Similarly, the recvfrom call is analogous to the accept read sequence we used for the server in the same example.
The sendto call is one of several alternate ways to write data to a socket. Table 10.19 provides a summary of three calls that can write data to a socket descriptor.
Table 10.19. Summary of the send , sendto , and sendmsg System Calls.
Include File(s) |
|
Manual Section |
2 |
|
Summary |
int send (int s, const void *msg,size_t len, int flags); int sendto (int s,const void *msg, size_t len, int flags, const struct sockaddr *to, socklen_t tolen); int sendmsg(int s, const struct msghdr *msg, int flags); |
|||
Return |
Success |
Failure |
Sets errno |
|
Number of bytes sent. |
-1 |
Yes |
The send call, since it contains no destination addressing information, can only be used with connected (SOCK_STREAM) sockets. The sendto and sendmsg calls can be used with either socket type but are most commonly used with datagram sockets (SOCK_DGRAM). The send and sendto calls dispatch a sequence of bytes. The sendmsg call is used to transmit data that resides in noncontiguous ( scattered ) memory locations (such as in a structure).
In all three calls, the integer argument s is a valid socket descriptor. The *msg argument references the message to be sent. In the sendmsg call, the msg reference is to a structure of type msghdr that contains additional addressing/messaging information. [12] This structure is defined as
[12] As its use is rather complex, we will only mention sendmsg (and its reciprocal recvmsg ) in passing (no pun intended!).
struct msghdr { void *msg_name; /* optional address */ socklen_t msg_namelen; /* size of address */ struct iovec *msg_iov; /* scatter/gather array */ size_t msg_iovlen; /* # elements in msg_iov */ void *msg_control; /* ancillary data, see below */ socklen_t msg_controllen; /* ancillary data buffer len */ int msg_flags; /* flags on received message */ };
where the type iovec is
struct iovec { void *iov_base; /* Pointer to data. */ size_t iov_len; /* Length of data. */ };
The len argument is the length of the message to send. Message size is limited by the underlying protocol. The sendto call contains an additional argument that references the address structure with the information of where to send the message. With sendto this argument is followed by an argument containing the size of the addressing structure. If sendto is used with a connection-oriented socket, these two arguments are ignored. All three calls have an integer-based flag argument. Bitwise OR ing the value 0 with one or more of the defined constants in Table 10.20 generates the flag value.
Table 10.20. Flags for the send , sendto , and sendmsg Calls.
Flag |
Meaning |
---|---|
MSG_OOB |
Message out of band . At present this flag is valid only for Internet stream-based sockets. Specifying MSG_OOB allows the process to send urgent data. The receiving process can choose to ignore the message. |
MSG_DONTROUTE |
Bypass routing tables and attempt to send message in one hop. This is often used for diagnostics purposes. |
MSG_DONTWAIT |
Adopt non-blocking operation for operations that block return EAGAIN. |
MSG_NOSIGNAL |
On stream-based socket do not send a SIGPIPE error when one end of connection is broken. |
MSG_CONFIRM |
With SOCK_DGRAM and SOCK_RAW sockets, in Linux 2.3+, notify the link layer of successful reply from the other side. |
In some settings when the above calls are used, the network and socket library must be specified. In such settings use the -lnsl and/or -lsocket compiler option to notify the linker. These calls return the number of bytes sent or, in case of error, a -1, setting errno to one of the values found in Table 10.21. Data sent to an unbound socket is discarded.
Table 10.21. send , sendto , and sendmsg Error Messages.
# |
Constant |
perror Message |
Explanation |
---|---|---|---|
4 |
EINTR |
Interrupted system call |
A signal was received by the process before data was sent. |
9 |
EBADF |
Bad file descriptor |
The socket reference is invalid. |
11 |
EWOULDBLOCK, EAGAIN |
Resource temporarily unavailable |
The socket is set to non-blocking, and no connections are pending. |
12 |
ENOMEM |
Cannot allocate memory |
Insufficient memory to perform operation. |
14 |
EFAULT |
Bad address |
Argument references location outside user address space. |
22 |
EINVAL |
Invalid argument |
tolen argument contains an incorrect value. |
32 |
EPIPE |
Broken pipe |
Local end of a connection-oriented socket is closed. |
88 |
ENOTSOCK |
Socket operation on non-socket |
The socket argument is a file descriptor, not a socket descriptor. |
90 |
EMSGSIZE |
Message too long |
Socket type requires message to be sent to be atomic (all sent at once) and the message to send is too long. |
105 |
ENOBUFS |
No buffer space available |
Output queue is full. |
Other than read , there are three system calls comparable to send for receiving data from a socket descriptor. These calls are recv , recvfrom , and recvmsg . Unless otherwise specified (such as with fcntl ), these calls will block if no message has arrived at the socket. Table 10.22 provides a summary of these calls.
Since it contains no sender address information, the recv network call should only be used with connection-oriented (SOCK_STREAM) sockets. The recvfrom and recvmsg calls can be used with connection-oriented or connectionless sockets. Usually, when data is written to a socket with send , sendto , or sendmsg , it is read with the corresponding recv , recvfrom , or recvmsg call.
Table 10.22. Summary of the recv , recvfrom , and recvmsg System Calls.
Include File(s) |
|
Manual Section |
2 |
|
Summary |
int recv(int s, void *buf, size_t len, int flags); int recvfrom(int s, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen); int recvmsg(int s, struct msghdr *msg, int flags); |
|||
Return |
Success |
Failure |
Sets errno |
|
Number of bytes received |
-1 |
Yes |
In each call, the integer argument s is a valid socket descriptor. The *buffer argument references the location where the received message will be stored. The user is responsible for allocating the storage space for the received message. As with the sendmsg network call, the *msg argument for recvmsg references a msghdr structure. The len argument is the length of the receive message buffer. Remember that the message size is limited by the underlying protocol and exceedingly long messages may be truncated. The receive calls return the actual number of bytes received. If the *from argument for the recvfrom call is not NULL, it should reference a sockaddr structure containing the address information of the host that sent the message. The *fromlen argument should reference the length of this addressing structure. OR ing the value 0 with one or more of the defined flags shown in Table 10.23 forms the flag argument.
Table 10.23. Flags for the recv , recvfrom , and recvmsg Calls.
Flag |
Meaning |
---|---|
MSG_ERRQUEUE |
Receive error messages from the error message queue. The details of how to implement error message retrieval is beyond the scope of this text (see the manual page on recv for specifics). |
MSG_NOSIGNAL |
With a stream socket, do not raise a SIGPIPE error when the other end of the socket disappears. |
MSG_OOB |
Message out of band. At present, this flag is valid only for Internet stream-based sockets. Specifying MSG_OOB allows the process to read urgent out-of-band data. |
MSG_PEEK |
Look at the current data but do not consume it. Subsequent read -receive type calls will retrieve the same peeked -at data. |
MSG_TRUNC |
With datagram socket, return the real length of the message even if it exceeds specified amount. |
MSG_WAITALL |
Wait until the full request for data has been satisfied. |
In cases of error, these calls will return a -1 and set errno to one of the values found in Table 10.24.
Table 10.24. recv , recvfrom , and recvmsg Error Messages
# |
Constant |
perror Message |
Explanation |
---|---|---|---|
4 |
EINTR |
Interrupted system call |
A signal was received by process before data was received. |
9 |
EBADF |
Bad file descriptor |
The socket reference is invalid. |
11 |
EAGAIN |
Resource temporarily unavailable |
|
14 |
EFAULT |
Bad address |
Argument references a location outside user address space. |
22 |
EINVAL |
Invalid argument |
An argument contains incorrect value. |
88 |
ENOTSOCK |
Socket operation on non-socket |
The socket argument is a file descriptor, not a socket descriptor. |
107 |
ENOTCONN |
Transport endpoint is not connected |
A connection-oriented socket has not been connected. |
111 |
ECONNREFUSED |
Connection refused |
Remote host has refused the connection request. |
10.5.1 A UNIX Domain Datagram Socket Example
Our UNIX domain datagram socket example is somewhat similar in function to the stream socket example presented in Section 10.4. In this example, the server creates a datagram socket (SOCK_DGRAM) in the UNIX domain and binds it to an address (file name). The client also creates a datagram socket and binds it to an address (using a different file name , unique to each client process). The client and server use the sendto and recvfrom network calls for communication. The client generates 10 messages (the output of the / usr/ games / fortune utility), which are sent to the server. The server displays the messages that it has received. The code for the server process is shown in Program 10.8.
Program 10.8 The UNIX domain connectionless server .
File : p10.8.cxx /* SERVER - UNIX domain - connectionless */ #include "local_sock.h" + void clean_up(int, const char *); // Close socket and remove int main( ) { socklen_t clnt_len; // Length of client address 10 int orig_sock; // Original socket descriptor static struct sockaddr_un clnt_adr, // Client address serv_adr; // Server address static char buf[BUFSIZ]; // Buffer for messages + // Generate socket if ((orig_sock = socket(PF_UNIX, SOCK_DGRAM, 0)) < 0) { perror("generate error"); return 1; } // Assign address information 20 serv_adr.sun_family = AF_UNIX; strcpy(serv_adr.sun_path,SERVER_FILE); unlink( SERVER_FILE); // Remove old copy if present // BIND the address if (bind(orig_sock, (struct sockaddr *) &serv_adr, + sizeof(serv_adr.sun_family)+strlen(serv_adr.sun_path)) < 0) { perror("bind error"); clean_up(orig_sock, SERVER_FILE); return 2; } // Process 30 for (int i = 1; i <= 10; i++) { recvfrom(orig_sock, buf, sizeof(buf), 0, (struct sockaddr *) &clnt_adr, &clnt_len); cout << "S receives " << buf; } + clean_up(orig_sock, SERVER_FILE); return 0; } void clean_up( int sd, const char *the_file ){ 40 close( sd ); // Close socket unlink( the_file ); // Remove it }
The code for the client process is shown in Program 10.9.
Program 10.9 The UNIX domain connectionless client .
File : p10.9.cxx /* CLIENT - UNIX domain - connectionless */ #include "local_sock.h" + void clean_up(int, const char *); // Close socket and remove int main( ) { int orig_sock; // Original socket descriptor 10 static struct sockaddr_un // UNIX addresses to be used clnt_adr, // Client address serv_adr; // Server address static char clnt_buf[BUFSIZ], // Message from client pipe_buf[BUFSIZ], // Output from fortune command + clnt_file[]="XXXXXX";// Temporary file name FILE *fin; // File for pipe I/O // Assign SERVER address information serv_adr.sun_family = AF_UNIX; strcpy(serv_adr.sun_path, SERVER_FILE); 20 if ((orig_sock = socket(PF_UNIX, SOCK_DGRAM, 0)) < 0) { perror("generate error"); return 1; } mkstemp(clnt_file); + clnt_adr.sun_family = AF_UNIX; // Assign CLIENT address information strcpy( clnt_adr.sun_path, clnt_file ); unlink( clnt_file ); // Remove // BIND the address if (bind(orig_sock, (struct sockaddr *) &clnt_adr, 30 sizeof(clnt_adr.sun_family)+strlen(clnt_adr.sun_path)) < 0) { perror("bind error"); return 2; } // Process for (int i=0; i < 10; i++) { + sleep(1); // slow things down a bit fin = popen("/usr/games/fortune -s", "r"); memset( pipe_buf, 0x0, BUFSIZ ); // clear buffer before reading cmd output read( fileno(fin), pipe_buf, BUFSIZ ); sprintf( clnt_buf, "%d : %s", getpid(), pipe_buf ); 40 sendto( orig_sock, clnt_buf, sizeof(clnt_buf), 0, (struct sockaddr *) &serv_adr, sizeof(struct sockaddr) ); } clean_up( orig_sock, clnt_file ); return 0; + } void clean_up( int sd, const char *the_file ){ close( sd ); unlink( the_file ); 50 }
In the client, the mkstemp library function is used to generate a unique file name to be bound to the client's socket. This function is passed a template of XXXXXX that is replaced by a unique file name. The function also opens the file. As only the file name is needed, the file itself can be removed ( unlinked ). If there are multiple clients communicating with the server, it is imperative that each has its own unique file name for binding.
A standard compilation/output sequence using this clientserver pair is shown in Figure 10.15.
Figure 10.15 Compiling and running the UNIX domain connectionless clientserver application.
linux$ g++ p10.8.cxx -o server linux$ g++ p10.9.cxx -o client linux$ ./server & [3] 31801 linux$ ./client S receives 31802 : Go to a movie tonight. Darkness becomes you. S receives 31802 : Out of sight is out of mind. S receives 31802 : Q: What's tan and black and looks great on a lawyer? A: A doberman. . . . [3] Done server
Figure 10.16 shows what happens if we run two clients and use the ls command to check for the file names to which the client and server sockets are bound. Notice that the server still processes 10 messages. However, it receives half of the messages from one client and half from the other. No error message is generated when the clients continue to send their data to the unbound (closed) server socket.
Figure 10.16 Running the same application with multiple clients.
linux$ ./server & [1] 32244 linux$ ./client & ./client & ls -l grep ^s [2] 32248 [3] 32249 srwxr-xr-x 1 gray faculty 0 May 17 09:37 eUc0Xq <-- 1 srwxr-xr-x 1 gray faculty 0 May 17 09:37 qOC0Tq srwxr-xr-x 1 gray faculty 0 May 17 09:37 server_socket S receives 31754 : Anything worth doing is worth overdoing. S receives 31755 : Marriage causes dating problems. S receives 31754 : A handful of patience is worth more than a bushel of brains. S receives 31755 : OK, so you're a Ph.D. Just don't touch anything. . . . [3] - Done ./client [2] - Done ./client [1] + Done ./server
(1) Unique file names generated for client sockets.
10.5.2 An Internet Domain Datagram Socket Example
In the next example, we create a clientserver application that uses connectionless sockets. This application will act like a rudimentary chat program. A user running the server process can interactively read messages from and write messages to the user running the client program, and vice versa. When this application is run, the server program is invoked first and remains in the foreground. At startup, the server displays the port to which the client should bind . The client program, also run in the foreground, can be on a different host or in a separate window on the same host, and is passed on the command line the name of the host where the server process is executing and the port number. Once both processes are up and running, the user on the client enters a line of text and presses enter. The client's input is displayed on the screen of the server. The user running the server process then enters a response that in turn is displayed on the screen of the client, and so on. In a regimented lock-step, send and receive manner, the two users can carry on a very basic form of interactive communication. [13] The client process terminates when the user enters a ^D. The server, which is iterative, continues until removed with a kill command. The program for the server process is shown in Program 10.10.
[13] Granted, this will never replace the talk utility, IRC , or some of the current instant messaging applications, but it could serve as a base for a more sophisticated application.
Program 10.10 Internet domain connectionless server .
File : p10.10.cxx /* Program 10.10 - SERVER - Internet Domain - connectionless */ #include "local_sock.h" + int main( ) { int sock, n; socklen_t server_len, client_len; struct sockaddr_in server, // Internet Addresses 10 client; // SOCKET if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0) { perror("SERVER socket "); return 1; } + memset(&server, 0, sizeof(server)); // Clear structure server.sin_family = AF_INET; // Set address type server.sin_addr.s_addr = htonl(INADDR_ANY); server.sin_port = htons(0); // BIND 20 if (bind(sock, (struct sockaddr *) &server, sizeof(server) ) < 0) { perror("SERVER bind "); return 2; } server_len = sizeof(server); // Obtain address length + // Find picked port # if (getsockname(sock, (struct sockaddr *) &server, &server_len) < 0) { perror("SERVER getsocketname "); return 3; } 30 cout << "Server using port " << ntohs(server.sin_port) << endl; while ( 1 ) { // Loop forever client_len = sizeof(client); // set the length memset(buf, 0, BUFSIZ); // clear the buffer if ((n=recvfrom(sock, buf, BUFSIZ, 0, // get the client's msg + (struct sockaddr *) &client, &client_len)) < 0){ perror("SERVER recvfrom "); close(sock); return 4; } write(fileno(stdout), buf, n); // display msg on server 40 memset(buf, 0, BUFSIZ); // clear the buffer if ( read(fileno(stdin), buf, BUFSIZ) != 0 ){// get server's msg if ((sendto(sock, buf, strlen(buf) ,0, // send to client (struct sockaddr *) &client, client_len)) <0){ perror("SERVER sendto "); + close(sock); return 5; } } } return 0; 50 }
Keep in mind that for communications to occur between cooperating processes, a unique association must be established. In the Internet domain, the association is characterized by a quintuple consisting of
protocol, local address, local port, remote address, remote port
In the server program, a datagram (connectionless) socket is created with the socket call. The address family is set to AF_INET, and by default the protocol (which was set to 0) will be UDP. The addressing information for the server is assigned next. The defined constant INADDR_ANY, a wildcard address, indicates the server can use (receive messages at) any valid address for this protocol. Setting the port number to 0 (line 18) directs the system to select a port. When passed a 0 value, the system picks a port that is not in use and is greater than IPPORT_USERRESERVED. On our system this constant is set to 5000. Additional information about port numbers is stored in the file ip_local_port_range found in the /proc/sys/net/ipv4 subdirectory. The first value stored in this file is the number of the first local port for TCP and UDP traffic on the system. The second value is the last local port number. On our system this files contains the values 32768 and 61000.
The getsockname call (line 26) is issued to determine which port the system selected. Note it is important to initialize the third argument of this call to the length of the address argument before the call to getsockname is made (see line 24). The server process displays the port number so a user running a client process will know which port to specify (a more elegant solution would be to store the port number in an environment varible). The server program then enters an endless loop. It clears a receiving buffer and issues a recvfrom call. The recvfrom call will, by default, cause the server process to block until information is received. Once information is received, the remaining parts of the association, the remote address and port, are realized, as this information is contained in the received data. The received message is written to standard output (the screen). The server then clears the buffer and collects the user's response with a call to read . Again, read will cause the serving process to block while awaiting input. If the user enters a non-null response, the sendto call is used to send the response to the address/port of the client from which the message was received. The server process remains active until removed with a kill command or an interrupt (^C) is entered from the keyboard.
As noted, when the client program is invoked, the name of the host running the server process and the port on the server is passed on the command line. The client then uses the gethostbyname call to obtain additional information about the server. This information, along with the passed port number, is stored in the server socket address structure of the client. In an Internet domain setting, a datagram socket is created next, the client addressing information is set, and a call to bind is issued. At this juncture, the client process has sufficient information to initiate communications with the server process. The client enters a loop. The read call is used to obtain user input. If user input does not indicate an end-of-file condition (i.e., the user has not entered ^D), the input is sent to the serving process with the sendto call. The receiving buffer is then cleared, and a call to recvfrom retrieves the response from the user running the server program. The response is displayed to the screen, the buffer cleared, and the loop repeated. If the user running the client process enters ^D, the processing loop is exited, the socket is closed, and the client process terminates.
In this example, the conventions for clientserver communications are extremely regimented. The server process is always started first. The client process must be passed the name of the host running the server process and the proper port number. The client process obtains its user input first, which it then sends to the server. The user running the server process then responds. The user running the client responds to this response, and so on. As can be seen, there is a lot of room for improvement in this application! The code for the client is shown in Program 10.11.
Program 10.11 Internet domain connectionless client .
File : p10.11.cxx /* Program 10.11 - CLIENT - Internet Domain - connectionless */ #include "local_sock.h" + int main(int argc, char *argv[]){ int sock, n; socklen_t server_len; struct sockaddr_in // Internet addresses 10 server, client; struct hostent *host; // For host information if ( argc < 3 ) { // We need server name & port # cerr << "usage: " << argv[0] << "server_name port_#" << endl; return 1; + } // Server information if (!(host=gethostbyname(argv[1]))){ perror("CLIENT gethostname "); return 2; } // Set server address info memset(&server, 0, sizeof(server)); // Clear structure 20 server.sin_family = AF_INET; // Address type memcpy(&server.sin_addr, host->h_addr, host->h_length); server.sin_port = htons(atoi(argv[2])); // SOCKET if ((sock=socket(PF_INET, SOCK_DGRAM, 0)) < 0 ) { + perror("CLIENT socket "); return 3; } // Set client address info memset(&client, 0, sizeof(client)); // Clear structure client.sin_family = AF_INET; // Address type client.sin_addr.s_addr = htonl(INADDR_ANY); 30 client.sin_port = htons( 0 ); // BIND if (bind(sock, (struct sockaddr *) &client, sizeof(client)) < 0) { perror("CLIENT bind "); return 4; + } cout << "Client must send first message." << endl; while( read(fileno(stdin), buf, BUFSIZ) != 0 ){// get client's msg server_len=sizeof(server); // length of address if (sendto( sock, buf, strlen(buf), 0, // send msg to server 40 (struct sockaddr *) &server, server_len) < 0 ){ perror("CLIENT sendto "); close(sock); return 5; } memset(buf,0,BUFSIZ); // clear the buffer + if ((n=recvfrom(sock, buf, BUFSIZ, 0, // get server's msg (struct sockaddr *) &server, &server_len)) < 0){ perror("CLIENT recvfrom "); close(sock); return 6; } 50 write( fileno(stdout), buf, n ); // display msg on client memset(buf,0,BUFSIZ); // clear the buffer } close(sock); return 0; + }
A sample compilation and run of this application is shown in Figure 10.17.
Figure 10.17. A run of Programs 10.10 and 10.11 using two different hosts .