Monday, December 24, 2012

Coding lessons: Network client software

So, in the last coding lesson post I covered how to make a simple message server.

The message that it sent only said Hello, but it doesn't take too much imagination to realise that you could make this read data from a text file, or make it get some system parameters, or any number of things. perhaps the weather from a weather station.

For now we'll stick with the simple hello message.

We tested the server program by running telnet and connecting to the server that was running on port 66.

Now we're going to create some client software to connect to the server and get our message.

Again this is pretty simple proof of concept type software so don't get too excited about the code samples that I'm creating, on the other hand do imagine the possibilities of what you can create with these building blocks, and do get excited about that!


so as last time I'm going to step through the code roughly explaining what each line does.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
void error(const char *msg)
{
    perror(msg);
    exit(0);
}

First as before we set-up our required header files, and that place to go to in the case of a terminal error.
then we start our program.

int main(int argc, char *argv[])
{


The first thing that you should notice is that something is different, what is argc and argv?

They are simple command line arguments.
The idea here is that we don't know where our software will run, it's all very well saying that the server is always going to be called "my_home_server", but what abuot when it's not. what about when it's out on the internet and you need to contact it by name, or what about when you change the host that the server software runs on. you don't want to have to compile a new binary for every host that you want to connect to.
so we're going to accept arguments about what host to connect to from the command line when this program is run.

Argc is argument count, Argv is the argument values it's an array of values, I.e. what we write after the program name

    int sockfd, portno, n;
    struct sockaddr_in serv_addr;
    struct hostent *server;
    char buffer[256];


next (as before) we set up all the little bits of data variables we'll use in the program.

Now as we are specifying the host name to connect to as a command line argument, we need to check if there are command line arguments.

    if (argc < 2) {
       fprintf(stderr,"You Must specify a hostname: usage %s hostname\n", argv[0]);
       exit(0);
    }


if the program runs on it's own with no arguments then argc = 1 -just the program name,
we're running the software in the format "program_name hostname" so there are two arguments.
We also can see what the value of that first argument is, as it's argv[0], it's the program name, in this way even if we rename the executable, this help message is going to be correct.

    portno = 66;

Once again we specify the ports that we're going to use, (if you changed from port 66 in the server software part, remember to change this again!)

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
        error("ERROR opening socket");


And we setup our socket the same as we did on the server, a simple TCP/IP stream

    server = gethostbyname(argv[1]);
    if (server == NULL) {
        fprintf(stderr,"ERROR, no such host\n");
        exit(0);
    }


Next we get the address of the server that we're going to connect to.
this uses a special function called gethostbyname that enables us to have literal addresses (like www.google.com) as the host that we want to connect to. We check to make sure that the address exists, if it doesn't the gethostbyname function returns nothing. and if this happens how are we going to open a socket to nowhere? -we can't so we throw and error message and close the program.

    bzero((char *) &serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    bcopy((char *)server->h_addr,
         (char *)&serv_addr.sin_addr.s_addr,
         server->h_length);
    serv_addr.sin_port = htons(portno);


then we setup out port using roughly the same sort of instructions as we used for the server port. specifying our protocols, where it's going to connect to, and the port number that the socket will be bound to.

    if (connect(sockfd,(struct sockaddr *) &serv_addr,sizeof(serv_addr)) < 0)
        error("ERROR connecting");


In the server example the next thing we did was bind the port, and set it to listen.
this is where client software dramatically differs here. rather than binding the port to listen, we now tell this port to connect.
(and obviously trap an error if this fails)

This software isn't just sitting and waiting this software is activly going out and connectting.

  bzero(buffer,256);

So the first thing we do now is zero our buffer, basically get rid of any data in it.

you remember how with files if we opened a file for reading and writing with the pointer at the start of a file.

if the file said
Goodbye
and we wanted to write
Hello

we'd end up with
Helloye

As the second string doesn't completely replace the first.

Same thing here, and we don't want to display garbage to the user, so we clear out the buffer first.

Then we read from the socket, (in just the same way as we read from files, (except using read, not fread.

    n = read(sockfd,buffer,255);
    if (n < 0)
         error("ERROR reading from socket");


and if we can't read from the socket, obviously we give an error!

but if we can read from the socket then we display what we received from that socket by printing the buffer to the screen.

    printf("%s\n",buffer);
    close(sockfd);
    return 0;
}


then we close the socket and exit gracefully.

complete code is here:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
void error(const char *msg)
{
    perror(msg);
    exit(0);
}
int main(int argc, char *argv[])
{
    int sockfd, portno, n;
    struct sockaddr_in serv_addr;
    struct hostent *server;
    char buffer[256];
    if (argc < 2) {
       fprintf(stderr,"You Must specify a hostname: usage %s hostname\n", argv[0]);
       exit(0);
    }
    portno = 66;
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
        error("ERROR opening socket");
    server = gethostbyname(argv[1]);
    if (server == NULL) {
        fprintf(stderr,"ERROR, no such host\n");
        exit(0);
    }
    bzero((char *) &serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    bcopy((char *)server->h_addr,
         (char *)&serv_addr.sin_addr.s_addr,
         server->h_length);
    serv_addr.sin_port = htons(portno);
    if (connect(sockfd,(struct sockaddr *) &serv_addr,sizeof(serv_addr)) < 0)
        error("ERROR connecting");
    bzero(buffer,256);
    n = read(sockfd,buffer,255);
    if (n < 0)
         error("ERROR reading from socket");
    printf("%s\n",buffer);
    close(sockfd);
    return 0;
}



this code is compiled on a linux system with the command.

gcc -o client client.c

then you can run the software with the command

./client localhost
(where the server software is also running on your computer)

No comments: