Relearning C Part 1 – Windows TCP Server

October 22, 2024

Recently, I was inspired to dive back into C, this time with a Windows focus. I’ve had previous experience with the basics in college, but I spent all that time working in Linux. This time around, I wanted to focus on developing C code for use on Windows. I wanted to start with something familiar, so I went with a simple TCP server. I’ve done several variations of this using other languages, and I’ve done something similar in C targeting Linux. So, I figured that this would be a relatively simple task. In a way it was. I was already familiar with the basics, so it didn’t take long to readjust from a PowerShell/Python mindset back to C, but I also found that documentation outside of MSDN was almost non-existent or, in some cases, was extremely out of date. Maybe I’m just bad at Googling, but where are all of the basics of C in Windows guides!? I found a bunch of TCP servers targeting Linux with socket.h, but I didn’t find easy-to-digest examples of the same thing with WinSock2.h and ws2_32.lib. Also, side note, but the whole #pragma comment(lib, “ws2_32.lib”) thing threw me off my game. I couldn’t figure out why my tests weren’t building at first. Anyway, I thought it would be useful to document my progress with relearning C. Starting with a basic TCP server that accepts messages and sends a canned response back to the client.

The main goal of this is to explain at a high level the why’s and how’s of the code. I’m not going to go into depth on what a pointer is, or the basic data types. I will, however, try to go into depth as to how sockets work on Windows, how to create a basic TCP server, the various functions needed to achieve this, and how we can improve our simple TCP server into something more useful. This will probably be spread across 3 posts (and I actually finished all of the code before starting this time!). I’ll likely state this in each post, but I am NOT a programmer. I don’t write the cleanest, most pristine, code. Don’t expect to learn any standards of excellence here! That being said, it should be usable and readable, and I try to adjust for security flaws and memory issues. Don’t judge me too harshly!

Starting off, we’ll create the skeleton for our server. This just adds the necessary headers, including time.h for later use.

// Basic #includes for our server
#include <stdio.h>
#include <WinSock2.h>
#include <time.h>

// Needed to link the library for compilation
#pragma comment(lib, "ws2_32.lib")

int main(){
  printf("Hello World!\n");
  return 0;
}
C

What we have so far adds the header files and links the ws2_32.lib library needed to work with sockets on Windows. The main function simply prints a standard hello world statement before returning. To initialize our socket, we’ll need to declare a WSADATA struct and a SOCKET. To be specific, the WSADATA struct defines the Windows Socket implementation, and SOCKET is a data type that is used to refer to an opaque kernel data struct that defines a socket.

// Basic #includes for our server
#include <stdio.h>
#include <WinSock2.h>
#include <time.h>

// Needed to link the library for compilation
#pragma comment(lib, "ws2_32.lib")

int main(){
	WSADATA wsa; // WSADATA struct 
	SOCKET server_socket, client_socket; // Sockets for the server and client connections


  printf("Hello World!\n");
  return 0;
}
C

After declaring these, we can start to initiate our socket. This is done through WSAStartup and the WinSock2 socket function.

// Basic #includes for our server
#include <stdio.h>
#include <WinSock2.h>
#include <time.h>

// Needed to link the library for compilation
#pragma comment(lib, "ws2_32.lib")

int main(){
	WSADATA wsa; // WSADATA struct 
	SOCKET server_socket, client_socket; // Sockets for the server and client connections
  
	struct sockaddr_in server, client; // sockaddr_in is a struct for holding address info
	
	// Initiates the use of Winsock.DLL by a process
	/*  
	    int WSAAPI WSAStartup(
        [in]  WORD      wVersionRequested, // 2.2 is the current version
        [out] LPWSADATA lpWSAData
      );
  */
	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0{
	  return 1; // Return 1 on error
	}
	
	// Create TCP socket
	/*
	    SOCKET WSAAPI socket(
        [in] int af, // Address type - AF_INET is IPv4
        [in] int type,
        [in] int protocol
      );
	*/
	server_socket = socket(AF_INET, SOCK_STREAM, 0);
	if (server_socket == INVALID_SOCKET) {
		return 1; // Return 1 on error
	}
	
	
  printf("Hello World!\n");
  return 0;
}
C

