пятница, 18 марта 2011 г.

Клиент-серверная пара. API в исходнике.

 Я собираюсь создать программную пару - клиент-сервер, выступающую в том числе и в роли чата. (Ведь в нем тоже нужно делать передачу данных).
  Эту пару я собираюсь делать в виде библиотек. То есть разработать API.
  Все, что далее будет необходимо - это использовать это API при разработке графического или консольного интерфейса.
  Поскольку мы будем реализовывать библиотеки а не целостные модули, то необходимо будет разработать интерфейс их общения с основной программой.
  В добавок, необходимо будет реализовать без-ожидательное выполнение функций. То есть не должно быть никаких ожиданий при, например, чтении - приеме из сети.
  Реализация должна быть проведена на C/C++.



P.S.:
  •     Для реализации безождитательности сетевых запросов будем использовать неблокируемые сокеты.
  •     Остается вопросом - Реализация неблокируемости ввода-вывода. Реализация распараллеливания нас духовно не впечатляет потому, как имеет слишком бругденгбергскую структуру межпроцессных сообщений. Оказывает, неблокируемы ввод-вывод мы могём реализовать используя вызов select с нулевым временем ожидания. При этом нет необходимости переключать поток ввода в посимвольный режим. Нам достаточно и построчного ибо только строка для нас чего-нибудь да значит, зане по <ENTER>-у определяется.
  •     Когда я говорю о библиотеке я не имею в виду подключаемые модули библиотеки, но набор исходников с заголовочными файлами. Ведь так любому будет удобнее что-нибудь поменять в этой "библиотеке".

======= П О Д Р О Б Н Е Е  о  Р Е А Л И З А Ц И И ======
                 ------- С Е Р В Е Р -------
Сервер - создает сокет.
         привязывает сокет.
         ожидает запроса на подключение.
         принимает запрос на подключение.
         принимает пакеты данных.
         отправляет реакцию на принятые пакеты данных
.

Мы не будем делать сервер демоническим, хотя это было бы полезно. Наоборот - мы сделаем его отдельно запускаемым. Как приложение. К тому же с организацией получения команд от оператора. Аналогично будем организовывать работу и клиента.

                 ------- К Л И Е Н Т -------
Клиент - создает сокет.
         привязывает сокет.
         подает заявку на подключение.
         производит обмен данными.
         при необходимости - отключается от сервера.


====== Е Щ Е  П О Д Р О Б Н Е Е  О  Р Е А Л И З А Ц И И ========
Вспомогательная база.

/* Отдельные, служебные объявления - Размер данных Ввода-Вывода, Порты сервера и клиента, 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.