diff --git a/udp/CurrencyApplicationProtocol b/udp/CurrencyApplicationProtocol new file mode 100644 index 0000000..e8c225b --- /dev/null +++ b/udp/CurrencyApplicationProtocol @@ -0,0 +1,66 @@ +Протокол разрабатывается исходя из подхода "запрос-ответ", так как он очень хорошо подходит для задачи: +клиенты запрашивают какое-то действие с валютой, сервер ее выполняет. + +Данные на сервере: + Массив валют, где валюта это: + Название валюты - 16 байта + История курса валюты - N * 4 байта (каждое значение курса это 4 байта) + +... - повторение описанных блоков выше. + +Клиент и сервер обмениваются пакетами раземра 508 байт (либо меньше, если размер сообщения небольшой). +В начале каждого пакета с запросом клиент передает -- номер его сессии, +и при получении ответа от сервера обрабатывает только те пакеты, которые соответствуют этой сессии. +Перед отправкой ответа с сервера содержимое сообщения разбивается на пакеты по 508 байт. +В начало каждого записывается служебная информация: + -- номер сессии, количество пакетов и порядковый номер текущего соответственно. +Клиент расставляет пришедшие пакеты в правильном порядке, при нехватке пакетов отправляет запрос заново с новым номером сессии. + +Поддерживаемые типы запросов: + 0. Получение и вывод списка валют с котировками/изменениями + Номер команды: 0. + + Клиент отправляет: "<номер команды: 4 байта>", + + Сервер отвечает: + "<название валюты: 16 байт><текущий курс: 4 байта><есть ли предыдущий курс: 1 байт><абсолютное приращение: 4 байта><относительное приращение: 4 байта>"..., + если предыдущего курса нет, то абсолютное и относительное значения равны нулям и игнорируются, + относительное значение выражается в процентах и округляется до целых. + Формулы: + абсолютное = current - previous + относительное = (current * 100.0) / previous + + 1. Передача команды на добавление валюты + Номер команды: 1. + + Клиент отправляет: "<номер команды: 4 байта><название валюты: 16 байт><текущий курс: 4 байта>", + + Сервер отвечает: + "<успех запроса: 1 байт>", + возвращает 1 если такой валюты еще не было, 0 если такая валюта была. + + 2. Передача команды на удаление валюты + Номер команды: 2. + + Клиент отправляет: "<номер команды: 4 байта><название валюты: 16 байт>", + + Сервер отвечает: + "<успех запроса: 1 байт>", + возвращает 1 если такая валюта была и была удалена успешно, 0 если такой валюты не было. + + 3. Передача команды на добавление курса валюты + Номер команды: 3. + + Клиент отправляет: "<номер команды: 4 байта><название валюты: 16 байт><новый курс: 4 байта>", + + Сервер отвечает: + "<успех запроса: 1 байт>", + возвращает 1 если успешно выставлен новый курс, 0 иначе. + + 4. Получение истории котировок валюты + Номер команды: 4. + + Клиент отправляет: "<номер команды: 4 байта><название валюты: 16 байт>", + + Сервер отвечает: + "<курс1: 4 байта><курс2: 4 байта>"... diff --git a/udp/server/CMakeLists.txt b/udp/server/CMakeLists.txt new file mode 100644 index 0000000..ab80432 --- /dev/null +++ b/udp/server/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 3.5) +project(homework02) + +set(CMAKE_CXX_STANDARD 11) +find_package(Threads REQUIRED) + +include_directories(.) + +add_executable(homework02 + src/CurrencyServerApplication.cpp + src/Server.cpp include/Server.h + src/Currency.cpp include/Currency.h) + +target_link_libraries(homework02 Threads::Threads) diff --git a/udp/server/include/Currency.h b/udp/server/include/Currency.h new file mode 100644 index 0000000..02bd819 --- /dev/null +++ b/udp/server/include/Currency.h @@ -0,0 +1,33 @@ +#ifndef CURRENCY_H +#define CURRENCY_H + +#include +#include + +class Currency { +public: + static const int32_t ABSENT_CHANGE_VALUE = -1; + + Currency(std::string name, int32_t currentRate); + + const std::string &getName() const; + + int32_t getCurrentRate() const; + + const std::vector &getRates() const; + + void addRate(int32_t rate); + + int32_t getAbsoluteChange() const; + + int32_t getRelativeChange() const; + +private: + std::string name; + std::vector rates; + int32_t absoluteChange; + int32_t relativeChange; +}; + +#endif // CURRENCY_H + diff --git a/udp/server/include/Server.h b/udp/server/include/Server.h new file mode 100644 index 0000000..abc92f4 --- /dev/null +++ b/udp/server/include/Server.h @@ -0,0 +1,51 @@ +#ifndef SERVER_H +#define SERVER_H + +#include +#include +#include + +#include "include/Currency.h" + +class Server { +public: + Server(uint16_t portNumber); + + void start(); + + void stop() const; + +private: + void processCurrencyListQuery(); + void processNewCurrencyQuery(); + void processDeleteCurrencyQuery(); + void processAddCurrencyRateQuery(); + void processCurrencyRateHistoryQuery(); + + int32_t readRequestId(); + int32_t readCommand(); + const std::string readCurrencyName(); + int32_t readCurrencyRate(); + + int32_t readInt32(); + void readChars(char *dst, size_t size); + + void sendString(const std::string &message, size_t len); + void sendInt32(int32_t n); + void sendInt8(int8_t n); + + void checkStatus(int n) const; + + static const int CURRENCY_NAME_SIZE = 16; + static const size_t BUFFER_SIZE = 508; + int8_t buffer[BUFFER_SIZE]; + int bufferPosition; + std::vector message; + std::map currencies; + + int sockfd; + uint16_t portNumber; +}; + +#endif // SERVER_H + diff --git a/udp/server/src/Currency.cpp b/udp/server/src/Currency.cpp new file mode 100644 index 0000000..d293d97 --- /dev/null +++ b/udp/server/src/Currency.cpp @@ -0,0 +1,41 @@ +#include "include/Currency.h" + +#include +#include +#include + +using namespace std; + +Currency::Currency(string name, int32_t currentRate) : name(move(name)) { + rates = {currentRate}; + absoluteChange = ABSENT_CHANGE_VALUE; + relativeChange = ABSENT_CHANGE_VALUE; +} + +const string &Currency::getName() const { + return name; +} + +int32_t Currency::getCurrentRate() const { + return rates[rates.size() - 1]; +} + +const vector &Currency::getRates() const { + return rates; +} + +void Currency::addRate(int32_t rate) { + int32_t lastRate = getCurrentRate(); + rates.push_back(rate); + absoluteChange = rate - lastRate; + relativeChange = static_cast((rate * 100.0) / lastRate); +} + +int32_t Currency::getAbsoluteChange() const { + return absoluteChange; +} + +int32_t Currency::getRelativeChange() const { + return relativeChange; +} + diff --git a/udp/server/src/CurrencyServerApplication.cpp b/udp/server/src/CurrencyServerApplication.cpp new file mode 100644 index 0000000..1d9702f --- /dev/null +++ b/udp/server/src/CurrencyServerApplication.cpp @@ -0,0 +1,50 @@ +#include +#include +#include + +#include "include/Server.h" + +using namespace std; + +int main(int argc, char *argv[]) { + if (argc < 2) { + fprintf(stderr, "usage: .%s \n", argv[0]); + return 0; + } + long portNumber = strtol(argv[1], nullptr, 10); + + if (portNumber <= 0 || portNumber > UINT16_MAX) { + fprintf(stderr, "illegal port number\n"); + return 0; + } + + Server server(static_cast(portNumber)); + pthread_t serverThread; + typedef void * (*PTHREAD_FUNC_PTR)(void *); + + int rc = pthread_create(&serverThread, + nullptr, + (PTHREAD_FUNC_PTR) &Server::start, + &server); + + if (rc) { + cout << "Failed to create server." << endl; + exit(1); + } + + std::string input; + while (true) { + cin >> input; + + if (input == "q") { + server.stop(); + break; + } + } + + void *status; + pthread_join(serverThread, &status); + + return 0; +} + diff --git a/udp/server/src/Server.cpp b/udp/server/src/Server.cpp new file mode 100644 index 0000000..f89cba2 --- /dev/null +++ b/udp/server/src/Server.cpp @@ -0,0 +1,251 @@ +#include "include/Server.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +const int Server::CURRENCY_NAME_SIZE; +const size_t Server::BUFFER_SIZE; + +Server::Server(uint16_t portNumber) + : portNumber(portNumber) {} + +void Server::start() { + sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + + if (sockfd < 0) { + perror("ERROR opening socket"); + exit(1); + } + + struct sockaddr_in serv_addr, cli_addr; + bzero((char *) &serv_addr, sizeof(serv_addr)); + + serv_addr.sin_family = AF_INET; + serv_addr.sin_addr.s_addr = INADDR_ANY; + serv_addr.sin_port = htons(portNumber); + + if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) { + perror("ERROR on binding"); + exit(1); + } + + unsigned int clilen = sizeof(cli_addr); + int n; + int8_t writingBuffer[BUFFER_SIZE]; + + while (true) { + bufferPosition = 0; + message.clear(); + + n = recvfrom(sockfd, buffer, BUFFER_SIZE, 0, (struct sockaddr *) &cli_addr, &clilen); + + if (n == 0) { + break; + } + + if (n < 0) { + perror("ERROR on receiving"); + continue; + } + + int32_t requestId = readRequestId(); + int32_t command = readCommand(); + + switch (command) { + case 0: + processCurrencyListQuery(); + break; + case 1: + processNewCurrencyQuery(); + break; + case 2: + processDeleteCurrencyQuery(); + break; + case 3: + processAddCurrencyRateQuery(); + break; + case 4: + processCurrencyRateHistoryQuery(); + break; + } + + int32_t dataSize = BUFFER_SIZE - 3 * sizeof(int32_t); + int32_t dGramNumber = (message.size() + dataSize - 1) / dataSize; + + if (message.size() == 0) { + dGramNumber = 1; + int32_t currentDGram = 0; + + bzero((char *) &writingBuffer, BUFFER_SIZE); + memcpy(writingBuffer, &requestId, sizeof(int32_t)); + memcpy(writingBuffer + sizeof(int32_t), &dGramNumber, sizeof(int32_t)); + memcpy(writingBuffer + 2 * sizeof(int32_t), ¤tDGram, sizeof(int32_t)); + + n = sendto(sockfd, writingBuffer, 3 * sizeof(int32_t), 0, (struct sockaddr*) &cli_addr, clilen); + + if (n < 0) { + perror("ERROR on sending"); + } + + continue; + } + + for (int32_t i = 0, currentDGram = 0; i < message.size(); i += dataSize, currentDGram++) { + bzero((char *) &writingBuffer, BUFFER_SIZE); + + memcpy(writingBuffer, &requestId, sizeof(int32_t)); + memcpy(writingBuffer + sizeof(int32_t), &dGramNumber, sizeof(int32_t)); + memcpy(writingBuffer + 2 * sizeof(int32_t), ¤tDGram, sizeof(int32_t)); + + for (int j = 0; j < dataSize && i + j < message.size(); j++) { + writingBuffer[3 * sizeof(int32_t) + j] = message[i + j]; + } + + n = sendto(sockfd, writingBuffer, min(BUFFER_SIZE, 3 * sizeof(int32_t) + message.size() - i), 0, (struct sockaddr*) &cli_addr, clilen); + + if (n < 0) { + perror("ERROR on sending"); + break; + } + } + } +} + +void Server::stop() const { + shutdown(sockfd, SHUT_RDWR); +} + +void Server::processCurrencyListQuery() { + for (auto it = currencies.begin(); it != currencies.end(); ++it) { + Currency ¤cy = it->second; + const string &name = currency.getName(); + int32_t rate = currency.getCurrentRate(); + int32_t absoluteChange = currency.getAbsoluteChange(); + int32_t relativeChange = currency.getRelativeChange(); + int8_t hasPreviousRate = 1; + + if (relativeChange == Currency::ABSENT_CHANGE_VALUE) { + hasPreviousRate = 0; + absoluteChange = 0; + relativeChange = 0; + } + + sendString(name, CURRENCY_NAME_SIZE); + sendInt32(rate); + sendInt8(hasPreviousRate); + sendInt32(absoluteChange); + sendInt32(relativeChange); + } + +} + +void Server::processNewCurrencyQuery() { + string name = readCurrencyName(); + int32_t rate = readCurrencyRate(); + + Currency currency = Currency(name, rate); + auto result = currencies.emplace(name, currency); + int8_t success = result.second ? 1 : 0; + + sendInt8(success); +} + +void Server::processDeleteCurrencyQuery() { + string name = readCurrencyName(); + + int8_t success; + + if (currencies.find(name) != currencies.end()) { + success = 1; + currencies.erase(name); + } else { + success = 0; + } + + sendInt8(success); +} + +void Server::processAddCurrencyRateQuery() { + string name = readCurrencyName(); + int32_t rate = readCurrencyRate(); + + int8_t success; + + if (currencies.find(name) != currencies.end()) { + success = 1; + Currency ¤cy = currencies.find(name)->second; + currency.addRate(rate); + } else { + success = 0; + } + + sendInt8(success); +} + +void Server::processCurrencyRateHistoryQuery() { + string name = readCurrencyName(); + + Currency ¤cy = currencies.find(name)->second; + auto rates = currency.getRates(); + + for (auto rate : rates) { + sendInt32(rate); + } +} + +int32_t Server::readCommand() { + return readInt32(); +} + +int32_t Server::readRequestId() { + return readInt32(); +} + +const string Server::readCurrencyName() { + char name[CURRENCY_NAME_SIZE + 1]; + bzero(name, CURRENCY_NAME_SIZE + 1); + readChars(name, CURRENCY_NAME_SIZE); + return string(name); +} + +int32_t Server::readCurrencyRate() { + return readInt32(); +} + +int32_t Server::readInt32() { + int32_t intValue = 0; + memcpy(&intValue, buffer + bufferPosition, sizeof(intValue)); + bufferPosition += sizeof(intValue); + return intValue; +} + +void Server::readChars(char *dst, size_t size) { + memcpy(dst, buffer + bufferPosition, size); + bufferPosition += size; +} + +void Server::sendString(const string &src, size_t len) { + std::copy(src.begin(), src.end(), std::back_inserter(message)); + + for (int i = 0; i < len - src.size(); i++) { + message.push_back(0); + } +} + +void Server::sendInt32(int32_t d) { + message.push_back(d & 0x000000ff); + message.push_back((d & 0x0000ff00) >> 8); + message.push_back((d & 0x00ff0000) >> 16); + message.push_back((d & 0xff000000) >> 24); +} + +void Server::sendInt8(int8_t d) { + message.push_back(d); +}