This recitation will be a walk through of the following code, which implements
last time's code skeleton for a simple web server and
client. This code will compile and run (I tested it), so feel free to download
it and play with it. One important point for compiling: the libraries for socket calls are
not in the standard library directories, so you need to pass -lsocket
as a parameter to gcc when compiling any code with socket calls in it. Similarly,
you must pass -lnsl for gethostbyname.
We'll start with the client, since it's the simpler of the two.
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
//Port would be 80 for a real web server, but only root can use ports < 1024
#define PORT 31337
#define BUFLENGTH 256
int main(int argc, char *argv[]) {
int clientsocket;
struct sockaddr_in serv_adr;
struct hostent *host;
char response[BUFLENGTH]; //create a read/write buffer
if (argc < 2) { //check that server is specified
printf("The server must be specified\n");
printf("Usage: %s <hostname> \n",argv[0]);
exit(1);
}
host=gethostbyname(argv[1]); //get host address from name
if (host == (struct hostent *) NULL) {
perror("gethostbyname: ");
exit(0);
}
memset(&serv_adr, 0, sizeof(serv_adr)); //init serv_adr memory to 0
serv_adr.sin_family=AF_INET; //set internet family
memcpy(&serv_adr.sin_addr, host->h_addr, host->h_length); //set host information from above
serv_adr.sin_port=htons(PORT); //set port
//create socket: internet family, stream based, let computer decide network method
if ((clientsocket=socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket creation error");
exit(0);
}
//connect to host socket: socket to connect, server info from above, address struct size
if (connect(clientsocket, (struct sockaddr *) &serv_adr, sizeof(serv_adr)) == -1) {
perror("socket connection error");
close(clientsocket);
exit(0);
}
//send HTTP request for index.html: socket to write to, string, max length
write(clientsocket, "GET /index.html HTTP/1.0", BUFLENGTH);
//read reply: socket to read from, char array to write to, max length
if (read(clientsocket, response, BUFLENGTH) == -1) { //wait for and read response
perror("socket read error");
exit(0);
}
printf("%s", response); //print response
close(clientsocket); //close socket
return 0;
}
Now we are ready to tackle the sever. Note that this is not a very useful server, since it always returns 404 instead of finding requested resources, and it only handles GET requests.
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define BUFLENGTH 256
#define PORT 31337
//function to serve requests, passed an open socket descriptor
void handleRequest(int clnt) {
char request[BUFLENGTH];
if (read(clnt, request, BUFLENGTH) == -1) { //read request
perror("socket read error");
exit(0);
}
printf("Request received: %s\n", request); //print request
if (strncmp("GET", request, 3) == 0) { //check if starts with "GET"
write(clnt, "HTTP/1.0 404 Not Found\r\n\r\n", BUFLENGTH); //write 404
}
close(clnt); //close socket
}
int main() {
int srvsock, clntsock, adr_length;
struct sockaddr_in clnt_adr, serv_adr;
if ((srvsock=socket(AF_INET, SOCK_STREAM, 0)) == -1) {//create socket
perror("socket creation error");
exit(0);
}
memset(&serv_adr, 0, sizeof(serv_adr)); //set address info
serv_adr.sin_family=AF_INET; //internet family again
serv_adr.sin_addr.s_addr=htonl(INADDR_ANY); //don't care about address
serv_adr.sin_port=htons(PORT); //set port
//bind socket to address and port
if (bind(srvsock, (struct sockaddr *) &serv_adr, sizeof(serv_adr)) == -1) {
perror("socket binding error");
close(srvsock);
exit(0);
}
//set up a connection queue: socket to listen on, max connections in queue
if (listen(srvsock, 5) == -1) {
perror("socket listening error");
close(srvsock);
exit(0);
}
while (1) { //loop forever as servers often do
adr_length=sizeof(clnt_adr);
//wait for a connection, then assign the connection to a new socket descriptor
//note that here clnt_adr is filled in for us by the accept call, and has client info
if ((clntsock=accept(srvsock, (struct sockaddr *) &clnt_adr, &adr_length)) == -1) {
perror ("socket accept error");
close(srvsock);
exit(0);
}
handleRequest(clntsock); //handle the request
//NOTE: in my pseudo-code I had a fork here, but I didn't bother to code it for this
//example. In reality, a child process (or even more realistically, a new thread)
//would handle the request so the server could accept new connections.
}
return 0;
}
Stuart Morgan, 2003