27.2 Functions of the Socket API

   


This section gives an overview of the most important functions for the socket application programming interface. The original TCP/IP implementation of the BSD UNIX version, which the socket API is derived from, used only the six system calls of the input and output interface for file operations to communicate over networks. Only the next version added the whole number of additional operations that will be discussed below.

27.2.1 Functions for Connection Management

The functions described in this section serve to manage communication relationships: to create and delete sockets, to open and close connections, and so on.

int socket (int family, int type, int protocol)


Berkeley sockets, or sockets for short, are the basis for communication relationships over the socket interface. A socket represents the endpoint of a communication relationship in an end system and forms the interface between the network protocols and applications. This means that, in a communication using the TCP protocol, the two sockets in the two communicating end systems form the endpoints for this communication. In a multicast communication, more than two sockets normally participate in a communication.

An application can use the socket() system call to cause the operating system to create a socket, always the first step in communicating over networks. In the creating of a socket, the required resources are reserved in the operating system, and the type of communication protocol to be used is determined (e.g., TCP or UDP).

The result of a socket() system call consists of the socket descriptor an integer number that uniquely identifies the socket. This descriptor has to be used in all subsequent system calls to identify the socket.

The following parameters are passed with the socket() system call:

  • int family denotes the protocol family used and thus, mainly, the address type used. Constants for the following address families (AF_...) are defined:

    • AF_UNIX: Sockets for interprocess communication in the local computer.

    • AF_INET: Sockets of the TCP/IP protocol family based on the Internet Protocol Version 4

    • AF_INET6: TCP/IP protocol family based on the new Internet Protocol, Version 6. (See Chapter 23.)

    • AF_IPX: IPX protocol family.

  • int type denotes the type of the desired communication relationship. Within the TCP/IP protocol family, we mainly distinguish the following three types:

    • SOCK_STREAM (stream socket) specifies a stream-oriented, reliable, in-order full duplex connection between two sockets.

    • SOCK_DGRAM (datagram socket) specifies a connectionless, unreliable datagram service, where packets may be transported out of order.

    • SOCK_RAW (raw socket).

  • int protocol selects a protocol for the specified socket type, if several protocols with the specified type properties are available. In the AF_INET address family, TCP is always selected for the SOCK_STREAM socket type, and UDP is always used as the transport protocol for SOCK_DGRAM.

If the socket type in itself uniquely identifies a protocol, then the protocol argument in the socket() system call can be set to zero.

Application Example: A TCP socket should be set up. The first parameter is set to AF_INET, the second to SOCK_STREAM. The third parameter is not required; therefore, it is initialized to 0.

 #include <sys/types.h> #include <sys/socket.h> int          sockfd; sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0)        printf("ERROR: Error when creating the socket."); 


 int close (int sockfd) 


The normal UNIX system call close() is used to close a socket. When the socket is closed, the system has to ensure that data waiting in kernel buffers ready to be sent (be acknowledged) is actually sent (acknowledged). Normally, the system returns immediately after a close() system call, while the kernel still tries to handle data waiting in the queues.

The only parameter of the close() function is the following:

  • int sockfd is the descriptor of the socket that should be closed. A process that terminates and releases all open sockets automatically.

Application Example:

 #include <sys/socket.h> int   sockfd; /* Creating a socket. */ /* Communication operations. */ close(sockfd); 


[View full width]

int bind(int sockfd, struct sockaddr *mAddress, graphics/ccc.gif int AddrLength)


A newly created socket has no allocation to local addresses or port numbers. On the client side, user programs do not necessarily have to care about the local addresses they use, because they can be allocated automatically. In contrast, server processes have to specify the port they are working at, because this port is used to address the corresponding service. This means that the server has to bind a new socket to a local address and a port; this binding is the task of the bind() system call.

The bind() system call requires the following parameters:

  • int sockfd is a socket descriptor.

  • struct sockaddr *mAddress is a pointer to a structure with the address the socket is to be bound to.

  • int AddrLength specifies the size of the address structure provided in the second parameter. The size of the address structure has to be specified, because different address families use different address formats.

The bind() system call is used in three cases:

  • Servers register their own addresses within the system. They inform the system about the kind of packets to be forwarded at this socket, e.g., packets with a specific port number. Server applications use special globally defined port numbers, the so-called well-known ports. They are assigned by IANA upon request and listed in the file /etc/services.

  • A client itself can store a specific address.

  • A connectionless client has to ensure that the system creates an individual address for it, so that the other end of the communication relationship has a valid return address for replies.

Application Example: A TCP server registers its own address (IP address and port number) and declares itself ready to receive arbitrary client requests. The constant INADDR_ANY is used for the IP address, which tells the system that an appropriate local IP address should be used. This 32-bit value has to be brought into the network byte order (see Section 27.2.3) by htonl(). The port number should be a value higher than 1024, to avoid collisions with well-known ports. The port number is a 16 bits in size, so it is initially brought into the network byte order by htons(). Notice that it is a good idea to have previously initialized the entire address structure with zero values.

 #include <sys/types.h> #include <sys/socket.h> #define SERVER_TCP_PORT 2001 int sockfd; struct sockaddr_in serv_addr; /* Initialize address area. */ memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(SERVER_TCP_PORT); /*  Since a sockaddr_in structure was used above for the address,     it has to be transformed to the more general sockaddr     before using it as second parameter of the bind call. */ bind(sockfd, (struct sockaddr*) &serv_addr, sizeof(serv_addr)); 


 int listen(int sockfd, int backlog) 


The listen() system call enables a server to prepare a socket for incoming connections. The socket is switched into a passive mode and is then ready to accept connections. In addition, the operating system is informed that the protocol instance should order incoming requests in a queue.

listen() is normally used after the socket() and the bind() system calls and immediately before accept(). listen() is suitable only for connection-oriented socket types (e.g., SOCK_STREAM).

The listen() system call takes two parameters:

  • int sockfd is a socket descriptor.

  • int backlog denotes the number of possible connection requests (the maximum number of connection requests that can be placed in the queue). This is done while the system is waiting for the accept() call to be executed by the server process. This value is normally given as 5, corresponding to the current maximum value. Requests beyond this value are denied.

Application Example: A server process calls the listen() routine after it has used bind() to announce the port number it uses:

 #include <sys/socket.h> /* Create a socket and bind it to an address. */ listen(sockfd, 5); 


[View full width]

int accept(int sockfd, struct sockaddr *Peer, int graphics/ccc.gif *AddrLength)


Once a connection-oriented server process has executed the listen() call described above, the server has to wait for a connection, using the accept() call. It blocks the process until a connection request arrives.

As soon as a connection request arrives, the address of the requesting client is stored in the data structure named Peer. The length of this address is stored in AddrLength. Subsequently, the system creates a new socket, which is connected to the client, and returns the descriptor of this socket as result of the accept() system call. A negative value is returned if an error occurs.

The accept() call takes the following parameters:

  • int sockfd is a descriptor of the ready-to-receive socket.

  • struct sockaddr *Peer is a pointer to a previously reserved memory space for the address of the communication partner. This address is entered in this structure when a connection request arrives.

  • int *AddrLength specifies the length of the reserved memory space in bytes before the call. Once accept() has returned, this parameter shows the actual address length (in bytes).

Application Example: A successful accept() call has returned, and the server starts a child process to continue communication with the client in this new process. The parent process can close the new socket or wait for other client connection requests.

 #include      <sys/types.h> #include      <sys/socket.h> int newsockfd, clilen; struct sockaddr_in cli_addr; clilen = sizeof(cli_addr); /* socket, bind, listen, ... */ newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen); if (newsockfd < 0)        printf("ERROR: Creating new socket"); if (childpid = fork()) < 0)         printf("ERROR: Creating child process"); else if (childpid == 0) {              /* Child process */         close(sockfd);            /* Parent socket */         doit(newsockfd);          /* Handle client request */         exit(0); } close(newsockfd);                 /* Parent closes the new socket */ 


[View full width]

int connect(int sockfd, struct sockaddr *ServAddr, graphics/ccc.gif int AddrLength)


A newly created socket is not connected (i.e., it is not associated with a destination address or a communication partner). However, a user program has to establish a connection before data can be sent over a reliable connection. Once a connection has been successfully established, no destination address needs to be specified to transmit data in a connection-oriented communication (e.g., SOCK_STREAM). Sockets for connectionless datagram services don't have to be connected; they can transmit directly.

For the connection-oriented TCP protocol, the connect() system call results in the actual setup of a connection from the local system to a (remote) communication partner.

The following parameters are required to open a connection:

  • int sockfd: Socket descriptor.

  • struct sockaddr *ServAddr: Pointer to a structure that specifies the destination address to which the socket should be connected.

  • int AddrLength: Length of the address structure in bytes.

Application Example: Once it has opened a socket, the client fills the data structure for the server address. If the IP address is available in dotted decimal notation, it can be converted by the inet_addr function. The port number of the server is brought into network byte order by the htons() routine. Again, it is useful to first initialize the entire address memory.

 #include <sys/types.h> #include <sys/socket.h> #define SERVER_TCP_PORT 2001 #define SERVER_HOST_ADDR "129.13.35.77" struct sockaddr_in serv_addr; /* A sockfd was already created ... */ /* Initialize data space. */ memset(&serv_addr, 0, sizeof(serv_addr)); /* Enter address family in address structure. */ serv_addr.sin_family = AF_INET; /* Set IP address. */ serv_addr.sin_addr.s_addr = inet_addr(SERVER_HOST_ADDR); /* Set port number. */ serv_addr.sin_port = htons(SERVER_TCP_PORT); /* Establish connection to server. */ connect(sockfd, (struct sockaddr*) &serv_addr, sizeof(serv_addr)); /* Transmit data ... */ 


27.2.2 Functions for Data Transmission

 write(), send(), sendto() 


The following are some of the system calls available for transmitting data over a socket:

  • size_t write(sockfd, buffer, length)

  • int send(sockfd, buffer, length, flags)

  • int sendto(sockfd, buffer, length, flags, destaddr, addrlen)

send() and write() can be invoked only when a socket is in the connected state; sendto() can be invoked at any time.

These calls take the following parameters:

  • int sockfd: socket descriptor.

  • void *buffer: starting address of a buffer, which contains a sequence of bytes that should be sent.

  • size_t length: length of the byte sequence to be sent in the buffer (in bytes).

  • int flags: transmission control. (See details in [Come00].)

  • struct sockaddr *destaddr: pointer to a sockaddr structure with the destination address.

  • int addrlen: length of the destination address structure.

All four functions return the length of the data that was actually sent.

 read(), recv(), recvfrom() 


In analogy to the transmit functions described above, the following functions are available for receiving data:

  • size_t read(sockfd, buffer, length)

  • int recv(sockfd, buffer, length, flags)

  • int recvfrom (sockfd, buffer, length, flags, fromaddr, addrlen)

The parameters these functions take are similar to those for transmit operations. All three functions return the length of data received.

 readv(), writev(), sendmsg(), recvmsg() 


As their arguments, the transmit and receive functions discussed so far each took a pointer to a single memory area that had to be transmitted or was to take in the data received, respectively. This is different with the following functions:

  • int readv(int sockfd, const struct iovec *vector, size_t count)

  • int writev(int sockfd, const struct iovec *vector, size_t count)

  • int sendmsg(int sockfd, const struct msghdr *msg, int flags)

  • int recvmsg(int sockfd, struct msghdr *msg, int flags)

The vector pointer in the readv and writev system calls (that otherwise behave like read and write) references an array of iovec structures. In turn, each of these structures references a memory location (iov_base) and specifies its length (iov_len):

struct iovec {        void *iov_base;    /* Starting address */        size_t iov_len;    /* Number of bytes */ };

The specified memory locations are used one after the other for sending or receiving data. The benefits of this behavior can be used when messages are composed of several parts.

The sendmsg and recvmsg system calls receive a reference to a msghdr structure, which also contains an iovec pointer. In addition, this structure can accommodate addresses for communication over unconnected sockets, and it makes possible the delivery of specific control messages to the transport protocol instance. (See also Section 25.2.1.)

27.2.3 Byte Ordering Methods

Unfortunately, not all computer architectures store the individual bytes of multibyte values in the same order. We distinguish between little-endian and big-endian orders. (See Figure 27-2.)

Figure 27-2. Little-endian and big-endian orders of 16-bit structures, using the number 0xAA BB as an example.

graphics/27fig02.gif


