C Sockets - No Need For A Web Server!
Written by Mike James   
Monday, 15 August 2016
Article Index
C Sockets - No Need For A Web Server!
Web Client
Web Server

A Server

A server is more or less the same as a client from an implementation point of view. The only real difference is that it has to wait around until a client connects before dealing with a transaction. 

The first step is to create the socket and this follows the same pattern as for the client. We could simply set up the address structures and create a socket but now we know how to use getaddrinfo it is easier to use this to do the job automaticaly and flexibly:

struct addrinfo hints, *server;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
getaddrinfo(NULL, "80", &hints, &server);

The AI_PASSIVE flag assigns the current system's IP address. You can easily get address structures for alternative addresses such as IPTv6 using this but for simplicity we just ask for an IPv4 address.

Notice the specification of port 80 for an HTTP server socket. 

After the call to getaddrinfo the structs we need to create the socket are ready to be used:

int sockfd = socket(server->ai_family,
                    server->ai_socktype,
                    server->ai_protocol);
bind(sockfd, server->ai_addr,
             server->ai_addrlen);
listen(sockfd, 10);

You can see how easy getaddrinfo makes everything. The call to bind assigns the socket the IP address of the machine on port 80 and listen starts things going with a queue of 10 pending clients.

We can now use accept to wait for a client to connect:

struct sockaddr_storage client_addr;
socklen_t addr_size = sizeof client_addr;
int client_fd = accept(sockfd,
                   (struct sockaddr *) &client_addr,
                   &addr_size);

At this point our program is blocked waiting for a client to connect to the socket. If you want to keep processing things then you need to use a socket in non-blocking mode - see later.  For the moment we can assume that when accept returns there is a new socket descriptor in client and details of the client in clien_addr. Again for simplicity we are not going to check to see who the client is just serve them a web page. 

The client will first send the server an HTTP GET packet - assuming they do want to GET a web page. We can read this in using:

char buffer[2048];
int n = read(client_fd, buffer, 2048);
printf("%s", buffer);

The data in the GET headers tell the server which file is required and you can do some string handling to process it to get the name. In this case we are going to send the same HTML file no matter what the client asked for. 

To do this we need some HTTP headers defining what we are sending back and some HTML to define the page we are sending. The simplest set of headers that work is:

char headers[] = "HTTP/1.0 200 OK\r\n
       Server: CPi\r\n
       Content-type: text/html\r\n\r\n";

which corresponds to sending

HTTP/1.0 200 OK
Server: CPi
Content-type: text/html 

with two blank lines to mark the end of the headers. 

Notice that we have swapped to HTTP 1.0 because this is simpler and works with a smaller set of headers. If you want to support HTTP 1.1 then you need to specify the Content-Length header and the Connection header. 

The HTML data could be anything but:

char html[] = "<html><head><title>Temperature</title></head><body>{\"humidity\":81%,\"airtemperature\":23.5C}</p></body></html>\r\n";

is a typical small page for a small server reporting back temperature and humidity say. The html could be anything as long as it is not too big.

Now we can assemble the data and send it to the client:

char data[2048] = {0};
snprintf(data, sizeof data, "%s %s", headers, html);
n = write(client_fd, data, strlen(data));
close(client_fd);

If you put all of this together and run the program you will find that the server waits until a client - any web browser - connects. The web page will then be displayed in the browser. 

Of course this only works once. To make the whole thing continue to work we have to put the entire client handling code into a loop. 

for (;;) {
 int client_fd = accept(sockfd,
     (struct sockaddr *) &client_addr,
     &addr_size);
 int n = read(client_fd, buffer, 2048);
 printf("%s", buffer);
 fflush(stdout);

 n = write(client_fd, data,  strlen(data));
close(client_fd);
}

The only problem with this loop is that accept is a blocking call which means you can't include any additional processing in the loop. Sometimes this doesn't matter. For example if this was a processing loop for a sensor then the sensors could be read after a client connected and the web data served. 

If this isn't the case we need to make the call to accept non-blocking. The simplest way of doing this is to use the getaddrinfo to add a non-blocking parameter to the structs it creates: 

hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM ;
hints.ai_flags = AI_PASSIVE|| SOCK_NONBLOCK;
getaddrinfo(NULL, "80", &hints, &server);

Note that this only works under Linux - if you want Unix compatibility then use ioctl. 

fcntl(sockfd, F_SETFL, O_NONBLOCK);

Following this the call to accept will return immediately and the value of client_fd is negative if there is no client waiting:

for (;;) {
 int client_fd = accept(sockfd,
     (struct sockaddr *) &client_addr, &addr_size);
 if (client_fd > 0) {
  int n = read(client_fd, buffer, 2048);
  printf("%s", buffer);
  fflush(stdout);
  n = write(client_fd, data, strlen(data));
  close(client_fd);
 }
}

Notice that this polling loop is a bad idea if the machine is a general purpose system as it uses 100% of one core's time. However, if the system is a dedicated to doing a single job this is the most logical and effective solution. In this case the polling loop also implements other repetitive essential tasks. 

In a more general context the problem of how to handle incoming client connects has to solutions that divide opinion on which is best. Servers like Apache create a new thread for each client that has to be served. This is efficient but handling lots of threads and cleaning up after threads terminate can be a problem. Node.js on the other hand uses a single thread to deal with all client requests and manages things using events. Event handling is basically an elaboration on the polling loop shown above and it is claimed that event based servers can be faster than thread based. 

Use whichever method suits your application. 

The complete listing for the server with non-blocking calls is:

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

int main(int argc, char** argv) {
 struct addrinfo hints, *server;
 memset(&hints, 0, sizeof hints);
 hints.ai_family =  AF_INET;
 hints.ai_socktype = SOCK_STREAM;
 hints.ai_flags = AI_PASSIVE || SOCK_NONBLOCK;
 getaddrinfo(NULL, "80", &hints, &server);

 int sockfd = socket(server->ai_family,
        server->ai_socktype, server->ai_protocol);
 bind(sockfd, server->ai_addr, server->ai_addrlen);
 listen(sockfd, 10);
 
 struct sockaddr_storage client_addr;
 socklen_t addr_size = sizeof client_addr;
 char headers[] = "HTTP/1.0 200 OK\r\n
    Server: CPi\r\nContent-type: text/html\r\n\r\n";
 char buffer[2048];
 char html[] = "<html><head><title>Temperature</title>
 </head><body>{\"humidity\":81%,
  \"airtemperature\":23.5C}</p></body></html>\r\n";
 char data[2048] = {0};
 snprintf(data, sizeof data,
       "%s %s", headers, html);

 for (;;) {
  int client_fd = accept(sockfd,
   (struct sockaddr *) &client_addr, &addr_size);
  if (client_fd > 0) {
   int n = read(client_fd, buffer, 2048);
   printf("%s", buffer);
   fflush(stdout);
   n = write(client_fd, data, strlen(data));
   close(client_fd); 
  }
 }
 return (EXIT_SUCCESS);
}

 operators

 

 

Banner

 

graphics

 



 

Comments




or email your comment to: comments@i-programmer.info

 



Last Updated ( Monday, 15 August 2016 )