Monday, December 10, 2012

Coding lessons: Socket servers

So in the last lesson I looked at socket servers, I touched on a problem with these lessons that will now become very apparent.

Windows Vs. the world,

So, there is a problem here, windows sockets work different to the way that practically everyone else does it. I might come back to winsock later. and certainly if you want to learn how to program network aware programs on windows then those lessons will be useful. the changes are not insurmountable, but the code is not directly portable

The lessons that I'm writing here apply to Linux (every distribution to date) Unix, BSD Unix, Solaris, and I think MacOS.

Right now if you're reading this lesson thinking that you only have access to windows, then I suggest that you download VMware player, it's free and lets you host virtual machines on your PC, or Microsoft's virtual PC, or any other virtualisation software out there.
Then download a free copy of Linux to go with it. I'll leave it to you to decide which copy of Linux.

But I recommend Debian Linux, I wouldn't suggest using it as a desktop OS, (there are much more polished distributions based on Debian,) so I'd recommend it more based on what it forces you to learn more than because it's a really comfortable OS for a beginner to use.

Anyway, go somewhere else to figure out how to use Linux, when you've finished with learning how to do that, come back and learn how to make server software.



Libraries
There is going to be a whole heap of headers included in this program. these are needed because of the types of data that we're dealing with, and the sorts of data that we need, (that these libraries can provide).


Functions
We're going to be using the error function that was created in the last post so that we can provide some helpful errors in case of failure.


Program
So I figure the best way to do this is to go through the program line by line explaining what each line/block is doing then I'll paste the whole code at the end.

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

So first we've included all the libraries that we're going to use, it's a reasonably large list! I won't go through what every library does, it'd be pointless without the context provided by showing the code later.


void error(const char *msg)
{
    perror(msg);
    exit(1);
}

We setup our error function just as we did in the last lesson.

Then onto our main program.

int main()
{
     int n, sockfd, newsockfd, portno;

First we're setting up integers that are going to be used for some error checking, we also set-up an integer to decide what port we want to open the server program on.

     socklen_t clilen;

This line is declaring a variable called clien, it's data type is socklen_t.

tis is a bit weird, so far we've only looked at variable that are ints, chars, floats etc, socklen_t is a new variable type it's defined in the socket.h header file that we included.

     struct sockaddr_in serv_addr, cli_addr;

now we're setting up a structured data type called sockaddr_in, this struct is defined in the library file #include <netinet/in.h>

     sockfd = socket(AF_INET, SOCK_STREAM, 0);

Now we get to the meat of the program, setting up a socket.

to set-up a socket first we use a return variable, (in this case the integer sockfd) then we use the socket function.

the socket function has three arguments.
the first of which is AF.

There are lots of AF types, (these are all defined in the header file socket.h) we're defining the socket as AF_INET this means internet protocol version 4. (notice I didn't say TCP/IP! just IP at this point).

we could also use AF_IPX to setup an IPX/SPX connecttion, or AF_APPLETALK to setup  a socket for use with the apple talk protocol AF_INET6 for use with IP6 AF_ROUTE for using the software with the internet routing protocol.

(look in socket.h for an extensive list)

The next argument that socket function needs is instructions as to the type of socket it's going to be, in this case we say it'll be a stream, and we use it just like file streams, but we could have said it'll be raw.

and the final piece of information is the protocol used,
in this case we're setting it to 0, this means use the default type for the family and type of socket, (AF_INET and SCOK_STREAM default protocol is TCP) but we could always specify a different protocol.

Next we need to check if we're able to create a socket at all
 
     if (sockfd < 0)
        error("ERROR opening socket");

