Справочник функций

Ваш аккаунт

Войти через: 
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:

Почтовая рассылка

Подписчиков: -1
Последний выпуск: 19.06.2015

Потоки и Сокеты

19K
05 июля 2007 года
Rost
45 / / 05.07.2007
Возникла проблемка при написание клиентсокого приложеня. Схематично код выглядит так (:

Код:
//-- Создается сокет, коннектится
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
connect(sock, ...);

...

//--Создается поток в котором обрабатываются данные приходящие от сервера
SECURITY_ATTRIBUTES sSecurAttrib;
 
sSecurAttrib.nLength = sizeof(SECURITY_ATTRIBUTES);
sSecurAttrib.bInheritHandle = TRUE;
sSecurAttrib.lpSecurityDescriptor = NULL;
 
CreateThread(&sSecurAttrib, 0, pThread_Proc, pParam, 0, &nThread_ID);

...

Суть проблемы в том что создаваемый поток не может, по неизвестным мне причинам (:, читать, писать в сокет в отличие от потока в котором собственно сокет создавался. Можно конечно и создание сокета запихнуть в поток с обработкой поступающих данных, но в моем случае необходимо иметь доступ к сокету из двух потоков, т.к. все это дело реализованно в классе и вызываются методы из основной программы которые работают с сокетом. Искал в инете решение этой проблемы, единственное на что натыкался так это на то что не наследуются дескрипторы потока.. хотя не уверен что это имеет здесь место. Темболее что sSecurAttrib.bInheritHandle = TRUE; как раз разрешает такое наследование если я не ошибаюсь.... Помогите разрулить проблемму плиз.
22K
05 июля 2007 года
Pastor
43 / / 16.05.2007
сокет сдалай глобальным... да и без мьютексов не обойтись....
353
05 июля 2007 года
Nixus
840 / / 04.01.2007
 
Код:
CreateThread(NULL, 0, pThread_Proc, pParam, 0, &nThread_ID);

А так не робит?
А что вообще поток делает с сокетом и как получает его хэндл?
19K
05 июля 2007 года
Rost
45 / / 05.07.2007
Цитата: Pastor
сокет сдалай глобальным... да и без мьютексов не обойтись....


Он и так можно сказать глобальный он же в классе определен.
Зачем мьютексы? Если ты по поводу thread safe, то это я все контролирую при помощи CriticalSection, проблема то не в этом, а в том что не читает и не пишет в сокет из других потоков, кроме как из того в котором он (сокет) создан.

Цитата: Nixus
 
Код:
CreateThread(NULL, 0, pThread_Proc, pParam, 0, &nThread_ID);

А так не робит?
А что вообще поток делает с сокетом и как получает его хэндл?


Нет с NULL в Secur Attribut'ах ниче не меняется.
В качестве параметра передаю указатель на класс.

 
Код:
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)CommandThread, [COLOR="Blue"]this[/COLOR], 0, &nThread_ID);

Вот собственно как выглядит частичное описание класса.
Код:
class CClient_Base
{
 protected:
      SOCKET sockClient;
     
      ...
     
      static DWORD __stdcall CommandThread(CClient_Base *_this);
 
 public:
      ...
};

Ну и естественно в теле потока получаю доступ к сокету так.
 
Код:
DWORD __stdcall CClient_Base::CommandThread(CClient_Base *_this)
{
 ...
 recv([COLOR="Blue"]_this->sockClient[/COLOR], bfr, ...);
 ...
}

recv ничего не получает, он просто тупо ждет пока пакет придет, хотя там приходит куча просто.., send тоже ничего не отправляет.. проверенно сниффером (:
350
06 июля 2007 года
cheburator
589 / / 01.06.2006
Если идентификатор унаследован от родительского процесса, т. е. от другого процесса, его надо "переоткрыть", т. е. вызвать DuplicateHandle. Честно говоря, насчет сокетов ничего не могу сказать. Лично мои все проблемы заключались в использовании одного сокета в пределах одного процесса, но между разными потоками, и эта проблема решалась легко без всякого наследования.
353
06 июля 2007 года
Nixus
840 / / 04.01.2007
Проверь получает ли поток адекватныё хэндл.
Возможно, проблема в привидении типов. CICQ_Client от каких классов наследуется, в каком порядке, и в каком классе создается сам поток?
19K
06 июля 2007 года
Rost
45 / / 05.07.2007
Цитата: Nixus
Проверь получает ли поток адекватныё хэндл.
Возможно, проблема в привидении типов. CICQ_Client от каких классов наследуется, в каком порядке, и в каком классе создается сам поток?


CClient_Base сам является базовым классом, в нем же и создается сокет и запускается тред. Проверил хендлы по значению - одинаковы.

Цитата: cheburator
Если идентификатор унаследован от родительского процесса, т. е. от другого процесса, его надо "переоткрыть", т. е. вызвать DuplicateHandle. Честно говоря, насчет сокетов ничего не могу сказать. Лично мои все проблемы заключались в использовании одного сокета в пределах одного процесса, но между разными потоками, и эта проблема решалась легко без всякого наследования.


Попробую разобраться. MSDN мне в помощь :)

342
06 июля 2007 года
Yos
209 / / 21.06.2003
У Вас проблема с приведением типов "в разных выполняемых областях кода". У меня в одном решении применен именно такой же способ обработки. Работает все зер гуд. Это болезнь компилера - ОСОБЕННО КОГДА ВЫ УКАЗЫВАЕТЕ sockClient КАК protected. Поставьте public и при вызове CreateThread первый параметр как NULL...

Кстати остерегайтесь при межпотоковой работе передавать указатели на наследуемые объекты...
353
06 июля 2007 года
Nixus
840 / / 04.01.2007
Цитата: Rost
CICQ_Client сам является базовым классом, в нем же и создается сокет и запускается тред. Проверил хендлы по значению - одинаковы.


Значит где-то неправильно вызываются winsock-функции.
А вообще, судить по этим обрывкам могут только телепаты, а их думаю здесь нет. :)

342
06 июля 2007 года
Yos
209 / / 21.06.2003
У меня сначало было желание попросить выложить весь код... А потом Думаю а пускай сам подумает после MSDNа. Но вот что сразу бросилось в глаза то и написал...

А телепатов тут действительно нет, иначи бы мы тут не сидели :)))
19K
06 июля 2007 года
Rost
45 / / 05.07.2007
Цитата: Nixus
Значит где-то неправильно вызываются winsock-функции.
А вообще, судить по этим обрывкам могут только телепаты, а их думаю здесь нет. :)


Думаю в правильности вызова функций сомневаться не стоит, да и WSAGetLastError это подтверждает.

Цитата: Yos
У Вас проблема с приведением типов "в разных выполняемых областях кода". У меня в одном решении применен именно такой же способ обработки. Работает все зер гуд. Это болезнь компилера - ОСОБЕННО КОГДА ВЫ УКАЗЫВАЕТЕ sockClient КАК protected. Поставьте public и при вызове CreateThread первый параметр как NULL...

Кстати остерегайтесь при межпотоковой работе передавать указатели на наследуемые объекты...


Решение нашел, случайно :), нужно в методе создающем поток в конце вставить Sleep(1000). Но это и решением назвать нельзя, т.к. почему так происходит не понимаю.. а следовательно может это у меня работает, а на другом компе не будет. Вот более полный листинг.

Класс реализующий работу с тредом

Код:
typedef DWORD (__stdcall * PROC_THREAD)(PVOID _pParam);

class CThread
{
    private:
        PROC_THREAD pThread_Proc;
        PVOID       pThread_Handle;
        DWORD       nThread_ID;
       
    public:
        CThread();
        CThread(PROC_THREAD _pThread_Proc);
        virtual ~CThread();
       
        bool        Start(PVOID _pParam, bool bSuspended = false);
        bool        Pause();
        bool        Resume();
        bool        Join();
        bool        Stop();
       
        bool        SetPriority(int _nPriority);
        int     GetPriority();
       
        void        SetThread(PROC_THREAD _pThread_Proc,   PVOID _pThread_Handle,   DWORD _nThread_ID);
        void        GetThread(PROC_THREAD & _pThread_Proc, PVOID & _pThread_Handle, DWORD & _nThread_ID);
};

Код:
CThread::CThread()
{
 pThread_Proc   = NULL;
 pThread_Handle = NULL;
 nThread_ID = 0;
};
//-------------------------------------------------------------------------
CThread::CThread(PROC_THREAD _pThread_Proc)
{
 pThread_Proc   = _pThread_Proc;
 pThread_Handle = NULL;
 nThread_ID = 0;
};
//-------------------------------------------------------------------------
CThread::~CThread()
{
 //-- Stop();
};
//-------------------------------------------------------------------------
bool CThread::Start(PVOID _pParam, bool bSuspended)
{
 if((pThread_Handle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)pThread_Proc, _pParam, bSuspended ? CREATE_SUSPENDED : 0, &nThread_ID)) == NULL)
  {
   return false;
  }
 
 return true;
};
//-------------------------------------------------------------------------
bool CThread::Pause()
{
 return (SuspendThread(pThread_Handle) != -1);
};
//-------------------------------------------------------------------------
bool CThread::Resume()
{
 return (ResumeThread(pThread_Handle) != -1);
};
//-------------------------------------------------------------------------
bool CThread::Join()
{
 if(WaitForSingleObject(pThread_Handle, INFINITE) != WAIT_OBJECT_0)
  {
   return false;
  }
 
 return true;
};
//-------------------------------------------------------------------------
bool CThread::Stop()
{
 if(TerminateThread(pThread_Handle, 0))
  {
   CloseHandle(pThread_Handle);
   
   pThread_Handle   = NULL;
   nThread_ID       = 0;
   
   return true;
  }
 
 return false;
};
//-------------------------------------------------------------------------
bool CThread::SetPriority(int _nPriority)
{
 return (SetThreadPriority(pThread_Handle, _nPriority) != 0);
};
//-------------------------------------------------------------------------
int CThread::GetPriority()
{
 return GetThreadPriority(pThread_Handle);
};
//-------------------------------------------------------------------------
void CThread::SetThread(PROC_THREAD _pThread_Proc, PVOID _pThread_Handle, DWORD _nThread_ID)
{
 pThread_Proc       = _pThread_Proc;
 pThread_Handle     = _pThread_Handle;
 nThread_ID     = _nThread_ID;
};
//-------------------------------------------------------------------------
void CThread::GetThread(PROC_THREAD & _pThread_Proc, PVOID & _pThread_Handle, DWORD & _nThread_ID)
{
 _pThread_Proc      = pThread_Proc;
 _pThread_Handle    = pThread_Handle;
 _nThread_ID        = nThread_ID;
};

Код выполняемый в теле класса клиента
Код:
int CClient_Base::Connect(const char *_pszHostName, WORD _nPort)
{
 SOCKADDR_IN    addrClient;
 PHOSTENT   pHost;
 DWORD      nAddrInet;
 
 EnterCriticalSection(&mutexConnection);
 
 memset(&addrClient, 0x00, sizeof(SOCKADDR_IN));
 
 if((sockClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == SOCKET_ERROR)
  {
   LeaveCriticalSection(&mutexConnection);
   return -1;
  }
 
 if(isalpha(*_pszHostName))
  {
   pHost    = gethostbyname(_pszHostName);
  }
 else
  {
   nAddrInet    = inet_addr(_pszHostName);
   pHost    = gethostbyaddr((const char*)&nAddrInet, sizeof(DWORD), AF_INET);
  }
 
 if(pHost != NULL)  addrClient.sin_addr.s_addr  = *((PDWORD)pHost->h_addr);
 else           addrClient.sin_addr.s_addr  = inet_addr((const char *)_pszHostName);
 
 addrClient.sin_family      = AF_INET;
 addrClient.sin_port        = htons(_nPort);
 
 if(connect(sockClient, (PSOCKADDR)&addrClient, sizeof(SOCKADDR_IN)) == SOCKET_ERROR)
  {
   closesocket(sockClient);
   sockClient   = SOCKET_ERROR;
   
   LeaveCriticalSection(&mutexConnection);
   return -2;
  }
 
 LeaveCriticalSection(&mutexConnection);
 return 0;
};
//-------------------------------------------------------------------------
//-- [COLOR="#008000"]Тот самый метод который все и запускает... :)[/COLOR]
bool CClient_Base::Connect_Auth(const char *_pszHostName, WORD _nPort, const char *_pszLogin, const char * _pszPwd)
{
 if(sockClient != SOCKET_ERROR) return false;
 //--
 ...
 
 strncpy(szLGN, _pszLogin,  MAX_LEN_LOGIN);
 strncpy(szPWD, _pszPwd,    MAX_LEN_PASSWORD);
 
 //--
 int    nErrCode;
 
 //-- _THROW_EVENT - просто вызывает функцию обработчик событий
 _THROW_EVENT(EF_CONNECTION, EC_CONNECT_AUTH, NULL);

 //-- Подключаемся к указонному хосту
 if((nErrCode = this->Connect(_pszHostName, _nPort)) != 0)
  {
   _THROW_EVENT(EF_ERROR, (nErrCode == -1) ? EE_WSA_SOCKET : EE_WSA_HOST, NULL);
   _THROW_EVENT(EF_CONNECTION, EC_DISCONNECT, NULL);
   return false;
  }
 
 //-- cthrCommandLoop - экземпляр класса CThread
 //-- Запускаем поток обработчик входящих данных
 if(!cthrCommandLoop.Start(this))
  {
   _THROW_EVENT(EF_ERROR, EE_THREAD_COMMAND, NULL);
   Disconnect();
   return false;
  }
 
 //-- Если ЗДЕСЬ вставить Sleep не меньше 1 сек то все работает... но почему!?!
[COLOR="Red"] Sleep(1000);[/COLOR]
 return true;
};
//-------------------------------------------------------------------------
//-- [COLOR="Green"]Собственно сам поток[/COLOR]
DWORD __stdcall CClient_Base::CommandThread(CClient_Base *_this)
{
 ...
 
 while((bytesRecv = recv(_this->sockClient, ...)) != SOCKET_ERROR)
  {
   ...
  }
 
 ...
};
353
06 июля 2007 года
Nixus
840 / / 04.01.2007
 
Код:
//-- Если ЗДЕСЬ вставить Sleep не меньше 1 сек то все работает... но почему!?!
 Sleep(1000);
 return true;
};

Пока вижу одно объяснение: сокет убивется после возвращения из этой функции и до того как создаваемый поток начинает из него что-то ждать.
Куда передается выполнение после возвращения из функции? Может там сокет убивается?
19K
06 июля 2007 года
Rost
45 / / 05.07.2007
Цитата: Nixus
 
Код:
//-- Если ЗДЕСЬ вставить Sleep не меньше 1 сек то все работает... но почему!?!
 Sleep(1000);
 return true;
};

Пока вижу одно объяснение: сокет убивется после возвращения из этой функции и до того как создаваемый поток начинает из него что-то ждать.
Куда передается выполнение после возвращения из функции? Может там сокет убивается?


Т.к. тестю эту функцию то собственно после ее вызова просто бесконечный цикл, который ничего не делает

 
Код:
MSG msg;
 BOOL   bRet;
 while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
  {
   if (bRet != -1)
     {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
     }
  }

Не думаю что сокет убивается, т.к. если с задержкой в секунду то дальше все работает нормально и читает и пишет из сокета в другом потоке, а вот если без задержки то сокет не работоспособным становится, хотя WSAGetLastError после send возвращает 0... в чем дело не пойму...
353
06 июля 2007 года
Nixus
840 / / 04.01.2007
Цитата: Rost
Не думаю что сокет убивается, т.к. если с задержкой в секунду то дальше все работает нормально и читает и пишет из сокета в другом потоке, а вот если без задержки то сокет не работоспособным становится, хотя WSAGetLastError после send возвращает 0... в чем дело не пойму...


Точно не скажу, т.к. эмпирических данных по этому вопросу нет, а в документации этот вопрос не встречал, но думаю что если один поток ждет данные с сокета, а другой его убивает, то он не убъется.
Если разгадка не в этом, то сдаюсь. Ищите полтергейст. :)

P.S. И небольшой оффтоп. Определять что в строке: ip адрес или имя домена по первому символу - неправильно.

19K
06 июля 2007 года
Rost
45 / / 05.07.2007
Цитата: Nixus
Точно не скажу, т.к. эмпирических данных по этому вопросу нет, а в документации этот вопрос не встречал, но думаю что если один поток ждет данные с сокета, а другой его убивает, то он не убъется.
Если разгадка не в этом, то сдаюсь. Ищите полтергейст. :)


Зх буду думать...

Цитата: Nixus

P.S. И небольшой оффтоп. Определять что в строке: ip адрес или имя домена по первому символу - неправильно.


Почему же в MSDN везде так и делают (: Хотя конечно доменное имя может начинаться с цифры...

3.3K
10 июля 2007 года
GENA_DJ
123 / / 08.03.2005
А точно не возникает ситуации, когда 2 или более потока одновременно пытаются работать с сокетом?
19K
10 июля 2007 года
Rost
45 / / 05.07.2007
Цитата: GENA_DJ
А точно не возникает ситуации, когда 2 или более потока одновременно пытаются работать с сокетом?


да точно не возникает, нашел еще одно решение.. вместо Sleep(1000) в самом начале потока вызвать WSAStartup... хотя опять таки не понятно, ведь в самом начале приложения в основном потоке я ее вызываю.. или ее нужно в каждом новом?.. вроде бы нет, она же просто библиотеки какието подгружает и все..

3.3K
11 июля 2007 года
GENA_DJ
123 / / 08.03.2005
Цитата: Rost
в самом начале потока вызвать WSAStartup... хотя опять таки не понятно, ведь в самом начале приложения в основном потоке я ее вызываю


Вызываешь до запуска второго потока или после?

Цитата:

.. или ее нужно в каждом новом?..


Нет

19K
12 июля 2007 года
Rost
45 / / 05.07.2007
Цитата: GENA_DJ
Вызываешь до запуска второго потока или после?



Первоначально вызываю в самом начале WinMain, и второй раз в начале самого второго потока который обрабатывает данные с сокета.. тогда все работает

342
13 июля 2007 года
Yos
209 / / 21.06.2003
Аллёёёёёёёёёёё - а где же bind() для сокета между socket и connect. Первый раз вижу что что-то должно работать...
353
13 июля 2007 года
Nixus
840 / / 04.01.2007
Кажется, bind не является обязательным, если connect используется. Только если хочется использовать какой-то заранее заданый порт.
342
13 июля 2007 года
Yos
209 / / 21.06.2003
Согласно общедоступных описаний включая специальные издания Microsoft Press это действительно не обязательно, но... у меня клиент (на стадии моей юности) ни как не хотел работать корректно, до тех пор пока явно не прописал bind(). Насколько я помню в "Недокументированные возможности Windows" этому было уделено достаточно много внимания. Теперь везде, где использую сокеты всегда это делаю, и проблем больше не возникает.

Но это чисто мое решение...

PS. Кстати если внимательно почитать "Программирование в сетях Microsoft Windows" то можно очень быстро наткнуться на следующее - при работе клиентских приложений с определенными протоколами bind() обязателен!!! Хотя прописано это не явно и надо читать все описание. А у нас как правило это делать не любят. (я кстати тоже не очень любил, но вметро делать часто нечего вот и приучился) Мои клиенты (те кто пользуются разработками) уж точно никогда ничего не читают...

Особенно мне нравяться фразы - а мы ничего не делали...

За оффтоп извиняюсь :)
19K
14 июля 2007 года
Rost
45 / / 05.07.2007
Цитата: Yos
Аллёёёёёёёёёёё - а где же bind() для сокета между socket и connect. Первый раз вижу что что-то должно работать...


bind не обязателен на клиенте... просто будет так называемый unnamed socket..
то что ты везде пишешь bind это неправильно, если у человека 2 интерфейса (читай ИП) откуда ты знаешь с какого работать?.. я думаю ты биндишь на 127.0.0.1 либо на дефолтный ип.. так зачем это делать ?! это и так делается автоматически, ты лишь повторяешь это, не продуктивно однако...
[QUOTE=MSDN]
Примечания

Воспользуйтесь методом Bind, если необходимо использовать заданную локальную конечную точку. Требуется вызвать метод Bind перед тем, как появится возможность обращения к методу Listen. Не нужно вызывать метод Bind перед применением метода Connect, если не требуется использовать определенную локальную конечную точку. Метод Bind можно использовать как для протоколов без установления соединения, так и для протоколов, работающих с предварительным установлением соединения.
[/QUOTE]

Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог