Applying C - Sockets The Client
Written by Harry Fairhead   
Tuesday, 26 April 2022
Article Index
Applying C - Sockets The Client
Connect a socket to an address
A Web Client
Connecting Using a URL

As long as there is no error then we can start to send and receive data. But what data? The answer is that it all depends on the protocol you are using. There is nothing about a socket that tells you what to send. It is a completely general I/O mechanism. You can send anything, but if you don't send what the server is expecting you won’t get very far. 

The web uses the HTTP protocol and this is essentially a set of text formatted headers that tell the server what to do and a set of headers that the server sends back to tell you what it has done. The most basic transaction the client can have with the server is to send a GET request for the server to send a particular file. Thus the simplest header is:

char header[] = "GET /index.html HTTP/1.1\r\n\r\n";

which is a request for the server to send index.html. However, in most cases we do need one more header, HOST, which gives the domain name of the server. Why do we need to do this? Simply because HTTP says you should, and many websites are hosted by a single server at the same IP address. Which website the server retrieves the file from is governed by the domain name you specify in the HOST header. 

This means that the simplest set of headers we can send the sever is:

char header[] = "GET /index.htm HTTP/1.1\r\n

which corresponds to the headers:

GET /index.html HTTP/1.1

An HTTP request always ends with a blank line. If you don't send the blank line then you will get no response from most servers. In addition the HOST header has to have the domain name with no additional syntax - no slashes and no http: or similar.

With the headers defined we can send our first HTTP request using write as if the socket was just another file to write data to:

int n = write(sockfd, header, strlen(header)); 

Of course to use the strlen function we need to add: 

#include <string.h>

The server receives the HTTP request and should respond by sending the data corresponding to the file specified, i.e. index.html. We can read the response just as if the socket was a file:

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

You can make this more complicated by checking the number of bytes read and reading more if the buffer is full. In fact you get more than the HTML as you get the entire HTTP response including the response headers:

HTTP/1.1 200 OK
Cache-Control: max-age=604800
Content-Type: text/html
Date: Sun, 14 Aug 2016 15:30:44 GMT
Etag: "359670651+gzip+ident"
Expires: Sun, 21 Aug 2016 15:30:44 GMT
Last-Modified: Fri, 09 Aug 2013 23:54:35 GMT
Server: ECS (ewr/15F9)
Vary: Accept-EncodinX-Cache: HIT
x-ec-custom-error: 1
Content-Length: 1270

<!doctype html> 

and so on...

Notice the blank line marking the end of the header and signaling that the data payload follows.

The complete program is:

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
int main(int argc, char** argv) {  int sockfd = socket(AF_INET, SOCK_STREAM, 0);
 struct sockaddr_in addr;addr.sin_family = AF_INET;  addr.sin_port = htons(80);  addr.sin_addr.s_addr = 0x22d8b85d;  if (connect(sockfd, (struct sockaddr *) &addr,sizeof (addr)) < 0)return -1;  char header[] = "GET /index.html HTTP/1.1\r\n\r\n\r\n";  int n = write(sockfd, header, strlen(header));  char buffer[2048];  n = read(sockfd, buffer, 2048);  printf("%s", buffer);  return (EXIT_SUCCESS); }

Of course, we can do much better than this simple example. For one thing each socket operation needs to be checked for errors. Here we only check for the mostly likely error that the sever refuses the connection.

A WinSock Web Client

In case you need to support Windows, it is worth knowing how relatively easy it is to convert a POSIX sockets program to use WinSock. As the previous example stands it does not run under Windows using either MinGW or Visual Studio and the Windows native libraries. To convert it to work is fairly easy. First you have to replace:

#include <sys/socket.h>


#include <winsock2.h>

You also have to make sure that the GCC compiler links the program to the appropriate library and MinGW provides a library for WinSock. Simply add wsock32.a which you should find in mingw/lib to the libraries.

The only other changes your program needs to make is to use recv and send in place of read and write and you need to start with a call to WSAStartup to initialize the sockets system with an integer value that specifies the WinSock version you want to use and a struct to accept data about the system:

WSADATA wsaData;
WSAStartup(0x202, &wsaData) ; 

0x202 specifies that you need at least WinSock 2.2.

Putting all this together the program becomes:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>
int main(int argc, char** argv) {
 WSADATA wsaData;
 WSAStartup(0x202, &wsaData) ; 
 int sockfd = socket(AF_INET, SOCK_STREAM, 0);
 struct sockaddr_in addr;
 addr.sin_family = AF_INET;
 addr.sin_port = htons(80); 
 addr.sin_addr.s_addr = 0x22d8b85d;
 if (connect(sockfd, (struct sockaddr *) &addr,
                        sizeof (addr)) < 0)return -1;
 char header[] = "GET /index.html
 int n = send(sockfd, header, strlen(header),0);
 char buffer[2048];
 n = recv(sockfd, buffer, 2048,0);
 printf("%s", buffer);
 return (EXIT_SUCCESS);

Now that you have a basic WinSock client working you can read the documentation and find out how to handle errors and close the socket after use, both of which are slightly different to POSIX sockets.

Last Updated ( Tuesday, 26 April 2022 )