diff --git a/README.md b/README.md index f1c324e..043b75d 100644 --- a/README.md +++ b/README.md @@ -1 +1,162 @@ -# Networks Lab 2019 (Higher School of Economics) +# Зависимости +- Boost 1.058.00 + +# Протокол общения на бирже фрилансеров + +## Формат сообщений + +Для передачи численных значений используется тип `uint32_t`, далее обозначаемый как `int`. Порядок байт big endian, что контролируется функцией `htons`. + + +--------------------+--------------------+-------------+ + | int message type | int body length | char[] body | + +--------------------+--------------------+-------------+ + +## Клиент + +- Запрос на вход заказчика + + type: 100 + body: "Name" + +- Запрос на вход фрилансера + + type: 200 + body: "Name" + +Следующие команды доступны только после авторизации пользователя с помощью одной из двух предыдущих команд. + +- Добавление заказа от заказчика + + type: 300 + body: "Description" + +- Вывод списка заказов определённого заказчика с их статусами + + type: 400 + +- Вывод всех открытых заказов + + type: 500 + +- Заявка от фрилансера на выполнение заказа + + type: 600 + body: "task_id" + +- Утверждение заявки заказчиком + + type: 700 + body: "task_id name" + +- Начало выполнения работы фрилансером + + type: 800 + body: "task_id" + +- Конец выполнения работы фрилансером + + type: 900 + body: "task_id" + +- Принятие работы заказчиком + + type: 1000 + body: "task_id" + +## Сервер + +- Ответ на сообщение от неавторизованного пользователя + + type: 1 + body: "Optional message" + +- Отчёт о получении некорректного сообщения + + type: 2 + body: "Optional message" + +- Отчёт о добавлении заказчика + + type: 101 + body: "" + +- Отчёт о том, что заказчик не был добавлен + + type: 102 + body: "Reason" + +- Отчёт о добавлении фрилансера + + type: 201 + body: "" + +- Отчёт о том, что фрилансер не был добавлен + + type: 202 + body: "Reason" + +- Добавлен заказ от заказчика + + type: 301 + body: "task_id" + +- Вывод списка заказов определённого заказчика с их статусами + + type: 401 + body: "*> + +- Список всех открытых заказов + + type: 501 + body: "*" + +- Принятние заявки фрилансера на выполнение + + type: 601 + body: "task_id" + +- Отклонение заявки фрилансера на выполнение + + type: 602 + body: "task_id Reason" + +- Заявка успешно подтверждена заказчиком + + type: 701 + body: "task_id" + +- Отклонение подтверждения заявки + + type: 702 + body: "task_id Reason" + +- Начало выполнения фрилансером работы принято + + type: 801 + body: "task_id" + +- Ответ на некорректное сообщение о начале работы + + type: 802 + body: "task_id Reason" + +- Конец выполнения фрилансером работы зафиксирован + + type: 901 + body: "task_id" + +- Ответ на некорректное сообщение о завершении работы + + type: 902 + body: "task_id Reason" + +- Отчёт об успешном принятии работы заказчиком + + type: 1001 + body: "task_id" + +- Ответ на некорректное сообщение о принятии готового заказа + + type: 1002 + body: "task_id Reason" + diff --git a/tcp/CMakeLists.txt b/tcp/CMakeLists.txt new file mode 100644 index 0000000..fff93cc --- /dev/null +++ b/tcp/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.13) +project(market) + +set(CMAKE_CXX_STANDARD 17) +find_package(Threads REQUIRED) + +add_subdirectory (client) +add_subdirectory (message) +add_subdirectory (server) \ No newline at end of file diff --git a/tcp/client/.gitkeep b/tcp/client/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/tcp/client/CMakeLists.txt b/tcp/client/CMakeLists.txt new file mode 100644 index 0000000..7d60e59 --- /dev/null +++ b/tcp/client/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.13) +project(client) + +set(CMAKE_CXX_STANDARD 17) +find_package(Boost REQUIRED COMPONENTS thread) + +add_executable(client src/main.cpp src/client.cpp include/client.h) +target_link_libraries(client LINK_PUBLIC message) \ No newline at end of file diff --git a/tcp/client/include/client.h b/tcp/client/include/client.h new file mode 100644 index 0000000..d114f69 --- /dev/null +++ b/tcp/client/include/client.h @@ -0,0 +1,63 @@ +#ifndef MARKET_CLIENT_H +#define MARKET_CLIENT_H + + +#include +#include +#include +#include +#include <../include/message.h> + +class MarketClient { +public: + + bool StartClient(const char *host, uint16_t port_number); + +private: + + int sockfd_; + + Message SendMessage(Message::Type type); + + Message SendMessage(Message::Type type, const std::string& text); + +public: + bool AuthoriseCustomer(const std::string& name); + + bool AuthoriseFreelancer(const std::string& name); + + void ListMyOrders(); + + void ListOpenOrders(); + + void NewOrder(const std::string& description); + + void RequestOrder(int order_id); + + void StartOrder(int order_id); + + void FinishOrder(int order_id); + + void GiveOrder(int order_id, const std::string& name); + + void ApproveDoneOrder(int order_id); + + void HandleIncorrectMessage(const Message &response); + + void HandleUnauthorised(); + + void HandleUnexpectedServerResponse(const Message &response); + + void HandleResponse(const Message &response); + + void Quit(); + + void PrintHeader(const std::string &header); + + void PrintPrompt(); + + bool GetLine(std::string &message); +}; + + +#endif //MARKET_CLIENT_H diff --git a/tcp/client/src/client.cpp b/tcp/client/src/client.cpp new file mode 100644 index 0000000..1343087 --- /dev/null +++ b/tcp/client/src/client.cpp @@ -0,0 +1,237 @@ +#include +#include +#include "../include/client.h" + +bool MarketClient::StartClient(const char* host, uint16_t port_number) { + sockfd_ = socket(AF_INET, SOCK_STREAM, 0); + + if (sockfd_ < 0) { + std::cerr << "failed to open socket\n"; + exit(1); + } + + hostent* server = gethostbyname(host); + + if (server == nullptr) { + std::cerr << "host not found\n"; + exit(0); + } + + sockaddr_in serv_addr{0}; + serv_addr.sin_family = AF_INET; + bcopy(server->h_addr, static_cast(&serv_addr.sin_addr.s_addr), + static_cast(server->h_length)); + serv_addr.sin_port = htons(port_number); + + if (connect(sockfd_, reinterpret_cast(&serv_addr), sizeof(serv_addr)) < 0) { + std::cerr << "connection failed\n"; + exit(1); + } + + PrintHeader("AUTHORISATION"); + + return true; +} + +bool MarketClient::AuthoriseCustomer(const std::string& name) { + Message response = SendMessage(Message::NEW_CUSTOMER, name); + switch (response.type) { + case Message::CUSTOMER_ADDED: + std::cout << "Authorisation successful, you can create orders now, " + name + "\n"; + return true; + case Message::CANT_ADD_CUSTOMER: + std::cout << response.body.empty() ? "Sorry, something went wrong" : response.body + + "\n"; + return false; + default: + HandleResponse(response); + return false; + } + +} + +bool MarketClient::AuthoriseFreelancer(const std::string& name) { + Message response = SendMessage(Message::NEW_FREELANCER, name); + switch (response.type) { + case Message::FREELANCER_ADDED: + std::cout << "You can look for orders now\n"; + return true; + case Message::CANT_ADD_FREELANCER: + std::cout << response.body.empty() ? "Sorry, something went wrong\n" : response.body + + "\n"; + return false; + default: + HandleResponse(response); + return false; + } +} + +void MarketClient::ListMyOrders() { + Message response = SendMessage(Message::GET_MY_ORDERS); + switch (response.type) { + case Message::LIST_OF_MY_ORDERS: + std::cout << response.body << "\n"; + return; + default: + HandleResponse(response); + return; + } +} + +void MarketClient::ListOpenOrders() { + Message response = SendMessage(Message::GET_OPEN_ORDERS); + switch (response.type) { + case Message::LIST_OF_OPEN_ORDERS: + std::cout << response.body << "\n"; + return; + default: + HandleResponse(response); + return; + } +} + +void MarketClient::HandleUnexpectedServerResponse(const Message& response) { + std::cout << "server responded with {type: " + std::to_string(response.type) + ", message: " + + "}\n"; +} + +void MarketClient::HandleUnauthorised() { std::cout << "Please enter who u are\n"; } + +void MarketClient::HandleIncorrectMessage(const Message& response) { + std::cout << response.body.empty() ? "You are not permitted to do it" : response.body + "\n"; +} + +void MarketClient::HandleResponse(const Message& response) { + switch (response.type) { + case Message::INCORRECT_MESSAGE: + HandleIncorrectMessage(response); + return; + case Message::UNAUTHORIZED: + HandleUnauthorised(); + return; + case Message::UNDEFINED: + Quit(); + exit(0); + default: + HandleUnexpectedServerResponse(response); + return; + } +} + +void MarketClient::RequestOrder(int order_id) { + Message response = SendMessage(Message::TAKE_ORDER, std::to_string(order_id)); + switch (response.type) { + case Message::TAKE_ORDER_SUCCESSFUL: + std::cout << "Order" + response.body + " requested.\n"; + return; + case Message::TAKE_ORDER_NOT_SUCCESSFUL: + std::cout << response.body << "\n"; + return; + default: + HandleResponse(response); + return; + } +} + +void MarketClient::StartOrder(int order_id) { + Message response = SendMessage(Message::WORK_STARTED, std::to_string(order_id)); + switch (response.type) { + case Message::WORK_STARTED_SUCCESSFUL: + std::cout << "Order" + response.body + " started successfully.\n"; + return; + case Message::WORK_STARTED_NOT_SUCCESSFUL: + std::cout << response.body << "\n"; + return; + default: + HandleResponse(response); + return; + } +} + +void MarketClient::FinishOrder(int order_id) { + Message response = SendMessage(Message::WORK_FINISHED, std::to_string(order_id)); + switch (response.type) { + case Message::WORK_FINISHED_SUCCESSFUL: + std::cout << "Order" + response.body + " finished successfully.\n"; + return; + case Message::WORK_FINISHED_NOT_SUCCESSFUL: + std::cout << response.body << "\n"; + return; + default: + HandleResponse(response); + return; + } +} + +void MarketClient::NewOrder(const std::string& description) { + Message response = SendMessage(Message::NEW_ORDER, description); + switch (response.type) { + case Message::ORDER_ACCEPTED: + std::cout << "You can now track this order by id: " + response.body + "\n"; + return; + default: + HandleResponse(response); + return; + } +} + +void MarketClient::GiveOrder(int order_id, const std::string& name) { + Message response = SendMessage(Message::GIVE_ORDER_TO_FREELANCER, + std::to_string(order_id) + name); + switch (response.type) { + case Message::GIVE_ORDER_SUCCESSFUL: + std::cout << "Order" + response.body + " given successfully.\n"; + return; + case Message::GIVE_ORDER_NOT_SUCCESSFUL: + std::cout << response.body << "\n"; + return; + default: + HandleResponse(response); + return; + } +} + +void MarketClient::ApproveDoneOrder(int order_id) { + Message response = SendMessage(Message::WORK_ACCEPTED, std::to_string(order_id)); + switch (response.type) { + case Message::WORK_ACCEPTED_SUCCESSFUL: + std::cout << "Work for order" + response.body + " accepted successfully.\n"; + return; + case Message::WORK_ACCEPTED_NOT_SUCCESSFUL: + std::cout << response.body << "\n"; + return; + default: + HandleResponse(response); + return; + } +} + +void MarketClient::Quit() { + std::cout << "Closing connection...\n"; + shutdown(sockfd_, SHUT_RDWR); + std::cout << "Bye! Hope to see u again!"; +} + +void MarketClient::PrintHeader(const std::string& header) { + system("clear"); + std::cout << header + "\n> " << std::flush; +} + +void MarketClient::PrintPrompt() { + std::cout << "> " << std::flush; +} + +bool MarketClient::GetLine(std::string& message) { + auto& result = getline(std::cin, message); + return bool(result); +} + +Message MarketClient::SendMessage(Message::Type type) { + return SendMessage(type, ""); +} + +Message MarketClient::SendMessage(Message::Type type, const std::string& text) { + Message request{type, text}; + request.Write(sockfd_); + return Message::Read(sockfd_); +} diff --git a/tcp/client/src/main.cpp b/tcp/client/src/main.cpp new file mode 100644 index 0000000..9798c5a --- /dev/null +++ b/tcp/client/src/main.cpp @@ -0,0 +1,129 @@ +#include +#include +#include +#include +#include +#include +#include "../include/client.h" + +void Usage(char* const* argv) { std::cerr << "Usage: " << argv[0] << " \n"; } + +void AuthHelp() { + std::cout << "Please authorize as a customer or as a freelancer:\n" + "\ta freelancer \n" + "\ta customer \n"; +} + +void FreelancerHelp() { + std::cout << "List of commands:\n" + "\t orders\n" + "\t request \n" + "\t start \n" + "\t finish \n" + "\t bye\n"; +} + +void CustomerHelp() { + std::cout << "List of commands\n" + "\t new order \n" + "\t all orders\n" + "\t my orders\n" + "\t give \n" + "\t done \n" + "\t bye\n"; +} + +void WorkWithFreelancer(MarketClient& client) { + std::string line; + int order_id; + + FreelancerHelp(); + client.PrintPrompt(); + + while (true) { + client.GetLine(line); + if (line == "orders") { + client.ListOpenOrders(); + } else if (sscanf(line.c_str(), "request %d", &order_id)) { + client.RequestOrder(order_id); + } else if (sscanf(line.c_str(), "start %d", &order_id)) { + client.StartOrder(order_id); + } else if (sscanf(line.c_str(), "finish %d", &order_id)) { + client.FinishOrder(order_id); + } else if (line == "bye") { + client.Quit(); + return; + } else { + FreelancerHelp(); + } + client.PrintPrompt(); + } +} + +void WorkWithCustomer(MarketClient& client) { + char arg[256]; + std::string line; + int order_id; + + CustomerHelp(); + client.PrintPrompt(); + while (true) { + client.GetLine(line); + if (line == "all orders") { + client.ListOpenOrders(); + } else if (line == "my orders") { + client.ListMyOrders(); + } else if (sscanf(line.c_str(), "give %d %s", &order_id, arg)) { + client.GiveOrder(order_id, arg); + } else if (sscanf(line.c_str(), "done %d", &order_id)) { + client.ApproveDoneOrder(order_id); + } else if (sscanf(line.c_str(), "new order %s", arg)) { + client.NewOrder(arg); + } else if (line == "bye") { + client.Quit(); + return; + } else { + CustomerHelp(); + } + client.PrintPrompt(); + } + +} + +int main(int argc, char* argv[]) { + if (argc < 3) { + Usage(argv); + exit(0); + } + + auto port_number = static_cast(std::stoi(argv[2])); + + MarketClient client{}; + + if (!client.StartClient(argv[1], port_number)) + exit(1); + + char arg[256]; + std::string line; + + AuthHelp(); + + while (true) { + client.GetLine(line); + if (sscanf(line.c_str(), "a customer %s", arg)) { + if (client.AuthoriseCustomer(arg)) { + WorkWithCustomer(client); + break; + } + } else if (sscanf(line.c_str(), "a freelancer %s", arg)) { + if (client.AuthoriseFreelancer(arg)) { + WorkWithFreelancer(client); + break; + } + } else { + AuthHelp(); + } + } + + return 0; +} diff --git a/tcp/message/CMakeLists.txt b/tcp/message/CMakeLists.txt new file mode 100644 index 0000000..ea26e7e --- /dev/null +++ b/tcp/message/CMakeLists.txt @@ -0,0 +1,2 @@ +add_library(message src/message.cpp include/message.h) +target_include_directories (message PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src) diff --git a/tcp/message/include/message.h b/tcp/message/include/message.h new file mode 100644 index 0000000..846d67a --- /dev/null +++ b/tcp/message/include/message.h @@ -0,0 +1,75 @@ +#include + +#ifndef CLIENT_LINUX_MESSAGE_H +#define CLIENT_LINUX_MESSAGE_H + +#include +#include +#include +#include +#include + + +class Message { + public: + enum Type { + UNDEFINED = 0, + UNAUTHORIZED = 1, + INCORRECT_MESSAGE = 2, + NEW_CUSTOMER = 100, + CUSTOMER_ADDED = 101, + CANT_ADD_CUSTOMER = 102, + NEW_FREELANCER = 200, + FREELANCER_ADDED = 201, + CANT_ADD_FREELANCER = 202, + NEW_ORDER = 300, + ORDER_ACCEPTED = 301, + GET_MY_ORDERS = 400, + LIST_OF_MY_ORDERS = 401, + GET_OPEN_ORDERS = 500, + LIST_OF_OPEN_ORDERS = 501, + TAKE_ORDER = 600, + TAKE_ORDER_SUCCESSFUL = 601, + TAKE_ORDER_NOT_SUCCESSFUL = 602, + GIVE_ORDER_TO_FREELANCER = 700, + GIVE_ORDER_SUCCESSFUL = 701, + GIVE_ORDER_NOT_SUCCESSFUL = 702, + WORK_STARTED = 800, + WORK_STARTED_SUCCESSFUL = 801, + WORK_STARTED_NOT_SUCCESSFUL = 802, + WORK_FINISHED = 900, + WORK_FINISHED_SUCCESSFUL = 801, + WORK_FINISHED_NOT_SUCCESSFUL = 802, + WORK_ACCEPTED = 1000, + WORK_ACCEPTED_SUCCESSFUL = 1001, + WORK_ACCEPTED_NOT_SUCCESSFUL = 1002, + }; + + Type type; + std::string body; + + Message(); + + explicit Message(Type type); + + Message(Type type, std::string message); + + bool Write(int sockfd); + + static Message Read(int sockfd); + + private: + + static void PutInt32(uint32_t i, char* buf); + + void PutBody(char* buf); + + static bool GetInt32(uint32_t* i, int sockfd); + + static bool GetBody(std::string* body, size_t length, int sockfd); + + static bool Get(char* dst, size_t message_len, int sockfd); +}; + + +#endif //CLIENT_LINUX_MESSAGE_H diff --git a/tcp/message/src/message.cpp b/tcp/message/src/message.cpp new file mode 100644 index 0000000..b1027f4 --- /dev/null +++ b/tcp/message/src/message.cpp @@ -0,0 +1,84 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "../include/message.h" + +Message::Message() : type(UNDEFINED), body() {} + +Message::Message(Message::Type type) : type(type), body() {} + +Message::Message(Message::Type type, std::string message) : type(type), body(std::move(message)) {} + +bool Message::Write(int sockfd) { + size_t message_len = 2 * sizeof(uint32_t) + body.size(); + auto data = new char[message_len]; + PutInt32(type, data); + PutInt32(static_cast(body.size()), data + sizeof(uint32_t)); + PutBody(data + 2 * sizeof(uint32_t)); + + ssize_t written = ::write(sockfd, data, message_len); + if (written < 0) { + std::cerr << strerror(errno) << std::endl; + delete[] data; + return false; + } + + delete[] data; + return true; +} + +Message Message::Read(int sockfd) { + uint32_t type, length; + std::string data; + if (GetInt32(&type, sockfd) && + GetInt32(&length, sockfd) && + (!length || GetBody(&data, length, sockfd))) { + return Message(Type(type), data); + } + return Message(); +} + +void Message::PutInt32(uint32_t i, char *buf) { + i = htonl(i); + memcpy(buf, &i, sizeof(uint32_t)); +} + +void Message::PutBody(char *buf) { + memcpy(buf, body.c_str(), body.size()); +} + +bool Message::GetInt32(uint32_t *i, int sockfd) { + if (Get(reinterpret_cast(i), sizeof(uint32_t), sockfd)) { + *i = ntohl(*i); + return true; + } + return false; +} + +bool Message::GetBody(std::string *body, size_t length, int sockfd) { + auto buf = new char[length]; + if (Get(buf, length, sockfd)) { + *body = std::string(buf); + delete[] buf; + return true; + } + delete[] buf; + return length != 0; +} + +bool Message::Get(char *dst, size_t message_len, int sockfd) { + for (ssize_t read = ::read(sockfd, dst, message_len); message_len; read = ::read(sockfd, dst, message_len)) { + if (read <= 0) { + return false; + } else { + dst += read; + message_len -= read; + } + } + return true; +} diff --git a/tcp/server/.gitkeep b/tcp/server/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/tcp/server/CMakeLists.txt b/tcp/server/CMakeLists.txt new file mode 100644 index 0000000..a873e4a --- /dev/null +++ b/tcp/server/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.13) +project(server) + +set(CMAKE_CXX_STANDARD 17) + +find_package(Boost REQUIRED COMPONENTS thread) + +add_executable(server + src/main.cpp + src/market_server.cpp include/market_server.h + src/tcp_server.cpp include/tcp_server.h) +target_link_libraries(server LINK_PUBLIC message ${Boost_LIBRARIES}) \ No newline at end of file diff --git a/tcp/server/include/market_server.h b/tcp/server/include/market_server.h new file mode 100644 index 0000000..b625cc7 --- /dev/null +++ b/tcp/server/include/market_server.h @@ -0,0 +1,113 @@ +#include + +#ifndef SERVER_SERVER_H +#define SERVER_SERVER_H + +#include +#include +#include +#include +#include +#include +#include "../include/message.h" +#include "tcp_server.h" + +class MarketServer : public TcpServer { +public: + ~MarketServer(); + + bool BanUser(const std::string &name); + +private: + class Freelancer { + public: + explicit Freelancer(const std::string &name, int socket_fd); + + ~Freelancer(); + + const int socket_fd; + const std::string name; + std::thread writer; + boost::sync_queue messages_; + }; + + class Customer { + public: + explicit Customer(const std::string &name, int socket_fd); + + ~Customer(); + + const int socket_fd; + const std::string name; + std::thread writer; + boost::sync_queue messages_; + }; + + enum ClientStatus { + NEW, + FREELANCER, + CUSTOMER, + FINISH, + UNCHANGED + }; + + class Order { + public: + enum State { + OPEN, + ASSIGNED, + IN_PROGRESS, + PENDING, + DONE + }; + + explicit Order(std::string customer, std::string description) + : customer(std::move(customer)), + description(std::move(description)), + task_id(task_counter++), + state(OPEN) {} + + const int task_id; + const std::string description; + const std::string customer; + State state; + std::set workers; + + private: + static int task_counter; + }; + + std::map freelancers; + std::map customers; + std::map orders; + + std::mutex customers_mutex_; + std::mutex freelancers_mutex_; + + std::shared_timed_mutex orders_mutex_; + + void UserInteractionLoop(int sock_fd) override; + + MarketServer::ClientStatus + WorkWithUnauthorized(int sock_fd, const Message &message, Freelancer **freelancer, Customer **customer); + + MarketServer::ClientStatus WorkWithFreelancer(Freelancer *freelancer, const Message &message); + + MarketServer::ClientStatus WorkWithCustomer(Customer *customer, const Message &message); + + Message LookupOrdersOf(Customer *customer); + + Message LookupOpenOrders(); + + void DeleteCustomer(Customer *customer); + + void DeleteFreelancer(Freelancer *freelancer); + + std::string StateToString(Order::State state); + + std::string WorkersToString(std::set set); + + void OrderToString(std::string &result, Order *o); +}; + +#endif //SERVER_SERVER_H diff --git a/tcp/server/include/tcp_server.h b/tcp/server/include/tcp_server.h new file mode 100644 index 0000000..726d732 --- /dev/null +++ b/tcp/server/include/tcp_server.h @@ -0,0 +1,21 @@ +#ifndef SERVER_TCP_SERVER_H +#define SERVER_TCP_SERVER_H + + +#include +#include +#include + +class TcpServer { + public: + void StartServer(uint16_t port_number); + + protected: + std::vector threads_; + + private: + virtual void UserInteractionLoop(int sock_fd) = 0; +}; + + +#endif //SERVER_TCP_SERVER_H diff --git a/tcp/server/src/main.cpp b/tcp/server/src/main.cpp new file mode 100644 index 0000000..e728a42 --- /dev/null +++ b/tcp/server/src/main.cpp @@ -0,0 +1,39 @@ +#include +#include +#include "../include/market_server.h" + +const std::string USAGE = "Usage: ./server "; + +const std::string CMDS = "exit -- close server\n" + "ban -- force user disconnect"; + +int main(int argc, char* argv[]) { + if (argc != 2) { + std::cout << USAGE; + return 0; + } + std::cout << CMDS << std::endl; + + uint16_t port_number = static_cast(std::stoi(argv[1])); + + MarketServer server; + std::thread server_thread(&MarketServer::StartServer, &server, port_number); + server_thread.detach(); + + std::string in; + while (in != "exit") { + std::cin >> in; + if (in == "ban") { + std::string name; + std::cin >> name; + if (server.BanUser(name)) { + std::cout << "A player was successfully banned. Good job!\n"; + } else { + std::cout << name << " doesn't play at this moment.\n"; + } + } + } + close(port_number); + + return 0; +} \ No newline at end of file diff --git a/tcp/server/src/market_server.cpp b/tcp/server/src/market_server.cpp new file mode 100644 index 0000000..87c8809 --- /dev/null +++ b/tcp/server/src/market_server.cpp @@ -0,0 +1,344 @@ +#include +#include +#include +#include +#include + +#include "../include/market_server.h" + +int MarketServer::Order::task_counter = 0; + +void MarketServer::UserInteractionLoop(int sock_fd) { + ClientStatus status = ClientStatus::NEW; + Freelancer *freelancer; + Customer *customer; + while (status != ClientStatus::FINISH) { + Message message = Message::Read(sock_fd); + ClientStatus request_status; + if (status == ClientStatus::NEW) { + request_status = WorkWithUnauthorized(sock_fd, message, &freelancer, &customer); + } else if (status == ClientStatus::FREELANCER) { + request_status = WorkWithFreelancer(freelancer, message); + } else if (status == ClientStatus::CUSTOMER) { + request_status = WorkWithCustomer(customer, message); + } + + if (request_status != UNCHANGED) { + status = request_status; + } + } +} + +MarketServer::ClientStatus +MarketServer::WorkWithUnauthorized(int sock_fd, const Message &message, Freelancer **freelancer, Customer **customer) { + Message::Type ans_type; + const std::string &name = message.body; + ClientStatus status = ClientStatus::NEW; + if (Message::NEW_CUSTOMER == message.type) { + std::lock_guard customers_lock(customers_mutex_); + if (customers.count(name) == 0 and freelancers.count(name) == 0) { + *customer = new Customer(name, sock_fd); + customers[name] = *customer; + ans_type = Message::CUSTOMER_ADDED; + std::cout << "New customer\n"; + status = ClientStatus::CUSTOMER; + } else { + ans_type = Message::CANT_ADD_CUSTOMER; + } + } else if (Message::NEW_FREELANCER == message.type) { + std::lock_guard freelancers_lock(freelancers_mutex_); + if (freelancers.count(name) == 0 and customers.count(name) == 0) { + *freelancer = new Freelancer(name, sock_fd); + freelancers[name] = *freelancer; + ans_type = Message::FREELANCER_ADDED; + std::cout << "New freelancer\n"; + status = ClientStatus::FREELANCER; + } else { + ans_type = Message::CANT_ADD_FREELANCER; + } + } else if (Message::UNDEFINED == message.type) { + close(sock_fd); + return ClientStatus::FINISH; + } else { + ans_type = Message::UNAUTHORIZED; + } + + Message ans_message(ans_type); + ans_message.Write(sock_fd); + return status; +} + +MarketServer::ClientStatus MarketServer::WorkWithFreelancer(Freelancer *freelancer, const Message &message) { + Message ans_message; + switch (message.type) { + case Message::GET_OPEN_ORDERS: { + ans_message = LookupOpenOrders(); + break; + } + case Message::TAKE_ORDER: { + orders_mutex_.lock(); + int id = stoi(message.body); + if (orders.count(id) and orders[id]->state == Order::OPEN) { + orders[id]->workers.insert(freelancer->name); + ans_message.type = Message::TAKE_ORDER_SUCCESSFUL; + } else { + ans_message = Message(Message::TAKE_ORDER_NOT_SUCCESSFUL, "no such open order"); + } + orders_mutex_.unlock(); + break; + } + case Message::WORK_STARTED: { + orders_mutex_.lock(); + int id = stoi(message.body); + if (orders.count(id) and + orders[id]->state == Order::ASSIGNED and + orders[id]->workers.count(freelancer->name)) { + orders[id]->state = Order::IN_PROGRESS; + ans_message.type = Message::WORK_STARTED_SUCCESSFUL; + } else { + ans_message.type = Message::WORK_STARTED_NOT_SUCCESSFUL; + ans_message.body = "no such work assigned to you"; + } + orders_mutex_.unlock(); + break; + } + case Message::WORK_FINISHED: { + orders_mutex_.lock(); + int id = stoi(message.body); + if (orders.count(id) and + orders[id]->state == Order::IN_PROGRESS and + orders[id]->workers.count(freelancer->name)) { + orders[id]->state = Order::PENDING; + ans_message.type = Message::WORK_FINISHED_SUCCESSFUL; + } else { + ans_message.type = Message::WORK_FINISHED_NOT_SUCCESSFUL; + ans_message.body = "no such work in progress assigned to you"; + } + orders_mutex_.unlock(); + break; + } + case Message::UNDEFINED: { + DeleteFreelancer(freelancer); + std::cout << "Freelancer left\n"; + return FINISH; + } + + default: { + ans_message = Message(Message::INCORRECT_MESSAGE); + } + + } + freelancer->messages_.push(ans_message); + return UNCHANGED; +} + +MarketServer::ClientStatus MarketServer::WorkWithCustomer(Customer *customer, const Message &message) { + Message ans_message; + switch (message.type) { + case Message::NEW_ORDER: { + orders_mutex_.lock(); + auto *order = new Order(customer->name, message.body); + orders[order->task_id] = order; + ans_message = Message(Message::ORDER_ACCEPTED, std::to_string(order->task_id)); + orders_mutex_.unlock_shared(); + break; + } + case Message::GET_MY_ORDERS: { + ans_message = LookupOrdersOf(customer); + break; + } + case Message::GET_OPEN_ORDERS: { + ans_message = LookupOpenOrders(); + break; + } + case Message::GIVE_ORDER_TO_FREELANCER: { + int id; + char name[256]; + if (sscanf(message.body.c_str(), "%i %s", &id, name) == 2) { + orders_mutex_.lock(); + if (orders.count(id) and + orders[id]->state == Order::OPEN and + orders[id]->customer == customer->name and + orders[id]->workers.count(name)) { + orders[id]->state = Order::ASSIGNED; + orders[id]->workers = {std::string(name)}; + ans_message.type = Message::GIVE_ORDER_SUCCESSFUL; + } else { + ans_message.type = Message::GIVE_ORDER_NOT_SUCCESSFUL; + ans_message.body = "no such open order owned by you"; + } + orders_mutex_.unlock(); + } else { + ans_message.type = Message::GIVE_ORDER_NOT_SUCCESSFUL; + ans_message.body = "parsing error: expected got " + message.body; + } + break; + } + case Message::WORK_ACCEPTED: { + int id = std::stoi(message.body); + orders_mutex_.lock(); + if (orders.count(id) and + orders[id]->state == Order::PENDING and + orders[id]->customer == customer->name) { + orders[id]->state = Order::DONE; + ans_message.type = Message::WORK_ACCEPTED_SUCCESSFUL; + } else { + ans_message.type = Message::WORK_ACCEPTED_NOT_SUCCESSFUL; + ans_message.body = "no such work in progress owned by you"; + } + orders_mutex_.unlock(); + break; + } + case Message::UNDEFINED: { + DeleteCustomer(customer); + std::cout << "Customer left\n"; + return FINISH; + } + + default: { + ans_message = Message(Message::INCORRECT_MESSAGE); + } + } + customer->messages_.push(ans_message); + return UNCHANGED; +} + +Message MarketServer::LookupOpenOrders() { + orders_mutex_.lock_shared(); + std::string result; + for (auto &o: orders) { + if (o.second->state == Order::OPEN) { + OrderToString(result, o.second); + } + } + orders_mutex_.unlock_shared(); + return Message(Message::LIST_OF_OPEN_ORDERS, result); +} + +void MarketServer::OrderToString(std::string &result, MarketServer::Order *o) { + result += std::to_string(o->task_id); + result += " "; + result += o->description; + result += " "; + result += StateToString(o->state); + result += " "; + result += WorkersToString(o->workers); + result += '\n'; +} + +Message MarketServer::LookupOrdersOf(Customer *customer) { + orders_mutex_.lock_shared(); + std::string result; + for (auto &o: orders) { + if (o.second->customer == customer->name) { + OrderToString(result, o.second); + } + } + orders_mutex_.unlock_shared(); + return Message(Message::LIST_OF_MY_ORDERS, result); +} + +MarketServer::~MarketServer() { + for (const auto &p : customers) { + close(p.second->socket_fd); + } + for (const auto &p : freelancers) { + close(p.second->socket_fd); + } + for (const auto &p : orders) { + delete p.second; + } +} + +void MarketServer::DeleteCustomer(MarketServer::Customer *customer) { + std::lock_guard customers_lock(customers_mutex_); + customers.erase(customer->name); + delete customer; +} + +bool MarketServer::BanUser(const std::string &name) { + bool result = false; + { + std::lock_guard freelancers_lock(freelancers_mutex_); + if (freelancers.count(name)) { + DeleteFreelancer(freelancers[name]); + result = true; + } + } + { + std::lock_guard customers_lock(customers_mutex_); + if (customers.count(name)) { + DeleteCustomer(customers[name]); + result = true; + } + } + return result; +} + +std::string MarketServer::StateToString(MarketServer::Order::State state) { + switch (state) { + case Order::OPEN: + return "OPEN"; + case Order::IN_PROGRESS: + return "IN_PROGRESS"; + case Order::DONE: + return "DONE"; + case Order::ASSIGNED: + return "ASSIGNED"; + case Order::PENDING: + return "PENDING"; + } +} + +std::string MarketServer::WorkersToString(std::set set) { + std::string result; + for (auto &s : set) + result += s + " "; + return result; +} + +void MarketServer::DeleteFreelancer(MarketServer::Freelancer *freelancer) { + std::lock_guard freelancers_lock(freelancers_mutex_); + freelancers.erase(freelancer->name); + delete freelancer; +} + +MarketServer::Freelancer::Freelancer(const std::string &name, int socket_fd) : name(name), + socket_fd(socket_fd) { + writer = std::thread([this]() { + while (!messages_.closed()) { + try { + Message m = messages_.pull(); + m.Write(this->socket_fd); + } catch (boost::sync_queue_is_closed &) { + break; + } + } + }); +} + +MarketServer::Customer::Customer(const std::string &name, int socket_fd) : name(name), + socket_fd(socket_fd) { + writer = std::thread([this]() { + while (!messages_.closed()) { + try { + Message m = messages_.pull(); + m.Write(this->socket_fd); + } catch (boost::sync_queue_is_closed &) { + break; + } + } + }); +} + +MarketServer::Freelancer::~Freelancer() { + messages_.close(); + close(socket_fd); + writer.join(); +} + +MarketServer::Customer::~Customer() { + messages_.close(); + close(socket_fd); + writer.join(); +} diff --git a/tcp/server/src/tcp_server.cpp b/tcp/server/src/tcp_server.cpp new file mode 100644 index 0000000..489a3fc --- /dev/null +++ b/tcp/server/src/tcp_server.cpp @@ -0,0 +1,39 @@ +#include +#include +#include "../include/tcp_server.h" + +void TcpServer::StartServer(uint16_t port_number) { + int socket_fd = socket(AF_INET, SOCK_STREAM, 0); + + if (socket_fd < 0) { + perror("ERROR opening socket"); + exit(1); + } + + sockaddr_in serv_addr = {0}, cli_addr = {0}; + + serv_addr.sin_family = AF_INET; + serv_addr.sin_addr.s_addr = INADDR_ANY; + serv_addr.sin_port = htons(port_number); + + if (bind(socket_fd, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) < 0) { + perror("ERROR on binding"); + exit(1); + } + listen(socket_fd, 5); + + while (true) { + + unsigned int cli_len = sizeof(cli_addr); + + int new_socket_fd = accept(socket_fd, (struct sockaddr*) &cli_addr, &cli_len); + + if (new_socket_fd < 0) { + perror("ERROR on accept"); + exit(1); + } + + std::thread thread(&TcpServer::UserInteractionLoop, this, new_socket_fd); + thread.detach(); + } +}