|
|
|
Распределенная обработка данных |
|
|
БОЛЬШАЯ ЛЕНИНГРАДСКАЯ БИБЛИОТЕКА - РЕФЕРАТЫ - Распределенная обработка данных
Распределенная обработка данных
Содержание - Задание №1 Реализация интерфейса COM
- Задание №1А QueryInterface
- Задание №1B Подсчет ссылок
Задание №1 Реализация интерфейса COMЦель работы:Разработать код на С++, реализующий простой интерфейс СОМ без динамической компоновки:клиент и компонент взаимодействуют через два интерфейса IX иIY, интерфейсы COM реализованы как чисто абстрактные базовые классы С++;в качестве клиента использовать процедуру main;компонент реализуется классом СА, который наследует как IX так и IY;класс СА реализует функции-члены обоих интерфейсов (множественное наследование);клиент создает экземпляр компонента (для управления существованием компонента клиент применяет оператора new и delete), далее он получает указатели на интерфейсы, поддерживаемые компонентом, использовать эти указатели анологично указателям на классы С++;выводить промежуточные сообщения при использовании интерфейсов IX и IY;удалить компонент;вместо определения интерфейса как класса использовать определение из заголовочного файла OBJBASE.H#define interface structи функции-члены объявлять с помощьюvirtual void _stdcall … .Теоретические сведения:В СОМ интерфейсы -- это все. Для клиента компонент представляет собой набор интерфейсов. Клиент может взаимодействовать с компонентом СОМ только через интерфейс. С точки зрения программиста СОМ, интерфейсы -- важная часть любого приложения. Компоненты сами по себе есть просто детали реализации интерфейсов.Утверждение, что компонент -- всего лишь деталь реализации интерфейса, конечно, преувеличение. В конце концов, интерфейс без реализации ничего не сделает. Однако компонент можно удалить и заменить другим; если новый компонент поддерживает те же интерфейсы, что и старый, приложение будет работать по-прежнему. Отдельные компоненты сами по себе не определяют приложения. Приложение определяют интерфейсы между компонентами. Пока интерфейсы неизменны, компоненты могут появляться и исчезатьТеперь рассмотрим код, реализующий простой интерфейс. В приведенном ниже тексте программы компонент CA использует IX и IY для реализации двух интерфейсов.class IX // Первый интерфейс{public:virtual void Fx1() = 0;virtual void Fx2() = 0;};class IY // Второй интерфейс{public:virtual void Fy1() = 0;virtual void Fy2() = 0;};class CA : public IX, public IY // Компонент{public:// Реализация абстрактного базового класса IXvirtual void Fx1() { cout << “Fx1” << endl; }virtual void Fx2() { cout << “Fx2” << endl; }// Реализация абстрактного базового класса IYvirtual void Fy1() { cout << “Fy1” << endl; }virtual void Fy2() { cout << “Fy2” << endl; }};IX и IY -- это чисто абстрактные базовые классы, которые используются для реализации интерфейсов. Чистоабстрактный базовый класс (pure abstract base class) -- это базовый класс, который содержит только чисто виртуальные функции (pure virtual functions). Чисто виртуальная функция -- это виртуальная функция, «помеченная =0 -- знаком спецификатора чистоты (pure specifier). Чисто виртуальные функции не реализуютсяв классах, в которых объявлены. Как видно из приведенного выше примера, функции IX::Fx1, IX::Fx2, IY::Fy1 и IY::Fy2 только декларируются. Реализуются же они в производном классе. В приведенном фрагменте кода компонент CA наследует два чисто абстрактных базовых класса -- IX и IY -- и реализует их чисто виртуальные функции.Для того, чтобы реализовать функции-члены IX и IY, CA использует множественное наследование. Последнее означает, что класс является производным более чем от одного базового класса. Класс С++ чаще всего использует единичное наследование, т.е. имеет только один базовый класс.Текст программы:#include "stdafx.h"#include "iostream.h"#include "objbase.h" // Определить интерфейс#include "conio.h"void trace(const char* pMsg) { cout << pMsg << endl; }// Абстрактные интерфейсыinterface IX{virtual void __stdcall Fx1() = 0;virtual void __stdcall Fx2() = 0;};interface IY{virtual void __stdcall Fy1() = 0;virtual void __stdcall Fy2() = 0;};// Реализация интерфейсаclass CA : public IX,public IY{public:// Реализация интерфейса IXvirtual void __stdcall Fx1() { cout << "CA::Fx1" << endl; }virtual void __stdcall Fx2() { cout << "CA::Fx2" << endl; }// Реализация интерфейса IYvirtual void __stdcall Fy1() { cout << "CA::Fy1" << endl; }virtual void __stdcall Fy2() { cout << "CA::Fy2" << endl; }};// Клиентint main(){trace("Client: Sozdanie ekzemplyra komponenta");CA* pA = new CA;// Получить указатель IXIX* pIX = pA;trace("Client: Ispol'zovanie interface IX");pIX->Fx1();pIX->Fx2();// Получить указатель IYIY* pIY = pA;trace("Client: Ispol'zovanie interface IY");pIY->Fy1();pIY->Fy2();trace("Client: Delete komponent");delete pA;getch();return 0; }Результат работы программы:Вывод:В данном задании мы реализововали простой интерфейс СОМ без динамической компоновки. Интерфейсы COM реализованы как чисто абстрактные базовые классы С++, в качестве клиента использовали процедуру main.Задание 2 №1А QueryInterfaceЦель работы:Объявить интерфейсы IX, IY, IZ . Объявить интерфейс IUnknown.Реализация компонента. Класс СА реализует компонент, поддерживающий интерфейсы IX и IY. Реализовать QueryInterface описанным выше способом. Функцию CreateInstance определить после класса CA. Клиент использует ее, чтобы создать компонент, представляемый при помощи СА, и получить указатель на IUnknown этого компонента. После CreateInstance определить IID для интерфейсов. (Для того, чтобы определить IID для IUnknown компоновать с UUID.LIB).Реализация клиента, роль которого выполняет main. Клиент начинает с создания компонента при помощи CreateInstance. CreateInstance возвращает указатель на интерфейс IUnknown компонента. Клиент при помощи QueryInterface запрашивает через интерфейс IUnknown указатель на интерфейс IX компонента. Анологично запросить и IY. Использовать эти указатели для доступа к функциям-членам. Запросить интерфейс IZ. QueryInterface возвращает код ошибки, так как СА не реализует IZ. Далее Клиент запрашивает указатель на интерфейс IY через указатель на интерфейс IX, pIX. Поскольку компонент поддерживает IY, этот запрос будет успешным, и клиент сможет использовать возвращенный указатель на интерфейс IY так же, как он использовал первый указатель. Затем клиент запрашивает интерфейс IUnknown через указатель на IY. Поскольку все интерфейсы COM наследуют IUnknown, этот запрос должен быть успешным, причем возвращенный указатель совпадет с первым указателем, так как . QueryInterface возвращает один и тот же указатель на все запросы к IUnknown.Теоретические сведения:В COM клиент взаимодействует с компонентом с помощью интерфейсаIUnknown, который определен в заголовочном файле UNKWN.H:Interface IUnknown{virtual HREZULT --stdcall QueryInterface( const IID&iid,void * * ppv) = 0 ;virtual ULONG --stdcall Addref( ) = 0 ;virtual ULONG --stdcall Release( ) = 0 ;};Функцию с именем QueryInterface клиент вызывает, чтобы определить, поддерживает ли компонент некоторый интерфейс. У функции QueryInterface два параметра. Первый параметр - идентификатор интерфейса. Второй параметр - адрес, по которому QueryInterface помещает указатель на искомый интерфейс.QueryInterface возвращает HREZULT - 32-разрядный код результата.QueryInterface может возвратить либо S_OK, либо E_NOINTERFACE. Клиент не должен прямо сравнивать возвращаемое QueryInterface значение с этими константами; для проверки надо использовать макросы SUCCEEDED или FAILED.Получение указателя на IUnknownДля получения указателя на IUnknown использовать функцию, например, CreateInstance которая создает компонент и возвращает указатель на IUnknown:IUnknown * CreateInstance( )Реализация функции CreateInstance:IUnknown * pI = static_cast<IX*>(new CA) ;PI->Addref( ) ;Return pI ;Использование QueryInterface.Предположим, что у нас есть указатель на IUnknown, pI. Чтобы определить, можно ли использовать некоторый другой интерфейс, мы вызываем QueryInterface, передавая ей идентификатор нужного нам интерфейса. Если QueryInterface отработала успешно, мы можем пользоваться указателем:void foo(IUnknown* pI){// Определить указатель на интерфейсIX* pIX=NULL;// Запросить интерфейс IXHREZULT hr = pI-> QueryInterface(IID_IX, (void**)&pIX) ;// Проверить значение результатаif (SUCCEEDED(hr)){// Использовать интерфейсpIXFx( ) ;}}Реализация QueryInterface.Запишем QueryInterface для следующего компонента, реализуемого классом CA:Interface IX : IUnknown { /*…*/ } ;Interface IY : IUnknown { /*…*/ } ;Class CA : public IX, public IY { /*…*/ } ;Следующий фрагмент кода реализует QueryInterface для класса, приведенного выше фрагмента кода.HREZULT --stdcall CA:: QueryInterface( const IID&iid, void * * ppv);{if (iid ==IID_IUnknown){// Клиент запрашивает интерфейс IUnknown*ppv = static_cast<IX*>(this) ;}else if (iid ==IID_IX){// Клиент запрашивает интерфейс IX*ppv = static_cast<IX*>(this) ;}else if (iid ==IID_IY){// Клиент запрашивает интерфейс IY*ppv = static_cast<IY*>(this) ;}else{// Мы не поддерживаем запрашиваемый клиентом интерфейс.// Установить возвращаемый указатель в NULL.*ppv = NULL ;return E_NOINTERFACE ;}static_cast< IUnknown*>(*ppv)->AddRef( ) ;return S_OK ;}Текст программы:#include "stdafx.h"#include "iostream.h"#include "objbase.h"#include "conio.h"void trace(const char* msg) { cout << msg << endl; }// Интерфейсыinterface IX : IUnknown{virtual void __stdcall Fx() = 0;};interface IY : IUnknown{virtual void __stdcall Fy() = 0;};interface IZ : IUnknown{virtual void __stdcall Fz() = 0;};// Предварительные объявления GUIDextern const IID IID_IX;extern const IID IID_IY;extern const IID IID_IZ;//// Компонентclass CA : public IX, public IY{// Реализация IUnknownvirtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv);virtual ULONG __stdcall AddRef() { return 0; }virtual ULONG __stdcall Release() { return 0; }// Реализация интерфейса IXvirtual void __stdcall Fx() { cout << "Fx" << endl; }// Реализация интерфейса IYvirtual void __stdcall Fy() { cout << "Fy" << endl; }};HRESULT __stdcall CA::QueryInterface(const IID& iid, void** ppv){if (iid == IID_IUnknown){trace("QueryInterface: Vernyt' ykazatel' na IUnknown");*ppv = static_cast<IX*>(this);}else if (iid == IID_IX){trace("QueryInterface: Vernyt' ykazatel' na IX");*ppv = static_cast<IX*>(this);}else if (iid == IID_IY){trace("QueryInterface: Vernyt' ykazatel' na IY");*ppv = static_cast<IY*>(this);}else{trace("QueryInterface: Interface No!");*ppv = NULL;return E_NOINTERFACE;}reinterpret_cast<IUnknown*>(*ppv)->AddRef();return S_OK;}// Функция созданияIUnknown* CreateInstance(){IUnknown* pI = static_cast<IX*>(new CA);pI->AddRef();return pI;}// IID// {32bb8320-b41b-11cf-a6bb-0080c7b2d682}static const IID IID_IX ={0x32bb8320, 0xb41b, 0x11cf,{0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}};// {32bb8321-b41b-11cf-a6bb-0080c7b2d682}static const IID IID_IY ={0x32bb8321, 0xb41b, 0x11cf,{0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}};// {32bb8322-b41b-11cf-a6bb-0080c7b2d682}static const IID IID_IZ ={0x32bb8322, 0xb41b, 0x11cf,{0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}};// Клиентint main(){HRESULT hr;trace("Client: Polychit' ykazatel' na IUnknown");IUnknown* pIUnknown = CreateInstance();trace("Client: Polychit' ykazatel' na IX");IX* pIX = NULL;hr = pIUnknown->QueryInterface(IID_IX, (void**)&pIX);if (SUCCEEDED(hr)){trace("Client: IX polychen");pIX->Fx(); // Использовать интерфейс IX}trace("Client: Polychit' ykazatel na IY");IY* pIY = NULL;hr = pIUnknown->QueryInterface(IID_IY, (void**)&pIY);if (SUCCEEDED(hr)){trace("Client: IY polychen");pIY->Fy(); // Использовать интерфейс IY}trace("Client: Zaprosit' nepodderjivaemuy interface");IZ* pIZ = NULL;hr = pIUnknown->QueryInterface(IID_IZ, (void**)&pIZ);if (SUCCEEDED(hr)){trace("Client: Interface IZ polychen");pIZ->Fz();}else{trace("Client: No Interface IZ");}trace("Client: Polychit' Interface IY cherez Interface IX");IY* pIYfromIX = NULL;hr = pIX->QueryInterface(IID_IY, (void**)&pIYfromIX);if (SUCCEEDED(hr)){trace("Client: IY polychen");pIYfromIX->Fy();}trace("Client: Polechit' Interface IUnknown cherez IY");IUnknown* pIUnknownFromIY = NULL;hr = pIY->QueryInterface(IID_IUnknown, (void**)&pIUnknownFromIY);if (SUCCEEDED(hr)){cout << "Sovpadaut li ykazateli na IUnknown? ";if (pIUnknownFromIY == pIUnknown){cout << "Yes, pIUnknownFromIY == pIUnknown" << endl;}else{cout << "No, pIUnknownFromIY != pIUnknown" << endl;}}// Удалить компонентdelete pIUnknown;getch();return 0;}Результат работы программы:Вывод:В данном задании объявили интерфейсы IX, IY, IZ, и интерфейс IUnknown.Реализовали компонент. Класс СА который реализует компонент, поддерживающий интерфейсы IX и IY. Реализовали QueryInterface. Функцию CreateInstance, которая определяется после класса CA. Клиент использует ее, чтобы создать компонент, представляемый при помощи СА, и получили указатель на IUnknown этого компонента.Задание №1B Подсчет ссылокЦель работы:Добавить к предыдущей программе из лабороторной работы №1А подсчет ссылок. Для этого к компоненту добавить реализации двух методов IUnknown - AddRef и Release.используя функции Win32 InterlockedIncrement и InterlockedDecrement. Функцию AddRef вызывают CreateInstance и QueryInterface для соответствующих указателей на интерфейсы. Вызовы Release добавить в клиенте, чтобы обозначить окончание работы с различными интерфейсами.Ликвидировать компонент с помощью деструктора.Теоретические сведения:Вместо того, чтобы удалять компоненты напрямую, мы будем сообщать компоненту, что нам нужен интерфейс или что мы закончили с ним работать. Мы точно знаем, когда начинаем использовать интерфейс, и знаем (обычно), когда перестаем его использовать. Однако, как уже ясно, мы можем не знать, что закончили использовать компонент вообще. Поэтому имеет смысл ограничиться сообщением об окончании работы с данным интерфейсом -- и пусть компонент сам отслеживает, когда мы перестаем пользоваться всеми интерфейсами.Именно для реализации этой стратегии и предназначены еще две функции-члена IUnknown -- AddRef и Release.определение интерфейса IUnknown:interface IUnknown{virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv) = 0;virtual ULONG __stdcall AddRef() = 0;virtual ULONG __stdcall Release() = 0;};AddRef и Release реализуют и технику управления памятью, известную как подсчет ссылок (reference counting).Подсчет ссылок -- простой и быстрый способ, позволяющий компонентам самим удалять себя. Компонент СОМ поддерживает счетчик ссылок. Когда клиент получает некоторый интерфейс, значение этого счетчика увеличивается на единицу. Когда клиент заканчивает работу с интерфейсом, значение на единицу уменьшается. Когда оно доходит до нуля, компонент удаляет себя из памяти. Клиент также увеличивает счетчик ссылок, когда создает новую ссылку на уже имеющийся у него интерфейс. Как Вы, вероятно, догадались, увеличивается счетчик вызовом AddRef, а уменьшается -- вызовом Release.Для того, чтобы пользоваться подсчетом ссылок, необходимо знать лишь три простых правила:1. Вызывайте AddRef перед возвратом. Функции, возвращающие интерфейсы, перед возвратом всегдадолжны вызывать AddRef для соответствующего указателя. Это также относится к QueryInterface и функции CreateInstance. Таким образом, Вам не нужно вызывать AddRef в своей программе после получения (от функции) указателя на интерфейс.2. По завершении работы вызывайте Release. Когда Вы закончили работу с интерфейсом, следует вызвать для него Release.3. Вызывайте AddRef после присваивания. Когда бы Вы ни присваивали один указатель на интерфейс другому, вызывайте AddRef. Иными словами: следует увеличить счетчик ссылок каждый раз, когда создается новая ссылка на данный интерфейс.Приведенный ниже фрагмент кода создает компонент и получает указатель на интерфейс IX. Мы не вызываем AddRef, так как за нас это делают CreateInstance и QueryInterface. Однако мы вызываем Release как для интерфейса IUnknown, возвращенного CreateInstance, так и для интерфейса IX, возвращенного QueryInterface.// Создать компонентIUnknown* pIUnknown = CreateInstance();// Получить интерфейс IXIX* pIX = NULL;HRESULT hr = pIUnknown->QueryInterface(IID_IX, (void**)&pIX);if (SUCCEEDED(hr)){pIX->Fx(); // Использовать интерфейс IXpIX->Release(); // Завершить работу с IX}pIUnknown->Release(); // Завершить работу с IunknownВ приведенном выше примере мы фактически закончили работать с IUnknown сразу же после вызоваQueryInterface, так что его можно освободить раньше.// Создать компонентIUnknown* pIUnknown = CreateInstance();// Получить интерфейс IXIX* pIX = NULL;HRESULT hr = pIUnknown->QueryInterface(IID_IX, (void**)&pIX);// Завершить работу с IUnknownpIUnknown->Release();// Использовать IX, если он был получен успешноif (SUCCEEDED(hr)){pIX->Fx(); // Использовать интерфейс IXpIX->Release(); // Завершить работу с IX}Легко забыть, что всякий раз, когда Вы копируете указатель на интерфейс, надо увеличить его счетчик ссылок. В приведенном далее фрагменте кода делается еще одна ссылка на интерфейс IX. В общем случае необходимо увеличивать счетчик ссылок всякий раз, когда создается копия указателя на интерфейс, о чем говорит приведенное выше правило 3.// Создать компонентIUnknown* pIUnknown = CreateInstance();IX* pIX = NULL;HRESULT hr = pIUnknown->QueryInterface(IID_IX, (void**)&pIX);pIUnknown->Release();if (SUCCEEDED(hr)){pIX->Fx(); // Использовать интерфейс IXIX* pIX2 = pIX; // Создать копию pIXpIX2->AddRef(); // Увеличить счетчик ссылокpIX2->Fx(); // Что-то делать при помощи pIX2pIX2->Release(); // Завершить работу с pIX2pIX->Release(); // Завершить работу с pIX}Клиент сообщает компоненту о своем желании использовать интерфейс, когда вызываетсяQueryInterface. QueryInterface вызывает AddRef для запрашиваемого интерфейса. Когда клиент заканчивает работу с интерфейсом, он вызывает для этого интерфейса Release. Компонент остается в памяти, ожидая, пока счетчик ссылок не станет равен 0. Когда счетчик становится нулем, компонент сам себя удаляет.Текст программы:#include "stdafx.h"#include "iostream.h"#include "objbase.h"#include "conio.h"void trace(const char* msg) { cout << msg << endl; }// Предварительные описания GUIDextern const IID IID_IX;extern const IID IID_IY;extern const IID IID_IZ;// Интерфейсыinterface IX : IUnknown{virtual void __stdcall Fx() = 0;};interface IY : IUnknown{virtual void __stdcall Fy() = 0;};interface IZ : IUnknown{virtual void __stdcall Fz() = 0;};// Компонентclass CA : public IX, public IY{// Реализация IUnknownvirtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv);virtual ULONG __stdcall AddRef();virtual ULONG __stdcall Release();// Реализация интерфейса IXvirtual void __stdcall Fx() { cout << "Fx" << endl; }// Реализация интерфейса IYvirtual void __stdcall Fy() { cout << "Fy" << endl; }public:// КонструкторCA() : m_cRef(0) {}// Деструктор~CA() { trace("CA: Likvidirovat' seby"); }private:long m_cRef;};HRESULT __stdcall CA::QueryInterface(const IID& iid, void** ppv){if (iid == IID_IUnknown){trace("CA QI: Vozvratit' ykazateel na IUnknown");*ppv = static_cast<IX*>(this);}else if (iid == IID_IX){trace("CA QI: Vozvratit' ykazateel na IX");*ppv = static_cast<IX*>(this);}else if (iid == IID_IY){trace("CA QI:Vozvratit' ykazateel na IY");*ppv = static_cast<IY*>(this);}else{trace("CA QI: Interface No!");*ppv = NULL;return E_NOINTERFACE;}reinterpret_cast<IUnknown*>(*ppv)->AddRef();return S_OK;}ULONG __stdcall CA::AddRef(){cout << "CA: AddRef = " << m_cRef+1 << endl;return InterlockedIncrement(&m_cRef);}ULONG __stdcall CA::Release(){cout << "CA: Release = " << m_cRef-1 << endl;if (InterlockedDecrement(&m_cRef) == 0){delete this;return 0;}return m_cRef;}//// Функция создания//IUnknown* CreateInstance(){IUnknown* pI = static_cast<IX*>(new CA);pI->AddRef();return pI;}//// IID//// {32bb8320-b41b-11cf-a6bb-0080c7b2d682}static const IID IID_IX ={0x32bb8320, 0xb41b, 0x11cf,{0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}};// {32bb8321-b41b-11cf-a6bb-0080c7b2d682}static const IID IID_IY ={0x32bb8321, 0xb41b, 0x11cf,{0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}};// {32bb8322-b41b-11cf-a6bb-0080c7b2d682}static const IID IID_IZ ={0x32bb8322, 0xb41b, 0x11cf,{0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}};//// Клиент//int main(){HRESULT hr;trace("Client: Polychit' ykazatel IUnknown");IUnknown* pIUnknown = CreateInstance();trace("Client: Polychit' Interface IX");IX* pIX = NULL;hr = pIUnknown->QueryInterface(IID_IX, (void**)&pIX);if (SUCCEEDED(hr)){trace("Client: IX polychen");pIX->Fx(); // Использовать интерфейс IXpIX->Release();}trace("Client: Polychit' Interface IY");IY* pIY = NULL;hr = pIUnknown->QueryInterface(IID_IY, (void**)&pIY);if (SUCCEEDED(hr)){trace("Client: IY polychen");pIY->Fy(); // Использовать интерфейс IYpIY->Release();}trace("Client: Zaprosit' nepodderjivaemue Interface");IZ* pIZ = NULL;hr = pIUnknown->QueryInterface(IID_IZ, (void**)&pIZ);if (SUCCEEDED(hr)){trace("Client: Interface IZ polychen");pIZ->Fz();pIZ->Release();}else{trace("Client: No! Interface IZ");}trace("Client: Osvobodit' Interface IUnknown");pIUnknown->Release();getch();return 0;}Результат работы программы:Вывод:В этом задании мы добавили подсчет ссылок. Для этого к компоненту добавили реализацию двух методов IUnknown - AddRef и Release, используя функции Win32 InterlockedIncrement и InterlockedDecrement. Функцию AddRef вызывают CreateInstance и QueryInterface для соответствующих указателей на интерфейсы. Вызовы Release добавили в клиенте, чтобы обозначить окончание работы с различными интерфейсами. Ликвидировали компонент с помощью деструктора.
|
|
|
НОВОСТИ |
|
|
Изменения |
|
Прошла модернизация движка, изменение дизайна и переезд на новый более качественный сервер |
|