Network application example

The following example illustrates using socket functions in a network application program. The steps are written using many of the basic socket functions, C socket syntax, and conventions described in this information.

  1. First, an application program must get a socket descriptor using the socket() call, as in the example listed in Figure 1. For a complete description, see z/OS XL C/C++ Runtime Library Reference.
    Figure 1. An application using socket()
    #include <sys/socket.h>
    ⋮
    int s;
    ⋮
    s = socket(AF_INET, SOCK_STREAM, 0);

    The code fragment in Figure 1 allocates a socket descriptor s in the Internet address family. The domain parameter is a constant that specifies the domain where the communication is taking place. A domain is the collection of application programs using the same addressing convention. z/OS® UNIX supports three domains: AF_INET, AF_INET6, and AF_UNIX. The type parameter is a constant that specifies the type of socket, which can be SOCK_STREAM, or SOCK_DGRAM.

    The protocol parameter is a constant that specifies the protocol to use. For AF_INET, it can be set to IPPROTO_UDP for SOCK_DGRAM and IPPROTO_TCP for SOCK_STREAM. Passing 0 chooses the default protocol. If successful, the socket() call returns a positive integer socket descriptor. For AF_UNIX, the protocol parameter must be 0. These values are defined in the netinet/in.h include file.

  2. After an application program has a socket descriptor, it can explicitly bind a unique address to the socket, as in the example listed in Figure 2. For a complete description, see z/OS XL C/C++ Runtime Library Reference.
    Figure 2. An application using bind()
    int bind(int s, struct sockaddr *name, int namelen);
    ⋮
    int rc;
    int s;
    struct sockaddr_in myname;
    
       /* clear the structure to be sure that the sin_zero field is clear */
       memset(&myname, 0, sizeof(myname));
       myname.sin_family = AF_INET;
       myname.sin_addr = inet_addr("129.5.24.1");
    /* specific interface */
       myname.sin_port = htons(1024);
    ⋮
       rc = bind(s, (struct sockaddr *) &myname,
    sizeof(myname));
    This example binds socket descriptor s to the address 129.5.24.1 and port 1024 in the Internet domain. Servers must bind to an address and port to become accessible to the network. The example in Figure 2 shows two useful utility routines:
    • inet_addr() takes an IPv4 Internet address in dotted-decimal form and returns it in network byte order. Note that the inet_pton() function can take either an IPv4 or IPv6 Internet address in its standard text presentation form and return it in its numeric binary form. For a complete description, see z/OS XL C/C++ Runtime Library Reference.
    • htons() takes a port number in host byte order and returns the port in network byte order. For a complete description, see z/OS XL C/C++ Runtime Library Reference.

    Figure 3 shows another example of the bind() call. It uses the utility routine gethostbyname() to find the Internet address of the host, rather than using inet_addr() with a specific address.

    Figure 3. A bind() function using gethostbyname()
    int bind(int s, struct sockaddr_in name, int namelen);
    ⋮
    int rc;
    int s;
    char *hostname = "myhost";
    struct sockaddr_in myname;
    struct hostent *hp;
     
       hp = gethostbyname(hostname);
     
       /*clear the structure to be sure that
    the sin_zero field is clear*/
       memset(&myname,0,sizeof(myname));
       myname.sin_family = AF_INET;
       myname.sin_addr.s_addr = *((ip_addr_t
    *)hp->h_addr);
       myname.sin_port = htons(1024);
    ⋮
    rc = bind(s,(struct
    sockaddr *) &myname, sizeof(myname));
  3. After binding to a socket, a server that uses stream sockets must indicate its readiness to accept connections from clients. The server does this with the listen() call, as illustrated in the example in Figure 4.
    Figure 4. An application using listen()
    int listen(int s, int backlog);
    ⋮
    int s;
    int rc;
    ⋮
    rc = listen(s, 5);

    The listen() call tells the TCP/IP address space that the server is ready to begin accepting connections, and that a maximum of five connection requests can be queued for the server. Additional requests are ignored. For a complete description, see z/OS XL C/C++ Runtime Library Reference.

  4. Clients using stream sockets begin a connection request by calling connect(), as shown in Figure 5.
    Figure 5. An application using connect()
    int connect(int s, struct sockaddr *name, int namelen);
    ⋮
    int s;
    struct sockaddr_in servername;
    int rc;
    ⋮
    memset(&servername, 0,sizeof(servername));
    servername.sin_family = AF_INET;
    servername.sin_addr = inet_addr("129.5.24.1");
    servername.sin_port = htons(1024);
    ⋮
    rc = connect(s, (struct sockaddr *) &servername,
    sizeof(servername));

    The connect() call attempts to connect socket descriptor s to the server with an address servername. This could be the server that was used in the previous bind() example. The connect request is completed immediately and returns control to the caller, regardless of the server accepting the connection. After a successful return, the socket descriptor s is associated with the connection to the server. For a complete description, see z/OS XL C/C++ Runtime Library Reference.

  5. Servers using stream sockets accept a connection request with the accept() call, as shown in the example listed in Figure 6.
    Figure 6. An application using accept()
    int accept(int s, struct sockaddr *addr, int *addrlen);
    ⋮
    int clientsocket;
    int s;
    struct sockaddr clientaddress;
    int addrlen;
    ⋮
    addrlen = sizeof(clientaddress);
    ⋮
    clientsocket = accept(s, &clientaddress, &addrlen);

    When a connection request is accepted on socket descriptor s, the name of the client and length of the client name are returned, along with a new socket descriptor. The new socket descriptor is associated with the client that began the connection, and s is again available to accept new connections. For a complete description, see z/OS XL C/C++ Runtime Library Reference.

  6. Clients and servers have many calls from which to choose for data transfer. The read() and write(), readv() and writev(), and send() and recv() calls can be used only on sockets that are in the connected state. The sendto() and recvfrom(), and sendmsg() and recvmsg() calls can be used at any time on datagram sockets. The example listed in Figure 7 illustrates the use of send() and recv().
    Figure 7. An application using send() and recv()
    int send(int socket, char *buf, int buflen, int flags);
    int recv(int socket, char *buf, int buflen, int flags);
    ⋮
    int bytes_sent;
    int bytes_received;
    char data_sent[256];
    char data_received[256];
    int s;
    ⋮
    bytes_sent = send(s, data_sent,
    sizeof(data_sent), 0);
    ⋮
    bytes_received = recv(s,
    data_received, sizeof(data_received), 0);

    The example in Figure 7 shows an application program sending data on a connected socket and receiving data in response. The flags field can be used to specify additional options to send() or recv(), such as sending out-of-band data. For more information see z/OS XL C/C++ Runtime Library Reference.

  7. If the socket is not in a connected state, additional address information must be passed to sendto() and can be optionally returned from recvfrom(). An example of the use of the sendto() and recvfrom()calls is listed in Figure 8.
    Figure 8. An application using sendto() and recvfrom()
    int sendto(int socket, char *buf, int buflen, int flags,
    	   struct sockaddr *addr, int addrlen);
    int recvfrom(int socket, char *buf, int buflen, int flags,
    	   struct sockaddr *addr, int *addrlen);
    ⋮
    int bytes_sent;
    int bytes_received;
    char data_sent[256];
    char data_received[256];
    struct sockaddr_in to;
    struct sockaddr from;
    int addrlen;
    int s;
    ⋮
    memset(&to, 0, sizeof(to));
    to.sin_family = AF_INET;
    to.sin_addr   = inet_addr("129.5.24.1");
    to.sin_port   = htons(1024);
    ⋮
    bytes_sent = sendto(s, data_sent,
    sizeof(data_sent), 0, &to, sizeof(to));
    ⋮
    addrlen = sizeof(from); /* must be initialized */
    bytes_received = recvfrom(s, data_received,
       sizeof(data_received), 0, &from, &addrlen);

    The sendto() and recvfrom() calls take additional parameters that allow the caller to specify the recipient of the data or to be notified of the sender of the data. For more information see z/OS XL C/C++ Runtime Library Reference. Usually, sendto() and recvfrom() are used for datagram sockets, and send() and recv() are used for stream sockets.

  8. The writev(), readv(), sendmsg(), and recvmsg() calls provide the additional features of scatter and gather data—two related operations where data is received and stored in multiple buffers (scatter data), and then taken from multiple buffers and transmitted (gather data). Scattered data can reside in multiple data buffers. The writev() and sendmsg() calls gather the scattered data and send it. The readv() and recvmsg() calls receive data and scatter it into multiple buffers.
  9. Applications can handle multiple descriptors. In such situations, use the select() call to determine the descriptors that have data to be read, those that are ready for data to be written, and those that have pending exceptional conditions. An example of how the select() call is used is listed in Figure 9.
    Figure 9. An application using select()
    fd_set readsocks;
    fd_set writesocks;
    fd_set exceptsocks;
    struct timeval timeout;
    int number_of_sockets;
    int number_found;
    ⋮
    /* number_of_sockets previously set to the socket number of largest
    * integer value.
    * Clear masks out.
    */
    FD_ZERO(&readsocks);; FD_ZERO(&writesocks); FD_ZERO(&exceptsocks);
    /* Set masks for socket s only */
    FD_SET(s, &readsocks)
    FD_SET(s, &writesocks)
    FD_SET(s, &exceptsocks)
    ⋮
    /* go into select wait for 5 minutes waiting for socket s to become
    ready or the timer has popped*/
    rc = select(number_of_sockets+1,
    	    &readsocks, &writesocks, &exceptsocks, &timeout);
    ⋮
    /* Check rc for condition set upon exiting select */
    number_found = select(number_of_sockets,
    		      &readsocks, &writesocks, &exceptsocks, &timeout); 

    In this example, the application program uses bit sets to indicate that the sockets are being tested for certain conditions and also indicates a timeout. If the timeout parameter is NULL, the select() call blocks until a socket becomes ready. If the timeout parameter is nonzero, select() waits up to this amount of time for at least one socket to become ready on the indicated conditions. This is useful for application programs servicing multiple connections that cannot afford to block, waiting for data on one connection. For a complete description, see z/OS XL C/C++ Runtime Library Reference.

  10. In addition to select(), application programs can use the ioctl() or fcntl() calls to help perform asynchronous (nonblocking) socket operations. An example of the use of the ioctl() call is listed in Figure 10.
    Figure 10. An Application Using ioctl()
    int ioctl(int s, unsigned long command, char *command_data);
    ⋮
    int s;
    int dontblock;
    char buf[256];
    int rc;
    ⋮
    dontblock = 1;
    ⋮
    rc = ioctl(s, FIONBIO, (char *) &dontblock);
    ⋮
    if (((rc=recv(s, buf, sizeof(buf),
    0)) < 0)&&(errno == EWOULDBLOCK))
       /* no data available */
    else
       /* either got data or some other error occurred */

    This example causes the socket descriptor s to be placed into nonblocking mode. When this socket is passed as a parameter to calls that would block, such as recv() when data is not present, it causes the call to return with an error code, and the global errno value is set to EWOULDBLOCK. Setting the mode of the socket to be nonblocking allows an application program to continue processing without becoming blocked. For a complete description, see z/OS XL C/C++ Runtime Library Reference.

  11. A socket descriptor, s, is deallocated with the close() call. Figure 11 shows an example. For a complete description, see z/OS XL C/C++ Runtime Library Reference.
    Figure 11. An application using close()
    int close(int s);
    ⋮
    int rc;
    int s;
    rc = close(s);