Applying C - Socket Server
Written by Harry Fairhead   
Monday, 08 August 2022
Article Index
Applying C - Socket Server
HTML
WinSock Server

Some sample HTML:

char html[] = "<html><head><title>Hello HTTP World</title>
</head><body><p>Hello HTTP World</p></body></html>\r\n";

The HTML could be anything you need to construct a page.

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. To connect use:

http://IPAddress:1024/

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 OR SOCK_NONBLOCK in the call to socket: 

int sockfd = socket(server -> ai_family,
server -> ai_socktype| SOCK_NONBLOCK,
server -> ai_protocol);

Note that this only works under Linux. If you want Unix compatibility then use ioctl via the fcntl function:

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 dedicated to doing a single job, this is the most logical and effective solution. In this case the polling loop also implements other repetitive and essential tasks. 

In a more general context, the problem of how to handle incoming client connects has two solutions that divide opinion on which is better. 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 ones. Use whichever method suits your application. 

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

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.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;
    getaddrinfo(NULL, "1024", &hints, &server);
    int sockfd = socket(server -> ai_family,
server -> ai_socktype| SOCK_NONBLOCK,
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 buffer[2048]; char headers[] = "HTTP/1.0 200 OK\r\nServer:C\r\n Content-type: text/html\r\n\r\n"; char html[] = "<html><head><title>Hello HTTP World
</title>
</head><body><p>Hello HTTP World</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); }

Notice the use of snprintf – the safe version of sprintf which is like printf but sends the output to a string.



Last Updated ( Wednesday, 28 December 2022 )