college-projects/server.cpp

237 lines
7.1 KiB
C++

#include <iostream>
#include <string.h>
#include <unistd.h>
#include "server_utils.hpp"
#include "tcp_handler.hpp"
#include "udp_handler.hpp"
#include "helpers.h"
#include "protocol.hpp"
using namespace std;
int main(int argc, char **argv) {
if (argc < 2) {
usage(argv[0]);
exit(EXIT_SUCCESS);
}
/*
* Initialize the sockets used for TCP and UDP communication.
*
*/
int port = atoi(argv[1]);
DIE(port == 0, "server: atoi");
int tcp_listener = establish_tcp_connection(port);
int udp_listener = establish_udp_connection(port);
/*
* Keep a list of file descriptors associated with each TCP client
* that is connected to server.
*
*/
list<ClientIO> clients;
/*
* Keep a table that will save the clients subscribed to different
* topics.
*
*/
unordered_map<string, list<Client>> topics_table;
/*
* Keep a table that will save for each user the packets to be
* transmitted after a client reconnects and has SF option on.
*
*/
unordered_map<string, list<Topic>> pending_table;
/*
* File descriptors used for I/O multiplexing.
*
*/
fd_set read_fds;
int max_fd;
while (true) {
/*
* Initialize the file descriptors set and call select for I/O multiplexing.
* Listen on udp, tcp accept and stdin. Also listen on the connections
* established with the clients.
*
*/
FD_ZERO(&read_fds);
FD_SET(tcp_listener, &read_fds);
FD_SET(udp_listener, &read_fds);
FD_SET(STDIN_FILENO, &read_fds);
max_fd = max(tcp_listener, udp_listener);
max_fd = max(STDIN_FILENO, max_fd);
fd_set_add_clients(read_fds, max_fd, clients);
/*
* Call select for I/O multiplexing.
*
*/
int read_activity;
read_activity = select(max_fd + 1, &read_fds, NULL, NULL, NULL);
DIE(read_activity < 0, "server: select");
/*
* If something happened on tcp_listener_socket it means that a new connection
* waits to be accepted.
*
*/
if (FD_ISSET(tcp_listener, &read_fds)) {
/*
* If accept_connection was not successful, then skip
* the operations below.
*
*/
int ac_ret = accept_connection(tcp_listener, clients);
if (ac_ret == ACCEPT_SUCCESSFUL) {
/*
* Maybe the client reconnected, so the server must validate its
* entry in topics table. The entry was unvalidated by #unvalidate_client.
* It(unvalidate_client) is called when resolve_subscribe_request
* returns a CONNECTION_CLOSED_ERROR value.
*
*/
string last_connected_id = clients.back().clientID;
int last_connected_fd = clients.back().fd;
bool sf = validate_client(last_connected_id, last_connected_fd, topics_table);
/*
* If the client has the SF option on then send the topics in
* @pending_table assigned to it.
*
*/
if (sf == true) {
int fpt_ret = forward_pending_topics(last_connected_fd,
last_connected_id, pending_table);
/*
* If forwarding failed the remove the client.
*
*/
if (fpt_ret == FORWARD_FAILED) {
cout << "Client removed after pending topics forwarding was not"
<< " successful." << endl;
auto last_connected = --clients.end();
clients.erase(last_connected);
unvalidate_client(last_connected_fd, topics_table);
}
}
}
}
/*
* Check the rest of the file descriptors in read_fds. Here the server
* resolves the subscribe and unsubscribe requests.
*
*/
auto client = clients.begin();
while (client != clients.end()) {
int fd = (*client).fd;
if (FD_ISSET(fd, &read_fds)) {
int rsr_ret = resolve_subscribe_request(fd, topics_table);
/*
* If the client has disconnected then delete its entry
* from @clients. Also make its entry from @topics_table
* unavailable to let the #forward_topic function know that
* it should not send a topic on the file descriptor associated
* with this client.
*
*/
if (rsr_ret == CONNECTION_CLOSED_ERROR) {
cout << "Client " << (*client).clientID <<
" made unavailable until reconnection" << endl;
unvalidate_client((*client).fd, topics_table);
close(fd);
client = clients.erase(client);
continue;
}
}
client++;
}
/*
* If something happened on udp_listener_socket it means that a new topic arrived
* and waits to be forwarded to its subscribers.
*
*/
if (FD_ISSET(udp_listener, &read_fds)) {
Topic topic = parse_datagram(udp_listener);
/*
* If #parse_datagram returns an empty topic it means an error occured and the
* operation will be skipped.
*
*/
Topic empty_topic{};
if (topic == empty_topic) {
continue;
}
forward_topic(topic, topics_table, pending_table);
/*
* Delete invalid clients. These are the clients that disconnected
* during #forward_topic. The condition in if assures that the topic
* for which we delete the invalid clients appears in @topics_table.
*
*/
if (topics_table.find(topic.topic) != topics_table.end()) {
delete_invalid_clients(topics_table.at(topic.topic), clients);
}
}
/*
* If something happened on stdin then the server must be closed.
*
*/
if (FD_ISSET(STDIN_FILENO, &read_fds)) {
char buffer[sizeof(EXIT_COMMAND) + 1];
memset(buffer, 0x00, sizeof(EXIT_COMMAND) + 1);
fgets(buffer, sizeof(EXIT_COMMAND), stdin);
/*
* End the session if the server receives EXIT_COMMAND.
*
*/
if (strncmp(buffer, EXIT_COMMAND, sizeof(EXIT_COMMAND)) == 0) {
cout << "Disconnecting.." << endl;
break;
}
}
}
/*
* Close the connection with the tcp clients.
*
*/
clients_close_connection(clients);
close(tcp_listener);
close(udp_listener);
return EXIT_SUCCESS;
}