🧪 Building an HTTP Server Step by Step with Winsock
🌐 中文版:繁體中文

The main goal here is to try building a network-layer socket with Winsock on Windows and then do application-layer protocol parsing on top of it. The language is C, the platform is VSCode, and this time I learned by way of the recently very popular ChatGPT, then did my own debugging and dug into the syntax from there.
Sample code
1 |
|
Main program flow
- Initialize the WinSock library
- Listen on a socket and wait for a client to connect
- Receive the client request and parse its method, URL, and protocol version
- Build the HTTP response and send it to the client
- Close the socket and clean up the Winsock resources
Walking through the code
- First you must include the
<winsock2.h>header - Then load the WinSock DLL
1
2
3
4
5WSADATA wsaData;
int result = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (result != 0) {
// error handling
} - Create a socket
1
2
3
4
5// Create a socket
SOCKET listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (listenSocket == INVALID_SOCKET) {
// error handling
} - Bind the socket to the local address and port
1
2
3
4
5
6
7
8
9// Bind the socket to the local address and port
struct sockaddr_in serverAddress;
serverAddress.sin_family = AF_INET;
serverAddress.sin_addr.s_addr = INADDR_ANY;
serverAddress.sin_port = htons(PORT);
result = bind(listenSocket, (SOCKADDR*)&serverAddress, sizeof(serverAddress));
if (result == SOCKET_ERROR) {
// error handling
} - Listen on the socket
1
2
3
4result = listen(listenSocket, BACKLOG);
if (result == SOCKET_ERROR) {
// error handling
} - Wait for a client to connect
1
2
3
4
5
6
7
8printf("Waiting for client to connect...\n");
struct sockaddr_in clientAddress;
int clientAddressLen = sizeof(clientAddress);
SOCKET clientSocket = accept(listenSocket, (SOCKADDR*)&clientAddress, &clientAddressLen);
if (clientSocket == INVALID_SOCKET) {
// error handling
}
printf("Client connected.\n"); - Read the client request
1
2
3
4
5
6
7char buffer[BUFFER_SIZE];
int recvLen = recv(clientSocket, buffer, BUFFER_SIZE, 0);
if (recvLen <= 0) {
// error handling
}
buffer[recvLen] = '\0';
printf("Received request:\n%s", buffer); - Parse the HTTP request
1
2
3
4
5char method[16];
char url[256];
char version[16];
sscanf_s(buffer, "%s %s %s", method, (unsigned int)sizeof(method), url, (unsigned int)sizeof(url), version, (unsigned int)sizeof(version));
printf("Parsed request: method=%s, url=%s, version=%s\n", method, url, version); - Build the HTTP response
1
2
3char response[BUFFER_SIZE];
sprintf_s(response, BUFFER_SIZE, "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nHello, World!");
int responseLen = (int)strlen(response); - Send the HTTP response
1
2
3
4
5result = send(clientSocket, response, responseLen, 0);
if (result == SOCKET_ERROR) {
// error handling
}
printf("Sent response:\n%s", response); - Close the sockets
1
2
3closesocket(clientSocket);
closesocket(listenSocket);
WSACleanup();
Program output
- Start the HTTP server

- Visit http://localhost/

- Terminal output

Usage and meaning of the functions in Winsock2.h
- WSAStartup
- Used to initialize the Winsock library. It takes a version argument of type WORD and a struct pointer of type WSADATA
- Returns an integer: 0 if the call succeeds, otherwise an error code
- socket
- Used to create a new socket. It takes three arguments: protocol family, socket type, and protocol type
SOCKET WSAAPI socket( [in] int af, [in] int type, [in] int protocol );1
2
3
4
5
6
7
8
9- It returns a **SOCKET**
- **bind**
- Binds a socket to the local address and port. It takes three arguments: socket descriptor, address, and address length
- ```c=
int WSAAPI bind(
[in] SOCKET s,
[in] const sockaddr *name,
[in] int namelen
);- Returns an integer
- listen
- Used to listen on a socket and wait for a client to connect. It takes two arguments: the socket descriptor and the max length of the pending-connection queue
int WSAAPI listen( [in] SOCKET s, [in] int backlog );1
2
3
4
5
6
7
8
9
10- Returns an integer
- **accept**
- Accepts a client connection and returns a new socket descriptor
- Takes two arguments: the socket descriptor and a pointer to the client address
- ```c=
SOCKET WSAAPI accept(
[in] SOCKET s,
[out] sockaddr *addr,
[in, out] int *addrlen
);- Returns a value of type SOCKET representing the new socket descriptor
- recv
- Receives data sent by the client
- Takes four arguments: the socket descriptor, a pointer to the receive buffer, the buffer size, and receive options
int WSAAPI recv( [in] SOCKET s, [out] char *buf, [in] int len, [in] int flags );1
2
3
4
5
6
7
8
9
10
11- Returns an integer
- **send**
- Sends data to the client
- Takes four arguments: the socket descriptor, a pointer to the send buffer, the buffer size, and send options
- ```c=
int WSAAPI send(
[in] SOCKET s,
[in] const char *buf,
[in] int len,
[in] int flags
);- Returns an integer
- closesocket
- Closes a socket
- Takes a socket descriptor as its argument
int WSAAPI closesocket( [in] SOCKET s );1
2
3
4
5- Returns an integer
- **WSACleanup**
- Cleans up the WinSock library resources. Takes no arguments and returns an integer
- ```c=
int WSAAPI WSACleanup();
For any of the functions above that return an integer, a return value of 0 means the call succeeded, otherwise it is an error code.
All articles on this blog are licensed under CC BY-NC-SA 4.0 unless otherwise stated.
Comments


