# COMP4621 Project ## Overview This is a simple P2P file transfer project. There are two roles in the architecture: Client and Server. The server stores all the participating clients and their file bitmaps. It listens for incoming UDP packets from the clients, and manages their registration, file updates and file queries. The client acts as a P2P server and P2P client at the same time. On one hand, it needs to register its files to the server and be ready to transfer files to other clients. On the other, it can query the clients with the requested files and get them by TCP. To join the p2p network: 1. Register the IP address and port number in the central server 2. Update the available file bitmap To download a file: 1. Query the central server and get the IP addresses and port numbers of the clients that have the file 2. Initialize a TCP connection, request and receive the file from the selected client 3. Update the new file bitmap The program uses a protocol with messages `REGISTER`, `UPDATE`, `QUERY`, `RESPONSE`, `FINISH`, `ACK`, `GET`, and `EXIT` to communicate between the central server, P2P clients, and P2P servers. ## client.c The main function initializes the P2P server and client. It creates a P2P server thread using pthread and starts the server to listen for incoming connections. When a connection is established, it sends the requested file to the client. Since the file could be larger than the `MAXSIZE` of a transfer, the file is transferred in pieces. The P2P client can register its IP address and port with the central server, update its file information, and query the central server for available files with UDP. It can also request a file from another P2P client. It connects to the other client's server, sends the file name, and receives the file content with TCP. ### 1. Bonus I have implemented the bonus task. #### Demostration (Test 1):![image](https://hackmd.io/_uploads/HkYaqur-0.png) #### Implementation: I am using a key-value like data structure to store the ip and port returned from `receive_query()`. When the user want to get the file with the sequence number, the program would query the map and return the corresponding ip and port. ```c struct seq_ip_port_map { char seq; unsigned int ip; unsigned short port; }; ``` To accomodate the new data structure, I have added 3 helper functions. They are adding a key-value pair, emptying the map, and querying the value with the key respectively. ```c void add_to_map(char seq, unsigned int ip, unsigned short port, struct seq_ip_port_map** query_map, int* num_elements); void clear_map(struct seq_ip_port_map** query_map, int* num_elements); void query(char seq, unsigned int* ip, unsigned short* port, struct seq_ip_port_map* query_map, int num_elements); ``` In the for-loop in `receive_query()`, every record `(seq, ip, port)` received is added to the map. For user-friendliness, I have decided to store the sequence number as numeric `'0'` and `'1'` instead of `0` and `1` in character. ```c printf("%s : %d (%c)\n", ip_str, port, (seq+48)); add_to_map((seq+48), ip, port, query_map, num_elements); ``` I have also added a function to to initiate p2p client with the sequence number. It will query the map and find the corresponding ip and port. Then, it will call `p2p_client()` as usual with the received ip and port. ```c int p2p_client_from_seq (char seq, char* file_name, struct seq_ip_port_map* query_map, int num_elements) { unsigned int ip = 0; unsigned short port = 0; query(seq, &ip, &port, query_map, num_elements); return p2p_client(ip, port, file_name); } ``` For every `QUERY` command, we will need to empty to map. ```c clear_map(&query_map, &num_elements); send_query(sockfd, servaddr, file_name, strlen(file_name), &query_map, &num_elements); ``` Lastly, we have to parse the input from user when they use `GET` command. My approach is the check if the first input is a `'0'` or a `'1'`, because we only have two possible options in the sequence number. If not, an ip address is inputed and the second input is needed. ```c printf("\nInput ip port (e.g., 127.0.0.1 6001) or seq num (e.g. 0): "); scanf("%s", input_ip); if (strcmp(input_ip, "0") == 0) { p2p_client_from_seq('0', file_name, query_map, num_elements); } else if (strcmp(input_ip, "1") == 0) { p2p_client_from_seq('1', file_name, query_map, num_elements); } else { scanf("%hu", &input_port); p2p_client(inet_addr(input_ip), input_port, file_name); } ``` ### 2. rdt3_send() This loop receives and parses the response. It is first checked whether it is the ACK packet waiting for by comparing the sequence number and checking packet type. If an error or timeout occurs, `n<0`, the packet would be resent. ```c while (waiting) { recv_len = sizeof(recv_addr); int n = recvfrom(sockfd, (char *) recv_buf, MAXMSG, 0, ( struct sockaddr *) &recv_addr, &recv_len); if (n < 0) { sendto(sockfd, (const char *)buffer, len, 0, (const struct sockaddr *) &servaddr, sizeof(servaddr)); } else { recv_buf[n] = '\0'; if (recv_buf[0] == noack_num && strncmp(recv_buf + 2, ACK, strlen(ACK)) == 0) { waiting = 0; } } bzero(recv_buf, MAXMSG); } ``` ### 3. send_update() The implementation is basically the same as `send_register()`. The only difference is "REGISTER" and "UPDATE" has different length. ```c memcpy(buffer + total_len, UPDATE, strlen(UPDATE)); total_len += strlen(UPDATE); ``` ### 4. p2p_server() The code uses the poll() function to handle multiple connections in the P2P server simultaneously. The implementation is similar to the one in tutorial. First, we add the `POLLIN` socket to the array of pfds. ```c pfds[0].fd = server_fd; pfds->events = POLLIN; fd_count = 1; ``` ```c new_socket = accept(server_fd, (struct sockaddr*)&address, &addrlen); if (new_socket == -1) { perror("error accepting the connection"); } else { add_to_pfds(&pfds, new_socket, &fd_count, &fd_size); } ``` Whenever we receive a request, we would send the requested file to the client. However, the file size could be larger than `MAXMSG`. We have to send the file in pieces. The following code first send the file size to the client, then send the file piece by piece at a file size of `MAXMSG`. ```c long sent_file_size = htonl(file_size); bytes_read = send(new_socket, &sent_file_size, sizeof(file_size), 0); while ((bytes_read = fread(buffer, 1, MAXMSG, fp)) > 0) { send(new_socket, buffer, bytes_read, 0); } ``` ### 5. p2p_client() To accomodate the problem described in `p2p_server()`, we will need to receive the file in pieces as well. ```c long recv_file_size; long file_size; recv(sock, &recv_file_size, sizeof(recv_file_size), 0); file_size = ntohl(recv_file_size); while (file_size > 0) { bytes_read = recv(sock, buffer, sizeof(buffer), 0); fwrite(buffer, 1, bytes_read, fp); file_size -= bytes_read; } ``` ### 6. Creating thread in main() ```c pthread_create(&tid, NULL, p2p_server, &arg); ``` ## server.c The main function initializes the server socket, binds it to the specified IP address and port, and listens for incoming UDP packets. The server maintains a linked list, where each node represents a P2P client with its IP address, port, and file map. It also maintains a linked list of `rdt3_sender_ctx` structures, which store the context for each client during the file query process. The server uses a non-blocking socket with a timeout to handle incoming packets. It checks for timeouts and resends packets if necessary. When the server receives a `REGISTER` packet from a client, it adds the client's information to the node list if it doesn't already exist. It then sends an `ACK` packet back to the client. When the server receives an `UPDATE` packet from a client, it updates the file map for the corresponding node in the list and sends an `ACK` packet back to the client. When the server receives a `QUERY` packet from a client, it searches the node list for clients with the requested file. It sends `RESPONSE` packets containing the IP and port of clients with the file and an `ACK` packet to the querying client. The server also maintains a context for the querying client to handle timeouts and retransmissions. The server sends a `FINISH` packet to the client when there are no more clients with the requested file. ### 1. send_return() The following code starts from current node to search for the linked node list. Until you find a node that has the file. To prevent accessing a null pointer, `NULL` will be returned before entering the subsequent code. ```c while (current != NULL && ((current->file_map & bitmap) != bitmap)) { current = current->next; } if (current == NULL) { return NULL; } ``` ### 2. check_timeout() The following ```c if (current->waiting_ack && now - current->clock >= TIMEOUT) { struct sockaddr_in clientaddr; clientaddr.sin_family = AF_INET; clientaddr.sin_addr.s_addr = current->ip; clientaddr.sin_port = htons(current->port); current->clock = now; send_return(sockfd, clientaddr, current->file_idx, current->noack_node, current->noack_num); set_timeout(sockfd, TIMEOUT); } ``` ### 3. Update in main() This part is similar to the register implementation. The only difference is the size of `"UPDATE"` and we will update the node's bitmap. ```c parse_idx += strlen(UPDATE); ``` ```c update_node->file_map = new_map; ```