Клиент-серверная пара. API в исходнике
Я собираюсь создать программную пару — клиент-сервер, выступающую в том числе и в роли чата. (Ведь в нем тоже нужно делать передачу данных).
Эту пару я собираюсь делать в виде библиотек. То есть разработать API.
Все, что далее будет необходимо — это использовать это API при разработке графического или консольного интерфейса.
Поскольку мы будем реализовывать библиотеки а не целостные модули, то необходимо будет разработать интерфейс их общения с основной программой.
В добавок, необходимо будет реализовать без-ожидательное выполнение функций. То есть не должно быть никаких ожиданий при, например, чтении — приеме из сети.
Реализация должна быть проведена на C/C++.
P.S.:
* Для реализации безождитательности сетевых запросов будем использовать неблокируемые сокеты.
* Остается вопросом — Реализация неблокируемости ввода-вывода. Реализация распараллеливания нас духовно не впечатляет потому, как имеет слишком бругденгбергскую структуру межпроцессных сообщений. Оказывает, неблокируемы ввод-вывод мы могём реализовать используя вызов select с нулевым временем ожидания. При этом нет необходимости переключать поток ввода в посимвольный режим. Нам достаточно и построчного ибо только строка для нас чего-нибудь да значит, зане по -у определяется.
* Когда я говорю о библиотеке я не имею в виду подключаемые модули библиотеки, но набор исходников с заголовочными файлами. Ведь так любому будет удобнее что-нибудь поменять в этой «библиотеке».
П О Д Р О Б Н Е Е о Р Е А Л И З А Ц И И
С Е Р В Е Р
Сервер
- создает сокет.
- привязывает сокет.
- ожидает запроса на подключение.
- принимает запрос на подключение.
- принимает пакеты данных.
- отправляет реакцию на принятые пакеты данных.
Мы не будем делать сервер демоническим, хотя это было бы полезно. Наоборот — мы сделаем его отдельно запускаемым. Как приложение. К тому же с организацией получения команд от оператора. Аналогично будем организовывать работу и клиента.
К Л И Е Н Т
Клиент –
- создает сокет.
- привязывает сокет.
- подает заявку на подключение.
- производит обмен данными.
- при необходимости — отключается от сервера.
Е Щ Е П О Д Р О Б Н Е Е О Р Е А Л И З А Ц И И
Вспомогательная база.
/* Отдельные, служебные объявления - Размер данных Ввода-Вывода, Порты сервера и клиента, TimeToLive */ /* DataSize = 1k */ #define DataSize 0x400 #define ServerPort #define ClientPort #define TTL 0xfe /* Общий класс для клиента и сервера */ class clsCommonClass { public: clsCommonClass(); ~clsCommonClass(); int createSocket (int); // Создание сокета int bindSocket (struct sockaddr*, socklen_t); // привязывает сокет int RecieveData (); // Прием данных int SendData (); // Посылка данных virtual int AnalyzeData(); // читаем полученные данные генерируя отправляемые данные protected: int socketDomain; int socketType; int socketProtocol; int Socket; // Сам сокет char DataToRecv [DataSize]; // Принятые (Принимаемые) данные char DataToSend [DataSize]; // Посланные (посылаемые данные) fd_set fds; // Структура списка файл-дескрипоторов для ожидания (5ms) struct timeval tv; // А это те самые 5 миллисекунд struct sockaddr_in SocketAddr; // Адрес (структура адреса) для привязки private: } /* PF_INET - TCP/IP, SOCK_STREAM - connection-oriented protocol, 0 - IP protocol (/etc/protocols) */ clsCommonClass::clsCommonClass () { this->socketDomain = PF_INET; this->socketType = SOCK_STREAM; this->socketProtocol = 0; } int clsCommonClass::createSocket (int nonBlock) { int flags; this->Socket = socket(this->socketDomain, this->socketType, this->socketProtocol); if (this->Socket < 0) { perror ("On socket creation "); return (-1); } if (nonBlock) setsockopt(this->Socket, ); setsockopt(this->Socket, SOL_SOCKET, SO_RCVBUF, DataSize, sizeof(DataSize)); setsockopt(this->Socket, SOL_SOCKET, SO_SNDBUF, DataSize, sizeof(DataSize)); setsockopt(this->Socket, SOL_IP, IP_TTL, TTL, sizeof(TTL)); if (-1 == (flags = fcntl(this->Socket, F_GETFL, 0))) flags = 0; if (fcntl(this->Socket, F_SETFL, flags | O_NONBLOCK) < 0) { perror("On setting non blocking mode to socket "); return (-1); } return 0; } int clsCommonClass::bindSocket () { bzero (&this->SocketAddr, sizeof(this->SocketAddr)); this->SocketAddr.sin_family = AF_INET; this->SocketAddr.sin_port = htons (ServerPort); this->SocketAddr.sin_addr.s_addr = htonl(INADDR_ANY); if ( bind (this->Socket, &this->SocketAddr, sizeof(this->SocketAddr)) ) { perror ("On socket binding "); return (-1); } return 0; } int clsCommonClass::RecieveData () { tv.tv_sec = 0; // заполняем 5-ю миллисекундами tv.tv_usec = 5; FD_ZERO(&this->fds); // файл-дескриптор для проверки на чтение FD_SET(this->Socket, &this->fds); select(1, &this->fds, NULL, NULL, &tv); // Waiting for 5ms at reading. if ( ! FD_ISSET(this->Socket, &this->fds) ) { // Ну - не судьбец нам их принять сейчас. printf("Sorry, can't recieve data. I'll try it later.\n"); return (-2); } if ( send (this->Socket, &this->DataToRecv, DataSize, 0) < 0) { // Прием if (errno == EAGAIN) { printf("Assuming we've successfully recieved data.\n"); return 0; } perror("On send attempt "); return (-1); } return 0; } int clsCommonClass::SendData () // Отправка - аналогично посылке { tv.tv_sec = 0; tv.tv_usec = 5; FD_ZERO(&this->fds); FD_SET(this->Socket, &this->fds); select(1, NULL, &this->fds, NULL, &tv); // Waiting for 5ms at writing. if ( ! FD_ISSET(this->Socket, &this->fds) ) { printf("Sorry, can't send data. I'll try it later.\n"); return (-2); } if ( send (this->Socket, &this->DataToSend, DataSize, 0) < 0) { if (errno == EAGAIN) { printf("Assuming we've successfully sent data.\n"); return 0; } perror("On send attempt "); return (-1); } return 0; } ~clsCommonClass::clsCommonClass () { shutdown(this->Socket, 2); // close in and out chennels }
Теперь дело за малым — опиать в обоих классах потомках класса clsCommonClass (серверном и клиентском) функцию AnalyzeData.