Раздел «Язык Си».OOP-Instrumental_3sem2:

Очереди сообщений.

Задача Игра.

Требуется написать программы, для организации игры в крестики-нолики или морской бой. Предполагается, наличие программы-сервера, одной или двух программ-клиентов, которые общаются с сервером.

Программа-сервер обеспечивает:

1. прием и пересылку сообщений от клиентов

2. проверку правильности хода и решение о завершении игры (выигрыш, проигрыш, прервана)

Программа-клиент обеспечивает:

1. возможность сделать ход (либо через консоль, либо автоматически через реализованный алгоритм стратегии игры).

2. пересылку и прием сообщений от сервера.

Для взаимодействия между процессами испрользуется системная структура очередь сообщений.

Пример взаимодействия клиент-сервер на языке С

Заголовочный файл

#define MAXBUF 100
#define PERM 0666

// Структура для передачи сообщений
typedef struct msgbuf{
// тип сообщения для чтения или записи
   long type;       
// для содержания сообщения
   char buf[MAXBUF]; 
// могут быть и еще другие поля
}Message;

Сервер Клиент

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdlib.h>
#include <sys/msg.h>
#include "mes.h"

int main(){
// сообщение
  Message privet;
// ключ для создания системного ресурса
  key_t key; 
// дескриптор очереди сообщений
  int mesid; 

  int lng,n;
// получение ключа 
// нужно имя заведомо существующего файла
// и символ
  if ((key = ftok( "server", 'A' ) ) < 0 ){
     printf("Can't get key\n");
     exit(1);
  }
// для записи сообщения, его тип 
//указывается в структуре
// будем читать и писать тип 1
  privet.type=1L;

// создание очереди сообщений
  if( ( mesid = msgget(key, PERM|IPC_CREAT ) ) < 0 ){
    printf("Can't create message's queue\n");
    exit(1);
  }
// чтение из очереди сообщений
// если сообщений в очереди нет, программа в ожидании
// сообщение должен послать клиент
  n = msgrcv( mesid, &privet, sizeof(Message), privet.type, 0);
// для печати - 0 в конце
  privet.buf[n]='\0';

// n - количество прочитанных байт
// если что-то прочитали, то печать 
  if ( n > 0 ){
   printf("Privet: %s poluchen ot %dl\n", privet.buf, privet.type);

// Подготовка нового сообщения для отправки    
  lng = sprintf(privet.buf, "Thank's\n");

// отправка сообщения 
     if ( msgsnd( mesid, (void*)&privet, lng, 0) < 0 ){
       printf("Can't write message\n");
       exit(1);
     }
  }else{
    printf("The message is not valid\n");
    exit(1);
 }

 return 0;
}

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>

#include "mes.h"

int main(){
  Message privet;
  key_t key;
  int mesid; 
  int lng,n;

  if ( ( key = ftok("server", 'A' ) ) < 0 ){
    printf("Can't get key\n");
    exit(1);
  }
// Очередь уже создана, получаем 
// дескриптор
  if( ( mesid = msgget( key, 0 ) ) < 0 ){
    printf("Can't get message's queue\n");
    exit(1);
  }

// Создаем сообщение для посылки 
  privet.type = 1L;
 lng = sprintf(privet.buf, "Q-ku\0");

// отсылаем сообщение (сервер его ждет)
  if ( msgsnd( mesid, (void*)&privet, lng, 0) < 0 ){
    printf("Can't write message\n");
    exit(1);
 }
 char c;
 c = getchar();
// Удалаем очередь сообщений когда не нужна
// сервер может и не успеть прочитать сообщение
 if( msgctl( mesid, IPC_RMID, 0 ) < 0 ){
   printf("Can't delete queue\n");
   exit(1);
 }
return 0;
}

Задачи

Задача 1. Класс Sender

Класс Sender служит для создания и открытия очередей сообщений, для передачи и приема сообщений, а также для удаления очереди сообщений

Заголовочный файл

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <stdlib.h>
#include <sys/msg.h>
#include <time.h>
#include <errno.h>
#include <string.h>

#include <iostream>
#include <cstdlib>
#include <fstream>

#define PERM 0666

using namespace std;

class Sender{
// внутренняя структура для сообщений
   struct Mess{
      long long type;// тип сообщения
// само сообщение
        char buf[100];
// Дополнительные поля: 
      int len; // размер сообщения в байтах
// pid процесса, посылавшего сообщение
        int pid; 
   };
// сообщение
   Mess msg;
   int key; // ключ
// дескриптор очереди сообщений
   int mesid; 
// pid текущего процесса
   int pid;
   public:
// Конструктор для сервера
// Требуется имя файла и символ
      Sender(const char*, char);
// Конструктор для клиента. 
// Добавляется еще параметр для различия
      Sender(const char*, char, int);
// деструктор
      ~Sender();
// чтение с возвратом кода ошибки
// ms - тело сообщения, type - тип сообщения
      unsigned char read( void* ms, long long type);

// посылка с возвратом кода ошибки
// ms - тело сообщения, type - тип сообщения
// len - размер тела сообщения
      unsigned char send( void* ms, int len, long long type);
// удаление очереди сообщений
      void delQueue();
};

Пример тестирующей программы

Клиент Сервер

#include "sender.h"

int main(){
   int err;
   string buf;
   int len, queue = 0;
   cout << getpid() << endl;
   Sender snd("serv", 'A', 77);
   cout << "читаем..." << endl;
   err = snd.read( (void*) buf.c_str(), 1L);
   cout << "прочитали" << buf << endl;
// очередь удалять будет сервер
}

#include "sender.h"

int main(){
   string buf = "1";
   int err;
   int len, queue = 0;
   char m1;
   cout << getpid() << endl;
   Sender snd("serv",'A');
   err = snd.send((void*)buf.c_str(), sizeof(char), 1L);
   buf = "2";
   err = snd.send((void*)buf.c_str(), sizeof(char), 1L);
   while( buf != "*" ){
      cin >> buf;
  }
  delQueue();
}

Написать код для всех функций класса Sender и отладить

Задача 2.

Дан примерный заголовочный файл для описания игры (класс Game).

#include "sender.h"
using namespace std;

class Game{
// Внутрення структура для описания игрового поля
   struct Pole{
// поле зависит от размера. 
// память будет выделена динамически
      int *pl; 
// размер по горизонтали и вертикали
      int n, m;
// конструктор
      Pole(int n, int m);
// деструктор
      ~Pole();
// оператор, возвращающий указатель на строку с номером n
// обращение через объект a[i][j] 
      int* operator[](int n); 
   };
// Указатель на игровое поле
// конструктор только с параметрами, поэтому по умолчанию
// объект не получится
   Pole *gameP;
// размеры
   int m, n;
// указатель на Sender
// для обмена сообщениями
   Sender *snd;
   public:
// конструктор
      Game(int, int);
// в деструукторе удаляется динамическая память и 
// очередь сообщений через Sender
      ~Game();
// один игровой ход
      void step(int, int);
// показать состояние поля 
      void show();
};

Пример реализации отдельных функций класса Game

#include "game.h"
// описание коструктора Pole 
Game::Pole::Pole(int a, int b){
   n = a;
   m = b;
   pl = new int[ n * m ];
   bzero(pl, n * m * sizeof(int));
};
//деструктор Pole
Game::Pole::~Pole(){
   if (pl)
      delete[] pl;
};
//оператор [].
// просто возвращает указатель на строку k
int* Game::Pole::operator[](int k){
      return pl + k * n;
};

Game::Game(int a, int b){
// создаем игровое поле
   gameP = new Pole(a,b);
   n = a; 
   m = b; 
};

Game::~Game(){
   delete gameP;
};

void Game::show(){
// пример использования []
// если есть указатель нна объект
  int *z = (*gameP)[0];
  cout<<*z<<endl;

// печать игрового поля
  for(int y = 0; y < m; y++){
    for(int x = 0; x < n; x++)

// получив указатель на int, можем поступать
// с ним как с обычным массивом
      cout << (*gameP)[y][x] << ' ';

    cout<<endl;
  }

};

1. Написать все функции для класса Game (крестики-нолики или морской бой).

2. Написать программу-сервер для получения сообщений от клиентов (ходов), передачи им (информации о сотоянии поля), организации очередности игры и определения победителя.

3. Написать программу-клиент для предоставления возможности сделать ход.

4**. Написать программу-клиент, которая играет за компьютер. Проверить работу с другой такой же программой.

-- TatyanaOvsyannikova2011 - 26 Oct 2017