The aim of this assignment was to implement an API which provides a number of calls allowing processes to memory map sections of a file located on a remote machine. Processes can then read and write to this memory area using the provided calls, with changes being propagated to the remote ﬁle by the library. Multiple processes from multiple client machines should be able to access and modify the same ﬁle on a remote machine. This was implemented in C on Ubuntu. Check out my Github for the full implementation.
The requirements consisted of a Client-Server model with:
- The server having the ability to open a number of files (or section of files) on the machine using a file descriptor, and storing them in memory.
- The client having the ability to demand that the server maps a particular file (or section of file) to memory.
- The client having the ability to demand that the server unmaps a particular file (or section of a file) from memory.
- The client having the ability to demand a part of the memory from the server.
- The client having the ability to alter a part of the memory from the server.
A communication protocol must be implemented in order for clients and server machines to connect over a network connection. The protocol must be capable of handling all client/server request and reply functionality including data transfer, async notiﬁcations and re-connection mechanisms in case of lost connections.
The header file fmmap.h consists of the required API library which will be used by both client and server machines. The remote sever, on which the file is hosted, is accessible over TCP/IP.
The server maintains a structure typical to servers, that is, it goes through the socket(), Listen(), Accept(), Recv()/Send(), Close() process in order to interact with clients. However in order to deal with multiple clients consistently a system of looping through Accept() and Recv() was maintained. Therefore the server would:
- Once accepted, start a concurrent measure (Thread) to deal with a single accepted client.
- Within the Thread, loop over a Recv() call until no messages are received, in which case the Thread shall die.
In addition, three linked lists maintain the data related to the server.
PACKETS & COMMUNICATION PROTOCOL (TYPES OF MESSAGES)
There are four types of messages. By including the pathname in the message, the API would be able to cater for multiple files on one server while still maintaining the same open socket.
The rmmap() api call requires not only to map a file, but also sections of files – therefore it was necessary to reflect the offset of the map, and the name of the file to be considered. Thus the message for rmmap() to the server would be of the form: 1 | offset | pathname
The read call must specify a ‘bracket’ of information to be read, therefore it is necessary to have a lower and upper bound value, as well as the pathname of a given file within which the section lies. Thus the message for read to the server would be of the form: 2 | start | end | pathname
The read call must specify a ‘bracket’ of information to be written, therefore it is necessary to have a lower and upper bound value, as well as the pathname of a given file within which the section lies. Thus the message for read to the server would be of the form: 3 | start | buffer | pathname
The read call must specify a ‘bracket’ of information to be unmapped from the memory, therefore it is necessary to have a lower and upper bound value, as well as the pathname of a given file within which the section lies. Thus the message for read to the server would be of the form: 4 | offset | length | pathname In order to implement these sections, a protocol for defining packets was created to satisfy the nature of socket streams.
The fmmap API will always be used by the client, since the client will always be requesting an action from the server. In order to start using the API, the client must first define the fileloc_t struct with the port, IP address and pathname. The IP address and port number will only be used in the rmmap() in order to create a socket and a the required connection. This connection will remain open until rmunmap() is called which closes it. It is extremely important that only one socket is used to establish a connection. Not only is opening and closing sockets extremely inefficient, but each new socket is viewed as a new client. In the rmmap() function will request the server to send back a page starting from the defined offset. Each page is 1024 bits. The read function will request the server to read from this returned page. this does not necessarily have to be the whole page. The client defines what needs to be read by defining an offset(according to page) and a count(read length). if the requested reading surpasses the maximum page size, then the function will calculate how many other pages are needed and call rmmap() for each new page. The client must always read before writing. The mwrite() function has the same parameters as mread() however, it sends over a chunk of data which will update the corresponding segment of file. Finally the rmunmap() tells the server too free the whole memory mapped segment and close the socket.
- Some values are hardcoded such as IP addresses and port number in the client.
- While the rmmap() and mread() functions work perfectly fine, A segmentation fault occurs when calling mwrite() and rmunmap(). The protocol messages are being sent from all four functions in the API correctly, however we ran into a few problems on the handling them.
I could go into more detail regarding how conflicts are solved and how data is handled within the server, however this article would end up being quite long (which it already is). Send me and email on email@example.com or comment below if you’d like to ask me a question!