This will initialize our socket, but it does not create an active, listening, process. For that, we will need to bind our process to a local address and port. To do this, we set values to the server struct we initialized.

// Basic #includes for our server
#include <stdio.h>
#include <WinSock2.h>
#include <time.h>

// Needed to link the library for compilation
#pragma comment(lib, "ws2_32.lib")

int main(){
	WSADATA wsa; // WSADATA struct 
	SOCKET server_socket, client_socket; // Sockets for the server and client connections
  
	struct sockaddr_in server, client; // sockaddr_in is a struct for holding address info
	
	
	/*  
	    int WSAAPI WSAStartup(
        [in]  WORD      wVersionRequested, // 2.2 is the current version
        [out] LPWSADATA lpWSAData
      );
  */
	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0{ // Initiates the use of Winsock.DLL by a process
	  return 1; // Return 1 on error
	}
	

	/*
	    SOCKET WSAAPI socket(
        [in] int af, // Address type - AF_INET is IPv4
        [in] int type,
        [in] int protocol
      );
	*/
	server_socket = socket(AF_INET, SOCK_STREAM, 0); 	// Create TCP socket
	if (server_socket == INVALID_SOCKET) {
		return 1; // Return 1 on error
	}
	
	server.sin_family = AF_INET; // Set address family to IPv4
	server.sin_addr.s_addr = INADDR_ANY; // Bind on any/all active interfaces
	server.sin_port = htons(1234); // htons converts the port to a network byte order


	/*
	  int WSAAPI bind(
      [in] SOCKET         s,
      [in] const sockaddr *name, // pointer to the sockaddr_in struct we declared
      [in] int            namelen
    );
	*/
	if (bind(server_socket, (struct sockaddr*)&server, sizeof(server)) == SOCKET_ERROR) { 	// Bind socket 
		return 1; // Return 1 on error
	}
	
  printf("Hello World!\n");
  return 0;
}
C

Now that we have bound our socket, we can put the socket in a listening state, so that it can accept incoming communications. This is done with the listen() and accept() functions. Of course, we want our server to constantly listen and accept connections, so we’ll also need to set up an infinite loop for these. Otherwise, the server will accept a singular connection and then exit. NOTE: we are initializing client_size on line 15.

// Basic #includes for our server
#include <stdio.h>
#include <WinSock2.h>
#include <time.h>

// Needed to link the library for compilation
#pragma comment(lib, "ws2_32.lib")

