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

This extract, from my book on programming C in an IoT context explains how to use sockets to create a simple web server. Servers are more difficult because they require asychronous programming.

Now available as a paperback or ebook from Amazon.

Applying C For The IoT With Linux

  1. C,IoT, POSIX & LINUX
  2. Kernel Mode, User Mode & Syscall
  3. Execution, Permissions & Systemd
    Extract Running Programs With Systemd
  4. Signals & Exceptions
    Extract  Signals
  5. Integer Arithmetic
    Extract: Basic Arithmetic As Bit Operations
  6. Fixed Point
    Extract: Simple Fixed Point Arithmetic
  7. Floating Point
  8. File Descriptors
    Extract: Simple File Descriptors 
    Extract: Pipes 
  9. The Pseudo-File System
    Extract: The Pseudo File System
    Extract: Memory Mapped Files ***NEW
  10. Graphics
    Extract: framebuffer
  11. Sockets
    Extract: Sockets The Client
    Extract: Socket Server
  12. Threading
    Extract:  Pthreads
    Extract:  Condition Variables
    Extract:  Deadline Scheduling
  13. Cores Atomics & Memory Management
    Extract: Applying C - Cores 
  14. Interupts & Polling
    Extract: Interrupts & Polling 
  15. Assembler
    Extract: Assembler

Also see the companion book: Fundamental C

<ASIN:1871962609>

<ASIN:1871962463>

<ASIN:1871962617>

<ASIN:1871962455>

 

Earlier in the chapter we looked at the basic use of sockect and how to use them to create an HTML web page client. In this extract the subject is extended to using sockets to implement a server - slighly more difficult because of the need to use asychronous programming.

In Chapter but not in this extract.

  • Socket Basics
  • Socket Functions
  • Create a socket
  • Connect a socket to an address
  • Bind a socket to an address
  • Reading and Writing
  • Listen and Accept
  • A Web Client
  • A WinSock Web Client
  • Connecting Using a URL

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 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 automatically 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, "1024", &hints, &server);

The AI_PASSIVE flag assigns the current system's IP address. You can easily get address structures for alternative addresses such as IPv6 using this, but for simplicity we just ask for an IPv4 address. Notice the specification of port 1024, which isn’t the usual HTTP port. The reason for using it is that ports below 1024 are restricted and programs need to be run as root to use them. If you want to use 80 for an HTTP server socket you have to compile the program and run it using sudo, or however you give a program root access in the operating system you are using. After the call to getaddrinfo, the structs we need to create sockets 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 1024 and listen starts things going with a queue of ten 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 client_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: C\r\n
       Content-type: text/html\r\n\r\n";

which corresponds to sending:

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

with a blank line 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. 



Last Updated ( Wednesday, 28 December 2022 )