| Winsock & .NET | Winsock | < TCP/IP Socket Programming Interfaces (APIs) | Linux Socket Index | More Socket APIs & C Code Snippets > |


 

 

 

 

 

NETWORK PROGRAMMING

LINUX SOCKET PART 6: THE APIs

 

 

 

 

 

 

Menu

 

Network Story 1

Network Story 2

Network Story 3

Network Story 4

Network Story 5

Network Story 6

Socket Example 1

Socket Example 2

Socket Example 3

Socket Example 4

Socket Example 5

Socket Example 6

Socket Example 7

Advanced TCP/IP 1

Advanced TCP/IP 2

Advanced TCP/IP 3

Advanced TCP/IP 4

Advanced TCP/IP 5

My Training Period:  xx  hours

 

This is a continuation from Part I series, Introduction to Socket Programming.  Working program examples if any compiled using gcc, tested using the public IPs, run on Linux/Fedora Core 3, with several times of update, as normal user.  The Fedora machine used for the testing having the "No Stack Execute" disabled and the SELinux set to default configuration.

 

The abilities that supposed to be acquired:

  • Able to understand and use the Unix / Linux C language socket APIs.

  • Able to understand and implement several simple TCP and UDP Client and server basic designs.

Client Design Consideration

  • Some of the information in this section is a repetition from the previous one.

Identifying a Server's Address

  • A server's IP address must be used in connect.

  • Usually the name is used to get the address.

  • The name could be in the code mailhost for an email program.

  • The user could specify the name common because it is flexible and simple.

  • The name or address could be in a file.

  • Can broadcast on the network to ask for a server.

  • The following is an example of telneting the telserv.test.com server through the standard telnet port 25:

telnet   telserv.test.com

  • Or using the IP address of the telnet server:

telnet    131.95.115.204

  • Client software typically allows either names or numbers.

  • Ports usually have a default value in the code if not explicitly mentioned.

Looking Up a Computer Name

NAME
       gethostbyname() - get network host entry

SYNOPSIS
       #include <netdb.h>
       extern int h_errno;

       struct hostent
       *gethostbyname(const char *name);
struct hostent {
   char  *h_name;
   char  **h_aliases;
   int   h_addrtype;
   int   h_length;
   char  **h_addr_list;
};
#define h_addr h_addr_list[0]
  • name could be a name or dotted decimal address.

  • Hosts can have many names in h_aliases.

  • Hosts can have many addresses in h_addr_list.

  • Addresses in h_addr_list are not strings network order addresses ready to copy and use.

Looking Up a Port Number by Name

NAME
   getservbyname() - get service entry

SYNOPSIS
   #include <netdb.h>

   struct servent *getservbyname(const char *name, const char *proto);

struct servent {
   char  *s_name;
   char  **s_aliases;
   int   s_port;
   char  *s_proto;
}

Looking Up a Protocol by Name

NAME
       getprotobyname() - get protocol entry

SYNOPSIS
       #include <netdb.h>

       struct protoent
       *getprotobyname(const char *name);
struct protoent {
   char  *p_name;
   char  **p_aliases;
   int   p_proto;
}

getpeername()

#include <sys/socket.h>

int getpeername(int sockfd, struct sockaddr *addr, int *addrlen);

Allocating a Socket

#include <sys/types.h>
#include <sys/socket.h>

int s;

s = socket(PF_INET, SOCK_STREAM, 0);

Choosing a Local Port Number

Connecting to a Server with TCP

NAME
       connect - initiate a connection on a socket

SYNOPSIS
       #include <sys/types.h>
       #include <sys/socket.h>

       int connect(int s, struct sockaddr *serv_addr, int addrlen);

RETURN VALUE
       If the connection or binding succeeds, zero is returned.
       On error, -1 is returned, and errno is set appropriately.

Communicating with TCP

char *req = "send cash";
char buf[100], *b;

write (s, req, strlen(req));

left = 100;
b = buf;
while (left && (n = read(s, buf, left)) > 0)
{
   b += n;
   left -= n;
}

Closing a TCP Connection

res = shutdown(s, 1);

Connected vs Unconnected UDP Sockets

  • A client can call connect with a UDP socket or not.

  • If connect is called read and write will work.

  • Without connect the client needs to send with a system call specifying a remote endpoint.

  • Without connect it might be useful to receive data with a system call which tells the remote endpoint.

  • Connect with TCP involves a special message exchange sequence.

  • Connect with UDP sends no messages.  You can connect to non-existent servers.

Communicating with UDP

  • UDP data is always a complete message (datagram).

  • Whatever is specified in a write becomes a datagram.

  • Receiver receives the complete datagram unless fewer bytes are read.

  • Reading in a loop for a single datagram is pointless with UDP.

  • close() is adequate, since shutdown() does not send any messages.

  • UDP is unreliable.  UDP software needs an error protocol.

Client Example – Some variations

 

A Simple Client Library

  1. select UDP or TCP...

  2. determine a server's IP address...

  3. determine the proper port...

  4. make the socket call...

  5. make the connect call...

connectTCP()

int connectTCP(const char *host, const char *service)
{ return connectsock(host, service, "tcp"); }

connectUDP()

int connectUDP(const char *host, const char *service)
{ return connectsock(host, service, "udp"); }

connectsock()

int connectsock(const char *host, const char *service, const char *transport)
{
    struct hostent      *phe;   /* pointer to host information entry    */
    struct servent      *pse;   /* pointer to service information entry */
    struct protoent     *ppe;   /* pointer to protocol information entry*/
    struct sockaddr_in sin;     /* an Internet endpoint address         */
    int s, type;                /* socket descriptor and socket type    */

    memset(&sin, 0, sizeof(sin));
    sin.sin_family = AF_INET;

    /* Map service name to port number */
    if(pse = getservbyname(service, transport))
        sin.sin_port = pse->s_port;
    else if ((sin.sin_port = htons((u_ short)atoi(service))) == 0)
        errexit("can't get \"%s\" service entry\n", service);

    /* Map host name to IP address, allowing for dotted decimal */
    if(phe = gethostbyname(host))
        memcpy(&sin.sin_addr, phe->h_addr, phe->h_length);
    else if ((sin.sin_addr.s_addr = inet_addr(host)) == INADDR_NONE)
        errexit("can't get \"%s\" host entry\n", host);

    /* Map transport protocol name to protocol number */
    if((ppe = getprotobyname(transport)) == 0)
        errexit("can't get \"%s\" protocol entry\n", transport);

    /* Use protocol to choose a socket type */
    if(strcmp(transport, "udp") == 0)
        type = SOCK_DGRAM;
    else
        type = SOCK_STREAM;

    /* Allocate a socket */
    s = socket(PF_INET, type, ppe->p_proto);
    if(s < 0)
       errexit("can't create socket: %s\n", strerror(errno));

    /* Connect the socket */
    if(connect(s, (struct sockaddr *)&sin, sizeof(sin)) < 0)
       errexit("can't connect to %s.%s: %s\n", host, service, strerror(errno));
    return s;
}

A TCP DAYTIME Client

#define LINELEN 128

int main(int argc, char *argv[ ])
{
    /* host to use if none supplied */
    char *host = "localhost";
    /* default service port */
    char *service = "daytime";
    switch (argc) {
    case 1:
        host = "localhost";
        break;
    case 3:
        service = argv[2];
        /* FALL THROUGH */
    case 2:
        host = argv[1];
        break;
    default:
        fprintf(stderr, "usage: TCPdaytime [host [port]]\n");
        exit(1);
    }
    TCPdaytime(host, service);
    exit(0);
}

void TCPdaytime(const char *host, const char *service)
{
    /* buffer for one line of text */
    char buf[LINELEN+1];
    /* socket, read count */
    int s, n;

    s = connectTCP(host, service);

 while((n = read(s, buf, LINELEN)) > 0)
 {
      /* ensure null-terminated */
      buf[n] = '\0';
      (void) fputs(buf, stdout);
 }
}

A UDP TIME Client

#define MSG  "What time is it?\n"
 
int main(int argc, char *argv[ ])
{
    char   *host = "localhost"; /* host to use if none supplied */
    char   *service = "time";   /* default service name   */
    time_t now;                 /* 32-bit integer to hold time  */ 
    int    s, n;                /* socket descriptor, read count */

    switch (argc) {
    case 1:
        host = "localhost";
        break;
    case 3:
        service = argv[2];
        /* FALL THROUGH */
    case 2:
        host = argv[1];
        break;
    default:
        fprintf(stderr, "usage: UDPtime [host [port]]\n");
        exit(1);
    }

    s = connectUDP(host, service);

    (void) write(s, MSG, strlen(MSG));

    /* Read the time */
    n = read(s, (char *)&now, sizeof(now));
    if(n < 0)
        errexit("read failed: %s\n", strerror(errno));
    /* put in host byte order */
    now = ntohl((u_long)now);
    printf("%s", ctime(&now));
    exit(0);
}