For this reason, we have to define a fixed byte order to ensure correct communication. In the TCP/IP protocol family, this is the big-endian format for 16-bit and 32-bit integer values. (The protocols handle only integer sizes.) The protocol has no influence on and no control of the format of data transmitted by an application over the network. The protocol defines the format only for the fields it manages itself.

 htonl(), htons(), ntohl(), ntohs() 


To enable computers with different byte orders (big endian or little endian) to communicate, the so-called network byte order (big endian) was defined for transmissions in the Internet. The following functions are available to convert 16-bit and 32-bit values from the local format (host byte order) into the network byte order and vice versa:

  • unsigned long htonl (unsigned long hostlong): host network (32 bits)

  • network (16 bits)

  • host (32 bits)

  • host (16 bits)

27.2.4 Functions to Handle Internet Addresses

Because the socket interface also supports protocol families, other than TCP/IP (e.g., the ISO/OSI protocols), handling IP addresses and DNS names is not as simple as it might seem. The most frequently needed functions are introduced below.

To handle 32-bit IPv4 addresses or socket addresses (IP address plus TCP/UDP port), the socket-programming interface defines the data structures described in Section 27.1.1, struct sockaddr and struct sockaddr_in.

All socket functions expect address information as parameters in the form of a generic sockaddr structure. However, this structure serves merely as a placeholder for one of the protocol-specific address formats (e.g., sockaddr_in, sockaddr_x25). This is the reason why a type conversion is normally required when calling these functions:

 struct sockaddr_in *localaddr; error = bind (sockfd, (struct sockaddr*)localaddr,addrlen) 

[View full width]

inet_addr(), inet_aton(), inet_ntoa(), inet_ntop() graphics/ccc.gif, inet_pton()


IPv4 addresses are normally written in dotted decimal notation, e.g., 192.25.10.72. The following functions can be used to convert formatted character strings into in_addr structures and vice versa. The parameters are actually self-explanatory:

  • int inet_aton(const char *cp, struct in_addr *inp)

  • char *inet_ntoa(struct in_addr in);

  • unsigned long int inet_addr(const char *cp)

The following two functions can be used instead of inet_ntoa() and inet_aton(). Their benefit is that they support several address families, mainly AF_INET and AF_INET6. It is, therefore, recommended to use inet_ntop() and inet_pton().

  • int inet_pton(int af, const char *src, void *dst)

  • const char *inet_ntop(int af, const void *src, char *dst, size_t cnt)

[View full width]

int getpeername(int sockfd, struct sockaddr *name. graphics/ccc.gif int *name1en)


The function getpeername() serves to find out the IP address of the communication partner with a connected socket. The following parameters are passed:

  • int sockfd: Socket descriptor.

  • struct sockaddr *name:Pointer to a previously reserved memory location for the communication partner's address.

  • int *namelen contains the length of the reserved memory location in bytes before the call. After the call, namelen shows the address length in bytes.

If an error occurs, the functions do not return a value called "unequal null," but some integer value that is not equal to F (the exact value is determined by the type of error. If everything went okay, then the functions return F.

 int gethostname(char *name, size_t 1en) 


The function gethostname() serves to find out the DNS name (not the IP address) of the local computer:

  • char *name: Pointer to a previously reserved memory location for the name.

  • size_t len: Length of the reserved memory location. The call fails if the length is insufficient.

If an error occurs, then the value unequal null is returned.

 struct hostent *gethostbyname(const char *name) 


If the name of the communication partner is known, the function gethostbyname() can find its address. The name of the computer it looks for is passed in the name parameter.

gethostbyname() supplies a pointer to a hostent structure, which is defined in the header file <netdb.h>:

 struct hostent {     char     *h_name;                /* Official name of host. */     char     **h_aliases;            /* Alias list. */     int      h_addrtype;             /* Host address type. */     int      h_length;               /* Length of address. */     char    **h_addr_list;           /* List of addresses from name server. */     #define h_addr h_addr_list[0]    /* Address, for backward compatibility. */ };

The field h_addr_list contains a list with valid addresses, where the end of this list is denoted by an entry consisting of all zero bits. For IPv4, the list contains IP addresses in the form of 32-bit binary values, which can be copied into a data structure of the type in_addr for example, by the memcopy() function.


       


    Linux Network Architecture
    Linux Network Architecture
    ISBN: 131777203
    EAN: N/A
    Year: 2004
    Pages: 187

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net