we check that sockfd (the returned integer from the socket function is greater than zero, (as a 0 or negative number would specify an error, -if there is an error the program is useless so we break off into our error routine.

if there is no error we carry on through the program.

     bzero((char *) &serv_addr, sizeof(serv_addr));

Bzero is a function defined in the header string.h, it writes zeros to every location in the char array that makes up a string.

     portno = 66;
     serv_addr.sin_family = AF_INET;
     serv_addr.sin_addr.s_addr = INADDR_ANY;
     serv_addr.sin_port = htons(portno);

Now we need to set-up the data that's used in the structs for our socket creation.
again we're setting the socket family at AF_INET

Most machines will have more than one interface, this line :
  serv_addr.sin_addr.s_addr = INADDR_ANY;
is telling the struct what address it will bind to, in this case the answer is any address

Then we set-up out port number, we do this using the function htons.

Htons is simply a function that converts the order or numbers.
the reason that it does this is because there are some machines on the internet that use big endian byte order, and some that use little endian byte order

If you consider how to store variables.
lets say we have a 8 bit number 00101101, and we need to store this 8 bit number, in a memory space that's only 2 bits wide.

we'll use 4 memory spaces, we'll number the memory spaces 1 - 4 with big endian number storage
1, - 00
2, - 10
3, - 11
4, - 01

so you see we read the most significant bits from memory location 1, then work through the memory locations until we reach the least significant bit.

little endian system store the least significant bit in the lowest addressed memory location. a map of the memory in this case would look like this.

1, - 01
2, - 10
3, - 11
4, - 01

the internet protocol specifies that big endian is the correct order, that means that anyone using a machine with an intel processor (for example) has to re-order the words that they speak in to devices on the internet.

     if (bind(sockfd, (struct sockaddr *) &serv_addr,
              sizeof(serv_addr)) < 0)
              error("ERROR on binding");

so next we say if (this stuff) <0 br="" error="" function.="" so="" to="">we're binding our socket here if it fails we want to break out to our error function, we could have written:
ec = sockfd, (struct sockaddr *) & serv....... [so on]

if (ec < 0) { error(); }

but it takes a bit less space to combine it all on one line.]

so we're calling the bind function, this opens the socket and binds it to a port. we pass out serv_addr struct that contains all the data about the port and address etc to the bind function.

     listen(sockfd,5);

Next we tell the socket to enter listen mode.
Now the socket is sitting patiently waiting for a client to make a connection.

Once a client connects, (as described in an earlier post) the client will have an address and a port for communications.

now we set-up a new socket for communication so that our existing socket can handover and continue to listen for new connections.

     clilen = sizeof(cli_addr);
     newsockfd = accept(sockfd,
                 (struct sockaddr *) &cli_addr,
                 &clilen);


just as we setup our listening socket we set-up our communication socket.
     if (newsockfd < 0)
          error("ERROR on accept");

and we check that it's opened properly, if not they we'll break out of the main routine to our error function.

     n = write(newsockfd,"Hello",5);

Now we'll send the client a message.
again we say n = some function so that we can see what that function returned, zero or less is error.
     if (n < 0) error("ERROR writing to socket");
which again means break out to the error routine.

finally we need to close the sockets, just as we close files when we're done reading and writing.

     close(newsockfd);
     close(sockfd);
     return 0;
}





here's the whole code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
void error(const char *msg)
{
    perror(msg);
    exit(1);
}
int main()
{
     int n, sockfd, newsockfd, portno;
     socklen_t clilen;
     struct sockaddr_in serv_addr, cli_addr;
     sockfd = socket(AF_INET, SOCK_STREAM, 0);
     if (sockfd < 0)
        error("ERROR opening socket");
     bzero((char *) &serv_addr, sizeof(serv_addr));
     portno = 66;
     serv_addr.sin_family = AF_INET;
     serv_addr.sin_addr.s_addr = INADDR_ANY;
     serv_addr.sin_port = htons(portno);
     if (bind(sockfd, (struct sockaddr *) &serv_addr,
              sizeof(serv_addr)) < 0)
              error("ERROR on binding");
     listen(sockfd,5);
     clilen = sizeof(cli_addr);
     newsockfd = accept(sockfd,
                 (struct sockaddr *) &cli_addr,
                 &clilen);
     if (newsockfd < 0)
          error("ERROR on accept");
     n = write(newsockfd,"Hello",5);
     if (n < 0) error("ERROR writing to socket");
     close(newsockfd);
     close(sockfd);
     return 0;
}



when compiled on a linux system using gcc

(gcc -o server server.c)

a new binary file called server is created.

You should run this with the command ./server
(you may need to use sudo)

then you can connect to the server using telnet

c:\>telnet server 66
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
HelloConnection closed by foreign host.

you can see that the server is working, it's listening, it's accepting connections, displaying the string, and then closing the socket. (just as it was programmed to do so!)

No comments: