Unix Network Socket Tutorial

Version: 2
Author: Jacob Rhoden
Released Date: January 1998.

This is a basic introduction into using sockets in C. Once you have completed this tutorial, you can learn more advanced techniques in the following non-blocking sockets tutorial

  1. What is a socket
  2. How to create a socket
  3. Connecting a socket to a server
  4. The read and write commands
  5. Using this to create a program
  6. Writing some better programs
  7. Where to go next
  8. About Me

Good luck and if you have any questions, queries, or god forbid you have found any mistakes in this primer please don't hesitate to email me.

You are free to post my tutorial on any WWW or FTP site on the condition that it is not modified in any way and I would appreciate it if you tell me so that I can mention your link it in this document.

What is a socket

A socket can be thought of like a file handle or a byte stream. If you want to open a file from a disk you declare a file handle, if you want to connect to a server you declare a socket and set it up.

A socket has many capabilities, protocols if you like, that it can use to communicate, here we will start off with the most common, the TCP protocol.

How to create a socket

This command checks a socket for incoming data or to see if data is ready to be written to the socket. The syntax of the select command is as follows:

  s = socket(AF_INET, SOCK_STREAM, 0);
  memset(&sin, 0, sizeof(struct sockaddr_in));
  sin.sin_family = AF_INET;
  sin.sin_port = htons(port);
  sin.sin_addr.s_addr = inet_addr(hstname);
  if(sin.sin_addr.s_addr == INADDR_NONE) {
  connect(s, (struct sockaddr *)&sin, sizeof(sin))

Connecting a socket to a server

The flags in the select command are used to check and get data about sockets, as follows:

FD_ZERO(s, &write_flags)      sets all associated flags
                              in the socket to 0
FD_SET(s, &write_flags)       used to set a socket for checking
FD_CLR (s, &write_flags)      used to clear a socket from  being checked
FD_ISSET(s, &write_flags)     used to query as to if the socket is ready
                              for reading or writing.

How they are used will be demonstrated by example in the example code

The read and write commands

You should already know how to use the following commands, they do however act slightly different when using non-blocking sockets.

  write(s,buffer,sizeof(buffer))   send the text in "buffer"
  read(s,buffer,sizeof(buffer))    read available data into "buffer"

When a socket is used in non-blocking mode, these two commands will always return as soon as you call them, ie, if there is nothing for the command to do it will not block and wait for data, instead it will return with an error code. Now we are ready to look at some real code.

Using this to create a program

Firstly lets declare the variables that will be used.

  fd_set read_flags,write_flags; // the flag sets to be used
  struct timeval waitd;          // the max wait time for an event
  char buffer[8196];             // input holding buffer
  int stat;                      // holds return value for select();

Most of our programs time will be spent calling select over and over again (which uses up lots of CPU time) until data is ready to be read or written. That is the purpose of the waitd variable. It determines how long the select() command will wait for data before returning. Following our learn by example style, look carefully at the following code:

// Insert Code to create a socket

while(1) // put program in an infinite loop of reading and writing data
 {
  }

loop over and over and over really fast and use up to 99% of your CPU time on a *nix machine, which your sys-admin probably wont like.

Writing some better programs

Finally to put into practice all we have learn't we will write a simple finger client. Of course using non-blocking sockets is a bit of an overkill for a finger client but its for examples sake only. For more realistic examples see section 7.

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

// this routine simply converts the address into an
// internet ip
unsigned long name_resolve(char *host_name)
{
struct in_addr addr;
struct hostent *host_ent;
  if((addr.s_addr=inet_addr(host_name))==(unsigned)-1) {
    host_ent=gethostbyname(host_name);
    if(host_ent==NULL) return(-1);
    memcpy(host_ent->h_addr, (char *)&addr.s_addr, host_ent->h_length);
    }
  return (addr.s_addr);
}

// The connect routine including the command to set
// the socket non-blocking.
int doconnect(char *address, int port)
{
int x,s;
struct sockaddr_in sin;

  s=socket(AF_INET, SOCK_STREAM, 0);
  x=fcntl(s,F_GETFL,0);              // Get socket flags
  fcntl(s,F_SETFL,x | O_NONBLOCK);   // Add non-blocking flag
  memset(&sin, 0, sizeof(struct sockaddr_in));
  sin.sin_family=AF_INET;
  sin.sin_port=htons(port);
  sin.sin_addr.s_addr=name_resolve(address);
  if(sin.sin_addr.s_addr==NULL) return(-1);
  printf("ip: %s\n",inet_ntoa(sin.sin_addr));
  x=connect(s, (struct sockaddr *)&sin, sizeof(sin));
  if(x<0) return(-1);
return(s);
}

int main (void)
{
fd_set read_flags,write_flags; // you know what these are
struct timeval waitd;          
int thefd;             // The socket
char outbuff[512];     // Buffer to hold outgoing data
char inbuff[512];      // Buffer to read incoming data into
int err;	       // holds return values

  memset(&outbuff,0,sizeof(outbuff)); // memset used for portability
  thefd=doconnect("203.1.1.1",79); // Connect to the finger port
  if(thefd==-1) {
    printf("Could not connect to finger server\n");
    exit(0);
    }
  strcat(outbuff,"jarjam\n"); //Add the string jarjam to the output
                              //buffer
  while(1) {
    waitd.tv_sec = 1;     // Make select wait up to 1 second for data
    waitd.tv_usec = 0;    // and 0 milliseconds.
    FD_ZERO(&read_flags); // Zero the flags ready for using
    FD_ZERO(&write_flags);
    FD_SET(thefd, &read_flags);
    if(strlen(outbuff)!=0) FD_SET(thefd, &write_flags);
    err=select(thefd+1, &read_flags,&write_flags,
               (fd_set*)0,&waitd);
    if(err < 0) continue;
    if(FD_ISSET(thefd, &read_flags)) { //Socket ready for reading
      FD_CLR(thefd, &read_flags);
      memset(&inbuff,0,sizeof(inbuff));
      if (read(thefd, inbuff, sizeof(inbuff)-1) <= 0) {
        close(thefd);
        break;
        }
      else printf("%s",inbuff);
      }
    if(FD_ISSET(thefd, &write_flags)) { //Socket ready for writing
      FD_CLR(thefd, &write_flags);
      write(thefd,outbuff,strlen(outbuff));
      memset(&outbuff,0,sizeof(outbuff));
      }
    // now the loop repeats over again
    }
}

This code compiles and was tested using FreeBSD2.2.6

Where to go next

The extra example code has been removed from this file and placed into a ZIP file for neatness. To get a better understanding of what you have learnt here it would be a good idea to check out more complicated and technically more correct code available at:

Example Source: http://rhoden.id.au/doc/sock.zip

To learn how to use more advanced sockets to write better programs move onto the next page on Non-Blocking sockets. Non-Blocking sockets are a way of being able to read and write data while still allowing your program to do other things at the same time.

Also I recommend you check out the man pages on your system because a lot of online references are just repeats of what is already available on your system.

About Me

No I'm not some highly capable programmer but I do have a fair bit of background experience in programming. I am currently a 1st year student in the Latrobe University of Melbourne, Australia. Note that non-blocking sockets is a topic that usually isn't covered in any 1st year course, so I will be learning all this again next year (:

URL: http://rhoden.id.au/
Email: jrhoden@unimelb.edu.au
Last Updated: 18th of August, 1998. By Jacob Rhoden (c)1998 Dominoid Productions.