TCP and UDP Echo Clients

TCPecho() function

int TCPecho(const char *host, const char *service)
{
    char buf[LINELEN+1];   /* buffer for one line of text  */
    int s, n;              /* socket descriptor, read count*/
    int outchars, inchars; /* characters sent and received */

    s = connectTCP(host, service);

while(fgets(buf, sizeof(buf), stdin))
{
   /* insure line null-terminated */
   buf[LINELEN] = '\0';
   outchars = strlen(buf);
   (void) write(s, buf, outchars);

  /* read it back */
 for(inchars = 0; inchars < outchars; inchars+=n)
 {
    n = read(s, &buf[inchars], outchars - inchars);
    if(n < 0)
      errexit("socket read failed: %s\n", strerror(errno));
 }
 fputs(buf, stdout);
}
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

UDPecho() function

int UDPecho(const char *host, const char *service)
{
    /* buffer for one line of text */
    char buf[LINELEN+1];
    /* socket descriptor, read count */
    int s, nchars;

    s = connectUDP(host, service);

 while(fgets(buf, sizeof(buf), stdin))
 {
    /* ensure null-terminated */
    buf[LINELEN] = '\0';
    nchars = strlen(buf);
    (void) write(s, buf, nchars);

    if(read(s, buf, nchars) < 0)
        errexit("socket read failed: %s\n", strerror(errno));
    fputs(buf, stdout);
}
}

Server Design Consideration

 

Concurrent vs Iterative Servers

Connection-Oriented vs Connectionless Servers

Connection-oriented Servers

Connectionless Servers

Stateless Servers

Request Processing Time

Iterative, Connection-Oriented Server Algorithm

create a socket
bind to a well-known port
place in passive mode
while (1)
{
    Accept the next connection
    while (client writes)
    {
        read a client request
        perform requested action
        send a reply
    }
    close the client socket
}
close the passive socket

Using INADDR_ANY

Iterative, Connectionless Server Algorithm

create a socket
bind to a well-known port
while (1)
{
    read a request from some client
    send a reply to that client
}

 

recvfrom(s, buf, len, flags, from, fromlen)

sendto(s, buf, len, flags, to, to_len)

Concurrent, Connectionless Server Algorithm

create a socket
bind to a well-known port
while (1)
{
    read a request from some client
    fork
    if(child)
    {
        send a reply to that client
        exit
    }
  }

Concurrent, Connection-Oriented Server Algorithm

create a socket
bind to a well-known port
use listen to place in passive mode
while (1)
{
    accept a client connection
    fork
    if (child)
    {
        communicate with new socket
        close new socket
        exit
     }
 else
 {close new socket}
}

Concurrency Using a Single Process

create a socket
bind to a well-known port
while (1)
{
    use select to wait for I/O
    if(original socket is ready)
    {
        accept() a new connection and add to read list
    }
 else if (a socket is ready for read)
 {
        read data from a client
        if(data completes a request)
        {
           do the request
           if(reply needed) add socket to write list
        }
   }
 else if (a socket is ready for write)
 {
   write data to a client
   if(message is complete)
   {
       remove socket from write list
   }
 else
 {
    adjust write parameters and leave in write list
 }
 }
}

When to Use the Various Server Types

  1. Iterative server is simpler to write.

  2. Concurrent server is faster.

  3. Use iterative if it is fast enough.

  1. Writing a single process concurrent server is harder.

  2. Use a single process if data must be shared between clients.

  3. Use multiple processes if each slave is isolated or if you have multiple CPUs.

  1. Use connectionless if the protocol handles reliability.

  2. Use connectionless on a LAN with no errors.

Avoiding Server Deadlock

Continue on next Module…

 

 

 

 

 

 

 

 

 

 

 

 

 

Further reading and digging:

 

  1. Check the best selling C / C++, Networking, Linux and Open Source books at Amazon.com.

  2. GCC, GDB and other related tools.

 

 

 

 

 

 

 


 

| Winsock & .NET | Winsock | < TCP/IP Socket Programming Interfaces (APIs) | Linux Socket Index | More Socket APIs & C Code Snippets > |