Стаття Побудова клієнт-серверної програми на основі класу CAsyncSocket (MFC)

  • Клієнт(позначення далі -1CL). Об'єкт, який підключається до сервера.
  • Сервер(позначення далі -SRV). Об'єкт, що здійснює очікування запиту на підключення від Клієнта (1CL) і підключає його до одного з елементів серверного масиву ПАР для Клієнтів.
  • Серверний клієнт(позначення далі -sCL). Цей об'єкт є елементом серверного масиву клієнтів. У цьому масиві містяться "точки" (або "клієнтські пари") для підключення Клієнтів (1CL).

програми

//повідомлення WM_SOCKET_NOTIFY, lParam==FD_READ //1CL або sCL отримав нові дані від пари - потрібно їх прийняти virtual void OnReceive ( int nErrorCode ) ;

//повідомлення WM_SOCKET_NOTIFY, lParam==FD_WRITE //1CL або sCL відправляє дані через Send virtual void OnSend ( int nErrorCode ) ;

//повідомлення WM_SOCKET_NOTIFY, lParam==FD_OOB //1CL або sCL відправляє дані out-of-band (дані з високим пріоритетом) отримав virtual void OnOutOfBandData ( int nErrorCode ) ;

//повідомлення WM_SOCKET_NOTIFY, lParam==FD_ACCEPT //SRV отримав («підслухав») запит на з'єднання від 1CL virtual void OnAccept ( int nErrorCode ) ;

//повідомлення WM_SOCKET_NOTIFY, lParam==FD_CONNECT //1CL отримав повідомлення про те, що сервер підключив його до sCL методом Accept virtual void OnConnect ( int nErrorCode ) ;

//повідомлення WM_SOCKET_NOTIFY, lParam==FD_CLOSE //1CL або sCL отримав повідомлення про те, що його (сокет) закрили virtual void OnClose ( int nErrorCode ) ;

namespace ALX1153 < //константи та допоміжні структури та класи namespace ASocketH < //клас даних користувача клієнта, // використовується за умовчанням class VoidUSERDATA < > ; . . > ;

//батько для сервера та клієнта class ASocketParent : public CAsyncSocket < . . > ;

//клас для 1CL class ASocketClient : public ASocketParent < . . > ;

//клас для SRV template class USERDATA = ASocketH :: VoidUSERDATA > class ASocketServer : public ASocketParent < //клас для sCL class CsCL : public ASocketClient < friend class ASocketServer;

USERDATA m_UserData; // дані користувача ASocketServer * m_pParentServer ; //Покажчик на сервер

public : //добути вказівник на дані користувача USERDATA * Us_GetUserData ( ) const < return (USERDATA *) & m_UserData; > . . > ;

private : //клас-контейнер покажчика на CsCL для розміщення в std::map struct CsCL_container < private : CsCL * m_p; . . CsCL_container ( ASocketServer * pParentServer ) < m_p = new CsCL (pParentServer); >

private : //ServerClientsMap - масив для зберігання серверних клієнтів typedef std :: map int ,CsCL_container > td_ClientsMap; td_ClientsMap m_ServerClientsMap ; . . > ; > ;

  • Конструктор за замовчуванням;
  • Оператор =;
  • Конструктор копіювання.

програми

//Визначення того, що сокет підключений bool Us_IsClient__Connected ( ) ;

//відключення від сервера та закриття сокету void Us_StopWorking ( ) const ;

//перевірка, чи включено отримання події прийому даних bool Us_GetIsEventOn_OnReceive ( ) ;

//зупинити одержання подій прийому/передачі/підключення void Us_PauseEvents ( ) ;

//відновити отримання подій прийому/передачі/підключення voidUs_StartEvents();

//Повертає константу ASocketH::e_net_MaxOnceSendLen static int Us_GetMaxOneSendLen ( ) ;

//перевірка того, що сокет був некоректно відключений //(зв'язок розірвався з незрозумілих причин) bool Us_IsClient__BadDisconnectedAfterLastConnect ( ) const ;

//перевірити об'єкт SOCKET (хендл сокету) на валідність bool Us_IsWorking_checkJustHandle ( ) const ;

// звичайні функції відсилання та прийому int Us_Send (const void * lpBuf, int nBufLen, int nFlags = 0) const; int Us_Receive (void * lpBuf, const int nBufLen, int nFlags = 0);

//викликається з Send при кожній невдалій спробі надіслати дані //(вірніше, коли (CAsyncSocket::Send()==SOCKET_ERROR і //this->GetLastError()==WSAEWOULDBLOCK) //dwdTicksPassed - мс, приблизно скільки пройшло з початку спроби відправити virtual bool VF_FeedBackFromSend_RetTrueIfNeedCancel (DWORD dwdTicksPassed) = 0;

//викликається після закриття клієнта virtual void VF_OnAfterClose ( ) = 0 ;

//викликається після підключення клієнта сервером (коли сервер викликає Accept) virtual void VF_OnConnected ( ) = 0 ;

//викликається, коли є дані прийому (але ще прийняті - //это треба зробити у цьому обробнику) virtual void VF_OnReceive ( ) = 0 ;

програми

//перевірити відповідність індексу та покажчика на серверний клієнт у масиві клієнтів bool Us_CheckClientPointerEqualsTheIndex (DWORD Index_zb, const CsCL * cli_test);

//перевірка сокету на валідність хендла bool Us_IsWorking_checkJustHandle () const;

//перевірка сокету на підключеність bool Us_IsClient__Connected (const CsCL * cli); bool Us_IsClient__Connected (DWORD Index_zb);

//Визначення факту«нештатного» розриву зв'язку bool Us_IsClient__BadDisconnectedAfterLastConnect (DWORD Index_zb);

//отримати покажчик на серверний клієнт за індексом const CsCL * Us_GetClientByIndex (DWORD Index_zb);

//задати максимальну кількість клієнтів void Us_SetClientCount (DWORD ClientsMax_in);

//дістати поточну максимальну кількість клієнтів DWORD Us_GetClientsCount ( ) ;

//запуск сервера bool Us_StartWorkingServer ( WORD wServerPort, int nClientsCountLimit, bool bPauseEventsBeforeWorking ) ;

//зупинка сервера void Us_StopWorking ();

//викликається, коли клієнт відключився virtual void VF_OnAfterClose ( const CsCL * sCL ) = 0 ;

//викликається, коли клієнт підключився virtual void VF_Accepted ( const CsCL * sCL ) = 0 ;

//викликається, сервер запущено virtual void VF_SRV_OnAfterServerCreated ( ) = 0 ;

//викликається з Send при кожній невдалій спробі надіслати дані //(вірніше, коли (CAsyncSocket::Send()==SOCKET_ERROR і //this->GetLastError()==WSAEWOULDBLOCK) //dwdTicksPassed - мс, приблизно скільки пройшло з початку спроби відправити virtual bool VF_FeedBackFromSend_RetTrueIfNeedCancel ( const CsCL * sCL, DWORD dwdTicksPassed ) = 0;

//викликається, коли є дані прийому (але ще прийняті - //это треба зробити у цьому обробнику) virtual void VF_OnReceive ( const CsCL * sCL ) = 0 ;

//добути покажчик на дані користувача USERDATA * Us_GetUserData ( ) const ;

//перевірка сокету на валідність хендла bool Us_IsWorking_checkJustHandle ( ) const

#pragma once #include "ASocket1153.h"

class CMyClient : public ALX1153 :: ASocketClient < virtualvoid VF_OnReceive ( ) < //вручну читаємо напряму із сокета //Us_Receive(. );//опціонально. >

віртуальна порожнеча VF_OnAfterClose ( ) < > віртуальна порожнеча VF_OnConnected ( ) < > віртуальний bool VF_FeedBackFromSend_RetTrueIfNeedCancel ( DWORD dwdTicksPassed ) < якщо (dwdTicksPassed > e_sendTOdefault) < TRACE ( " \r \n \r \n VF_FeedBackFromSend_RetTrueIfNeedCancel припинив надсилання \r \n \r \n " ) ; повернути істину; >

Сон (1); повернути false ; >

віртуальний bool VF_FeedBackFromSend_RetTrueIfNeedCancel ( const CsCL * sCL,DWORD dwdTicksPassed ) < якщо (dwdTicksPassed > e_sendTOdefault) < TRACE ( " \r \n \r \n VF_FeedBackFromSend_RetTrueIfNeedCancel припинив надсилання \r \n \r \n " ) ; повернути істину; >

Сон (1); повернути false ; >

//извещение для сервера virtual void VF_SRV_OnAfterServerCreated ( )

//примінюється, коли чергові повідомлення не обробляються, а поймати //событие підключення клієнта до сервера необхідно. Вихід із функцій //виходить за входом потрібного повідомлення WM_SOCKET_NOTIFY(FD_CONNECT) або //по істеченню таймаута (false). Функція повертає значення Us_IsClient__Connected() //якщо зворотний виклик є і повертає true, то запит переривається і повертає false bool Us_WaitOneConnectEvent ( DWORD dwdTO = 5000 , DWORD dwdTimeStep = 50 , bool ( * WaitCancelCallBack ) ( int percent, void * pData ) = 0 , void * pData = 0 ) ;

//примінюється, коли чергові повідомлення не обробляються, а поймати //побути прихід даних до сервера необхідно. Вихід із функцій //виходить за входом повідомлення WM_SOCKET_NOTIFY(FD_READ) або //по істеченню таймаута. Функція нічого не повертає //якщо callback є і повертає true, то опитування переривається //підходить цілісного повідомлення, але з розбитого на частини. bool Us_WaitOneReceiveEvent ( DWORD dwdTO = 10000, DWORD dwdTimeStep = 50, bool (* WaitCancelCallBack) (int percent, void * pData) = 0, void * pData 9>);

//застосовується, щоб очистити чергу від «непотрібних» повідомлень // WM_SOCKET_NOTIFY(FD_WRITE) void Us_WaitAndRemoveSendEvents();

static bool WaitingReceive (int percent, void * pData) < TRACE ("Чекання даних. Таймаут: %d%% \r \n",%); return false; > > ;

TRACE ("Підключаємося до сервера \r \n"); if (! Client. Us_StartWorkingClient ("localhost", 1234, false)) return false;

TRACE ("Чекаємо асепт \r \n"); if (! Client. Us_WaitOneConnectEvent (5000, 100, s_show :: WaitingConnect, this)) return false; if ( ! Client. Us_IsClient__Connected ( ) ) return false ; TRACE ("Підключилися \r \n");

//Видаляємо з черги повідомлення відправки //Якщо цього зробити, це повідомлення завадить //"побачити" повідомлення отримання даних Client. Us_WaitAndRemoveSendEvents();

TRACE ("Чекаємо відповідь \r \n"); //скидаємо прапор прийняття відповіді //(Прапор встановиться в Client.VF_OnReceive(), якщо дані-відповідь вірні) Client. m_bAnswerGot = false; if (! Client. Us_WaitOneReceiveEvent (5000, 100, s_show :: WaitingReceive, this)) return false;

//Сервер надіслав дані. // Перевіряємо, чи піднявся прапор if ( ! Client. m_bAnswerGot ) return false ;

TRACE ("аторизація отримана! \r \n"); return true; >