int main(){
	WSADATA wsa; // WSADATA struct 
	SOCKET server_socket, client_socket; // Sockets for the server and client connections
  
	struct sockaddr_in server, client; // sockaddr_in is a struct for holding address info
	
	int client_size, recv_size; //
	
	/*  
	    int WSAAPI WSAStartup(
        [in]  WORD      wVersionRequested, // 2.2 is the current version
        [out] LPWSADATA lpWSAData
      );
  */
	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0{ // Initiates the use of Winsock.DLL by a process
	  return 1; // Return 1 on error
	}
	

	/*
	    SOCKET WSAAPI socket(
        [in] int af, // Address type - AF_INET is IPv4
        [in] int type,
        [in] int protocol
      );
	*/
	server_socket = socket(AF_INET, SOCK_STREAM, 0); 	// Create TCP socket
	if (server_socket == INVALID_SOCKET) {
		return 1; // Return 1 on error
	}
	
	server.sin_family = AF_INET; // Set address family to IPv4
	server.sin_addr.s_addr = INADDR_ANY; // Bind on any/all active interfaces
	server.sin_port = htons(1234); // htons converts the port to a network byte order


	/*
	  int WSAAPI bind(
      [in] SOCKET         s,
      [in] const sockaddr *name, // pointer to the sockaddr_in struct we declared
      [in] int            namelen
    );
	*/
	if (bind(server_socket, (struct sockaddr*)&server, sizeof(server)) == SOCKET_ERROR) { 	// Bind socket 
		return 1; // Return 1 on error
	}
	
	while(1) { // Run forever 
  	/*
  	  int WSAAPI listen(
        [in] SOCKET s,
        [in] int    backlog
      );
  	*/
  	listen(server_socket, 3); // Start listening on server_socket
  	
  	client_size = sizeof(struct sockaddr_in); // Set to the size of the sockaddr_in struct
  	
  
    /*
      SOCKET WSAAPI accept(
        [in]      SOCKET   s,
        [out]     sockaddr *addr,
        [in, out] int      *addrlen
      );
    */
    client_socket = accept(server_socket, (struct sockaddr*)&client, &client_size); // accept an incoming connection and store the created socket as client_connection
    if (client_socket == INVALID_SOCKET) {
      return 1; // Return 1 on error
    }
	}
	
	
	
  printf("Hello World!\n");
  return 0;
}
C

Now that we’ve accepted a connection, we probably want to do something, right? First off, we’ll log the connection to the server. This is a basic step I like to do, to ensure that we’re getting a successful connection. For this, we’ll go back and add some variables using Time() to get the timestamp of the connection. After we get the local connection time, we’ll print it to the server console. We’ll also set up a buffer for receiving data from the client. We’ll be printing the received data to the console as well, so we might as well set it all up in one go. I also couldn’t find a good segue to go back and add all the buffers, so we’re doing it here!

// Basic #includes for our server
#include <stdio.h>
#include <WinSock2.h>
#include <time.h>

// Needed to link the library for compilation
#pragma comment(lib, "ws2_32.lib")

int main(){
	WSADATA wsa; // WSADATA struct 
	SOCKET server_socket, client_socket; // Sockets for the server and client connections
  
	struct sockaddr_in server, client; // sockaddr_in is a struct for holding address info
	
	int client_size, recv_size; // Int declaration for sizes used later
	char client_message[500];	// Buffer for client data
	char client_ip[20]; // Buffer for storing the client's IP address
	
	time_t t = time(NULL); // Initialize t as NULL for use later
	struct tm tm; // Intialize a time struct for logging purposes
	
	
	/*  
	    int WSAAPI WSAStartup(
        [in]  WORD      wVersionRequested, // 2.2 is the current version
        [out] LPWSADATA lpWSAData
      );
  */
	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0{ // Initiates the use of Winsock.DLL by a process
	  return 1; // Return 1 on error
	}
	

	/*
	    SOCKET WSAAPI socket(
        [in] int af, // Address type - AF_INET is IPv4
        [in] int type,
        [in] int protocol
      );
	*/
	server_socket = socket(AF_INET, SOCK_STREAM, 0); 	// Create TCP socket
	if (server_socket == INVALID_SOCKET) {
		return 1; // Return 1 on error
	}
	
	server.sin_family = AF_INET; // Set address family to IPv4
	server.sin_addr.s_addr = INADDR_ANY; // Bind on any/all active interfaces
	server.sin_port = htons(1234); // htons converts the port to a network byte order


	/*
	  int WSAAPI bind(
      [in] SOCKET         s,
      [in] const sockaddr *name, // pointer to the sockaddr_in struct we declared
      [in] int            namelen
    );
	*/
	if (bind(server_socket, (struct sockaddr*)&server, sizeof(server)) == SOCKET_ERROR) { 	// Bind socket 
		return 1; // Return 1 on error
	}
	
	while(1) { // Run forever 
  	/*
  	  int WSAAPI listen(
        [in] SOCKET s,
        [in] int    backlog
      );
  	*/
  	listen(server_socket, 3); // Start listening on server_socket
  	
  	client_size = sizeof(struct sockaddr_in); // Set to the size of the sockaddr_in struct
  	
  
    /*
      SOCKET WSAAPI accept(
        [in]      SOCKET   s,
        [out]     sockaddr *addr,
        [in, out] int      *addrlen
      );
    */
    client_socket = accept(server_socket, (struct sockaddr*)&client, &client_size); // accept an incoming connection and store the created socket as client_connection
    if (client_socket == INVALID_SOCKET) {
      return 1; // Return 1 on error
    }
    
    localtime_s(&tm, &t); // Get the local time
    
    /*
      PCSTR WSAAPI inet_ntop(
        [in]  INT        Family,
        [in]  const VOID *pAddr,
        [out] PSTR       pStringBuf,
        [in]  size_t     StringBufSize
      );
    */
    inet_ntop(AF_INET, &(client.sin_addr), client_ip, sizeof(client_ip)); // Get the client IP 
    printf("Client %s connected at %d-%02d-%02d %02d:%02d:%02d\n", client_ip, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); // Print connection time to server console
    
    
	}
	
	
	
  printf("Hello World!\n");
  return 0;
}
C

Of course, now that we’ve accepted a connection, created a buffer for receiving data, and logged the connection to the server, we need to read the client data. This needs to be embedded into another while(1) loop. If we do not loop the client communications, the server will read the data and then exit. To store the client data in our char buffer, we’ll use the recv() function. We’ll also need to add some code to print the message to the server console. Because we’re looping the receive operation, we’ll also want to clear the buffer before receiving each new piece of data. We’ll use memset() for this.

// Basic #includes for our server
#include <stdio.h>
#include <WinSock2.h>
#include <time.h>

// Needed to link the library for compilation
#pragma comment(lib, "ws2_32.lib")

int main(){
	WSADATA wsa; // WSADATA struct 
	SOCKET server_socket, client_socket; // Sockets for the server and client connections
  
	struct sockaddr_in server, client; // sockaddr_in is a struct for holding address info
	
	int client_size, recv_size; // Int declaration for sizes used later
	char client_message[500];	// Buffer for client data
	char client_ip[20]; // Buffer for storing the client's IP address
	
	time_t t = time(NULL); // Initialize t as NULL for use later
	struct tm tm; // Intialize a time struct for logging purposes
	
	
	/*  
	    int WSAAPI WSAStartup(
        [in]  WORD      wVersionRequested, // 2.2 is the current version
        [out] LPWSADATA lpWSAData
      );
  */
	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0){ // Initiates the use of Winsock.DLL by a process
	  return 1; // Return 1 on error
	}
	

	/*
	    SOCKET WSAAPI socket(
        [in] int af, // Address type - AF_INET is IPv4
        [in] int type,
        [in] int protocol
      );
	*/
	server_socket = socket(AF_INET, SOCK_STREAM, 0); 	// Create TCP socket
	if (server_socket == INVALID_SOCKET) {
		return 1; // Return 1 on error
	}
	
	server.sin_family = AF_INET; // Set address family to IPv4
	server.sin_addr.s_addr = INADDR_ANY; // Bind on any/all active interfaces
	server.sin_port = htons(1234); // htons converts the port to a network byte order


	/*
	  int WSAAPI bind(
      [in] SOCKET         s,
      [in] const sockaddr *name, // pointer to the sockaddr_in struct we declared
      [in] int            namelen
    );
	*/
	if (bind(server_socket, (struct sockaddr*)&server, sizeof(server)) == SOCKET_ERROR) { 	// Bind socket 
		return 1; // Return 1 on error
	}
	
	while(1) { // Run forever 
  	/*
  	  int WSAAPI listen(
        [in] SOCKET s,
        [in] int    backlog
      );
  	*/
  	listen(server_socket, 3); // Start listening on server_socket
  	
  	client_size = sizeof(struct sockaddr_in); // Set to the size of the sockaddr_in struct
  	
    /*
      SOCKET WSAAPI accept(
        [in]      SOCKET   s,
        [out]     sockaddr *addr,
        [in, out] int      *addrlen
      );
    */
    client_socket = accept(server_socket, (struct sockaddr*)&client, &client_size); // accept an incoming connection and store the created socket as client_connection
    if (client_socket == INVALID_SOCKET) {
      return 1; // Return 1 on error
    }
    
    localtime_s(&tm, &t); // Get the local time
    
    /*
      PCSTR WSAAPI inet_ntop(
        [in]  INT        Family,
        [in]  const VOID *pAddr,
        [out] PSTR       pStringBuf,
        [in]  size_t     StringBufSize
      );
    */
    inet_ntop(AF_INET, &(client.sin_addr), client_ip, sizeof(client_ip)); // Get the client IP 
    printf("Client %s connected at %d-%02d-%02d %02d:%02d:%02d\n", client_ip, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); // Print connection time to server console
    
    while (1) { // Run forever
      
      /*
        void *memset(
          void *dest,
          int c,
          size_t count
        );
      */
			memset(client_message, 0, sizeof(client_message)); // Overwrite client_message buffer with 0

      /*
        int WSAAPI recv(
          [in]  SOCKET s,
          [out] char   *buf,
          [in]  int    len,
          [in]  int    flags
        );
      */
			recv_size = recv(client_socket, client_message, 500, 0); // Receive data from client
			if (recv_size == SOCKET_ERROR) {
				printf("Error\n\r");
			}
			else if (recv_size == 0) { // If the client sends a blank message, exit the loop
				break;
			}

			client_message[recv_size] = '\0'; // Append the buffer with \0 (null bytes)
			
			printf("CLIENT (%s)> %s",client_ip, client_message); // Print the message to the console

		}
	}
	
	
	
  printf("Hello World!\n");
  return 0;
}
C

Now our TCP server can create a socket, bind it, log connections, and receive data from the client. The next step is to send a message back to the client acknowledging receipt of the message. For this first post/example, we won’t be doing anything special for this. Simple is best when you’re starting, after all. We’ll be using the send() function to send a message back to the client. After sending the message, we’ll also add in our code for closing the socket and stopping the server. Sockets must be closed or you risk a potential memory leak or other undefined behavior. We’ll also get rid of the Hello World, because I forgot that was in there and I’ve been copy/pasting.

// Basic #includes for our server
#include <stdio.h>
#include <WinSock2.h>
#include <time.h>

// Needed to link the library for compilation
#pragma comment(lib, "ws2_32.lib")

int main(){
	WSADATA wsa; // WSADATA struct 
	SOCKET server_socket, client_socket; // Sockets for the server and client connections
  
	struct sockaddr_in server, client; // sockaddr_in is a struct for holding address info
	
	int client_size, recv_size; // Int declaration for sizes used later
	char client_message[500];	// Buffer for client data
	char client_ip[20]; // Buffer for storing the client's IP address
	
	time_t t = time(NULL); // Initialize t as NULL for use later
	struct tm tm; // Intialize a time struct for logging purposes
	
	
	/*  
	    int WSAAPI WSAStartup(
        [in]  WORD      wVersionRequested, // 2.2 is the current version
        [out] LPWSADATA lpWSAData
      );
  */
	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0){ // Initiates the use of Winsock.DLL by a process
	  return 1; // Return 1 on error
	}
	

	/*
	    SOCKET WSAAPI socket(
        [in] int af, // Address type - AF_INET is IPv4
        [in] int type,
        [in] int protocol
      );
	*/
	server_socket = socket(AF_INET, SOCK_STREAM, 0); 	// Create TCP socket
	if (server_socket == INVALID_SOCKET) {
		return 1; // Return 1 on error
	}
	
	server.sin_family = AF_INET; // Set address family to IPv4
	server.sin_addr.s_addr = INADDR_ANY; // Bind on any/all active interfaces
	server.sin_port = htons(1234); // htons converts the port to a network byte order


	/*
	  int WSAAPI bind(
      [in] SOCKET         s,
      [in] const sockaddr *name, // pointer to the sockaddr_in struct we declared
      [in] int            namelen
    );
	*/
	if (bind(server_socket, (struct sockaddr*)&server, sizeof(server)) == SOCKET_ERROR) { 	// Bind socket 
		return 1; // Return 1 on error
	}
	
	while(1) { // Run forever 
  	/*
  	  int WSAAPI listen(
        [in] SOCKET s,
        [in] int    backlog
      );
  	*/
  	listen(server_socket, 3); // Start listening on server_socket
  	
  	client_size = sizeof(struct sockaddr_in); // Set to the size of the sockaddr_in struct
  	
    /*
      SOCKET WSAAPI accept(
        [in]      SOCKET   s,
        [out]     sockaddr *addr,
        [in, out] int      *addrlen
      );
    */
    client_socket = accept(server_socket, (struct sockaddr*)&client, &client_size); // accept an incoming connection and store the created socket as client_connection
    if (client_socket == INVALID_SOCKET) {
      return 1; // Return 1 on error
    }
    
    localtime_s(&tm, &t); // Get the local time
    
    /*
      PCSTR WSAAPI inet_ntop(
        [in]  INT        Family,
        [in]  const VOID *pAddr,
        [out] PSTR       pStringBuf,
        [in]  size_t     StringBufSize
      );
    */
    inet_ntop(AF_INET, &(client.sin_addr), client_ip, sizeof(client_ip)); // Get the client IP 
    printf("Client %s connected at %d-%02d-%02d %02d:%02d:%02d\n", client_ip, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); // Print connection time to server console
    
    while (1) { // Run forever
      
      /*
        void *memset(
          void *dest,
          int c,
          size_t count
        );
      */
			memset(client_message, 0, sizeof(client_message)); // Overwrite client_message buffer with 0

      /*
        int WSAAPI recv(
          [in]  SOCKET s,
          [out] char   *buf,
          [in]  int    len,
          [in]  int    flags
        );
      */
			recv_size = recv(client_socket, client_message, 500, 0); // Receive data from client
			if (recv_size == SOCKET_ERROR) {
				printf("Error\n\r");
			}
			else if (recv_size == 0) { // If the client sends a blank message, exit the loop
				break;
			}

			client_message[recv_size] = '\0'; // Append the buffer with \0 (null bytes)
			
			printf("CLIENT (%s)> %s",client_ip, client_message); // Print the message to the console
			
			const char* message = "Message received!\n"; // Declare a char* holding our return message
			
			/*
			  int WSAAPI send(
          [in] SOCKET     s,
          [in] const char *buf,
          [in] int        len,
          [in] int        flags
        );
			*/
			send(client_socket, message, strlen(message), 0); // Send our message to the client
			
		}
		
		localtime_s(&tm, &t); // Get the disconnect time of the client
		printf("Client %s disconnected at %d-%02d-%02d %02d:%02d:%02d\n", client_ip, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); // Print a disconnect log to the server console
		
	}
	
	closesocket(client_socket); // Close the client socket
	closesocket(server_socket); // Close the server socket
	WSACleanup(); // Terminate the use of Winsock 2 DLL

  return 0; // Exit the program
}
C

With that, we have our completed simple TCP server in C, for Windows! This is a very basic program and can easily be expanded on further. In the following posts, I’ll add threading for handling simultaneous connections and a broadcast function to enable sending messages to all connected hosts chatroom style. There may be formatting issues if the above code is copy/pasted. To see the original code (minus the comments I made for this post), see my GitHub repo for my C projects!

Leave a Reply

Your email address will not be published. Required fields are marked *