Introduction to BSD Sockets

Jump to: navigation, search

Table of Contents


The Berkeley Sockets 4.4 API (Applications Programmer Interface) is a set of standard function calls made available at the application level. These functions allow programmers to include Internet communications capabilities in their products.

The Berkeley Sockets API (also frequently referred to as simply `sockets') was originally released with 4.2BSD in 1983. Enhancements have continued through the 4.4BSD systems. Berkeley-based code can be found in many different operating systems, both commercial and public domain, such as BSD/OS, FreeBSD, NetBSD, OpenBSD, and UnixWare 2.x. Other popular operating systems such as Solaris and Linux employ the standard sockets interface, though the code was written from scratch.

Other sockets APIs exist, though Berkeley Sockets is generally regarded as the standard. Two of the most common APIs are Winsock and TLI. Winsock (Windows Sockets) was developed for the Microsoft Windows platform in 1993, and is based significantly on the BSD interface. A large subset of the BSD API is provided, with most of the exceptions being platform-specific to BSD systems. TLI (Transport Layer Interface) was developed by AT&T, and has the capability to access TCP/IP and IPX/SPX transport layers. XTI (X/Open Transport Interface, developed by X/Open Company Ltd.) is an extension of TLI that allows access to both TCP/IP and NetBios.


Contents

Overview of Sockets

BSD Sockets generally relies upon client/server architecture. For TCP communications, one host listens for incoming connection requests. When a request arrives, the server host will accept it, at which point data can be transferred between the hosts. UDP is also allowed to establish a connection, though it is not required. Data can simply be sent to or received from a host.

The Sockets API makes use of two mechanisms to deliver data to the application level: ports and sockets. Ports and sockets are one of the most misunderstood concepts in sockets programming.

All TCP/IP stacks have 65,536 ports for both TCP and UDP. There is a full compliment of ports for UDP (numbered 0-65535) and another full compliment, with the same numbering scheme, for TCP. The two sets do not overlap. Thus, communication over both TCP and UDP can take place on port 15 (for example) at the same time.

A port is not a physical interface - it is a concept that simplifies the concept of Internet communications for humans. Upon receiving a packet, the protocol stack directs it to the specific port. If there is no application listening on that port, the packet is discarded and an error may be returned to the sender. However, applications can create sockets, which allow them to attach to a port. Once an application has created a socket and bound it to a port, data destined to that port will be delivered to the application. This is why the term socket is used - it is the connection mechanism between the outside world (the ports) and the application. A common misunderstanding is that sockets-based systems can only communicate with other sockets-based systems. This is not true. TCP/IP or UDP/IP communications are handled at the port level - the underlying protocols do not care what mechanisms exist above the port. Any Internet host can communicate with any other, be it Berkeley Sockets, WinSock, or anything else. "Sockets" is just an API that allows the programmer to access Internet functionality - it does not modify the manner in which communications occur. Let us use the example of an office building to illustrate how sockets and ports relate to each other. The building itself is analogous to an Internet host. Each office represents a port, the receptionist is a socket, and the business itself is an application. Suppose you are a visitor to this building, looking for a particular business. You wander in, and get directed to the appropriate office. You enter the office, and speak with the receptionist, who then relays your message to the business itself. If there is nobody in the office, you leave. To rephrase the above in sockets terminology: A packet is transmitted to a host. It eventually gets to the correct port, at which point the socket conveys the packet's data to the application. If there is no socket at the destination port, the packet is discarded.


Byte-Ordering Functions

Because TCP/IP has to be a universal standard, allowing communications between any platforms, it is necessary to have a method of arranging information so that big-endian and little-endian machines can understand each other. Thus, there are functions that take the data you give them and return them in network byte-order. On platforms where data is already correctly ordered, the functions do nothing and are frequently macro'd into empty statements. Byte-ordering functions should always be used as they do not impact performance on systems that are already correctly ordered and they promote code portability. The four byte-ordering functions are htons, htonl, ntohs, and ntohl. These stand for host to network short, host to network long, network to host short, and network to host long, respectively. htons translates a short integer from host byte-order to network byte-order. htonl is similar, translating a long integer. The other two functions do the reverse, translating from network byte-order to host byte-order.


Data Structures

Before venturing into the realm of actual API functions, one must understand a few structures. The most important of these is sockaddr_in. It is defined as follows:

#define trsocket.h
 
struct sockaddr_in
{
    short          sin_family;
    u_short        sin_port;
    struct in_addr sin_addr;
    char           sin_zero[8];
};


The structure in_addr that is used in sockaddr_in is defined as:

#define trsocket.h
 
struct in_addr
{
    u_long s_addr;
};


These are the most important data structures used in sockets. The second consists of an unsigned long integer that contains the IP address that will be associated with the socket. The first has two other important fields: 'sin_family' and 'sin_port'. 'sin_family' tells sockets which protocol family to use. For IPv4, the constant AF_INET should always be passed in. 'sin_port' tells what port number will be associated with the socket.

sockaddr_in is a modification of the standard sockaddr structure:

struct sockaddr
{
    u_char sa_len;
    u_char sa_family;
    char   sa_data[14];
};


Socket calls expect the standard sockaddr structure. However, for IPv4 communications, it is proper to pass in a sockaddr_in structure that has been cast to a sockaddr.


Common Socket Calls

This section lists the most commonly used socket calls and describes their uses. This is purely introductory material. For a more complete description of how the calls work, please see the Programmer's Reference.

socket()

A socket, in the simplest sense, is a data structure used by the Sockets API. When the user calls this function, it creates a socket and returns reference a number for that socket. That reference number, in turn, must be used in future calls.

bind()

This call allows a user to associate a socket with a particular local port and IP address. In the case of a server (see listen and accept below), it allows the user to specify which port and IP address incoming connections must be addressed to. For outgoing connection requests (see connect below), it allows the user to specify which port the connection will come from when viewed by the other host.

Note Note: bind is unnecessary for sockets that are not going to be set up to accept incoming connections. In this case, the stack will pick an appropriate IP address and a random port (known as an ephemeral port).

listen()

This function prepares the given socket to accept incoming TCP requests. It must be called before accept().

accept()

This function detects incoming connection requests on the listening socket. In blocking mode, this call will cause a task to sleep until a connection request is received. In non-blocking mode, this call will return TM_EWOULDBLOCK indicating that no connection request is present and that accept must be called again. If the user calls accept and a connection request is pending, accept creates another socket based on the properties of the listening socket. If the call is successful, the socket descriptor of the newly created and connected socket is returned. The new socket is created to allow communications with multiple clients from a single port on the server (think of web servers, which listen on port 80 by default and are capable of communicating with thousands of hosts at the same time). Each time the user calls accept and there is a connection requests pending, it creates a new socket.

connect()

When a user issues a connect command, the stack creates a connection with another host. Before connect can instruct the stack to establish a connection, the user must pass a socket and a sockaddr_in structure containing the destination IP address and port. In TCP, an actual connection is negotiated. In UDP, however, no packets are exchanged.

send()

This call allows a user to send data over a connected socket. Unlike sendto(), this socket must be connected. Because the socket is already connected, it is not necessary to specify the destination address (the destination address was set in accept or connect). send can be used for either UDP or TCP data.

sendto()

Unlike send(), sendto requires users to specify the destination port and address. This is useful for UDP communications only, as TCP requires a pre-existing connection. sendto may be used on either connected or unconnected UDP sockets. In the case that a UDP socket is already connected, the destination address provided to sendto will override the default established on the socket with connect.

recv()

This function allows the user to receive data on the connected socket. recv can be used for either TCP or UDP.

recvfrom()

This function allows the user to receive data from a specified UDP socket (whether or not it is connected). It may not be used for TCP sockets, as they require a connection.

close()

This function closes (read: deletes) a socket that has been allocated with the socket call. If the socket is connected, it closes the connection before deleting it. Because the close call is frequently used for more than one purpose (closing open files, for example), it is renamed tfClose() in the Treck stack to avoid conflicts with the preexisting function.

Example Code

The following are simplified examples of using the Sockets API to create Internet connectivity in an application. They are all available on the Treck protocols CD, in the examples\ directory. Four examples are given: UDP Client, UDP Server, TCP Client, and TCP Server. All of the examples are coded in blocking-mode.


UDP Client

This first example shows how to code a UDP client. A socket is created, and sendto() is called the specified number of times. Note that bind() is never called. For outgoing connections, bind() is not necessary, as the stack will pick a random port and an appropriate IP address.

#include <trsocket.h>
 
#define TM_BUF_SIZE 1400
#define TM_PACKETS_TO_SEND 10
#define TM_DEST_ADDR “10.0.0.1”
#define TM_DEST_PORT 9999
 
char testBuffer[TM_BUF_SIZE];
char * errorStr;
 
int udpClient(void)
{
    int testSocket;
    unsigned int counter;
    struct sockaddr_in destAddr;
    int errorCode;
    int returnVal;
 
    counter = 0;
    returnVal = 0;
 
/* Specify the address family */
    destAddr.sin_family = AF_INET;
/* Specify the destination port */
    destAddr.sin_port = htons(TM_DEST_PORT);
/* Specify the destination IP address */
    destAddr.sin_addr.s_addr = inet_addr(TM_DEST_ADDR);
 
/* Create a socket */
    testSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
 
/*
 * Verify the socket was created correctly. If not, return
 * immediately
 */
    if (testSocket == TM_SOCKET_ERROR)
    {
        returnVal = tfGetSocketError(testSocket);
        errorStr = tfStrError(returnVal);
        goto udpClientEnd;
    }
 
/* While we haven’t yet sent enough packets... */
    while (counter < TM_PACKETS_TO_SEND)
    {
/* Send another packet to the destination specified above */
        errorCode = sendto(testSocket,
                           testBuffer,
                           TM_BUF_SIZE,
                           0,
                           &destAddr,
                           sizeof(destAddr));
 
/*
 * Check if there was an error while sending. If so, break from the
 * loop
 */
        if (errorCode < 0)
        {
            returnVal = tfGetSocketError(testSocket);
            errorStr = tfStrError(returnVal);
            break;
        }
 
/* Increment the number of packets sent by 1 */
        counter++;
    }
 
udpClientEnd:
/* Make sure the socket exists before we close it */
    if (testSocket != -1)
    {
/* Close the socket */
        tfClose(testSocket);
    }
 
    return(returnVal);
}

UDP Server

This code is a very simple UDP server. It creates a socket, binds it to the desired port (it is not necessary to supply an IP address as the stack will pick one; this is very useful for making code portable), and then receives data. Upon receipt of the data, the 'sourceAddr' structure is filled out with the originating IP Address and port of the incoming packet.

#include <trsocket.h>
 
#define TM_BUF_SIZE 1500
#define TM_DEST_PORT 9999
 
char testBuffer[TM_BUF_SIZE];
char * errorStr;
 
int udpServer(void)
{
    int testSocket;
    struct sockaddr_in sourceAddr;
    struct sockaddr_in destAddr;
    int errorCode;
    int addrLen;
    int returnVal;
 
    returnVal = 0;
 
/* Specify the address family */
    destAddr.sin_family = AF_INET;
/*
* Specify the dest port (this being the server, the destination
* port is the one we’ll bind to)
*/
    destAddr.sin_port = htons(TM_DEST_PORT);
/*
* Specify the destination IP address (our IP address). Setting
* this value to 0 tells the stack that we don’t care what IP
* address we use - it should pick one. For systems with one IP
* address, this is the easiest approach.
*/
    destAddr.sin_addr.s_addr = 0;
 
/*
* The third value is the specific protocol we wish to use. We pass
* in a 0 because the stack is capable of figuring out which
* protocol to used based on the second parameter (SOCK_DGRAM =
* UDP, SOCK_STREAM = TCP)
*/
    testSocket = socket(AF_INET, SOCK_DGRAM, 0);
/* Make sure the socket was created successfully */
    if (testSocket == TM_SOCKET_ERROR)
    {
        returnVal = tfGetSocketError(testSocket);
        errorStr = tfStrError(returnVal);
        goto udpServerEnd;
    }
 
/*
* Bind the socket to the port and address at which we wish to
* receive data
*/
    errorCode = bind(testSocket, &destAddr, sizeof(destAddr));
/* Check for an error in bind */
    if (errorCode < 0)
    {
        returnVal = tfGetSocketError(testSocket);
        errorStr = tfStrError(returnVal);
        goto udpServerEnd;
    }
 
/* Do this forever... */
    while (1)
    {
/* Get the size of the sockaddr_in structure */
        addrLen = sizeof(sourceAddr);
/*
* Receive data. The values passed in are:
* We receive said data on testSocket.
* The data is stored in testBuffer.
* We can receive up to TM_BUF_SIZE bytes.
* There are no flags we care to set.
* Store the IP address/port the data came from in sourceAddr
* Store the length of the data stored in sourceAddr in addrLen.
*The length that addrLen is set to when it’s passed in is
*used to make sure the stack doesn’t write more bytes to
*sourceAddr than it should.
*/
        errorCode = recvfrom(testSocket,
                             testBuffer,
                             TM_BUF_SIZE,
                             0,
                             &sourceAddr,
                             &addrLen);
/* Make sure there wasn’t an error in recvfrom */
        if (errorCode < 0)
        {
            returnVal = tfGetSocketError(testSocket);
            errorStr = tfStrError(returnVal);
            break;
        }
    }
 
udpServerEnd:
/* Make sure we have an actual socket before we try to close it */
    if (testSocket != -1)
    {
/* Close the socket */
        tfClose(testSocket);
    }
 
    return(returnVal);
}

TCP Client

This code is very much like the UDP client. Unlike UDP, however, it must call connect() before actually transferring data, as TCP requires a negotiated connection. It then calls send() the specified number of times. An interesting observation is that the number of TCP data packets actually sent out on the wire will very probably not equal the number defined in this code. TCP is stream-based rather than datagram based, so it will buffer data and attempt to send it in the most convenient size packets (generally, maximum sized packets).

#include <trsocket.h>
 
#define TM_BUF_SIZE 1400
#define TM_PACKETS_TO_SEND 10
#define TM_DEST_ADDR “10.129.36.52”
#define TM_DEST_PORT 9999
 
char testBuffer[TM_BUF_SIZE];
char * errorStr;
 
int tcpClient(void)
{
    int testSocket;
    unsigned int counter;
    struct sockaddr_in destAddr;
    int errorCode;
    int sockOption;
    int returnVal;
 
    returnVal = 0;
    counter = 0;
 
/* Specify the address family */
    destAddr.sin_family = AF_INET;
/* Specify the destination port */
    destAddr.sin_port = htons(TM_DEST_PORT);
/* Specify the destination IP address */
    destAddr.sin_addr.s_addr = inet_addr(TM_DEST_ADDR);
 
/* Create a socket */
    testSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
/*
* Verify the socket was created correctly. If not, return
* immediately
*/
    if (testSocket == TM_SOCKET_ERROR)
    {
        returnVal = tfGetSocketError(testSocket);
        errorStr = tfStrError(returnVal);
        goto tcpClientEnd;
    }
 
/* Connect to the server */
    errorCode = connect(testSocket, &destAddr, sizeof(destAddr));
/* Verify that we connected correctly */
    if (errorCode < 0)
    {
        returnVal = tfGetSocketError(testSocket);
        errorStr = tfStrError(returnVal);
        goto tcpClientEnd;
    }
 
/* While we haven’t yet sent enough packets... */
    while (counter < TM_PACKETS_TO_SEND)
    {
/* Send another packet to the destination specified above */
        errorCode = send(testSocket,
                         testBuffer,
                         TM_BUF_SIZE,
                         0);
/*
* Check if there was an error while sending. If so, break from the
* loop
*/
        if (errorCode < 0)
        {
            returnVal = tfGetSocketError(testSocket);
            errorStr = tfStrError(returnVal);
            break;
        }
/* Increment the number of packets sent by 1 */
        counter++;
    }
 
tcpClientEnd:
/* Make sure we have a socket before closing it */
    if (testSocket != -1)
    {
/* Close the socket */
        tfClose(testSocket);
    }
 
    return(returnVal);
}

TCP Server

This is the most complicated of the examples. It creates a socket, binds that socket to a port, and configures it as a listening socket. This allows it to receive incoming connections. It then calls accept(), which will block until an incoming connection request is received. When accept() returns, the 'sourceAddr' structure will have been filled out with the originating IP Address and port of the incoming connection request. accept() creates a new socket, which is then used to receive data until the connection is closed by the other side. When this happens, the application goes back to waiting for an incoming connection request.

#include <trsocket.h>
 
#define TM_BUF_SIZE 1400
#define TM_DEST_PORT 9999
 
char testBuffer[TM_BUF_SIZE];
char * strError;
 
int tcpServer(void)
{
    int listenSocket;
    int newSocket;
    struct sockaddr_in sourceAddr;
    struct sockaddr_in destAddr;
    int errorCode;
    int addrLen;
    int returnVal;
 
    returnVal = 0;
 
/* Specify the address family */
    destAddr.sin_family = AF_INET;
/*
* Specify the dest port (this being the server, the destination
* port is the one we’ll bind to
*/
    destAddr.sin_port = htons(TM_DEST_PORT);
/*
* Specify the destination IP address (our IP address). Setting
* this value to 0 tells the stack that we don’t care what IP
* address we use - it should pick one. For systems with one IP
* address, this is the easiest approach.
*/
    destAddr.sin_addr.s_addr = inet_addr("0.0.0.0");
 
/* Create a socket */
    listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
/* Make sure the socket was created successfully */
    if (listenSocket == TM_SOCKET_ERROR)
    {
        returnVal = tfGetSocketError(listenSocket);
        errorStr = tfStrError(returnVal);
        goto tcpServerEnd;
    }
 
/*
* Bind the socket to the port and address at which we wish to
* receive data
*/
    errorCode = bind(listenSocket, &destAddr, sizeof(destAddr));
/* Check for an error in bind */
    if (errorCode < 0)
    {
        returnVal = tfGetSocketError(listenSocket);
        errorStr = tfStrError(returnVal);
        goto tcpServerEnd;
    }
 
/* Set up the socket as a listening socket */
    errorCode = listen(listenSocket, 10);
/* Check for an error in listen */
    if (errorCode < 0)
    {
        returnVal = tfGetSocketError(listenSocket);
        errorStr = tfStrError(returnVal);
        goto tcpServerEnd;
    }
 
/* Do this forever... */
    while (1)
    {
/* Get the size of the sockaddr_in structure */
        addrLen = sizeof(sourceAddr);
/*
* Accept an incoming connection request. The address/port info for
* the connection’s source is stored in sourceAddr. The length of
* the data written to sourceAddr is stored in addrLen. The
* initial value of addrLen is checked to make sure too many
* bytes are not written to sourceAddr
*/
        newSocket = accept(listenSocket, &sourceAddr, &addrLen);
/* Check for an error in accept */
        if (newSocket < 0)
        {
            returnVal = tfGetSocketError(listenSocket);
            errorStr = tfStrError(returnVal);
            goto tcpServerEnd;
        }
 
/* Do this forever... */
        while (1)
        {
/* Receive data on the new socket created by accept */
            errorCode = recv(newSocket,
                             testBuffer,
                             TM_BUF_SIZE,
                             0);
/* Make sure there wasn’t an error */
            if (errorCode < 0)
            {
                tfClose(newSocket);
                returnVal = tfGetSocketError(newSocket);
                errorStr = tfStrError(returnVal);
                goto tcpServerEnd;
            }
/*
* Receiving 0 bytes of data means the connection has been closed.
* If this happens, close the new socket and break out of this
* (the inner) loop.
*/
            if (errorCode == 0)
            {
                tfClose(newSocket);
                break;
            }
        }
    }
 
tcpServerEnd:
/* Make sure there’s a socket there before closing it */
    if (listenSocket != -1);
    {
/* Close the listening socket */
        tfClose(listenSocket);
    }
 
    return(returnVal);
}


Table of Contents

Views
Personal tools