posts/*

i know this post looks bad. i cannot be bothered to rewrite the css for the code blocks, sorry for the inconvenience. .

hey guys, this is my first post.
i've been messing around with rust a lot and i like it quite a bit, but there has been one problem: i lack the feeling of objective superiority that comes with writing my stuff in c. as such, i've decided to tool around with c for a while so i can better understand how a computer works, better understand how to manipulate memory, etc., but most importantly so that when i die i can take my place in gnu/heaven at the right hand of our lord and savior rms.

so i followed a tutorial to make the initial tcp server and found it a lot simpler than i had made it out to be in my head. there are two distinct parts, the client and server. i'm going to just put down a base tcp message transfer system with some comments to explain what they do. then we can build the file transfer parts on top.

client.c - download
include <arpa/inet.h>
			#include <string.h>
			#include <unistd.h>
			#include <strings.h>
			#include <stdlib.h>
			#include <stdio.h>

			int main(int argc, char **argv) {
			struct sockaddr_in servaddr;

			// get a socket file descriptor
			int sockfd = socket(AF_INET, SOCK_STREAM, 0);

			// zero out the servaddr socketaddr_in struct object
			bzero(&servaddr, sizeof(servaddr));

			// initialize servaddr information, including the protocols and port.
			servaddr.sin_family = AF_INET;
			servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
			servaddr.sin_port = htons(atoi(argv[1]));

			// converts port to a network format and stores it in servaddr.sin_addr
			inet_pton(AF_INET, argv[1], &servaddr.sin_addr);

			// link the socket to the port
			if (connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) {
			printf("failed to connect to port %s\n", argv[1]);
			return 0;
			}

			// get the message from the 3rd argument
			char* msg = argv[2];
			// calculate the length of the string + the null terminator
			long msglength = strlen(msg) + 1;
			// write the length of the message to the socket to let the
			//server know how much data to expect
			write(sockfd, (void*)&msglength, sizeof(msglength));
			// write the buffer to the socket
			write(sockfd, msg, msglength);

			return 0;
			}

server.c - download
		#include <stdio.h>
		#include <sys/types.h>
		#include <string.h>
		#include <stdlib.h>
		#include <sys/socket.h>
		#include <arpa/inet.h>
		#include <unistd.h>

		int main(int argc, char **argv) {
		int fd, sockfd, listenfd, connfd;
		pid_t childpid;
		socklen_t client;
		struct sockaddr_in servaddr, cliaddr;
		listenfd = socket(AF_INET, SOCK_STREAM, 0);

		bzero(&servaddr, sizeof(servaddr));
		servaddr.sin_family = AF_INET;
		servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
		servaddr.sin_port = htons(atoi(argv[1]));

		// bind to a port
		if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) {
		printf("failed to bind\n");
		return -1;
		}

		for (;;) {
		// listen on said port
		listen(listenfd, 5);


		client = sizeof(cliaddr);
		// accept incoming traffic on the listener declared above
		connfd = accept(listenfd, (struct sockaddr*)&cliaddr, &client);

		long msglength;
		// read in the msglength that is sent first from the client
		read(connfd, (void*)&msglength, sizeof(msglength));
		// initialize buffer as a char array of that length
		char buffer[msglength];
		// zero it out to clear data from any previous messages
		bzero(&buffer, strlen(buffer));
		// read the buffer string sent from the client into the buffer
		read(connfd, buffer, sizeof(buffer));
		// print the buffer!
		printf("%s\n", buffer);
		}

		return 0;
		}

ok, this gives us a string transfer between a client and a server. this is cool but not file transfer. good thing that i can just transfer a file by reading out the contents to a string and then sending those bytes! however, i want to maintain the file name, but i don't want to send a ton of preface to the transfer with all that data, i want to send it all at once. something that c lets you do is cast freely so we can just make a strut with the data we want and send the request in two parts: the struct containing the filename and file size, and then the file itself that we can write to the filesystem at the server.

firstly, we declare a struct outside the main function with an array of chars for the filename and a long which will hold the length of the file.
		struct inbound_file {
		long buffer_size;
		char filename[100];
		}
	

this struct is named outbound_file for client.c.
editing client.c now: we will set up the relevant code to create and send our struct. to get the length of the file, we can seek to the end and get the stream position using
ftell(fp)
and then save that +1 (for null terminator) to the long in the struct. we can also just
strcpy()
the filename into the buffer (yes, i know that it limits it to 100 characters but if you have a filename longer than 100 characters you have bigger problems than a segfault in this program).
		FILE *fp = fopen(filename, "r");
		fseek(fp, 0, SEEK_END);
		long buffer_len = ftell(fp) + 1;
		fseek(fp, 0, SEEK_SET);

		char *buffer = malloc(buffer_len);

		struct outbound_file outbound_fd;
		strcpy(outbound_fd.filename, filename);

		outbound_fd.buffer_size = buffer_len;

		fread(buffer, buffer_len, 1, fp);
		fclose(fp);
	

great, now we have a populated struct containing some file data and then a buffer containing the contents of the file. all that's left to do on the client-side is to send it to the server, which is two lines.
		// v casting the struct to a void*
		write(sockfd, (struct outbound_file*)&outbound_fd, sizeof(outbound_fd));
		write(sockfd, buffer, buffer_len);
	

and thats all for the client! onto the server.
editing server.c now: now, we just need to catch the new struct and then pipe the buffer into a brand new file of the right name. to cast the data we pick up from the client into the inbound_file struct, we can do this:
		struct inbound_file inbound_fd;
		read(connfd, (struct inbound_file*)&inbound_fd, sizeof(inbound_fd));
	
then we can prepare our file object and buffer that we will catch the next request in.
		FILE *fp;
		char buffer[inbound_fd.buffer_size];

		read(connfd, buffer, inbound_fd.buffer_size);
	
and then write the buffer to the file who's name is the same as the struct:
		fp = fopen(inbound_fd.filename, "w");
		fprintf(fp, "%s", buffer);
		fclose(fp);
		memset(buffer, 0, strlen(buffer));
	
and we are done! the final
memset
call is to clear the buffer so that if you run the server in an infinite loop (like i do in my code) it won't cause random data to go at the end of your file.

my complete file transfer source is avaliable here (server) and here (client)! have a great day.