// aplSocketServ.cpp : implementation file
//

#include "stdafx.h"
#include "aplSocketTransport.h"
#include "commands.h"
#include <afxpriv.h>
#include <apl_gui.h>


#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif


bool global_mode_www=false;
bool global_mode_mail=false;


CRITICAL_SECTION LstenServList;

#define PSS_WINDOW_TITLE APL_T("Окно обработки сообщений сокета PSS")


// Счетчик ошибок создания окна в InitInstance
long count_init_errors = 0;
long count_socket_data_errors = 0;
long is_set_global_exit = 0;

// время жизни неактивных клиентов, работающих через http|rest
long global_timeout_life_noactive_http = TIMEOUT_LIFE_NOACTIVE_HTTP;

void (__cdecl *SetGlobalExit)()=0;


void SetFunctionGlobalExit(void (__cdecl *prevSetGlobalExit)())
{
	SetGlobalExit = prevSetGlobalExit;
}

void SetApplicationGlobalExit(bool _is_set_global_exit)
{
	if(_is_set_global_exit)InterlockedExchange(&is_set_global_exit,1);
}


void (__cdecl *GlobalEnvProtect)(enumEnvProtect param)=0;

void SetFunctionGlobalEnvProtect(void (__cdecl *prevOracleEnvProtect)(enumEnvProtect param))
{
	GlobalEnvProtect = prevOracleEnvProtect;
}

void (__cdecl *ProcessAplExeption)(SaplErrorDescription &error)=0;

void SetFunctionProcessAplExeption(void (__cdecl *prevProcessAplExeption)(SaplErrorDescription &error))
{
	ProcessAplExeption = prevProcessAplExeption;
}

long GetSocketInitErrors()
{
	return InterlockedExchangeAdd((LONG*)&count_init_errors,0);
}

long GetNumErrdataSocket()
{
	return InterlockedExchangeAdd((LONG*)&count_socket_data_errors,0);
}

// являются ли первые 4 байта началом http запроса - есть ли там GET POST и т.п. перевернутые из сетевого порядка байт
bool Check4ByteHttp(UINT32 *nRead)
{
	if(nRead == 0)
		return false;

	switch(*nRead)
	{
	case 0x20544547: // GET 
	case 0x54534f50: // POST 
	case 0x2054534F: // PUT 
	case 0x454C4544: // DELETE
	case 0x44414548: // HEAD
	case 0x4954504F: // OPTION
	case 0x43415254: // TRACE
	case 0x43544150: // PATCH

		return true;
		break;
	}

	return false;
}

// Макросы или просто выполняют работу с критическими секциями, или 
// выводят в стандартный лог сообщения о работе с секциями
// в отладочном режиме требует рядом с определением критической секции 
// определения переменной типа long с именем ИМЯ_СЕКЦИИ+"_COUNTER"
// для переключения в отдладочный режим в настройках проекта вкладка C++
// должен быть определен макрос _DEBUG_LOG_CRTITICAL_SECTION

#ifdef _DEBUG_LOG_CRTITICAL_SECTION 

long m_IsBlockedInternalBuf_COUNTER=0;
long m_OperateList_COUNTER=0;
long LstenServList_COUNTER=0;

long m_OperateListClients_COUNTER=0;

long m_MultithreadVarProtect_COUNTER=0;


#endif

#pragma intrinsic( memcpy,memset)

/////////////////////////////////////////////////////////////////////////////
// CPssServWnd  -- internal use only
//  Окно для отработки внутреннего таймера сервера


class CPssServWnd : public CWnd
{
// Construction
public:
	CPssServWnd();
	CaplSocketServ *serv;
	CString m_class_info;

protected:
	//{{AFX_MSG(CSocketWnd)
	afx_msg void OnTimer(UINT nIDEvent);
	//}}AFX_MSG
	DECLARE_MESSAGE_MAP()
};

// Do not edit the following lines, which are needed by ClassWizard.
BEGIN_MESSAGE_MAP(CPssServWnd, CWnd)
	//{{AFX_MSG_MAP(CPssServWnd)
	ON_WM_TIMER()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

CPssServWnd::CPssServWnd()
{
	m_class_info.Format(_T("CPssServWnd " POINTER_FORMAT ),this);
	serv=0;
}

void CPssServWnd::OnTimer(UINT nIDEvent) 
{
	// TODO: Add your message handler code here and/or call default
	CString str=_T("");

	try{
		if(serv!=0)
		{
			serv->m_funct_info=__APL_FUNC__;
			
			if(nIDEvent==ID_TIMER_BLOCK)
			{
				try
				{
					//LogDebugMessageIfDef(_T("ID_TIMER_BLOCK");
					//LOG_WARN_C_P(serv, _T("ID_TIMER_BLOCK..."));
					int buf=0,size=4;
					if(serv->IsReadyData())
					{
						CaplStackLogger stack_logger(__APL_FUNC__);
						size=serv->m_LastReadyData;
						LogDebugMessageIfDefFormat1( APL_T("По таймеру блокировки ID_TIMER_BLOCK пытаемся прочитать из канала остаток данных; всего нужно получить %i байт"),size);
						str.Format(APL_T("По таймеру блокировки ID_TIMER_BLOCK пытаемся прочитать из канала остаток данных; всего нужно получить %i байт"),size);
						stack_logger.SetFunctionInfo(str);

						if(serv->Receive(&buf,4,MSG_PEEK)!=-1)
						{
							stack_logger.SetFunctionInfo(_T("ID_TIMER_BLOCK... - gettingg"));
							LOG_WARN_C_P(serv, APL_T("Начали получать данные"));
						}
						else
						{
							stack_logger.SetFunctionInfo(_T("ID_TIMER_BLOCK... - error reading"));
							LOG_WARN_P(serv, APL_T("Не удалось прочитать данные из канала"));
						}
					}
		/*	Вывод в лог информации о размерах TCP буферов
					int send_buf,recv_buf,len=4;
					serv->GetSockOpt(SO_RCVBUF,&recv_buf,&len);
					serv->GetSockOpt(SO_SNDBUF,&send_buf,&len);
					str.Format("------------ Размер буфера на прием %i на передачу %i",recv_buf,send_buf);
					serv->LOG_WARN_C(str);
		*/
				}
				RETRANSLATE_CMEMORY_EXEPTION;
			}
			else if(nIDEvent==ID_TIMER_RENEW)
			{
				CaplStackLogger stack_logger(__APL_FUNC__);
				stack_logger.SetFunctionInfo(_T("ID_TIMER_RENEW"));
				LogDebugMessageIfDef(_T("ID_TIMER_RENEW"));
				serv->SendCommand(CMDRenewClientState);
 			}
			else if(nIDEvent==ID_TIMER_PROCESS_CLIENT_OVER_HTTP)
			{
				CaplStackLogger stack_logger(__APL_FUNC__);
				stack_logger.SetFunctionInfo(_T("ID_TIMER_PROCESS_CLIENT_OVER_HTTP"));
				LogDebugMessageIfDef(_T("ID_TIMER_PROCESS_CLIENT_OVER_HTTP"));
				try
				{
					serv->ProcessExternalHTTPClient();
				}
				RETRANSLATE_CMEMORY_EXEPTION;
			}
			else
			{
				//stack_logger.SetFunctionInfo(APL_T("Пользовательский таймер"));
				try
				{
					serv->ProcessUserTimer(nIDEvent);
				}
				RETRANSLATE_CMEMORY_EXEPTION;
			}
		}
		CWnd::OnTimer(nIDEvent);
	}
	catch(SaplErrorDescription error)
	{
		if(error.m_err_code == SAD_MEMORY_EXEPTION)
		{
			CString str,str1;
			str=_T("CMemoryException!!! ");
			str+=error.m_err_descr;
			aplGetDescriptionSystemError(GetLastError(),str1);str+=APL_T("\nПоследняя системная ошибка: ") + str1;
			TRACE(str);
			LOG_ERROR(str);	
			if(MainModuleLog!=0){MainModuleLog->PrintMyEmergencyLog(0,_T("CPssServWnd::OnTimer - catch(SaplErrorDescription error)"));}
			if(SetGlobalExit!=0)SetGlobalExit();
		}
		else if(ProcessAplExeption!=0)
		{
			SaplErrorDescription error2(error,_T(__FILE__),_T(__DATE__),__LINE__,_T(""),__APL_FUNC__,m_class_info);
			ProcessAplExeption(error2);
		}
	}
	APL_CATCH_ALL_EXEPTION(;,LOG_ERROR,{if(MainModuleLog!=0){MainModuleLog->PrintMyEmergencyLog(0,_T("CPssServWnd::OnTimer - APL_CATCH_ALL_EXEPTION"));}if(SetGlobalExit!=0)SetGlobalExit();};);
}




CSocketThread::CSocketThread(CaplSocketServ *pool,CaplSocketServ* pSocket)
{
	CaplStackLogger stack_logger(__APL_FUNC__);

	m_class_info.Format(_T(" CSocketThread ") POINTER_FORMAT _T(" "),this);

	m_state_kill=0;
	m_bAutoDelete=TRUE;
//	m_event=CreateEvent(0,TRUE,FALSE,0);

	m_pSocket=pSocket;
	m_pool=pool;
	LogDebugMessageIfDef( APL_T("Добавляем в пул нить ")+m_class_info);

	if(m_pool!=0){m_pool->PoolAddSocketThread(this);}

	LogDebugMessageIfDef( APL_T("Добавили"));
}

CSocketThread::~CSocketThread()
{
	CaplStackLogger stack_logger(__APL_FUNC__);

	// удаляем нить из пула слушающего сервера
	if(m_pool!=0)m_pool->PoolRemoveSocketThread(this);

	// грохаем окно и обнуляем сокет
	if(m_pMainWnd!=0)
	{
		//stack_logger.SetFunctionInfo("01");
		LogDebugMessageIfDef( APL_T("Удаляем окно ")+((CPssServWnd*)m_pMainWnd)->m_class_info);
		if(m_pMainWnd->m_hWnd!=0 && IsWindow(m_pMainWnd->m_hWnd))
		{
			m_pMainWnd->KillTimer(ID_TIMER_BLOCK);
		}
		//stack_logger.SetFunctionInfo("02");
		if(m_nThreadID != GetCurrentThreadId())
		{
			// объект нити удаляют из другой нити-пула
			// скорее всего, нить была терминирована и окна уже нет
			// и в деструткоре CWnd при вызове CWnd::DestroyWindow() получим Aplication Error
			// чтобы такого не было - 
			m_pMainWnd->m_hWnd = 0;
		}
		else
		{
			m_pMainWnd->CloseWindow();
		}
		//m_pMainWnd->DestroyWindow();
		delete m_pMainWnd;
		m_pMainWnd=0;
	}
	//stack_logger.SetFunctionInfo("10");
	if(m_pSocket!=0)
	{
		//stack_logger.SetFunctionInfo("11");
		CString str;
		// - сокет должен удаляться из ExitInstance! Но если нить была прервана TerminateThread - ExitInstance никто не вызвал
		// так что если н здесь еще не нулевой - это ошибка
		str.Format( APL_T("В деструкторе нити есть указатель на сокет! " POINTER_FORMAT ),m_pSocket);
		LOG_WARN(str);
		// нити уже походу нет, и в деструкторе CSocket нарвется на ошибку, если будет бывший сокет детачить
		// поэтому ставим ему инвалидный сокет
		m_pSocket->m_hSocket = INVALID_SOCKET;
		// ну и очищаем память
		//4 delete m_pSocket; //4 - вдруг поможет не виснуть при аварийном зхавершении главного сервера в процессе долгой операции
		m_pSocket=0;
	}

	LogDebugMessageIfDef( APL_T("Удаляем из пула себя - нить ")+m_class_info);


}


BOOL CSocketThread::InitInstance()
{
	CaplStackLogger stack_logger(__APL_FUNC__);

	CString str;
	m_pMainWnd=0;
	int line = 0;
	int num_attempt = 0;
	int max_num_attempt = 5;
	bool is_error = false;
	bool socket_attached = false;
	BOOL wnd_created = false;

	//#define LOG_WARN_C(text)	
	if(MainModuleLog!=0 && MainModuleLog->m_LogInfoLevel>=LogLevelFunctionInfo)
	{
		CString str;
		str.Format(_T("Begin CSocketThread::InitInstance thread ") THREAD_FORMAT ,this->m_nThreadID);
		MainModuleLog->LogMessage(str,true,true,true,true,__APL_FUNC__,(LPCTSTR)m_class_info,0,_T(__FILE__),_T(__DATE__),__LINE__);
#ifdef _DEBUG
		OutputDebugString(str);
#endif
	}

point1:

	try{
		line = __LINE__;

		// нить без созданного снаружи сокета смысла не имеет
		if(m_pSocket==0)
		{
			line = __LINE__;
			DEBUG_BEEP(1000,100);
			LOG_ERROR(_T("!! m_pSocket == 0 !!"));	
			return FALSE;
		}

		line = __LINE__;
		//	Во время выключения допустимы http-запросы - поэтому  закоментарен следующий кусок
		// 	if(m_pSocket->m_stopping)
		// 	{
		// 		m_pSocket->SendCommand(CMDDeadTime,5);
		// 		str="Ошибка: Попытка подключения во время выключения!";
		// 		LOG_ERROR_P(m_pSocket,str);
		// 		delete m_pSocket;
		// 		m_pSocket=0;
		// 		DEBUG_BEEP(1000,100);
		// 		return FALSE;
		// 	}

		line = __LINE__;

		LOG_WARN_C_P(m_pSocket,_T("Starting..."));
		
		// создаем окно для отработки таймеров
		line = __LINE__;

		if(m_pSocket->m_wnd == 0)
		{
			m_pSocket->m_wnd = new CPssServWnd();
		}

		line = __LINE__;

		if(m_pSocket->m_wnd ==0)
		{
			line = __LINE__;
			//m_pSocket->SendCommand(CMDTooManyClients);
			InterlockedIncrement(&count_init_errors);
			LOG_ERROR(_T("!! m_pSocket->m_wnd == 0 !!"));	
			return FALSE;
		}
		if(m_pSocket->m_wnd->m_hWnd !=NULL)
		{
			LOG_WARN(APL_T("Внимание! Ссылка m_pSocket->m_wnd->m_hWn не пустая!"));
		}
		
		line = __LINE__;

		// устанавливаем обратную связь от окна таймера к сокету
		m_pSocket->m_wnd->serv=m_pSocket;

		// запускаем окно
		line = __LINE__;

		if(wnd_created == FALSE)
		{
			EnterCriticalSectionODS(&LstenServList);
			LPCTSTR emptywndclass = AfxRegisterWndClass(0); 
			LeaveCriticalSectionODS(&LstenServList);
			wnd_created = m_pSocket->m_wnd->CreateEx(0, emptywndclass,PSS_WINDOW_TITLE,0, 0, 0, 0, 0, NULL, NULL);
		}

		if (wnd_created == FALSE)
		{
			CString str;
			GetDescriptionSystemError(GetLastError(),str);
			stack_logger.SetFunctionInfo(_T("!!!!!!! Error creating socket window !!! ") + str);
			line = __LINE__;
			LOG_ERROR( APL_T("Ошибка создания окна обработки сообщений сокета: ")+str);
			//m_pSocket->SendCommand(CMDTooManyClients); - глупость, сокет не привязан к потоку и пропускаь данные не может
			if(num_attempt < max_num_attempt)
			{
				num_attempt++;
				Sleep(500);
				str.Format(APL_T("Внимание! Повторяем попытку создать окно (попытка %i)"),num_attempt+1);
				LOG_WARN(str);
				goto point1;
			}
			InterlockedIncrement(&count_init_errors);
			return FALSE;
		}

		line = __LINE__;
		// ну и теперь при нити и окне - аттачим сокет
		m_pSocket->m_thread_id=GetCurrentThreadId();

		line = __LINE__;

		if(!socket_attached)
		{
			socket_attached = m_pSocket->AttachTempSocket();
		}

		line = __LINE__;

		m_pSocket->m_thread=this;

		int iOptVal = 1;
		int iOptLen = sizeof(int);
		m_pSocket->SetSockOpt(SO_KEEPALIVE,(void*)&iOptVal, iOptLen);

		line = __LINE__;
		m_pSocket->PrevInit();

		if(!m_pSocket->Init())
		{
			stack_logger.SetFunctionInfo(_T("51"));
			m_pSocket->SendCommand(CMDErrorInitServer);
			str= APL_T("Ошибка: Ошибка в перегруженной процедуре Init");
			LOG_ERROR_P(m_pSocket,str);
			delete m_pSocket;
			m_pSocket=0;
			InterlockedIncrement(&count_init_errors);
			DEBUG_BEEP(1000,100);
			return FALSE;
		}

		line = __LINE__;

		// запоминаем указатель на окно для отработки таймеров у себя
		m_pMainWnd=m_pSocket->m_wnd;

		m_class_info.Format( _T(" CSocketThread ") POINTER_FORMAT _T(" (") THREAD_FORMAT _T("); m_pSocket ") POINTER_FORMAT _T("; socket %X"),
								this, m_nThreadID, m_pSocket, m_pSocket->m_hSocket);

		line = __LINE__;
		str.Format( APL_T("; Клиент %s:%i; Окно "),(LPCTSTR)(m_pSocket->m_client_ip_s),m_pSocket->m_client_port);

		m_class_info+=str+m_pSocket->m_wnd->m_class_info;

		LogDebugMessageIfDefFormat1(APL_T("Заполнили m_class_info"));

		// к этой команде вторым байтом - номер версии транспорта, третьим - указатель на класс сокета
		// m_pSocket->SendCommand(CMDConnectionOKNew,VERSION_NUMBER,(int)this,__FILE__,__LINE__); 
		
		line = __LINE__;

		if(num_attempt > 0)
		{
			str.Format(APL_T("Окна для сокетов созданы на %i попытке. Сокет: "),num_attempt+1);
			LOG_WARN(str+m_pSocket->m_class_info);
		}
		else
		{
			LOG_WARN_C( APL_T("Создана нить для сокета ")+m_pSocket->m_class_info);
		}
		is_error = false;

		// считаем наличие данных достаточным основанием, что это клиент поверх http или rest
		if(/*m_pSocket->m_client_over_http &&*/ m_pSocket->m_session_data.m_Size>0)
		{
			// такой запрос как правило приходит с некоторым буфером. Надо принять остаток буфера и выполнить запрос
			m_pSocket->ProcessExternalHTTPClient();
		}
		line = __LINE__;
	}
	catch(SaplErrorDescription error)
	{
		CString str,str1;
		is_error = true;
		if(error.m_err_code == SAD_MEMORY_EXEPTION)
		{
			str.Format(_T("CMemoryException!!! Last line %i. "),line);
			str+=error.m_err_descr;
			aplGetDescriptionSystemError(GetLastError(),str1);str+=APL_T("\nПоследняя системная ошибка: ") + str1;
			if(m_pSocket!=0){m_pSocket->GetClientIpPortString(str1);str+=APL_T("Параметры сокета: ") + str1;}
			stack_logger.SetFunctionInfo(str);
			TRACE(str);
			LOG_ERROR(str);	
			if(MainModuleLog!=0){MainModuleLog->PrintMyEmergencyLog(0,_T("CSocketThread::InitInstance() - catch(SaplErrorDescription error)"));}
			if(num_attempt >= max_num_attempt)
			{
				if(SetGlobalExit!=0)SetGlobalExit();
			}
		}
		else if(ProcessAplExeption!=0)
		{
			str.Format(_T("Last line %s:%i.\n"),_T(__FILE__), line);
			SaplErrorDescription error2(error,_T(__FILE__),_T(__DATE__),__LINE__,str,__APL_FUNC__,m_class_info);
			ProcessAplExeption(error2);
		}

	}
	catch(CMemoryException* e)
	{ 
		is_error = true;
		TCHAR   szCause[255];e->GetErrorMessage(szCause,255);CString str,str1; str = _T("CMemoryException!!!"); str+=szCause; 
		aplGetDescriptionSystemError(GetLastError(),str1);str+=APL_T("\nПоследняя системная ошибка: ") + str1; 
		str1.Format(_T("Last line %s:%i.\n"),_T(__FILE__), line);str+=str1;
		if(m_pSocket!=0){m_pSocket->GetClientIpPortString(str1);str+=APL_T("Параметры сокета: ")+str1;};
		stack_logger.SetFunctionInfo(str);
		LOG_ERROR(str); 
		if(MainModuleLog!=0){MainModuleLog->PrintMyEmergencyLog(0,_T("CSocketThread::InitInstance() - CMemoryException"));}
		if(num_attempt >= max_num_attempt)
		{
			InterlockedIncrement(&count_init_errors);
			if(SetGlobalExit!=0)SetGlobalExit();
		}
	}
	catch (CException* e)
	{ 
		is_error = true;
		TCHAR   szCause[255];e->GetErrorMessage(szCause,255);CString str,str1;str=_T("CException!!!");str+=szCause; 
		aplGetDescriptionSystemError(GetLastError(),str1);str+=APL_T("\nПоследняя системная ошибка: ")+str1; 
		str1.Format(_T("Last line %s:%i.\n"),_T(__FILE__), line);str+=str1;
		if(m_pSocket!=0){m_pSocket->GetClientIpPortString(str1);str+=APL_T("Параметры сокета: ")+str1;};
		stack_logger.SetFunctionInfo(str);
		LOG_ERROR (str); 
		if(MainModuleLog!=0){MainModuleLog->PrintMyEmergencyLog(0,_T("CSocketThread::InitInstance() - CException"));}
		if(num_attempt >= max_num_attempt)
		{
			InterlockedIncrement(&count_init_errors);
			if(SetGlobalExit!=0)SetGlobalExit();
		}
	}
	catch(...)
	{ 
		is_error = true;
		CString str,str1;str = _T(" Unknown Exception!!!"); 
		aplGetDescriptionSystemError(GetLastError(),str1);str+=APL_T("\nПоследняя системная ошибка: ")+str1; 
		str1.Format(_T("Last line %s:%i.\n"),_T(__FILE__), line);str+=str1;
		if(m_pSocket!=0){m_pSocket->GetClientIpPortString(str1);str+=APL_T("Параметры сокета: ")+str1;};
		stack_logger.SetFunctionInfo(str);
		LOG_ERROR (str); 
		if(MainModuleLog!=0){MainModuleLog->PrintMyEmergencyLog(0,_T("CSocketThread::InitInstance() - Unknown Exception"));}
		if(num_attempt >= max_num_attempt)
		{
			InterlockedIncrement(&count_init_errors);
			if(SetGlobalExit!=0)SetGlobalExit();
		}
	}

	if(is_error && num_attempt < max_num_attempt)
	{
		num_attempt++;
		Sleep(500);
		str.Format(APL_T("Внимание! Повторяем попытку создать окно (попытка %i)"),num_attempt+1);
		LOG_WARN(str);
		goto point1;
	}
	LOG_WARN_C(_T("End"));

	return TRUE;
}	


int CSocketThread::ExitInstance()
{
	CaplStackLogger stack_logger(__APL_FUNC__);

//	ResetEvent(m_event);
#ifdef _DEBUG
	CString str;
	str.Format(_T("CSocketThread::ExitInstance tread ") THREAD_FORMAT _T("\n"),this->m_nThreadID);
	OutputDebugString(str);
#endif

	CaplSocketServ* pSocket;
	bool web_query=false;

	if(m_pool!=0)m_pool->PoolOperateListEnter();

	if(m_pSocket!=0) web_query=(m_pSocket->m_terminate_after.Get()>0);
	pSocket=m_pSocket;
	m_pSocket=0;

	if(m_pool!=0)m_pool->PoolOperateListLeave();

	// а собственно удаление - обязательно снаружи критической секции!
	// ибо при удалении транспорт посылает главному серверу команду исключить себя из списка!
	if(pSocket!=0)
	{
		LogDebugMessageIfDef(CString( APL_T("Завершаем нить сокета "))+pSocket->m_class_info);
		pSocket->m_thread=0;// чтобы не зациклиться при обнулениях
		pSocket->Close();
		delete pSocket;
		pSocket=0;
	}

//	SetEvent(m_event);

	if(InterlockedExchangeAdd(&m_state_kill,0)>0 && !web_query)
	{
		// в логе есть запись, что эту нить тормозят. ДОбавим, что мы завершились успешно
		LOG_WARN_C(_T("Exited!"));
	}
	_heapmin();
	return 0;
}

CAplAtomicLong counter(0);

int CaplStrStrMapLo::Add(LPCTSTR _str, LPCTSTR _val)
{
	CString str = _str; ReplaseServiceSymbols(str);
	CString val = _val; ReplaseServiceSymbols(val);
	CString str_l = str;
	if(str==_T("Set-Cookie")) // Кук может быть несколько в заголовке
	{
		str_l.Format(_T("uniq-%i"), counter.Increment());
		return CaplStrMap::Add(str_l, new SStrMapLoItem(str, val));
	}
	str_l.MakeLower();
	int indx=CaplStrMap::Find(str_l);
	if(indx==-1)
		return CaplStrMap::Add(str_l, new SStrMapLoItem(str, val));

	SStrMapLoItem *item = (SStrMapLoItem*)items[indx]->val;
	if(item==0){items[indx]->val = (INT_PTR)(new SStrMapLoItem(str, val));}
	else{ item->key_orig=str; item->val=val;}

	return indx;
}

int CaplStrStrMapLo::Add(LPCTSTR _str, int ival)
{
	CString sval;
	sval.Format(_T("%i"),ival);
	return Add(_str, sval);
}

int CaplStrStrMapLo::Replace(LPCTSTR str, LPCTSTR val)
{
	int indx = FindByIn(str);
	if(indx != -1)
		Remove(indx);

	return Add(str, val);
}

int CaplStrStrMapLo::FindByIn(LPCTSTR str) const
{
	CString str_l = str;
	str_l.MakeLower();
	return CaplStrMap::Find(str_l);
}

CString CaplStrStrMapLo::Get(LPCTSTR str) const
{
	CString str1 = str;
	str1.MakeLower();
	SStrMapLoItem *item = (SStrMapLoItem*)CaplStrMap::GetP(str1);
	if(item==0) return CString(_T(""));
	return item->val;
}

CString CaplStrStrMapLo::Get(int indx) const
{
	SStrMapLoItem *item = (SStrMapLoItem*)CaplStrMap::GetAt(indx)->val;
	if(item==0) return CString(_T(""));
	return item->val;
}

CString CaplStrStrMapLo::GetKey(int indx) const
{
	SStrMapLoItem *item = (SStrMapLoItem*)CaplStrMap::GetAt(indx)->val;
	if(item==0) return CString(_T(""));
	return item->key_orig;
}

void CaplStrStrMapLo::Clear()
{
	for(int i=0; i<items.GetSize(); i++)
		delete ((SStrMapLoItem*)items[i]->val);
	
	CaplStrMap::Clear();
}

CString CaplStrStrMapLo::PrintList(bool html_table)
{
	CString report(_T(""));
	for(int i=0; i< GetSize(); i++)
	{
		if(html_table)
			report += CString(_T("")) + APL_T("<tr><td><b>") + GetKey(i) + _T(":</b></td><td>") + Get(i) + _T("</td></tr>\n");
		else
			report += GetKey(i) + _T(": ") + Get(i) + _T("\n");
	}
	return report;
}


/////////////////////////////////////////////////////////////////////////////
// CaplSocketServ

CaplSocketServ::CaplSocketServ()
{
	CaplStackLogger stack_logger(__APL_FUNC__);

	InitializeCriticalSection(&m_OperateList);
	InitializeCriticalSection(&m_MultithreadVarProtect);

	m_pool=0;
	m_thread=0;
	m_serv_seans_id = 0;
	m_max_client=0;
	m_wnd=0;
	m_connect_status=true;
	m_num_data_connects=0;
	m_client_ip_s.Empty();
	m_client_ip=0;
	m_client_port=0;
	m_server_ip_str = _T("");
	m_funct_info = _T("");
	m_server_port=0;
	m_ClearedTimeInterval = INTERVAL_TIMER_CLEANUP;
	m_start_time = COleDateTime::GetCurrentTime();
	m_thread_id = 0;
	m_LastReadyData = 0;
	m_onclose_enter = 0;
	m_stop_time = -1;
	m_detach_after_send = false;
	//m_client_over_http = false;
	m_client_type = ECT_Unknown;
	m_ClientId = 0;

	m_indx_start_data = 0;
	m_session_key = _T("");

	m_free = CreateEvent(0, FALSE, TRUE, 0);

	m_supress_command = 0;
	m_wait_reconnect = false;

	m_size_waited=0;

	m_server_ip=0;
	// функция gethostname только однобайтная
	char name[256];
	int res = gethostname (name,256);
	if(res==0)
	{
		SOCKADDR_IN contact_sin;
		PHOSTENT phe = gethostbyname(name);
		if(phe!=0)
		{
			//Копируем адрес узла
			memcpy((char FAR *)&(contact_sin.sin_addr),phe->h_addr,phe->h_length);
			memcpy(&m_server_ip,&contact_sin.sin_addr,4);
			m_server_ip_str = Ip2String(m_server_ip);
		}
	}
	m_PostProcessing = 0;
	m_PostProcessingObj = 0;

	MakeClassInfo();

	LogDebugMessageIfDef( APL_T("Создали"));
}

CaplSocketServ::~CaplSocketServ()
{
	CaplStackLogger stack_logger(__APL_FUNC__);

#ifdef _DEBUG
	CString str;
	str.Format(_T("CaplSocketServ::~CaplSocketServ tread ") THREAD_FORMAT _T("\n"),GetCurrentThreadId());
	OutputDebugString(str);
#endif
	int i;
	DWORD timer_id;
	
	if(m_wnd != 0)
	{
		EnterLogCriticalSection(&m_OperateList);
		for(i=0;i<m_clients_timers.Size;i++)
		{
			timer_id = m_clients_timers[i].in;
			m_wnd->KillTimer(timer_id);
		}
		LeaveLogCriticalSection(&m_OperateList);
	}

	LogDebugMessageIfDef(_T("Begin"));
	DisconnectAll();

	if(m_wnd!=0)m_wnd->serv=0;
	m_wnd=0;

	if(m_thread!=0)
	{
		m_thread->m_pSocket=0;
	}
	m_thread=0;

	CloseHandle(m_free);
	DeleteCriticalSectionODS(&m_OperateList);
	DeleteCriticalSectionODS(&m_MultithreadVarProtect);

	LogDebugMessageIfDef(_T("'End "));
}

// функция формирует описание класса для вывода в лог
void CaplSocketServ::MakeClassInfo(LPCTSTR additional)
{
	m_class_info.Format(_T(" CaplServSocketQt ") POINTER_FORMAT _T("; socket %X; client_ip: %s:%i; thread ") THREAD_FORMAT,
		this, m_hSocket, (LPCTSTR)m_client_ip_s, m_client_port, m_thread_id);

	m_class_info += _T(" type: ") + EClientType2Str(m_client_type);
	m_class_info += _T(" created: ");
	m_class_info += m_start_time.Format(_T("%d.%m.%Y %H:%M:%S"));
	m_class_info += additional;
}


bool CaplSocketServ::PrevInit()
{
	if(m_client_ip==0)
	{
		// определяем параметры клиента для вывода в лог, устанавливаем параметры сокета и т.п.
		int iOptVal = 1;
		int iOptLen = sizeof(int);
		sockaddr_in SockAddr;
		int SockAddrLen=sizeof(SOCKADDR); 
		GetPeerName((struct sockaddr*)&SockAddr, &SockAddrLen);

		m_client_ip=SockAddr.sin_addr.s_addr;
		m_client_port=ntohs(SockAddr.sin_port);
		m_client_ip_s = Ip2String(m_client_ip);
		SetSockOpt(SO_KEEPALIVE,(void*)&iOptVal, iOptLen);

		MakeClassInfo();
	}
	return true;
}



UINT CaplSocketServ::SetUserTimer(DWORD elapse, AFX_THREADPROC lpfnTimer)
{
	CaplStackLogger stack_logger(__APL_FUNC__);

	int i;
	UINT timer_id = ID_TIMER_START_CLIENT_ID;

	if(m_wnd == 0) return 0;

	PoolOperateListEnter();
	for(i=0;i<m_clients_timers.Size;i++)
	{
		if( timer_id <= (UINT)m_clients_timers[i].in )
		{
			timer_id = m_clients_timers[i].in + 1;
		}
	}
	m_clients_timers.Add(timer_id,(long)lpfnTimer);
	PoolOperateListLeave();
	m_wnd->SetTimer(timer_id,elapse,0);
	return timer_id;
}

bool CaplSocketServ::KillUserTimer(UINT timer_id)
{
	CaplStackLogger stack_logger(__APL_FUNC__);

	int i;
	if(m_wnd == 0) return false;
	bool retval = false;

	PoolOperateListEnter();
	for(i=0;i<m_clients_timers.Size;i++)
	{
		if(timer_id == m_clients_timers[i].in)
		{
			m_clients_timers.Remove(i);
			retval = true;
			break;
		}
	}
	PoolOperateListLeave();
	return retval;
}

bool CaplSocketServ::ProcessUserTimer(UINT timer_id)
{
	CaplStackLogger stack_logger(__APL_FUNC__);

	int i;
	AFX_THREADPROC lpfnTimer = 0;
	if(m_wnd == 0) return false;

	PoolOperateListEnter();
	for(i=0;i<m_clients_timers.Size;i++)
	{
		if(timer_id == m_clients_timers[i].in)
		{
			lpfnTimer = (AFX_THREADPROC) m_clients_timers[i].out;
		}
	}
	PoolOperateListLeave();
	if(lpfnTimer == 0) return false;
	lpfnTimer(this);
	return true;
}


/////////////////////////////////////////////////////////////////////////////
// Удаление всех приконнектившихся
BOOL CaplSocketServ::DisconnectAll()
{
	CaplStackLogger stack_logger(__APL_FUNC__);

	LogDebugMessageIfDef(_T("Begin"));

	CString str;
	int i;
	DWORD nCount=0; 
//	DWORD dExitCode; 
//	DWORD t1,t2;
	HANDLE *h_arr;
	DWORD res=0;
// 	try{
		if(m_pool==0)
		{
			// перебираем нити и удиваем их - сначала ставя флаг а потом - прерывая terminate-ом
			// хаодно формируем массив эендлов для ожидания
			PoolOperateListEnter();
			nCount=m_list.GetSize(); 
			h_arr=new HANDLE[nCount];
			for(i=0;i<m_list.GetSize();i++)
			{
				h_arr[i]=m_list[i]->m_hThread;
			}
			// два цикла - так как при вызове KillThread размер массива m_list будет меняться
			for(i=0;i<m_list.GetSize();i++)
			{
				KillThread(i,false,_T("CaplSocketServ::DisconnectAll - 0"));
			}
			PoolOperateListLeave();
			//t1=GetTickCount();
			if(nCount>0)
			{
				// и вот от тех, кто остался - ждем уже завершения по хендлам нитей
				// ждем 3 секнуды, ну или пока все нити не завершатся
				res=WaitForMultipleObjects(nCount,h_arr,TRUE,3000);
				/*
				if(WAIT_OBJECT_0<= res && res<WAIT_OBJECT_0+nCount)
				{
					LOG_WARN("__222__ - all signaled");
				}
				else */ 
				if(WAIT_ABANDONED_0<= res && res<WAIT_ABANDONED_0+nCount)
				{
					LOG_WARN( APL_T("Среди event-ов есть некорректные"));
				}
				else if(res == WAIT_TIMEOUT)
				{
					LOG_ERROR( APL_T("Ожидание прервано по тайм-ауту!"));
				}
				else if(res == WAIT_FAILED)
				{
					GetDescriptionSystemError(GetLastError(),str);
					//LOG_ERROR( APL_T("Ожидание прервано из-за ошибки: ")+str);
				}
			}
			//t2=GetTickCount();
 			// проверка оставшихся и удаление нити системными средствами
			// удаляем до тех пор, пока ни одной нити не останется в списке
			PoolOperateListEnter();
			while(m_list.GetSize()>0)
			{
				KillThread(0,true,_T("CaplSocketServ::DisconnectAll - 1"));
			};

			PoolOperateListLeave();

// 			for(i=0;i<nCount;i++)
// 			{
// 				GetExitCodeThread(h_arr[i],&dExitCode);
// 				if(dExitCode==STILL_ACTIVE)
// 				{
// 					TerminateThread(h_arr[i],0);
// 				}
// 			}
			delete[] h_arr;
		}	
// 	}catch(CMemoryException* e){
// 		TCHAR   szCause[255];
// 		e->GetErrorMessage(szCause,255);
// 		str="CMemoryException";
// 		str+=szCause;
// 		LOG_ERROR(str);	
// 		TRACE(str);
// 	}catch(...){
// 		str="Неизвестное исключение";
// 		TRACE(str);
// 		LOG_ERROR(str);
// 	}

	LogDebugMessageIfDef(_T("'End "));
	
	return TRUE;
}

// функция обязательно должна вызываться внутри критической секции m_OperateList!
// иначе во время исполнения этой функции нить может завершиться и самоудалиться - с непредсказуемыми последствиями!
BOOL CaplSocketServ::KillThread(int pos,bool forsed, LPCTSTR text)
{
	CaplStackLogger stack_logger(__APL_FUNC__);

	CString str_about;
	bool found;
	CSocketThread *t_thread=(CSocketThread*)m_list.GetAt(pos);

	if(t_thread==0)
	{
		str_about.Format( APL_T("Объект нити в позиции %i пустой!"),pos);
		LOG_ERROR(str_about);
		return FALSE;
	}

	str_about.Format( CString(APL_T("Счетчик %i для нити ")) + THREAD_FORMAT _T(" (object ") POINTER_FORMAT _T(") "),
									InterlockedExchangeAdd(&t_thread->m_state_kill,0), t_thread->m_nThreadID, t_thread);

	if(t_thread->m_pSocket!=0){str_about+= APL_T("; Связанный объект: ")+t_thread->m_pSocket->m_class_info;}
	if(!aplIsStringEmpty(text)){str_about+= APL_T("; Доп: ");str_about+=text;}
	LOG_WARN_C(str_about);
	stack_logger.SetFunctionInfo(str_about);

	if(t_thread->m_pSocket!=0)
	{
		if(t_thread->m_pSocket->m_onclose_enter !=0 )
		{
			stack_logger.SetFunctionInfo(APL_T("Нить зашла в OnClose, не будем ей мешать"));
			return FALSE;
		}
	}

	if(InterlockedExchangeAdd(&t_thread->m_state_kill,0)==0)
	{
		// если первый заход в функцию
		// ставим флаг - при следующем плановом заходе в эту функцию нить будет убита насильно. Если конечно, пройдет заданное время
		InterlockedExchange(&t_thread->m_state_kill,1);

		// а пока стадия ноль - проверяем, есть ли сокет и не в работе ли он
		if(t_thread->m_pSocket!=0)
		{
			LOG_WARN_C( APL_T("Фаза 0. Запрещаем новые операции, прерываем текущую: ")+str_about);
			// первым делом запрещаем любые операции по приему и обработке данных
			m_terminate_after.Increment();

			if(WaitForSingleObject(t_thread->m_pSocket->m_free, 1) == WAIT_OBJECT_0)
			{
				// Если сервер сейчас что-то обрабатывает - ставим флаг экстренного прерывания операции, прерываем сессию оракла и т.п.
				// вызываем функцию наследника - чтобы он поставил флаг завершения своим работающим объектам и прервал оракловую сессию
				BreakOperation(t_thread->m_pSocket);
			}
			//else{SetEvent( m_free)} - а вот ставить в сигнальное состояние его не будем, ему умирать скоро, не до новых обработок

			LOG_WARN_C( APL_T("Фаза 1. Рассылаем клиентам сообщение о своей смерти: ")+str_about);
			if(t_thread->m_pSocket->m_client_type==ECT_Standart && t_thread->m_pSocket->m_terminate_after<2)
			{
				// рассылаем клиентам сообщение об завершении работы сервера
				t_thread->m_pSocket->SendCommand(CMDDeadTime,0);
				// ставим флаг, что клиенту уже было послано CMDDeadTime,0
				t_thread->m_pSocket->m_terminate_after.Increment();
			}

		}
		else
		{
			// сокета нет, нить подлежит немедленному уничтожению на шаге 2
			LOG_WARN_C( APL_T("Сокета нет. Переходим к следующей фазе."));
			forsed=true;
		}
		// посылаем нити команду завершиться
		LOG_WARN_C( APL_T("Останавливаем нить - посылаем WM_QUIT; ")+str_about);
		t_thread->PostThreadMessage(WM_QUIT,0,0);

		// на корректное завершение (по флагу BreakOperation и по обработке WM_QUIT) работы дается секунда
		t_thread->m_tick_kill_timer.Start();
	}
	else if(t_thread->m_tick_kill_timer.CheckTimeOut(1000))
	{
		// если отведенное для нити время корректного завершения прошло - будем ее убивать прямо сейчас
		forsed=true;
	}
	if(forsed)
	{
		if(t_thread->m_pSocket==0)
		{
			// нить уже завершается, надо подождать пока она завершится, иначе будет клинч
			HANDLE thread_h = t_thread->m_hThread;
			DWORD res = 0;
			do{
				res = WaitForSingleObject(thread_h, 100);
				PoolOperateListLeave();
				// даем возможность нити убрать себя из списка нитей
				Sleep(100);
				PoolOperateListEnter();

			}while(res == WAIT_TIMEOUT);
		}
		// Убиваем принудительно
		else if(t_thread->m_hThread!=0 /*t_thread->m_state_kill==3*/)
		{
			LOG_WARN_C( APL_T("Прерываем нить принудительно; ")+str_about);

			stack_logger.SetFunctionInfo(_T("TerminateThread - delete t_thread"));
			
			// если нить прервать, когда ее функция в защищенном участке - получим клинч.
			// сначала входим во все серверные критические секции, которые могут использоваться в переменных клиентского сеанса
			if(GlobalEnvProtect!=0)GlobalEnvProtect(EnvProtectEnter); // входим в защищенные участки
			TerminateThread(t_thread->m_hThread,0);
			// чтобы нить успела умереть
			Sleep(200);
			// выходим из серверных критических секций
			if(GlobalEnvProtect!=0)GlobalEnvProtect(EnvProtectLeave); // выходим из защищенных участков

			// ну и проверяем - если объект нити не удален - дестуктор объекта не был вызван - он до сих пор в списке
			found=false;
			int i;
			for(i=0;i<m_list.GetSize();i++)
			{
				if(m_list.GetAt(i)==t_thread)
				{
					found=true;
					// выкидываем из списка сами - черт его знает, как себя поведут функции в удаляемой нити
					// а если нить зависнет на своем удалении - по крайней мере, основная нить сможет завершить рабочий процесс
					m_list.RemoveAt(i);
					break;
				}
			}
			// если найден - удаляем объект ручками
			if(found)
			{
				//LOG_ERROR( APL_T("Вручную выкидываем из списка и удаляем объект: ")+str_about);
				delete t_thread; // и надо его удалить - объект нити-то самоудаляющийся, но только когда нить корректно завершается
				// а вот из списка m_list удалять не надо - в деструкторе CSocketThread сам себя из него удаляет
			}
		}
	}

	LogDebugMessageIfDef(_T("End "));

	return TRUE;
}



/////////////////////////////////////////////////////////////////////////////
// CaplSocketServ member functions

BOOL CaplSocketServ::Create(UINT nSocketPort, bool show_err_mess, int count_hide_attempt)
{
	CaplStackLogger stack_logger(__APL_FUNC__);
	
	BOOL val=TRUE;
	CString str,tmp;
	int nSocketType=SOCK_STREAM;
	long lEvent = /*FD_READ | FD_WRITE | FD_OOB |*/ FD_ACCEPT /*| FD_CONNECT | FD_CLOSE*/;
	LPCTSTR lpszSocketAddress = NULL;
	int nResult;

	if(m_terminate_after > 0)return FALSE;

	if (Socket(nSocketType, lEvent))
	{
		if (Bind(nSocketPort,lpszSocketAddress))
		{
			MakeClassInfo(_T(" -- Binded"));
			return TRUE;
		}
		nResult = WSAGetLastError();
		if(nResult == 10048)
		{
			str.Format( APL_T("Порт %i занят другим приложением."),nSocketPort);
			LOG_ERROR(str);
			// пытаемся перевести на себя порт в следющих случаях:
			//  - можно показывать сообщения - до тех пор, пока пользователь жмякает "да, попытаться захватить порт"
			//  - нельзя показывать сообщения и счетчик повторов больше нуля
			while((show_err_mess && AfxMessageBox(str+ APL_T("\nПопытаться его использовать еще раз?"),MB_YESNO | MB_ICONEXCLAMATION | MB_SYSTEMMODAL)==IDYES) ||
					(!show_err_mess && count_hide_attempt > 0))
			{
				str.Format( APL_T("Попытка перехватить порт %i"),nSocketPort);
				LOG_WARN_C(str);

				BOOL resb=SetSockOpt(SO_REUSEADDR,&val,4);
				if (Bind(nSocketPort,lpszSocketAddress))
				{
					str.Format( APL_T("Порт %i успешно активирован"),nSocketPort);
					LOG_ERROR(str);
					return TRUE;
				}
				if(count_hide_attempt > 0) count_hide_attempt--;
				Sleep(100);
			}
			GetDescriptionSystemError(WSAGetLastError(),str);
			LOG_ERROR(str);
			return FALSE;
		}
		else
		{
			str.Format( APL_T("Не удалось использовать для прослушивания порт %i. Описание ошибки: "),nSocketPort);
			GetDescriptionSystemError(nResult,tmp);
			LOG_ERROR(str+tmp);
			if(show_err_mess)AfxMessageBox(str+tmp+ APL_T("\nСервер приложений завершает работу"),MB_OK | MB_ICONERROR | MB_SYSTEMMODAL);

		}
		m_LastWsaError = WSAGetLastError();
		Close();
		WSASetLastError(m_LastWsaError);
	}
	return FALSE;
}

bool CaplSocketServ::StartListen(UINT port, int &error, bool show_err_mess, bool mode_www, int count_hide_attempt, bool mode_mail)
{
	CaplStackLogger stack_logger(__APL_FUNC__);
	
	CString str;
	error=0;
	// если это сокет для передачи данных - слушать ему вредно
	if(m_pool!=0)
	{
		LOG_ERROR( APL_T("Ошибка - это это сокет для передачи данных - слушать ему вредно"));
		return false;
	}

	global_mode_www = mode_www;
	global_mode_mail = mode_mail;

    if (Create(port,show_err_mess,count_hide_attempt))
	{
		if(CAsyncSocket::Listen())
		{
			m_server_port = port;
			str.Format(_T("Start listen port %i"),m_server_port);
			LOG_WARN_B(str);
			if(m_server_ip_str == _T(""))
				GetSockName(m_server_ip_str,m_server_ip);

			str.Format(_T("Real name %s real port %i"),m_server_ip_str,m_server_port);
			LOG_WARN_B(str);

			MakeClassInfo(APL_T(" Слушающий сокет в главной нити. ") + str);

			if(IsModeWWW())
				m_class_info+= APL_T(" Режим http-сервера");

			return true;
		}
    }
	error=WSAGetLastError();
	m_LastWsaError=error;
	TestErrOnHalt();
    return false;
}

void CaplSocketServ::OnClose(int nErrorCode) 
{
	CaplStackLogger stack_logger(__APL_FUNC__);
	
	// тормозим таймер
	LogDebugMessageIfDef( APL_T("Тормозим таймер ID_TIMER_BLOCK"));
	m_wnd->KillTimer(ID_TIMER_BLOCK);

	if(m_client_type == ECT_Standart)
	{
		// ставим флаг, что сокет в процессе закрытия
		m_onclose_enter = 1;
	}
	// перегруженная функция завершения наследника
	Exit();

	//Detach();
	if(m_hSocket != INVALID_SOCKET)
	{
		if(GetCurrentThreadId() == m_thread_id)
		{
			CString str;
			str.Format(APL_T("Закрываем сокет %X класса ") + m_class_info, m_hSocket);
			LOG_WARN_C(str);
			stack_logger.SetFunctionInfo(str);

			AsyncSelect(0);
			Close();
		}
		else
		{
			CString str;
			str.Format(APL_T("Объект сокета %X %s нельзя закрыть из другой нити. Остаются открытые хендлы."), m_hSocket, m_class_info);
			stack_logger.SetFunctionInfo(str);
		}
	}
	MakeClassInfo();
	if(m_client_type == ECT_Unknown || m_client_type == ECT_Standart || m_client_type == ECT_WwwMail || m_terminate_after > 0)
	{
		if(m_thread!=0)
		{
			LogDebugMessageIfDefFormat1( APL_T("Тормозим нить сокета %i"),m_hSocket);

			m_thread->m_pSocket->Exit();
			m_thread->PostThreadMessage(WM_QUIT,0,0);
			//		m_thread->m_pSocket=0; Если здесь очищать указатель - то он просто не будет реально удален до завершения программы
			m_thread=0;	
		}
		m_terminate_after.Set(10);
	}
	else
	{
		// ничего не делаем, http нить с сокетом с объектом CAplTransport умирают по счетчику смерти m_dead_counter
	}

	LogDebugMessageIfDef(_T("'End "));

	//PostQuitMessage(0);
}

void CaplSocketServ::OnReceive(int nErrorCode) 
{
	CaplStackLogger stack_logger(__APL_FUNC__);
	
	LogDebugMessageIfDefFormat1( APL_T("OnReceive; сокет %X"),this->m_hSocket);
	if(m_onclose_enter > 0)
	{
		LOG_WARN_C(APL_T("Прерываем обработку OnReceive; сокет %X - ибо m_onclose_enter %i"),this->m_hSocket, (long)m_onclose_enter);
	}
	else if(m_terminate_after<2)
	{
		ProcessedReceive(nErrorCode);
	}
	else
	{
		LOG_WARN_C( APL_T("Прерываем обработку OnReceive; сокет %X - ибо m_terminate_after %i"),this->m_hSocket, (long)m_terminate_after);
	}
}

#define TEST_NREAD1_SOCKET_ERROR  if(nRead1==SOCKET_ERROR){APL_TRANSPORT_THROW(ASC_ERROR_IN_SOCKET);}else if(nRead1==0){APL_TRANSPORT_THROW(ASC_MISSED_RECEIVE_SIZE);}

bool CaplSocketServ::ParseHttpHeaders()
{
	CaplStackLogger stack_logger(__APL_FUNC__);

	int i=0;
	UINT32 *nRead;
	// заголовки в http запросах и в почте - однобайтовые в UTF8!
	CStringA header = "";
	CStringA tmp = "";
	CString tmp_enc = _T("");

	m_http_headers_in.Clear();
	CString URI = _T("");
	m_indx_start_data = 0;
	m_uri_data.Clear();
	m_cookies.Clear();

	nRead=(UINT32*)m_session_data.m_data;
	if(!Check4ByteHttp(nRead))
		return false;

	bool is_first_row = true;

	for(i=0;i<m_session_data.m_Size;i++)
	{
		if(m_session_data.m_data[i] == '\r' ||
			m_session_data.m_data[i] == '\n')
		{
			if(m_session_data.m_data[i] == '\r')
			{
				// если возврат каретки идет рядом с переносом строки, рассматриваем его как часть того переноса
				// если возврат каретки идет без переноса строки, рассматриваем его как самостоятельный перенос строки
				if(i<m_session_data.m_Size-1 && (m_session_data.m_data[i+1]=='\n'))continue;
				if(i>0 && (m_session_data.m_data[i-1]=='\n'))continue;
				// По RFC 2068 перевод строки пара CRLF или 13 10 или 0D 0A или \r \n
				// Там не описано что делать, если \r встречается без \n. На всякий случай обрабатываем одиночный \r как перенос
			}

			// Декодируем из urlencode %D0%9A, проверяем кодировку исходной строки (UTF8 или ANSI) и раскодируем в текущую системную кодировку
			tmp_enc = DecodeURLString(tmp);

			if(is_first_row)
			{
				URI = tmp_enc;
				is_first_row=false;
			}
			else if(header != "")
			{
				// заголовки в http запросах и в почте - однобайтовые первой половины ASCII. Перекодирование не нужно.
#ifdef __linux__
				CString header_u;
				// считаем что в линуксе строки правильно конвертируются самим CaplStringW
				header_u = header;
				m_http_headers_in.Add((LPCTSTR)header_u, (LPCTSTR)tmp_enc);

#else // #ifdef __linux__

#ifdef _UNICODE
				CaplStringAdapter str_ad1(header);
				CaplStringAdapter str_ad2(tmp);
				m_http_headers_in.Add((LPCTSTR)str_ad1, (LPCTSTR)str_ad2);
#else // #ifdef _UNICODE
				m_http_headers_in.Add((LPCTSTR)header, (LPCTSTR)tmp_enc);
#endif // #ifdef _UNICODE

#endif // #ifdef __linux__
			}
			tmp = "";
			header = "";
			// проверяем на окончание заголовка
			if(i<m_session_data.m_Size-2 && (m_session_data.m_data[i+1]=='\r') && (m_session_data.m_data[i+2]=='\n'))
			{
				i+=3;
				// конец заголовка
				break;
			}
			continue;

		}
		else if(m_session_data.m_data[i] == ':')
		{
			if(!is_first_row && header == "")
			{
				header = tmp;
				tmp = "";
				if(i<m_session_data.m_Size-1 && (m_session_data.m_data[i+1]==' ')) i++;
				
				continue;
			}
		}
		else if(m_session_data.m_data[i] == '%')
		{
			if(header=="Content-Disposition")
			{
				// В заголовке "Content-Disposition" согласно RFC 6266 и RFC 8187 может быть сложное кодирование имени файла
				// Например для файла с именем Блюз.txt может быть такое значение:
				// Content-Disposition: attachment; filename="Bluse.txt"; filename*="utf8'ru-ru'%D0%91%D0%BB%D1%8E%D0%B7.txt"
				// Нехай его раскодирует вызывающая программа
				tmp+=m_session_data.m_data[i];
			}
			else
			{
				// Для остальных заголовков переводим urlencode в нормальные символы, и не забываем про UTF8
				if(i<m_session_data.m_Size-2)
				{
					CStringA str = "0x";
					str += m_session_data.m_data[i+1];
					str += m_session_data.m_data[i+2];
					char c = (char)strtol(str,NULL,0);
					i+=2;
					if(0==(c&0x80))// англ. буква 1 байт
					{
						tmp+=c;
						continue;
					}
					bool error = false;
					// проверяем что длины строки буфера хватает на все символы
					if(0xc0==(c&0xe0)){ if (i>=m_session_data.m_Size-3){error=true;}}   // символ на 2 байта, и данных для него не хватает
					else if(0xe0==(c&0xf0)){ if(i>=m_session_data.m_Size-6){error=true;}} // символ на 3 байта, и данных для него не хватает
					else if(0xf0==(c&0xf8)){ if(i>=m_session_data.m_Size-9){error=true;}} // символ на 4 байта, и данных для него не хватает
					else {error=true;} // неизвестный первый символ
					if(error)
					{
						// оборванные и некорректные urlencode не декодируем и копируем "как есть"
						tmp+=m_session_data.m_data[i-2];
						tmp+=m_session_data.m_data[i-1];
						tmp+=m_session_data.m_data[i];
						continue;
					}

					char u[5]={0,0,0,0,0};
					u[0]=c;
					// символ на 2 байта или больше
					str = "0x";
					str += m_session_data.m_data[i+2];
					str += m_session_data.m_data[i+3];
					u[1] = (char)strtol(str,NULL,0);
					i+=3;
					if(0xc0!=(c&0xe0))// символ не на 2 байта, наверно на 3 или больше
					{
						str = "0x";
						str += m_session_data.m_data[i+2];
						str += m_session_data.m_data[i+3];
						u[2] = (char)strtol(str,NULL,0);
						i+=3;
						if(0xe0!=(c&0xf0)) // символ не на 3 байта, наверно на 4
						{
							str = "0x";
							str += m_session_data.m_data[i+2];
							str += m_session_data.m_data[i+3];
							u[3] = (char)strtol(str,NULL,0);
							i+=3;
							// символ на 4 байта
						}
					}
					// получили один символ в виде строки char*, так можно прибавлять
					tmp+= u;
				}
				else
				{
					tmp+=m_session_data.m_data[i]; // оборванные urlencode не декодируем
				}
				continue;
			}
		}
		// все остальные символы просто прибавляем к временной строке
		if((m_session_data.m_data[i] & 0x80) != 0 )
		{
			// Упс, UTF8. Его так просто не прибавишь
			char u[5]={0,0,0,0,0};
			int z=0;
			for(z=0; (z<4 && i+z<m_session_data.m_Size); z++){u[z]=m_session_data.m_data[i+z];}
			if(0xc0==(u[0]&0xe0))// символ на 2 байта
			{
				if(z<2){tmp+="?";continue;}	// оборванные символы заменяем вопросиком
				z=2;
			}
			else if(0xe0==(u[0]&0xf0)) // символ на 3 байта
			{
				if(z<3){tmp+="?";continue;}// оборванные символы заменяем вопросиком
				z=3;
			}
			else if(0xf0==(u[0]&0xf8)) // символ на 4 байта
			{
				if(z<4){tmp+="?";continue;}// оборванные символы заменяем вопросиком
				z=4;
			}
			u[z]=0;

			// получили один символ в виде строки char*, так можно прибавлять
			tmp+= u;
			i+=(z-1); // пропускаем дополнительные символы UTF8
		}
		else
		{
			tmp+=m_session_data.m_data[i];
		}

		if(i==m_session_data.m_Size-1)
		{
			// флага конца заголовка (двух переносов строки) еще не было, а данные в буфере уже кончились. Надо дочитывать.
			ReadNextHttpFromSocket();
			// Если данных в сокете нет и буфер не увеличился - ругаемся и отправляемся ждать
			if(i==m_session_data.m_Size-1)
			{
				// надо дождаться как минимум двух переносов строки.
				m_size_waited = m_session_data.m_Size + 2;
				APL_TRANSPORT_THROW_EX(ASC_MISSED_RECEIVE_SIZE_ON_SERVER,APL_T("Слишком большой перерыв между пакетами данных, приходящими с клиента. Обработка запроса временно прервана."));
			}
		}
	}
	
	m_indx_start_data = i;
	

	// Разбираем строку URI на параметры/ Тут можно использовать CString, строка уже приведена к типу кодировки проекта 
	SUriElement uri_element;
	CString param, value, str, strU;
	int num_base_elements = -1;
	int pos1 = URI.Find(_T(' '));
	if(pos1 == -1) return false;
	int pos2 = URI.ReverseFind(_T(' '));
	if(pos2 != -1 && pos2 != pos1)
	{
		// выделяем версию http и обрезаем строку по последнему пробелу.
		m_uri_data.m_http_ver = URI.Mid(pos2+1);
		URI.Delete(pos2, URI.GetLength() - pos2);
	}
	
	m_uri_data.m_type = URI.Left(pos1);
	param = _T("");
	value = _T("");

	m_uri_data.m_uri_full = URI.Mid(pos1+1);

	// чтобы разбор всегда короректно отрабатывал последнюю группу параметров
	if(URI.Right(1) != _T("/"))
		URI += _T('/');

	for(i=pos1+1; i<URI.GetLength(); i++)
	{
		if(URI[i]=='?' && num_base_elements==-1)
			num_base_elements = m_uri_data.m_elements.Size+1;
		else
		{
			m_uri_data.m_uri_path += URI[i];
			uri_element.m_source+=URI[i];
		}

		switch(URI[i])
		{
		case _T('/'):
		case _T('?'):
			if(str == _T("") && param.IsEmpty())
				continue;
			if(uri_element.m_base == _T("") && param == _T("") && value == _T(""))
			{
				uri_element.m_base = str;
				uri_element.m_base.MakeLower();
			}
			else
			{
				if(param == _T(""))
				{
					param = str;
					param.MakeLower();
					uri_element.m_params.Add(param, _T(""));
				}
				else
				{
					value = str;
					//value.MakeLower(); - ни в коем случае! А если это пароль передают?
					// проверка на UTF8
					strU=ConvertFromUTF8toWIN1251(value);

					uri_element.m_params.Add(param, value);
				}
			}
			uri_element.m_source.Remove(_T('/'));
			m_uri_data.m_elements.Add(new SUriElement(&uri_element));
			uri_element.Clear();
			str = _T("");
			param = _T("");
			value = _T("");
			break;

		case _T('&'):
			if(str == _T(""))
			{
				if(param != _T(""))
				{
					uri_element.m_params.Add(param, _T(""));
				}
			}
			else if(uri_element.m_base == _T("") && param == _T(""))
			{
				uri_element.m_base = str;
				uri_element.m_base.MakeLower();
			}
			else
			{
				if(param == _T(""))
				{
					param = str;
					param.MakeLower();
					uri_element.m_params.Add(param, _T(""));
				}
				else
				{
					value = str;
					value.MakeLower();
					uri_element.m_params.Add(param, value);
				}
			}
			param = _T("");
			value = _T("");
			str = _T("");
			break;

		case _T('='):
			if(param == _T(""))
			{
				// первое равно - переход к значению параметра
				param = str;
				param.MakeLower();
				str = _T("");
			}
			else
			{
				// а если для одного параметра несколько раз встречается равно - считаем его частью значения
				str += URI[i];
			}
			break;

		default:
			str += URI[i];
		}
	}

	if(num_base_elements!=-1)
	{
		m_uri_data.m_params = new SUriElement(m_uri_data.m_elements[num_base_elements]);
		m_uri_data.m_elements.Remove(num_base_elements);
	}

	CompletionUriData(m_uri_data);

	/////////////////////////////////////////////////////////////////////
	// разбираем куки
	//
	CString cookie = m_http_headers_in.Get(_T("Cookie"));
	bool need_add = false;
	tmp_enc = _T("");
	param = _T("");
	for(i=0; i<cookie.GetLength(); i++)
	{
		need_add = false;
		switch(cookie[i])
		{
		case _T('\r'):
		case _T('\n'):
			if(param != _T(""))
			{
				need_add = true;
			}
			break;

		case '=':
			if(param == _T(""))
			{
				param = tmp_enc;
				tmp_enc = _T("");
			}
			else
			{
				// если это кодирование типа BASE64 - то в значениии могут встречаться символы =
				tmp_enc+=cookie[i];
			}
			break;

		case ';':
			need_add = true;
			i++; // пропускаем пробел, идущий за точкой с запятой
			break;
		default:
			tmp_enc+=cookie[i];
			break;
		}
		if(need_add || (i==cookie.GetLength()-1 && param!=_T("")))
		{
			m_cookies.Add(param, tmp_enc);
			param = _T("");
			tmp_enc = _T("");
		}
	}

	if(IsLogAll())
	{		
		CString csMessage = _T("Запрос HTTP:\n");
		csMessage += _T("******* URI: *******\n");
		csMessage += _T("\t") + m_uri_data.m_uri_full + _T("\n");

		csMessage += _T("******* Обработанные заголовки: *******\n");
		csMessage += m_http_headers_in.PrintList();
		csMessage += _T("\t");
		LOG_WARN(csMessage,true,true,true,true,__APL_FUNC__,(LPCTSTR)m_class_info,0,_T(__FILE__),_T(__DATE__),__LINE__);
	}


	return true;
}

void CaplSocketServ::CompletionUriData(SUriData &m_uri_data)
{
	m_uri_data.m_uri_path.Empty();

	CString str;
	for(int i=0; i<m_uri_data.GetSize(); i++)
	{
		if(i>0)m_uri_data.m_uri_path += _T("/");
		str = m_uri_data.GetStringAt(i);
		str.Replace(_T("/"), _T("\\/"));
		m_uri_data.m_uri_path += str;
	}
	if(m_uri_data.m_uri_path.Right(1)==_T("/"))m_uri_data.m_uri_path.TrimRight(_T("/"));
}



void CaplSocketServ::ProcessedReceive(int nErrorCode) 
{
	CaplStackLogger stack_logger(__APL_FUNC__);
	m_funct_info=__APL_FUNC__;
	
	m_wnd->KillTimer(ID_TIMER_BLOCK);
	// TODO: Add your specialized code here and/or call the base class
	LogDebugMessageIfDef(_T("Begin"));
	m_uri_data.Clear();
	CString	str=_T(""),str1=_T("");
	BYTE *buff_arr=0, *buff_const=0;
	m_http_headers_in.Clear();

	int size_portion=0;
	int line_src = __LINE__;

	m_funct_info.Empty();

	try{
		LOG_WARN_C(_T("Start Receive"));line_src = __LINE__;
		if(m_pool!=0)
		{
			int nRead1 = 0;
			UINT32 nRead = 0;
			int num_att = 50;
			do{
				nRead1 = CSocket::Receive(&nRead, 4);
				if(nRead1 != 0) break;
				Sleep(10);
				num_att--;
				if(num_att==30)if(!IsReadyData()){APL_TRANSPORT_THROW_EX(ASC_MISSED_RECEIVE_SIZE, APL_T("Невозможно прочитать размер порции данных. Буфер tcp-сокета пустой. "));}
			}while(nRead1 == 0 && num_att >0);
			if(num_att==0){APL_TRANSPORT_THROW_EX(ASC_MISSED_RECEIVE_SIZE, APL_T("Невозможно прочитать размер порции данных. Закончился интервал времени, отведенный на чтение. "));}
			
			line_src = __LINE__;
			TEST_NREAD1_SOCKET_ERROR
			else if(nRead1 < 4 && !IsModeMail() && (nRead1 != m_size_waited - m_session_data.m_Size))
			{
				LOG_ERROR( APL_T("Ошибка: Размер команды меньше допустимого"));
				SendCommand(CMDErrorReceiveData);
			}
			else
			{
				line_src = __LINE__;
				LogDebugMessageIfDefFormat1( APL_T("Server: ProcessedReceive первые 4 байта %i "),nRead);
				if((IsModeWWW() || IsModeMail() || 
					(m_connect_status && Check4ByteHttp(&nRead)) // GET и POST и ... перевернутые из сетевого порядка байт
					)
					&& m_terminate_after < 2)
				{
					line_src = __LINE__;
					// сюда заходим или в режиме www
					// или при приеме http-запроса
					m_cdbAnswer.Clear();
					m_terminate_after = 1;

					size_portion = WWW_PORTION_SIZE;
					// Браузер может отправить запрос к обычной веб-морде через сокет сеанса REST. Проверяем, чтобы не завершить REST сеанс раньше времени
					if(m_client_type != ECT_Rest) 
						m_client_type = ECT_WwwMail;
					MakeClassInfo();
					ClearAnswerData();

					// в режиме web-сервера нам могут придти только 1) начало 
					// http-запроса  2) обрывок непринятого запроса 
					// отрабатываем обрывок
					if(m_session_data.m_Size!=0 && m_size_waited!=0)
					{
						// есть оборванные данные, а еще мы знаем, сколько данных нам надо получить
						if(IsLogAll())
						{
							if(IsModeMail()){LOG_WARN(_T(" Receive next part MAIL request"));}
							else{LOG_WARN(_T(" Receive next part HTTP request"));}
						}
						// если данные оборваны - скорей всего что-то большоеюю Дабы уменьшить число итераций - увеличиваем буыер
						size_portion=WWW_PORTION_SIZE*10;
					}
					else
					{
						// начало запроса
						if(IsLogAll())
						{
							if(IsModeMail()){LOG_WARN(_T(" MAIL request"));}
							else{LOG_WARN(_T(" HTTP request"));}
						}
					}

					// nRead1 байта для первой порции мы уже прочитали из канала
					m_session_data.Add(&nRead, nRead1);
					
					buff_arr = new BYTE[size_portion];

					if(IsReadyData())
					{
						// пока в буфере есть еще данные
						// дочитываем в цикле оставшиеся данные 
						do{
							//memset(buff,0,size_portion);
							nRead1 = CSocket::Receive(buff_arr, size_portion);
							TEST_NREAD1_SOCKET_ERROR
							else
							{
								m_session_data.Add(buff_arr,nRead1);
							}
						}while(IsReadyData());// пока в буфере есть еще данные

						// LogDebugMessageIfDef((LPCTSTR)m_session_data.GetBuffer()); - здесь можно выводить в лог HTTP-запрос - для отладки
					}
					delete buff_arr;
					buff_arr=0;

					// если это было дочитывание из буфера - проверяем, все ли получили
					if(m_size_waited!=0)
					{
						if(m_session_data.m_Size<m_size_waited)
						{
							// да что ж такое, опять надо ждать! Второй раз передача данных прерывается - ну его нафиг такой запрос!
							LOG_WARN( APL_T("Слишком большой перерыв между пакетами данных, приходящими с клиента. Обработка HTTP запроса прервана."));
							InterlockedExchangeAdd((LONG*)&count_socket_data_errors,1);
							OnClose(1);
							APL_TRANSPORT_THROW_EX(ASC_NO_ERROR, APL_T("Слишком большой перерыв между пакетами данных, приходящими с клиента. Обработка HTTP запроса прервана."));
						}
					}
					
					CString str;

					if(IsLogAll())
					{
						str.Format( APL_T("Получили %i байт"),m_session_data.m_Size);
						LOG_WARN(str);
					}

					if(IsModeMail())
					{
						ProcessedMail();
					}
					else
					{
						// ищем признаки, что это запрос от PSS-ного клиента, работающего через http, или от rest
						// сначала разбираем заголовки
						ParseHttpHeaders();
						MakeClassInfo();

						if(m_client_type == ECT_Rest && (m_uri_data.m_elements.GetSize()==0 || m_uri_data.m_elements[0]->m_base != _T("rest")))
						{
							// Сокет использоваля для запросов rest, но по нему пришел другой запрос. Тип сокета не меняем, чтобы 
							//	контекст не убили при дисконнекте, но выполняем обычный http-запрос
							LOG_WARN_C( APL_T("передаем HTTP запрос приложению"));
							ProcessedHTTP();
						}
						// только для клиентов поверх http
						else if(m_client_type == ECT_Rest || m_client_type == ECT_ClientOverHttp)
						{
							DWORD dw_cLength;

							if(MainModuleLog!=0 && MainModuleLog->m_LogInfoLevel>=LogLevelFunctionInfo)
							{
								if(m_client_type == ECT_ClientOverHttp)
								{
									str.Format(APL_T("Это pss-ный клиент с id %i работает по протоколу http. SPEC_REQUEST_INDEX: "), m_ClientId);
									str+= m_http_headers_in.Get(HTTP_SPEC_REQUEST_INDEX);
								}
								else if(m_ClientId==-1)
									str.Format( APL_T("Это pss-ный REST клиент с ключем сессии %s."), m_session_key);
								else
									str.Format( APL_T("Это pss-ный REST клиент с id %i."), m_ClientId);

								MainModuleLog->LogMessage(str,true,true,true,true,__APL_FUNC__,(LPCTSTR)m_class_info,0,_T(__FILE__),_T(__DATE__),__LINE__);
							}

							/*m_client_over_http = true;*/
							str = m_http_headers_in.Get(HTTP_TRANSFER_ENCODING);
							if(str==_T("chunked"))
							{
								// проверяем, принят ли заголовок первого блока с телом. Его минимальная длинна 5 байт
								dw_cLength = 5;
							}
							else
							{
								str=m_http_headers_in.Get(HTTP_CONTENT_LENGTH);
								dw_cLength = _atoi(str);
							}
							if(dw_cLength > 0)
							{
								// проверяем, все ли данные пришли
								if(m_indx_start_data + dw_cLength > (DWORD)m_session_data.m_Size)
								{
									if(IsLogAll())
									{
										str.Format( APL_T("Приняты не все данные: indx_start_data (%i) + Content-Length или блок chunked (%i)> m_session_data.m_Size (%i). Нить обработки будет дочитывать"),m_indx_start_data,dw_cLength, m_session_data.m_Size);
										LOG_WARN(str);
									}

									m_size_waited = m_indx_start_data + dw_cLength;
									//Данные будут дочитаны из сокета в нити обработки данного клиента. APL_TRANSPORT_THROW_EX(ASC_NO_ERROR, APL_T("Слишком большой перерыв между пакетами данных, приходящими с клиента. Обработка запроса временно прервана."));
								}
							}

							DispatchHTTPClient();
							
						}
						else
						{
							LOG_WARN_C( APL_T("передаем HTTP запрос приложению"));
							ProcessedHTTP();
						}
					}

					// отсылаем обратно
					if(m_cdbAnswer.m_Size!=0)
					{
						int sendet = Send(m_cdbAnswer.GetBuffer(), m_cdbAnswer.m_Size);
						if(sendet != m_cdbAnswer.m_Size)
						{
							CString str;
							str.Format(APL_T("Ошибка при передаче: должны были отправить %i байт, а передали %i. "), m_cdbAnswer.m_Size, sendet);
							CString str1;
							aplGetDescriptionSystemError(GetLastError(),str1);

							LOG_ERROR(str + str1);
						}
						m_cdbAnswer.Clear();
					}
// 					else if(!IsModeMail())
// 					{
// 						OnClose(0);
// 					}
					
					m_session_data.Clear();
					m_size_waited = 0;

					// Ну и разрываем связь - либо HTTP отработали, либо ошибка была
					if(m_terminate_after > 0)
					{
						LOG_WARN_C( APL_T("m_terminate_after: %i"), (long)m_terminate_after);
						/*m_client_over_http = false;*/
						m_client_type = ECT_Standart;
						OnClose(0);
					}
				}
				else if(m_size_waited==0 && nRead == 0)
				{
					line_src = __LINE__;
					// Это служебное сообщение. Тоже 4 байта
					nRead1 = Receive(&nRead, 4);

					if(m_client_type == ECT_Unknown)
					{	
						if(nRead != CMDGetServerStatus){ APL_TRANSPORT_THROW(ASC_UNKNOWN_PROTOCOL);}
						else m_client_type = ECT_Standart;
					}

					line_src = __LINE__;
					TEST_NREAD1_SOCKET_ERROR
					else
					{ 
						if(IsLogAll())
						{
							str.Format( APL_T("Приняли команду %i "),nRead);
						}
						line_src = __LINE__;
							
						switch(nRead)
						{
						case CMDGetServerStatus:
							line_src = __LINE__;
							if(IsLogAll()){LOG_WARN(str + _T(" CMDGetServerStatus"));}
							// к этой команде вторым байтом - номер версии транспорта, третьим - указатель на нить сокета
							SendCommand(CMDConnectionOKNew,VERSION_NUMBER,(int)m_serv_seans_id); 
							// это наш клиент, HTTP запросы от него пока не ходят
							m_connect_status =false;
							break;
							
						case CMDGetVersionBuffer:
							line_src = __LINE__;
							if(IsLogAll()){LOG_WARN(str + _T(" CMDGetVersionBuffer"));}
#ifdef _UNICODE
							// заголовок буфера - всегда однобайтовый
							buff_const=(BYTE*)VERSION_INFO_U;
							SendData(buff_const, strlen((const char*)buff_const),0,0);
#else // #ifdef _UNICODE
							// заголовок буфера - всегда однобайтовый
							buff_const=(BYTE*)VERSION_INFO;
							SendData(buff_const, strlen((const char*)buff_const),0,0);
#endif // #ifdef _UNICODE
							buff_const=0;// чтоб не удалить нечаянно
							break;
						case CMDBreakOperation:
							line_src = __LINE__;
							if(IsLogAll()){LOG_WARN(str + _T(" CMDBreakOperation"));}
							// Так, кого-то из нитей сервера надо тормознуть
							// принимаем указатель на нить
							nRead1 = Receive(&nRead, 4);
							TEST_NREAD1_SOCKET_ERROR
							// проверяем корректность указателя и если правильный - запускает перывание операции
								// смысла прерывать termonate-ом нить нет -
								// для Lite хватит флага прервать, он часто проверяетс
								// а для ORACLE или PostgreSQL запрос все равно будет выполняться пока 
								// не тормознут целиком сервер
								// И клиент при прерывании "зависшей" нити не получит никакого собщения
								// TerminateThread(((CWinThread*)nRead)->m_hThread,0);
							if(m_pool->ProcessBreakThread(nRead)){SendCommand(CMDBreakOk);}
							else{SendCommand(CMDCriticalError);}
							break;
						case CMDDisconnect:
							line_src = __LINE__;
							if(IsLogAll()){LOG_WARN(str + _T(" CMDDisconnect"));}
							// так, клиент уходит
							//4 OnClose(0);
							break;
						default:
							line_src = __LINE__;
							if(IsLogAll()){LOG_WARN(str + _T(" - Error: command undefined!"));}
							if(!SendCommand(CMDErrorMissedData))
							{
								APL_TRANSPORT_THROW(ASC_ERROR_IN_SOCKET);
							}
							str.Format( APL_T("Ошибка: Команда %i не имеет обработчика"),nRead);

							LogDebugMessageIfDef(str);
							LOG_ERROR(str);
						}				
					}
				}
				else if(m_client_type == ECT_Standart)
				{
					line_src = __LINE__;
					// транзитный поток данных от клиента
					//LOG_WARN_C("транзитный поток данных от клиента");

					if(m_size_waited==0)
					{
						line_src = __LINE__;
						// это первая порция данных. До нее обрывов не было
						// в nREad размер следующей посылки с клиента
						//  проверяем на размер
						if(nRead > 100*1024*1024)
						{
							line_src = __LINE__;
							// считаем что это ненормальнеый размер, прерываем
							InterlockedExchangeAdd((LONG*)&count_socket_data_errors, 1);
							str.Format( APL_T("Ошибка: с клиента пришел слишком большой буфер размером %i байт. Прерываем соединение."),nRead);
							LogDebugMessageIfDef(str);
							LOG_ERROR(str);
							OnClose(2);
							return;
						}

						m_session_data.Clear();
						m_session_data.SetSize(nRead);
						m_size_waited = nRead;
						line_src = __LINE__;
						// в буфер m_size_waited данные надо писать с начала
						buff_const=(BYTE*)m_session_data.GetBuffer();
						// хотим принять все сразу - если получится, конечно
						size_portion=m_size_waited;
					}
					else
					{
						line_src = __LINE__;
						// передача прерывалась. По адресу nRead nRead1 байта предыдущей посылки
						m_session_data.Add(&nRead, nRead1);
						// следующие данные надо писать в конец буфера. Определяем указатель - куда писать следющие данные
						buff_const=((BYTE*)m_session_data.GetBuffer()) + m_session_data.m_Size;
						// размер - сколько данных еще наджо принять
						size_portion=m_size_waited-m_session_data.m_Size;
					}

					if(buff_const==0)
					{
						line_src = __LINE__;
						InterlockedExchangeAdd((LONG*) &count_socket_data_errors, 1);
						str.Format( APL_T("Ошибка: не удалось выделить буфер размером %i байт"),size_portion);
						LogDebugMessageIfDef(str);
						LOG_ERROR(str);
						OnClose(0);
						return;
					}
					DWORD max_num_err=0;
					if(size_portion > 0)
					{
						line_src = __LINE__;
						do{
							line_src = __LINE__;
							// приходить может по частям!
							nRead1 = Receive(buff_const, size_portion);
							LogDebugMessageIfDefFormat2( APL_T("Server: Receive принял порцию %i BYTE из ожидавшихся %i"), nRead1, size_portion);
							if(nRead1==SOCKET_ERROR || nRead1==0)
							{
								line_src = __LINE__;
								// здесь по максимуму пытаемся прочитать сразу без таймеров и т.п. - вдруг данные просто медленно подходят по каналу...
								m_LastWsaError = WSAGetLastError();
								if(TestErrOnHalt())
								{
									// этот указатель здесь ссылается на буфер m_session_data. Чтобы дважды не удалить буфер - обнуляем указатель
									buff_const = 0;
									APL_TRANSPORT_THROW(ASC_ERROR_IN_SOCKET);
								}
								line_src = __LINE__;
								GetDescriptionSystemError(m_LastWsaError, str1);
								str.Format( APL_T("Ошибка сокета при приеме данных! N ошибки %i; Описание: "), m_LastWsaError);
								str+=str1;
								LOG_ERROR(str);

								if(IsBlocking())
								{
									line_src = __LINE__;
									CancelBlockingCall( );
								}
								if(max_num_err>100)
								{
									line_src = __LINE__;
									LogDebugMessageIfDef( APL_T("Количество ошибочных циклов Receive больше 100. Переходим на ожидание следующего сообщения OnReceive"));
									m_wnd->SetTimer(ID_TIMER_BLOCK, m_ClearedTimeInterval, 0);
									// этот указатель здесь ссылается на буфер m_session_data. Чтобы дважды не удалить буфер - обнуляем указатель
									buff_const = 0;
									return;
								}
								max_num_err++;
								continue;
							}
							line_src = __LINE__;
							m_session_data.m_Size += nRead1;
							if(m_session_data.m_Size >= m_size_waited) 
								break;

							line_src = __LINE__;
							// определяем позицию конца буфера - с какого места писать
							// следующие данные надо писать в конец буфера. Определяем указатель - куда писать следющие данные
							buff_const=((BYTE*)m_session_data.GetBuffer()) + m_session_data.m_Size;

							// размер - сколько данных еще надо принять
							size_portion=m_size_waited-m_session_data.m_Size;

						}while(true);
					}

					line_src = __LINE__;
					// этот указатель здесь ссылается на буфер m_session_data. Чтобы дважды не удалить буфер - обнуляем указатель
					buff_const=0;

					LogDebugMessageIfDefFormat1(_T("Server: OnReceive get %i BYTE"), nRead);
					if(m_session_data.m_Size != m_size_waited)
					{
						line_src = __LINE__;
						str.Format( APL_T("Ошибка: Размер принятого буфера %i не равен размеру переданного %i"), m_session_data.m_Size, m_size_waited);
						LOG_ERROR(str);
						SendCommand(CMDErrorReceiveData);
					}
					else
					{
						line_src = __LINE__;
						// Обработку буфера вызываем в этой же нити
						LOG_WARN_C( APL_T("вызываем функцию Processed"));

						// проверяем, нет ли флага экстиренного завершения
						if(m_terminate_after==0)
						{
							CaplDataBuf dbuf_head, dbuf_out;
							line_src = __LINE__;
							if(Processed(m_session_data, dbuf_head, dbuf_out))
							{
								// Обработали успешно - отсылаем обратно клиенту
								if(IsLogAll())
								{
									CString str;
									str.Format( APL_T("вызываем функцию SendData. Посылаем %i байт заголовка и  %i байт тела"), dbuf_head.m_Size, dbuf_out.m_Size);
									LOG_WARN(str);
								}
								line_src = __LINE__;
								//SendData(buff,nRead);
								SendData((BYTE*)dbuf_head.GetBuffer(), dbuf_head.m_Size, (BYTE*)dbuf_out.GetBuffer(), dbuf_out.m_Size);

								if(m_detach_after_send)
								{
									line_src = __LINE__;
									PostProcessed();
								}
							}
							else
							{
								line_src = __LINE__;
								LOG_ERROR(APL_T("Ошибка при обработке принятых данных"));
								SendCommand(CMDErrorServerProcessed);
							}
							if(m_PostProcessing!=0 && m_PostProcessingObj!=0)
							{
								m_PostProcessing(m_PostProcessingObj);
								m_PostProcessing = 0;
								m_PostProcessingObj = 0;
							}
						}
					}
					m_size_waited=0;
				}
				else
				{
					line_src = __LINE__;
					m_terminate_after.Set(1);
					APL_TRANSPORT_THROW(ASC_UNKNOWN_PROTOCOL);
				}
			}
			line_src = __LINE__;
			// если сервер послал клиенту мало данных - гребаный tcp|ip может некоторое время ждать, чтобы для
			// "улучшения производительности" не гонять по сети много маленьких пакетов
			// приходится подхлестывать его ссаными тряпками
			IsReadyData();
		
		}
		else
		{
			line_src = __LINE__;
			LOG_ERROR( APL_T("Ошибка - через слушающий сокет пытались передать данные"));
		}
	}
	catch(SaplErrorDescription error)
	{
		CString err_descr, temp_buf;
		if(buff_arr!=0){delete buff_arr; buff_arr=0;}

		m_session_data.Clear();
		m_size_waited = 0;

		if(m_wait_reconnect&& TEST_DISCONNECT(m_LastWsaError))
		{
			LOG_INFO(APL_T("Клиент разорвал соединение по команде на переконнект."));
		}
		else if(error.m_core_level==SaplErrorDescriptionLevel_Transport)
		{
			err_descr= APL_T("Исключение уровня транспорта: ");
			
			GetDescriptionTransportError((ASC_ERROR_CODES)error.m_err_code, m_LastWsaError, temp_buf);
			err_descr += temp_buf;
			if(error.m_err_descr != _T(""))
			{
				err_descr += _T(" ");
				err_descr += error.m_err_descr;
			}
			error.m_err_descr = err_descr;
			if(TestErrOnHalt())
			{
				LOG_ERROR_ED(error);
				return ;
			}
			else if(error.m_err_code == ASC_NO_ERROR)
			{
				// Receive принял 0 байт, значит, данные застряли в канале. Надо их пропихнуть
				m_wnd->SetTimer(ID_TIMER_BLOCK, m_ClearedTimeInterval, 0);
				return;
			}

		}
		else
		{
			err_descr= APL_T("Исключение SaplErrorDescription не поймано на уровне обработки и прошло до уровня транспорта;")+
				CString(_T("\r\n\tDescr: "))+error.Print(0, false);
			error.m_err_code=ASC_ERROR_PROCESSED_ON_SERVER;
			error.m_err_descr = err_descr;
		}

		if(IsModeMail())
		{
			CString answer = _T("421 PSS Mail Server - Error: ");
			answer += error.Print(0, false);
			answer += _T("\r\n");
			Send(answer.GetBuffer(),answer.GetLength());
			OnClose(0);
		}
		else if(m_client_type == ECT_Standart)
		{
			SendCommand(CMDCriticalError,error.m_err_code);
		}
		LOG_ERROR_ED(error);
	}
	APL_CATCH_ALL_EXEPTION({CString str_l;str_l.Format(_T(" Line %i"),line_src); str+=str_l; if(m_funct_info != _T("")){str += APL_T("\r\nИнформация о функции: ") + m_funct_info;}},LOG_ERROR,{stack_logger.SetFunctionInfo(str);SendCommand(CMDCriticalError,APLAPIERR_OUT_OF_MEMORY);if(MainModuleLog!=0){MainModuleLog->PrintMyEmergencyLog(0,_T("CaplSocketServ::ProcessedReceive - APL_CATCH_ALL_EXEPTION"));}if(SetGlobalExit!=0)SetGlobalExit();};);


// 	catch(CMemoryException* e)
// 	{
// 		TCHAR   szCause[255];
// 		e->GetErrorMessage(szCause,255);
// 		CString str;
// 		str="Поймано исключение памяти CMemoryException! Включите режим расширенного лога, попробуйте повторить ошибку. В случае повторения ошибки пошлите лог разработчикам!";
// 		str+=szCause;
// 		if(m_funct_info != ""){
// 			str+="\r\nИнформация о функции: "+m_funct_info;
// 		}
// 		LOG_ERROR(str);	
// 		TRACE(str);
// 		SendCommand(CMDCriticalError,APLAPIERR_OUT_OF_MEMORY);
// 		if(SetGlobalExit!=0)SetGlobalExit();
// 		if(MainModuleLog!=0){MainModuleLog->PrintMyEmergencyLog();}
// 	}catch(...){
// 		CString str;
// 		str="Неизвестное исключение! Включите режим расширенного лога, попробуйте повторить ошибку. В случае повторения ошибки пошлите лог разработчикам!";
// 		if(m_funct_info != ""){
// 			str+="\r\nИнформация о функции: "+m_funct_info;
// 		}
// 		LOG_ERROR(str);
// 		TRACE(str);
// 		SendCommand(CMDCriticalError,APLAPIERR_UNKNOWN_EXEPTION);
//	}

	LogDebugMessageIfDef(_T("End Receive"));

	// проверяем, не осталось ли чего в буфере
/* Это не ошибка - просто следующие данные на хвосте * /
	if(IsReadyData()){
		str.Format("После OnReceive в буфере осталось %i BYTE",m_LastReadyData);
		LOG_WARN(str);
	}
/**/
	if(m_terminate_after>0 || (m_thread!=0 && InterlockedExchangeAdd(&m_thread->m_state_kill,0) > 0))
	{
		if(IsLogAll()){LOG_WARN(APL_T("По сигналу из обработчика завершаем нить и удаляем объект CaplSocketServ."));}
		OnClose(0);
	}
	else
	{
		m_wnd->SetTimer(ID_TIMER_BLOCK,m_ClearedTimeInterval,0);
	}
}


bool CaplSocketServ::SendData(BYTE *data1, DWORD size1, BYTE *data2, DWORD size2)
{
	CaplStackLogger stack_logger(__APL_FUNC__);
	
	CString str,str1,strm;
	int num_buf=2;
	int i;
	int ret;
	DWORD sent=0;
	DWORD sendet;
	// структура из недр винды, в ней используется char*
	WSABUF data_bufs[3];
	u_long portion_size=MAX_PORTION_SIZE;
	u_long iterator_data=0,iterator_buf=0,length;

	if(m_pool==0)
	{
		LOG_ERROR( APL_T("Ошибка - через слушающий сокет пытались передать данные"));
		return false;// это же слушающий сокет, ему передавать вредно
	}
	if(m_terminate_after>1)return false;
	
	LogDebugMessageIfDefFormat2(_T("CaplSocketServ::SendData: sending %i (+%i) BYTE of data"), size1, size2);

	if(data1==0 || size1==0)
	{
		return false;
	}
	if(data2==0 || size2==0)
	{
		sendet=size1;
		num_buf=2;
	}
	else
	{
		sendet=size1+size2;
		num_buf=3;
		// Потом сам буфер
		data_bufs[2].buf=(char*)data2;
		data_bufs[2].len=size2;
	}

	if(IsLogAll())
	{
		str.Format( APL_T("Посылаем клиенту %i (+%i) байт"), size1, size2);
		LOG_WARN(str);
	}

//	try{

		// Сначала длина буфера
		data_bufs[0].buf=(char*)&sendet;
		data_bufs[0].len=4;
		// Потом сам буфер
		data_bufs[1].buf=(char*)data1;
		data_bufs[1].len=size1;

		ret=WSASend(m_hSocket,data_bufs,num_buf,&sent,0,0,0);
		m_LastWsaError=WSAGetLastError();

		if(SOCKET_ERROR==ret)
		{
			if(TestErrOnHalt()) return false;
			if(WSAENOBUFS==m_LastWsaError)
			{
				CString cur_state;
				cur_state.Format( APL_T("CaplSocketServ::SendData: Ошибка передачи WSAENOBUFS. Разбиваем %i буферов суммарной длиной %i на части"),
																						num_buf,sendet+4);
				LOG_ERROR(cur_state);
				// Разбиваем на части и посылаем маленькими порциями
				do
				{
					try
					{			
						sent=0;
						BYTE* buf=0;
						buf=new BYTE[portion_size];
						if(buf==0)
						{
							// вот блин памяти совсем нет, даже на такой буфер...
							m_LastWsaError=WSAENOBUFS;
							APL_TRANSPORT_THROW(ASC_ERROR_IN_SOCKET);
						}
						
						for(i=0;i<num_buf;i++)
						{
							iterator_data=0;
							if(data_bufs[i].len==0)
							{
								break;
							}
							do
							{
								length=(data_bufs[i].len-iterator_data)<(portion_size-iterator_buf)?
									(data_bufs[i].len-iterator_data):(portion_size-iterator_buf);
								memcpy((BYTE*)(buf+iterator_buf),(BYTE*)(data_bufs[i].buf+iterator_data),length);
								iterator_buf+=length;
								iterator_data+=length;
								if(iterator_buf==portion_size)
								{
									// буфер полон. посылаем
									ret=Send(buf,iterator_buf);
									// буфер после посылки заполняем c начала
									iterator_buf=0;
									if(SOCKET_ERROR==ret)
									{
										m_LastWsaError=WSAGetLastError();
										delete buf;
										APL_TRANSPORT_THROW(ASC_ERROR_IN_SOCKET);
									}
									sent+=ret;
									if(iterator_data<data_bufs[i].len)
									{
										// этот буфер данных еще не кончился
										continue;
									}
								}
								if(i>=2 || data_bufs[i+1].len==0)
								{
									// Этот буфер входных данных - последний
									ret=Send(buf,iterator_buf);
									if(SOCKET_ERROR==ret)
									{
										m_LastWsaError=WSAGetLastError();
										delete buf;
										APL_TRANSPORT_THROW(ASC_ERROR_IN_SOCKET);
									}
									sent+=ret;
								}
								break;
							}while(true);
						}
						delete buf;
					}
					catch(SaplErrorDescription err)
					{
						LogDebugMessageIfDef( APL_T("Ошибка передачи буфера."));
						
						if(m_LastWsaError!=WSAENOBUFS)
						{
							// пересылаем дальше
							GetDescriptionSystemError(m_LastWsaError,strm);
							str = APL_T("Ошибка %i передачи данных клиенту! Нить ");
							str1.Format( THREAD_FORMAT _T(":\r\n"), m_LastWsaError,GetCurrentThreadId());
							LOG_ERROR(str+str1+strm);
							TestErrOnHalt();
							return false;
						}
						LogDebugMessageIfDef( APL_T("Уменьшаем размер передачи!"));

						portion_size/=2;
						if(portion_size<MIN_PORTION_SIZE)
						{
							// дальше уж дробить некуда. Совсем ошибка однако
							str.Format( APL_T("Ошибка передачи данных клиенту в нити 0x%04X:\r\nРазмер передачи слишком мал!\r\n"),
																												GetCurrentThreadId());
							LOG_ERROR(str+str1);
							return false;
						}
						if(sent>0)
						{
							// начать передачу по новой мы не могем - часть данных уже была отослана
							// дальше уж дробить некуда. Совсем ошибка однако
							str.Format( APL_T("Ошибка передачи данных клиенту в нити 0x%04X:\r\nЧасть данных уже была отослана!\r\n"),
																													GetCurrentThreadId());
							LOG_ERROR(str);
							return false;
						}
						continue;
					}
				}while(false);
				m_LastWsaError=0;
				LogDebugMessageIfDefFormat1( APL_T("CaplSocketServ::SendData: В результате размер порции %i"), portion_size);
			}
			else
			{
				str.Format( APL_T("Ошибка при передаче клиенту %i байт в нити 0x%04X; "), sendet+4, GetCurrentThreadId());
				GetDescriptionSystemError(m_LastWsaError,strm);
				LOG_ERROR(str+strm);
				TestErrOnHalt();
				return false;

			}
		}

// 	}catch(CMemoryException* e){
// 		TCHAR   szCause[255];
// 		e->GetErrorMessage(szCause,255);
// 		CString str;
// 		str="CMemoryException";
// 		str+=szCause;
// 		LOG_ERROR(str);	
// 		TRACE(str);
// 	}catch(...){
// 		CString str;
// 		str="Неизвестное исключение";
// 		TRACE(str);
// 		LOG_ERROR(str);
// 	}

	if(sent!=sendet+4)
	{
		str.Format( APL_T("Ошибка: Должны были передать клиенту %i байт, а передали %i в нити 0x%04X; "),
														sendet+4, sent, GetCurrentThreadId());
		GetDescriptionSystemError(m_LastWsaError, strm);
		LOG_ERROR(str+strm);
		TestErrOnHalt();
		return false;
	}
	
	return true;
}

bool CaplSocketServ::TestErrOnHalt()
{
	if(TEST_DISCONNECT(m_LastWsaError))
	{
		CaplStackLogger stack_logger(__APL_FUNC__);
		if(m_wait_reconnect)
		{
			LOG_INFO(APL_T("Клиент разорвал соединение по команде на переконнект"));
		}
		else if(InterlockedExchangeAdd(&is_set_global_exit,0)==0)
		{
			CString str = APL_T("Серьёзная ошибка TCP соединения. Принудительно разрываем соединение и удаляем все структуры.");
			str += APL_T("\n Стек вызовов: \n");
			str += stack_logger.GetCurrStackList();
			LOG_ERROR(str);
		}
		OnClose(0);
		return true;
	}
	return false;
}

bool CaplSocketServ::SendCommand(int command, INT32 ext_data, INT32 ext_data_2, LPCTSTR mess)
{
	CaplStackLogger stack_logger(__APL_FUNC__);
	
	if(m_pool==0)
	{
		LOG_ERROR( APL_T("Ошибка - через слушающий сокет пытались передать данные"));
		return false;// это же слушающий сокет, ему передавать вредно
	}
	if(m_terminate_after>1)return false;

	// при работе поверх http передавать команды мы пока что не умеем
	// так как WinInet тупо ждет первую посылку от сервера и все.
	if(m_client_type != ECT_Standart) return true;
	if(InterlockedExchangeAdd(&m_supress_command,0)>1 && (command == CMDNeedReconnect || command == CMDRenewClientState ))return false;

	int flag=0;
	UINT32 len_mess = 0;
	CString str,strm;
	LogDebugMessageIfDefFormat3(_T("Server: Send sending 4 BYTE of command %i; ext_data %i; ext_data_2 %i"), command, ext_data, ext_data_2);
	str.Format(_T("Command %s, ext_data %i, ext_data_2 %i; mess %s"), (LPCTSTR)GetDescriptionTransportCommand(command) ,ext_data, ext_data_2, mess);
	stack_logger.SetFunctionInfo(str);
	if(IsLogAll())
	{
		LOG_WARN(str);
	}

	int num_buf=2;
	int ret;
	DWORD sent=0;
	WSABUF data_bufs[4];

	if(command == CMDNeedReconnect)
		m_wait_reconnect = true;

// 	try{

		// Сначала флаг команды
		data_bufs[0].buf=(char*)&flag;
		data_bufs[0].len=4;
		// Потом сама команда
		data_bufs[1].buf=(char*)&command;
		data_bufs[1].len=4;
		if(ext_data!=-1 )
		{
			num_buf=3;
			data_bufs[2].buf=(char*)&ext_data;
			data_bufs[2].len=4;
			if(ext_data_2!=-1 )
			{
				num_buf=4;
				data_bufs[3].buf=(char*)&ext_data_2;
				data_bufs[3].len=4;
			}
		}
		if(mess != 0 )
		{
			num_buf = 4;
			// длина указывается в байтах
			len_mess = _strlen(mess) * sizeof(TCHAR);
			data_bufs[2].buf = (char*)&len_mess;
			data_bufs[2].len = 4;
			data_bufs[3].buf = (char*)mess;
			data_bufs[3].len = len_mess;
		}
		ret = WSASend(m_hSocket, data_bufs, num_buf, &sent, 0, 0, 0);
		if(SOCKET_ERROR == ret)
		{
			m_LastWsaError = WSAGetLastError();
			if(InterlockedExchangeAdd(&is_set_global_exit, 0)==0 && !m_wait_reconnect)
			{
				if(WSAEWOULDBLOCK != m_LastWsaError)
				{
					GetDescriptionSystemError(m_LastWsaError,strm);
					str.Format( APL_T("Ошибка %i передачи команды %s клиенту в нити 0x%04X; Описание: "),
								m_LastWsaError, (LPCTSTR)GetDescriptionTransportCommand(command), GetCurrentThreadId());
					str+=strm;
					LOG_ERROR(str);
				}
				else
				{
					str.Format( APL_T("Блокировка при передаче команды %s в нити 0x%04X"),
							(LPCTSTR)GetDescriptionTransportCommand(command), GetCurrentThreadId());
					LOG_ERROR(str);
				}

			}
			TestErrOnHalt();
			return false;
		}


// 	}catch(CMemoryException* e){
// 		TCHAR   szCause[255];
// 		e->GetErrorMessage(szCause,255);
// 		CString str;
// 		str="CMemoryException";
// 		str+=szCause;
// 		LOG_ERROR(str);	
// 		TRACE(str);
// 	}catch(...){
// 		CString str;
// 		str="Неизвестное исключение";
// 		TRACE(str);
// 		LOG_ERROR(str);
// 	}
	if((mess==0 && (int)sent != num_buf*4) || (mess!=0 && (int)sent != num_buf * 3 + len_mess) )
	{
		str.Format( APL_T("Ошибка: Должны были передать клиенту %i байт при передаче команды %s, а передали %i. Нить " THREAD_FORMAT ),
				(mess==0)?(num_buf*4):(num_buf*3+len_mess), (LPCTSTR)GetDescriptionTransportCommand(command), sent, GetCurrentThreadId());
		GetDescriptionSystemError(m_LastWsaError,strm);
		LOG_ERROR(str+strm);
	}

	return true;
}


void CaplSocketServ::OnAccept(int nErrorCode)
{
	CaplStackLogger stack_logger(__APL_FUNC__);
	
	CString str,str1;
	LOG_WARN_C(_T("Begin"));
	
	// если это сокет для передачи данных - слушать ему вредно
	CaplSocketServ* pSocket = NewDataSocket();
	if(pSocket==0)
	{
		str= APL_T("Функция создания рабочего сокета вернула 0!");
		LOG_ERROR(str);
		return;
	}
	pSocket->m_pool = this;
	pSocket->m_stop_time = m_stop_time;
	if(Accept(*pSocket))
	{
		stack_logger.SetFunctionInfo(_T("Connected "));

		if(m_pool!=0)
		{
			SendCommand(CMDErrorMissedSocketType);
			str= APL_T("Ошибка: Текущий сокет предназначен для передачи данных, а ему пришло сообщение OnAccept");
			LOG_ERROR(str);
			delete pSocket;
		}
		else if(m_max_client!=0 && (DWORD)m_list.GetSize() > m_max_client)
		{
			pSocket->SendCommand(CMDTooManyClients);
			str= APL_T("Ошибка: Слишком много клиентов подключено к серверу! Новое подключение невозможно.");
			LOG_ERROR(str);
			delete pSocket;
// 		}
//		else if(m_stopping)
//			{
// 			pSocket->SendCommand(CMDDeadTime,m_stop_time);
// 			str+="Ошибка: Попытка подключения во время выключения!";
// 			LOG_ERROR(str);
// 			delete pSocket;
		}
		else
		{
			//
// 			bool is_test_mem = false;
// 			int test_size = 4*1024*1024;
// 			LPVOID buf = 0;
// 			CString test_str;
// 			stack_logger.SetFunctionInfo("Выделяем память для проверки ее наличия");
// 			try{
// 				test_str = "выделении памяти";
// 				buf = VirtualAlloc(0,test_size,MEM_COMMIT,PAGE_EXECUTE_READWRITE);
// 				if(buf!=0)
// 				{
// 					test_str = "заполнении памяти";
// 					memset(buf,0,test_size);
// 				}
// 				is_test_mem = true;
// 			}
// 			APL_CATCH_ALL_EXEPTION({str+="\nОшибка при проверке выделения памяти на "+test_str;},LOG_ERROR,{if(SetGlobalExit!=0)SetGlobalExit();};);
// 
// 			if(buf != 0) VirtualFree(buf, 0, MEM_RELEASE);

			if(true /*is_test_mem*/)
			{
				pSocket->m_temp_socket = pSocket->Detach();

				CSocketThread* thread = new CSocketThread(this,pSocket);
				if(thread == 0)
				{
					str= APL_T("Ошибка выделения памяти под объект нити!");
					LOG_ERROR(str);
					delete pSocket;
					return;
				}
				LOG_WARN_B( APL_T("Сокет создан. Запускаем нить ") + thread->m_class_info);
				thread->m_bAutoDelete = TRUE;
				thread->CreateThread();
#ifdef _DEBUG
				str.Format( APL_T("Создали нить 0x%04X\r\n"),thread->m_nThreadID);
				OutputDebugString(str);
#endif
			}
			else
			{
				LOG_ERROR( APL_T("Ошибка при тестовом выделении памяти!"));
				pSocket->SendCommand(CMDCriticalError, ASC_TOO_MANY_CLIENTS_ON_SERVER);
				delete pSocket;
			}
		}
	}
	else
	{
		m_LastWsaError = WSAGetLastError();
		delete pSocket;
		str = APL_T("Ошибка: ");
		GetDescriptionSystemError(m_LastWsaError, str1);
		str += str1;
		LOG_ERROR(str);
	}
	LOG_WARN_C(_T("End"));
}

void CaplSocketServ::SetMaxClient(DWORD size)
{
	// если это сокет для передачи данных - какие нафиг клиенты
	if(m_pool!=0)
		return;
	m_max_client=size;
}

bool CaplSocketServ::ProcessBreakThread(int param)
{
	CaplStackLogger stack_logger(__APL_FUNC__);
	
	// false возвращается только если в BreakOperation будет ошибка
	// а если объект нити не найден - возвращает true, все в порядке - такая нить уже давно прервана :-)
	bool ret=true;
	CSocketThread* thread = (CSocketThread*)param;

	PoolOperateListEnter();
	for(int i=0;i<m_list.GetSize();i++)
	{
		if(m_list.GetAt(i)==thread)
		{
			ret=BreakOperation(((CSocketThread*)thread)->m_pSocket);
			break;
		}
	}
	PoolOperateListLeave();
	return ret;
}

CaplSocketServ* CaplSocketServ::GetSocketFromList(DWORD pos)
{
	CaplStackLogger stack_logger(__APL_FUNC__);
	
	CaplSocketServ* socket=0;
	PoolOperateListEnter();
	if(pos < (DWORD)m_list.GetSize())
	{
		CSocketThread* thread=(CSocketThread*)m_list.GetAt(pos);
		socket=thread->m_pSocket;
	}
	PoolOperateListLeave();
	return socket;
}


bool CaplSocketServ::AttachTempSocket()
{
	if(m_temp_socket==0) return false;

	CaplStackLogger stack_logger(__APL_FUNC__);
	// Закрываем предыдущий сокет, иначе он потеряется
	Close();
	// Цепляем новый сокет
	if(CAsyncSocket::Attach(m_temp_socket, FD_READ | FD_WRITE | FD_CLOSE) == FALSE)
	{
		CString str;
		aplGetDescriptionSystemError(GetLastError(),str);
		LOG_ERROR(APL_T("Ошибка в CAsyncSocket::Attach: ") + str);
		return false;
	}

	// устанавливаем таймер проверки блокировки
	if(m_wnd != 0)
	{
		LogDebugMessageIfDefFormat1( APL_T("Запускаем таймер ID_TIMER_BLOCK с интервалом %i для окна ") + m_wnd->m_class_info, m_ClearedTimeInterval);
		m_wnd->SetTimer(ID_TIMER_BLOCK, m_ClearedTimeInterval, 0);
	}

	MakeClassInfo();

	m_temp_socket=0;

	return true;
}


int CaplSocketServ::PoolAddSocketThread(CSocketThread* thread)
{
	CaplStackLogger stack_logger(__APL_FUNC__);

	int pos=-1;
	PoolOperateListEnter();
	pos=m_list.Add(thread);
	PoolOperateListLeave();
	return pos;
}

bool CaplSocketServ::PoolRemoveSocketThread(CSocketThread* thread)
{
	CaplStackLogger stack_logger(__APL_FUNC__);

	int i;
	bool found=false;
	PoolOperateListEnter();
	for(i=0;i<m_list.GetSize();i++)
	{
		if(m_list.GetAt(i)==thread)
		{
			m_list.RemoveAt(i);
			LogDebugMessageIfDef( APL_T("Удалили"));
			found = true;
			break;
		}
	}
	PoolOperateListLeave();
	return found;
}

void CaplSocketServ::PoolOperateListEnter()
{
//	CaplStackLogger stack_logger(__APL_FUNC__);

	EnterLogCriticalSection(&m_OperateList);
}

void CaplSocketServ::PoolOperateListLeave()
{
//	CaplStackLogger stack_logger(__APL_FUNC__);

	LeaveLogCriticalSection(&m_OperateList);
}



DWORD CaplSocketServ::GetNumAllConnected()
{
	DWORD num;
	PoolOperateListEnter();
	num=m_list.GetSize();
	PoolOperateListLeave();
	return num;
}

//ZZZZZ
DWORD CaplSocketServ::GetNumDataConnected()
{
	DWORD num;
	PoolOperateListEnter();
	num=m_num_data_connects;
	PoolOperateListLeave();
	return num;
}

bool CaplSocketServ::SendPercentage(UINT32 percentage)
{
	CaplStackLogger stack_logger(__APL_FUNC__);

	if(!m_LastPercentageTimer.CheckTimeOut(500)) return true;
	m_LastPercentageTimer.Start();
	return SendCommand(CMDSetProgress, percentage);
}

bool CaplSocketServ::SendPercentageText(LPCTSTR mess)
{
	CaplStackLogger stack_logger(__APL_FUNC__);

	return SendCommand(CMDSetProgressText, -1, -1, mess);
}

bool CaplSocketServ::IsReadyData()
{
	CString str,str1;
	m_LastReadyData = 0;
	if(m_terminate_after > 1)
	{
		return false;
	}
	if(m_hSocket == INVALID_SOCKET)
	{
		return false;
	}
	if(IOCtl(FIONREAD, &m_LastReadyData))
	{
		return (m_LastReadyData!=0);
	}

	CaplStackLogger stack_logger(__APL_FUNC__);

	m_LastWsaError = WSAGetLastError();
	GetDescriptionSystemError(m_LastWsaError,str1);
	str.Format( APL_T("Ошибка (%i) определения готовности данных: "), m_LastWsaError);

	str += str1;
	LOG_ERROR(str);
	stack_logger.SetFunctionInfo(str);

	TestErrOnHalt();

	m_LastReadyData=0;
	return false;
}


long CaplSocketServ::GetStopTime()
{
	return (long)m_stop_time;
}

void CaplSocketServ::SetStopTime(long stop_time)
{
	CaplStackLogger stack_logger(__APL_FUNC__);

	LogDebugMessageIfDefFormat1(_T("Server: (m) CaplSocketServ::SetStopTime(%i);"), stop_time);

	CSocketThread* t_thread;
	int i;
	//Ставим местное время смерти
	m_stop_time = stop_time;

	// чтобы не попасть на значение по умолчанию в SendCommand
	if(stop_time == -1)stop_time = -2;
	if(m_pool == 0)
	{
		// это слушающий сокет. Рассылаем время смерти всем клиентам
		PoolOperateListEnter();
		for(i=m_list.GetSize(); i>0; i--)
		{
			t_thread = (CSocketThread*)m_list.GetAt(i-1);
			if (t_thread!=NULL)
			{
				if(t_thread->m_pSocket == 0) continue;
				if(t_thread->m_pSocket->m_client_type != ECT_Standart) continue;
				t_thread->m_pSocket->SendCommand(CMDDeadTime, stop_time);
			}
		}
		PoolOperateListLeave();
	}
	else
	{
		SendCommand(CMDDeadTime, stop_time);
	}
}

void CaplSocketServ::RenewClientState()
{
	m_wnd->PostMessage(WM_TIMER,ID_TIMER_RENEW,0);
}

void CaplSocketServ::GetServerIpPortString(CString &str)
{
	str.Format(_T("%s:%i (") LOGSERV_THREAD_FORMAT _T(")"), (LPCTSTR)m_server_ip_str, GetServerPort(), GetCurrentProcessId(), GetCurrentThreadId());
}

UINT CaplSocketServ::GetServerPort()
{
	if(m_pool != 0) return m_pool->m_server_port;
	return m_server_port;
}


void CaplSocketServ::GetClientIpPortString(CString &str)
{
	str.Format(_T(" %s:%i"), (LPCTSTR)m_client_ip_s, GetClientPort());
}

DWORD CaplSocketServ::GetClientIp()
{
	return m_client_ip;
}

//inline void CaplSocketServ::LogItemC(SaplLogItem &log_item)
//{
//	if(m_log!=0){
//		m_log->LogMessageC(LogLevelFunctionInfo,log_item);
//	}
//}
//
//inline void CaplSocketServ::LogItemA(SaplLogItem &log_item)
//{
//	if(m_log!=0){
//		m_log->LogMessage(log_item);
//	}
//}
//
inline bool CaplSocketServ::IsLogAll()
{
	if(MainModuleLog!=0)
	{
		return MainModuleLog->m_LogInfoLevel>=LogLevelFunctionInfo;
	}
	return false;
}

inline bool CaplSocketServ::IsModeWWW()
{
	return global_mode_www || m_client_type > ECT_Standart;
}

inline bool CaplSocketServ::IsModeMail()
{
	return global_mode_mail;
}

#define MY_THROW_EX_SYS(err_code,descr) {CString sbuf;GetDescriptionSystemError(GetLastError(),sbuf);throw(SaplErrorDescription(err_code,_T(__FILE__),_T(__DATE__),__LINE__,descr+CString(_T(": "))+sbuf,SaplErrorDescriptionLevel_Server,__APL_FUNC__,_T("")));}

CPSSServiceDescriptor::CPSSServiceDescriptor()
{
	m_service_name = _T("");
	m_process_id = 0;
	m_service_port = 0;
}

CPSSServiceDescriptor::CPSSServiceDescriptor(const CString &_service_name, int _process_id, int _service_port)
{
	m_service_name = _service_name;
	m_process_id = _process_id;
	m_service_port = _service_port;
}

void CPSSServiceDescriptor::Set(CPSSServiceDescriptor &source)
{
	m_service_name = source.m_service_name;
	m_process_id = source.m_process_id;
	m_service_port = source.m_service_port;
}

CPSSServicesList::CPSSServicesList(const CString &_service_name, int _service_port)
: CObject()
	, m_i_m(_service_name,GetCurrentProcessId(),_service_port)
{
	CaplStackLogger stack_logger(__APL_FUNC__);

	InitializeCriticalSection(&m_local_list_protect);

	PSECURITY_ATTRIBUTES psec_attr;
	HANDLE hFile=INVALID_HANDLE_VALUE;

//  	m_i_m.m_service_name = _service_name;
//  	m_i_m.m_service_port = _service_port;
//  	m_i_m.m_process_id = GetCurrentProcessId();

	m_hFileServicesList = 0;
	m_MutexServicesListProcessed = 0;
	m_GlobalServicesListData = 0;
	m_pListSize = 0;
	m_pRegionData = 0;

#ifdef _UNICODE

	m_hFileServicesListU = 0;
	m_MutexServicesListProcessedU = 0;
	m_GlobalServicesListDataU = 0;

	m_pListSizeU = 0;
	m_pRegionDataU = 0;

#endif

	m_last_local_list_reload_timer.Zero();

	m_class_info = _T("CPSSServicesList");

	try
	{
		// создаем объект проекция файла - его сначала
		if(!CreateSecurityAttributes(FILE_MAP_ALL_ACCESS,&psec_attr) || psec_attr==0 ){MY_THROW_EX_SYS(0, APL_T("Ошибка создания атрибутов доступа"));}
		m_hFileServicesList = CreateFileMapping(hFile, psec_attr, PAGE_READWRITE, 0, GLOBAL_SERVICES_LIST_BUFFER_SIZE, NAME_PSSGlobalServicesListPregion); 
		if(m_hFileServicesList == 0) {		
			// MY_THROW_EX_SYS(0, APL_T("Ошибка создания m_hFileServicesList"));
			LOG_ERROR(APL_T("Ошибка чтения списка сервисов"));
			return;
		}
		m_GlobalServicesListData=MapViewOfFile(m_hFileServicesList,FILE_MAP_WRITE,0,0,0);

		m_pRegionData=(BYTE*)m_GlobalServicesListData+GLOBAL_SERVICES_LIST_BUFFER_START_POS;

		// переменные управляющие буфером
		m_pListSize=((long*)m_GlobalServicesListData);
#ifdef _UNICODE
		m_hFileServicesListU = CreateFileMapping(hFile, psec_attr, PAGE_READWRITE, 0, GLOBAL_SERVICES_LIST_BUFFER_SIZE, NAME_PSSGlobalServicesListPregionU); 
		if(m_hFileServicesListU == 0) {		
			// MY_THROW_EX_SYS(0, APL_T("Ошибка создания m_hFileServicesList"));
			LOG_ERROR(APL_T("Ошибка чтения списка сервисов"));
			return;
		}
		m_GlobalServicesListDataU=MapViewOfFile(m_hFileServicesListU,FILE_MAP_WRITE,0,0,0);

		m_pRegionDataU=(BYTE*)m_GlobalServicesListDataU+GLOBAL_SERVICES_LIST_BUFFER_START_POS;

		// переменные управляющие буфером
		m_pListSizeU=((long*)m_GlobalServicesListDataU);
#endif


		// ну и event-ы и mutex-ы в конце
		if(!CreateSecurityAttributes(MUTEX_ALL_ACCESS,&psec_attr) || psec_attr==0 ){MY_THROW_EX_SYS(0, APL_T("Ошибка создания атрибутов доступа"));}
		m_MutexServicesListProcessed=CreateMutex(psec_attr,FALSE,NAME_PSSGlobalServicesListMutex);
		if(m_MutexServicesListProcessed==0){MY_THROW_EX_SYS(0,_T("m_MutexServicesListProcessed ==0"));}
#ifdef _UNICODE
		m_MutexServicesListProcessedU=CreateMutex(psec_attr,FALSE,NAME_PSSGlobalServicesListMutexU);
		if(m_MutexServicesListProcessedU==0){MY_THROW_EX_SYS(0,_T("m_MutexServicesListProcessedU ==0"));}
#endif

		Reload();
	}
	catch(SaplErrorDescription err)
	{
		LOG_ERROR(_T("Error! ") + err.Print(0, false));
		//SetInitError(err);
		Clear();
	}
}

void CPSSServicesList::Clear()
{
	CaplStackLogger stack_logger(__APL_FUNC__);

	if(m_GlobalServicesListData!=0){UnmapViewOfFile(m_GlobalServicesListData);}
	if(m_hFileServicesList!=0){CloseHandle(m_hFileServicesList);}
	if(m_MutexServicesListProcessed!=0){CloseHandle(m_MutexServicesListProcessed);};

	m_pListSize = 0;
	m_pRegionData = 0;

	m_hFileServicesList = 0;
	m_MutexServicesListProcessed = 0;
	m_GlobalServicesListData = 0;
#ifdef _UNICODE
	if(m_GlobalServicesListDataU!=0){UnmapViewOfFile(m_GlobalServicesListDataU);}
	if(m_hFileServicesListU!=0){CloseHandle(m_hFileServicesListU);}
	if(m_MutexServicesListProcessedU!=0){CloseHandle(m_MutexServicesListProcessedU);};
	m_pListSizeU = 0;
	m_pRegionDataU = 0;
	m_hFileServicesListU = 0;
	m_MutexServicesListProcessedU = 0;
	m_GlobalServicesListDataU = 0;
#endif

}

CPSSServicesList::~CPSSServicesList()
{
	CaplStackLogger stack_logger(__APL_FUNC__);

	Reload(false);
	Clear();
	m_local_list.Clear();
	DeleteCriticalSectionODS(&m_local_list_protect);

}

void CPSSServicesList::Reload(bool add)
{
	CaplStackLogger stack_logger(__APL_FUNC__);

	DWORD res;
	int i, count=0, len_str=0;
#ifdef _UNICODE
	wchar_t wbuf[1024];
#endif
	char cbuf[1024];

	bool not_in_list = true;
	bool changed = false;
	CPSSServiceDescriptor* sd = 0;
	CPSSServiceDescriptor sd_read;

	// указатель на буфер - для удобства чтения из расшаренной памяти
	CaplDataBuf dbuf;

	if(m_hFileServicesList == 0) return;
#ifdef _UNICODE
	if(m_hFileServicesListU == 0) return;
#endif

	if( !m_last_local_list_reload_timer.CheckTimeOut(GLOBAL_SERVICES_LIST_RELOAD_INTERVAL)) return;
	m_last_local_list_reload_timer.Start();

	EnterCriticalSectionODS(&m_local_list_protect);
	m_local_list.Clear();

	
	try{
#ifdef _UNICODE
		// список для юникодных приложений
		do{
			res=WaitForSingleObject(m_MutexServicesListProcessedU,GLOBAL_SERVICES_LIST_TIMEOUT);
			switch(res)
			{
			case WAIT_FAILED:
				//MY_THROW_EX_SYS(0, APL_T("Ошибка: WaitForSingleObject вернул WAIT_FAILED. "));
				LogDebugMessageIfDef(_T("WAIT_FAILED"));
				// если из деструктора и ошибка обращения к области списка - нафиг, не ждем
				if(!add) return;
				break;
			case WAIT_TIMEOUT:
				break;
			case WAIT_ABANDONED:
				LogDebugMessageIfDef(_T("WAIT_ABANDONED"));
				// break; мутекс захвачен нами, будем обрабатывать 
			case WAIT_OBJECT_0:
				// читаем расшаренный буфер, проверяем на мертвые
				dbuf.SetExternalData(m_pRegionDataU,*m_pListSizeU);
				dbuf.Read(&count);

				for(i=0;i<count;i++)
				{
					dbuf.Read(&sd_read.m_process_id);
					dbuf.Read(&sd_read.m_service_port);
					dbuf.Read(&len_str);// длина строки в символах
					dbuf.Read(wbuf, len_str * sizeof(TCHAR));
					cbuf[len_str] = 0;
					sd_read.m_service_name = wbuf;
					
					//проверяем, не наш ли процесс
					if(sd_read.m_process_id == GetCurrentProcessId()) 
					{
						// текущий сервис есть в глобальном списке
						not_in_list = false;
						// если вызвано из деструктора - надо текущий сервис убрать из локального списка и сохранить глобальный список
						if(!add)
						{
							changed = true;
							continue;
						}
					}
					//проверяем, не мертв ли процесс
					else if(PSSCheckProcessDead(sd_read.m_process_id))
					{
						// если сервис мертв, то в локаьный список его не заносим и глобальный список переписываем
						changed = true;
						continue;
					}
					bool is_new = true;
					for(int i1=0;i1<m_local_list.GetSize();i1++)
					{
						if(m_local_list[i1]->m_process_id==sd_read.m_process_id){is_new=false;break;}
					}
					if(is_new)
					{
						sd = new CPSSServiceDescriptor(sd_read);
						m_local_list.Add(sd);
					}
				}
				if(add && not_in_list)
				{
					sd = new CPSSServiceDescriptor(m_i_m);
					m_local_list.Add(sd);
					changed = true;
				}
				// по этому флагу выходит из внешнего цикла
				not_in_list = false;
				// пишем расшаренный буфер обратно
				if(changed)
				{
					dbuf.Clear();
					dbuf.Add(&(m_local_list.Size),4);
					for(i=0;i<m_local_list.Size;i++)
					{
						sd = m_local_list.GetAt(i);
						dbuf.Add(&(sd->m_process_id),4);
						dbuf.Add(&(sd->m_service_port),4);
						len_str = sd->m_service_name.GetLength();
						dbuf.Add(&len_str,4);// длина строки в символах
						dbuf.Add(sd->m_service_name.GetBufferSetLength(len_str),len_str * sizeof(TCHAR));
					}
					memcpy(((BYTE*)m_pRegionDataU),dbuf.GetBuffer(),dbuf.m_Size);
					*m_pListSize = dbuf.m_Size;
				}
				// освобождлаем мутекс - чтобы другие процессы могли обновить своим списки :-)
				ReleaseMutex(m_MutexServicesListProcessedU);
				break;
			}
		}while(not_in_list);
#endif

		changed = false;
		bool not_in_ansi_list = true;

		// список для ансишных и юникодных приложений
		do{
			res=WaitForSingleObject(m_MutexServicesListProcessed,GLOBAL_SERVICES_LIST_TIMEOUT);
			switch(res)
			{
			case WAIT_FAILED:
				//MY_THROW_EX_SYS(0, APL_T("Ошибка: WaitForSingleObject вернул WAIT_FAILED. "));
				LogDebugMessageIfDef(_T("WAIT_FAILED"));
				// если из деструктора и ошибка обращения к области списка - нафиг, не ждем
				if(!add) return;
				break;
			case WAIT_TIMEOUT:
				break;
			case WAIT_ABANDONED:
				LogDebugMessageIfDef(_T("WAIT_ABANDONED"));
				// break; мутекс захвачен нами, будем обрабатывать 
			case WAIT_OBJECT_0:
				// читаем расшаренный буфер, проверяем на мертвые
				dbuf.SetExternalData(m_pRegionData,*m_pListSize);
				dbuf.Read(&count);

				for(i=0;i<count;i++)
				{
					dbuf.Read(&sd_read.m_process_id);
					dbuf.Read(&sd_read.m_service_port);
					dbuf.Read(&len_str);// длина строки в символах
					if(sd_read.m_process_id < 0) break; // защита от неверных данных
					if(sd_read.m_service_port < 0) break; // защита от неверных данных
					if(len_str < 0 || len_str > 1024) break; // защита от неверных данных
					dbuf.Read(cbuf, len_str );
					cbuf[len_str] = 0;
#ifdef _UNICODE
					CaplStringAdapter str_ad(cbuf);
					sd_read.m_service_name = (LPCTSTR)str_ad;
#else
					sd_read.m_service_name = cbuf;
#endif
					
					//проверяем, не наш ли процесс
					if(sd_read.m_process_id == GetCurrentProcessId()) 
					{
						// текущий сервис есть в глобальном списке
						not_in_ansi_list = false;
						// если вызвано из деструктора - надо текущий сервис убрать из локального списка и сохранить глобальный список
						if(!add)
						{
							changed = true;
							continue;
						}
					}
					//проверяем, не мертв ли процесс
					else if(PSSCheckProcessDead(sd_read.m_process_id))
					{
						// если сервис мертв, то в локаьный список его не заносим и глобальный список переписываем
						changed = true;
						continue;
					}
					bool is_new = true;
					for(int i1=0;i1<m_local_list.GetSize();i1++)
					{
						if(m_local_list[i1]->m_process_id==sd_read.m_process_id){is_new=false;break;}
					}
					if(is_new)
					{
						sd = new CPSSServiceDescriptor(sd_read);
						m_local_list.Add(sd);
					}
				}
				if(add && not_in_list)
				{
					sd = new CPSSServiceDescriptor(m_i_m);
					m_local_list.Add(sd);
					changed = true;
				}
				// по этому флагу выходит из внешнего цикла
				not_in_list = false;
				// пишем расшаренный буфер обратно
				if(changed || not_in_ansi_list)
				{
					dbuf.Clear();
					dbuf.Add(&(m_local_list.Size),4);
					for(i=0;i<m_local_list.Size;i++)
					{
						sd = m_local_list.GetAt(i);
						dbuf.Add(&(sd->m_process_id),4);
						dbuf.Add(&(sd->m_service_port),4);
						len_str = sd->m_service_name.GetLength();
						dbuf.Add(&len_str,4);// длина строки в символах
#ifdef _UNICODE
						CaplStringAdapter str_ad(sd->m_service_name);
						dbuf.Add((VOID*)(const char*)str_ad, len_str);
#else
						dbuf.Add(sd->m_service_name.GetBufferSetLength(len_str),len_str);
#endif
					}
					memcpy(((BYTE*)m_pRegionData),dbuf.GetBuffer(),dbuf.m_Size);
					*m_pListSize = dbuf.m_Size;
				}
				// освобождлаем мутекс - чтобы другие процессы могли обновить своим списки :-)
				ReleaseMutex(m_MutexServicesListProcessed);
				break;
			}
		}while(not_in_list);
	}
	catch(SaplErrorDescription err)
	{
		LOG_ERROR(err.Print(0, false));
	}	

	LogDebugMessageIfDef( APL_T("Вышли из цикла not_in_list"));

	LeaveCriticalSectionODS(&m_local_list_protect);
}

void CPSSServicesList::GetHtmlTable(const CString &curr_server_name, CString &html_table)
{
	CaplStackLogger stack_logger(__APL_FUNC__);

	int i;
	CPSSServiceDescriptor* sd = 0;
	CString str;

	if(m_hFileServicesList == INVALID_HANDLE_VALUE)
	{
		html_table += _T("<p>");
		html_table += APL_T("Список служб не инициализирован");
		return;
	}
	else if(m_hFileServicesList == 0)
	{
		html_table += _T("<p>");
		html_table += APL_T("Нет доступа к списку служб");
		return;
	}

	Reload();

	EnterCriticalSectionODS(&m_local_list_protect);
	html_table += _T("<p>&nbsp;<p>&nbsp;<h4>");
	html_table += APL_T("Другие службы PSS, запущенные на этом сервере");
	html_table += _T("</h4>");

	CString serv_table=_T("");
	for(i=0;i<m_local_list.Size;i++)
	{
		sd = m_local_list.GetAt(i);
		if(sd->m_process_id == GetCurrentProcessId())
		{
			continue;
		}
		str.Format(_T("%i"),sd->m_service_port);
		serv_table += _T("\t<tr><td><a href = \"http://") + curr_server_name + _T(":") + str + _T("\">") + 
			sd->m_service_name + _T("</a></td><td>") + str + _T("</td></tr>\n");
	}

	// если в списке всего один сервис - это я.
	if(serv_table.IsEmpty())
	{
		html_table += _T("<p>");
		html_table += APL_T("Других служб не запущено");
	}
	else
	{
		html_table += _T("<table>");
		html_table += _T("<tr><td>");
		html_table += APL_T("Название службы");
		html_table += _T("</a></td><td>");
		html_table += APL_T("Порт");
		html_table += _T("</td></tr>\n");
		html_table += serv_table;
		html_table += _T("</table>\n");
	}
	LeaveCriticalSectionODS(&m_local_list_protect);
}


bool PSSCheckProcessDead(int process_id)
{
	CaplStackLogger stack_logger(__APL_FUNC__);

	HANDLE process_handle;
	bool dead=false;
	DWORD ExitCode;

	process_handle=OpenProcess(SYNCHRONIZE|PROCESS_QUERY_INFORMATION,TRUE,process_id);
	if(process_handle==0)
	{
		//DWORD err=::GetLastError();
		// с такими флагами открыться должен любой существующий процесс.
		// если HANDLE не определен - значит, процесса уже нет
		dead=true;
	}
	else
	{
		if(GetExitCodeProcess(process_handle,&ExitCode)==0)
		{
			// 							DWORD err=::GetLastError();
			// 							GetDescriptionSystemError(err,strr);
			// что-то непонятное... Пока проигнорируем
		}else if(ExitCode!=STILL_ACTIVE )
		{
			// ну как же - умер значит!
			dead=true;
		}
		CloseHandle(process_handle);
	}
	return dead;
}


bool CaplSocketServ::GetCookie(LPCTSTR param, CString &value)
{
	value = _T("");
	int indx = m_cookies.FindByIn(param);
	if(indx==-1)
		return false;
	value = m_cookies.Get(indx);
	return true;
}

// при http запросе дочитывает данные из сокета до конца заголовка (два переноса строки)
bool CaplSocketServ::ReadNextHttpFromSocket()
{
	int remainder=0;
	int time_out=0;
	BYTE *buff=0;
	int nRead1;
	CString	str="",str1="";

	int size_portion=WWW_PORTION_SIZE;

	buff=new BYTE[size_portion];

	if(m_size_waited!=0 ){
		remainder=m_size_waited-m_session_data.m_Size;
	}
	do{
		// 		memset(buff,0,size_portion);
		IsReadyData();

		if(m_LastReadyData==0)
		{
			// читать из канала нечего и тайм-ауты все вышли
			break;
		}
		if(remainder!=0 && size_portion>remainder){size_portion=remainder;}
		nRead1 = CSocket::Receive(buff, size_portion);
		if(nRead1==SOCKET_ERROR)
		{
			m_LastWsaError=WSAGetLastError();GetDescriptionSystemError(m_LastWsaError,str1);
			str.Format(APL_T("Ошибка сокета при приеме данных! N ошибки %i; Описание: "),m_LastWsaError);str+=str1;
			if(m_LastWsaError==WSAECONNABORTED || m_LastWsaError==WSAECONNRESET)
			{
				delete buff;
				buff=0;
				APL_TRANSPORT_THROW_EX(ASC_ERROR_IN_SOCKET, str);
			}
		}
		else
		{
			m_session_data.Add(buff,nRead1);
		}
		if(m_size_waited!=0 ){
			remainder=m_size_waited-m_session_data.m_Size;
		}
	}while(remainder!=0);// пока в буфере есть еще данные или - если доп чтение из заголовков - одно чтение

	delete buff;
	buff=0;

	return true;
}

// Проверяет кодировку исходной строки (UTF8 или ANSI) и раскодирует в текущую системную кодировку
CString DecodeURLString(CStringA &sSource)
{
	CString sEncoded = _T("");
	CStringA sTested = "";
	CString m_class_info = _T(""); // Для работы макросов APL_TRANSPORT_THROW_EX_SYS

	char c;
	int k=sSource.GetLength();

	if(sSource=="")
		return sEncoded;

	for(int i=0; i<k; i++)
	{
		c=sSource[i];
		if(c=='%')
		{
			if(i+2<k)
			{
				char c0=sSource[i+1];
				char c1=sSource[i+2];
				c=(c0>='A' ? ((c0&0xdf) -'A') +10 : (c0-'0'));
				c*=16;
				c+=(c1>='A' ? ((c1&0xdf) -'A') +10 : (c1-'0'));
				i+=2;
				sTested+=c;
			}
			else{sTested+=c;}
		}
		else{sTested+=c;}
	}
	
#ifdef __linux__
	// в линуксе строки UTF8 правильно конвертируются самим CaplStringW
	// А вот для декодирования ANSI надо будет дорабатывать код!
	sEncoded = sTested;

#else // #ifdef __linux__
	// если что-то пойдет не так - хоть кривая строчка останется
	sEncoded = sTested;

	BYTE *ucs_buf=0;
	int ucs_buf_len=0;

	// URI может быть в ANSI и в UTF-8
	// сначала пытаемся раскодировать как UTF-8
	if(!CaplStringFile::ConvertEncoding2UCS2(aplUTF8, (BYTE *)sTested.GetBuffer(), sTested.GetLength(), ucs_buf, ucs_buf_len) || ucs_buf==0)
	{
		APL_TRANSPORT_THROW_EX_SYS(ASC_ERROR_PROCESSED,"Ошибка определения кодировки URL.");
	}
	// Если в tmp_buf был ANSI, то при преобразовании в строке будет мусор. Ищем его.
	INT16 *test_buf = (INT16*)ucs_buf;
	int test_buf_len=ucs_buf_len/2;
	bool is_utf=true;

	for(int t=0; t<test_buf_len;t++)
	{
		if(test_buf[t] == (INT16)0xFFFD)
		{
			// мусор есть, это был не UTF-8
			is_utf = false;
			break;
		}
	}
#ifdef _UNICODE
	if(!is_utf)
	{
		// удаляем предыдущий буфер и декодируем URI как ANSI
		delete ucs_buf;
		ucs_buf=0;

		if(!CaplStringFile::ConvertEncoding2UCS2(aplANSI, (BYTE *)sTested.GetBuffer(), sTested.GetLength(), ucs_buf, ucs_buf_len) || ucs_buf==0)
		{
			APL_TRANSPORT_THROW_EX_SYS(ASC_ERROR_PROCESSED,APL_T("Ошибка декодирования URL."));
		}
	}
	sEncoded = (LPCTSTR)ucs_buf;
#else
	if(is_utf)
	{
		// возвращаем из UCS2 обратно в ANSI
		CaplStringAdapter str_ad((const wchar_t*)ucs_buf);
		sEncoded = (LPCTSTR)str_ad;
	}
	else
	{
		// проект ANSI и строка ANSI, лишние преобразования не нужны
	}
#endif
	if(ucs_buf!=0) delete ucs_buf;

#endif // #ifdef __linux__  #else 
	return sEncoded;
}

// В студии пока все проекты с одинаковыми заголовками
// Если надо будет что-то особенное - проще перегрузить эту функцию в одном проекте
void CaplSocketServ::CheckCompliteHttpHeaders()
{
	m_smHttpHeadersOut.Add(_T("Server"), _T("PSS Server 4.4"));

	if(m_smHttpHeadersOut.FindByIn(_T("Cache-Control"))==-1)
	{
		m_smHttpHeadersOut.Add(_T("Cache-Control"), _T("no-cache, must-revalidate"));
		m_smHttpHeadersOut.Add(_T("Pragma"), _T("no-cache")); // помечен как устаревший, но пусть дублируется
	}
	if(m_terminate_after > 0)
	{
		m_smHttpHeadersOut.Add(_T("Connection"), _T("close"));
	}
	else if(m_smHttpHeadersOut.FindByIn(_T("Connection"))==-1)
	{
		m_smHttpHeadersOut.Add(_T("Connection"), _T("Keep-Alive"));
		m_smHttpHeadersOut.Add(_T("Keep-Alive"), _T("timeout=31536000, max=31536000")); // таймаут: год, запросов - каждую секунду в году
	}

	m_smHttpHeadersOut.Add(_T("Accept-Ranges"), _T("none"));

	if(m_smHttpHeadersOut.FindByIn(_T("Date"))==-1 && m_smHttpHeadersOut.FindByIn(_T("ETag"))==-1
		&& m_smHttpHeadersOut.FindByIn(_T("Expires"))==-1)
	{
		//TODO: заменить на ETags
		SYSTEMTIME sys_time;
		GetSystemTime(&sys_time);
		COleDateTime odt(sys_time);
		//						Date: Thu, 2 March 2023 17:56:43 GMT
		CString str = odt.Format(_T("%a, %d %B %Y %H:%M:%S GMT"));
		m_smHttpHeadersOut.Add(_T("Date"), str);
	}

	// Кодировка по умолчанию Utf-8
	if(m_smHttpHeadersOut.FindByIn(_T("Content-Type"))==-1)
	{
		if(m_dbufFileBody.m_Size!=0)
			m_smHttpHeadersOut.Add(_T("Content-Type"), _T("application/force-download"));
		else if(!m_csHtmlAnswer.IsEmpty())
			m_smHttpHeadersOut.Add(_T("Content-Type"), _T("text/html; charset=UTF-8"));
		else
			m_smHttpHeadersOut.Add(_T("Content-Type"), _T("text/plain; charset=UTF-8"));
	}

	m_smHttpHeadersOut.Add(_T("Accept-Ranges"), _T("none"));

	//	if(m_smHttpHeadersOut.FindByIn(_T("Content-Security-Policy"))==-1)
	//	{
	//		m_smHttpHeadersOut.Add(_T("Content-Security-Policy"), _T("default-src 'self';")); //- надо разбираться, чтобы отрабатывали inline скрипты и css м все прочее с текущего домена
	//	}	
}

void CaplSocketServ::ClearAnswerData(int http_code)
{
	//LOG_SHOW_DEBUG_MESSAGE(_T("Установили код http %i"), http_code);
	m_http_code=http_code;
	if(http_code>=500 || http_code==0)m_smHttpHeadersOut.Clear();
	m_csHtmlAnswer.Empty();
	m_dbufFileBody.Clear();
	m_terminate_after = 0;
}


int CaplSocketServ::SetHttpCode(int http_code)
{
	//LOG_SHOW_DEBUG_MESSAGE(_T("Установили код http %i"), http_code);
	return (m_http_code=http_code);
}


void CaplSocketServ::AddHttpHeaderExpires(int sec)
{
	SYSTEMTIME sys_time;
	GetSystemTime(&sys_time);
	COleDateTime odt(sys_time);
	odt+=COleDateTimeSpan(0,0,0,sec);
	//						Date: Thu, 2 March 2023 17:56:43 GMT
	CString str = odt.Format(_T("%a, %d %B %Y %H:%M:%S GMT"));
	m_smHttpHeadersOut.Add(_T("Expires"), str);
}


// Функция для формирования ответа. По http коду возвращает строку заголовка
// Добавляет в шапку заголовки из
CString CaplSocketServ::MakeHttpCodeWithHeaders()
{
	CString answer(_T(""));
	// Заголовок HTTP
	HttpCode2Text(m_http_code, answer, false, _T("HTTP/1.1 "));
	answer += _T("\n");

	// Убираем из заголовков переносы, пустые параметры, и добавляем в шапку
	answer += m_smHttpHeadersOut.PrintList();

	return answer;
}

// Собирает из разных источников ответ на http запроси записывает его в m_cdbAnswer
// Если данные ответа записаны в сокет внутри функции, то m_cdbAnswer обнуляется
ULONGLONG CaplSocketServ::PrepareHttpAnswer(ULONGLONG ullExtSize)
{
	ULONGLONG size = -1;
	// Если при формировании ответа не заполнили обязательные http заголовки - заполняем их стандартными значениями
	//	и при необходимости формируем страничку с ошибкой
	CheckCompliteHttpHeaders();

	// Записываем в шапку версию HTTP и код ответа
	CString csAnswHead = MakeHttpCodeWithHeaders();

	// 	answer += _T("Transfer-Encoding: chunked\n"); // если размер данных неизвестен, то надо использовать этот
	//		заголовок, а потом передавать порциями формата: длина порции - порция - длина порции - порция
	// 	answer += _T("Vary: Accept-Encoding\n");

	CString buf;
	if(m_dbufFileBody.m_Size > 0 || ullExtSize!=(ULONGLONG)-1)
	{
		//LOG_SHOW_DEBUG_MESSAGE(_T("Готовим шапку: m_dbufFileBody %d байт, ullExtSize %lld байт"), m_dbufFileBody.m_Size, ullExtSize);
		//LOG_SHOW_DEBUG_MESSAGE(_T("Готовим шапку: m_dbufFileBody %d байт, ullExtSize %I64d байт"), m_dbufFileBody.m_Size, ullExtSize);
		if(m_dbufFileBody.m_Size > 0)
		{	buf.Format(_T("%i"), m_dbufFileBody.m_Size); size = m_dbufFileBody.m_Size;}
		else
		{	buf.Format(_T("%I64d"), ullExtSize); size = ullExtSize;}

		csAnswHead += _T("Content-length: ") + buf + _T("\n\n");

#ifdef _UNICODE
		CaplStringAdapter str_ad((LPCWSTR)csAnswHead);
		str_ad.m_bIsAnsiUtf8 = true; //  для гарантии
		CaplStringA csaAnswHeadUtf8 = (const char*)str_ad;
		m_cdbAnswer.Add(csaAnswHeadUtf8.GetBuffer(csaAnswHeadUtf8.GetLength()), csaAnswHeadUtf8.GetLength());
#else // #ifdef _UNICODE
		m_cdbAnswer.Add(csAnswHead.GetBuffer(csAnswHead.GetLength()), csAnswHead.GetLength());
#endif // #ifdef _UNICODE
		m_cdbAnswer.Add(m_dbufFileBody.GetBuffer(), m_dbufFileBody.m_Size);
	}
	else
	{
		//LOG_SHOW_DEBUG_MESSAGE(_T("Готовим шапку: m_dbufFileBody %d байт, ullExtSize %lld байт"), m_dbufFileBody.m_Size, ullExtSize);
		//LOG_SHOW_DEBUG_MESSAGE(_T("Готовим шапку: m_dbufFileBody %d байт, ullExtSize %I64d байт"), m_dbufFileBody.m_Size, ullExtSize);

#ifdef _UNICODE
		CaplStringAdapter str_ad((LPCWSTR)m_csHtmlAnswer);
		str_ad.m_bIsAnsiUtf8 = true; //  для гарантии
		CaplStringA csaHtmlAnswUtf8 = (const char*) str_ad;
#else // #ifdef _UNICODE
		CaplStringAdapter str_ad1((LPCSTR)m_csHtmlAnswer);
		CStringW str_dataW=(LPCWSTR)str_ad1;
		CaplStringAdapter str_ad2((LPCWSTR)str_dataW);
		str_ad2.m_bIsAnsiUtf8 = true;
		CStringA csaHtmlAnswUtf8 = (LPCSTR)str_ad2;
#endif // #ifdef _UNICODE

		//if(csaHtmlAnswUtf8.GetLength() > 0)
		{
			buf.Format(_T("%i"), csaHtmlAnswUtf8.GetLength());
			csAnswHead += _T("Content-length: ") + buf + _T("\n\n");
			size = csaHtmlAnswUtf8.GetLength();
		}

#ifdef _UNICODE
		str_ad = (LPCWSTR)csAnswHead;
		str_ad.m_bIsAnsiUtf8 = true; //  для гарантии
		CaplStringA csaAnswHeadUtf8 = (const char*)str_ad;
		m_cdbAnswer.Add(csaAnswHeadUtf8.GetBuffer(csaAnswHeadUtf8.GetLength()), csaAnswHeadUtf8.GetLength());
#else // #ifdef _UNICODE
		m_cdbAnswer.Add(csAnswHead.GetBuffer(csAnswHead.GetLength()), csAnswHead.GetLength());
#endif // #ifdef _UNICODE

		m_cdbAnswer.Add(csaHtmlAnswUtf8.GetBuffer(csaHtmlAnswUtf8.GetLength()), csaHtmlAnswUtf8.GetLength());
	}

	if(IsLogAll()){LOG_WARN(APL_T("Ответ\n\t HTTP запрос:\n '%s'\n\t Шапка и HTTP заголовки ответа:\n %s\n\t"),
								(LPCTSTR)m_uri_data.m_uri_path, (LPCTSTR)csAnswHead);}

	return size;
}
