информационная безопасность
без паники и всерьез
 подробно о проектеRambler's Top100
Атака на InternetЗа кого нас держат?
BugTraq.Ru
Русский BugTraq
 Анализ криптографических сетевых... 
 Модель надежности двухузлового... 
 Специальные марковские модели надежности... 
 Три миллиона электронных замков... 
 Doom на газонокосилках 
 Умер Никлаус Вирт 
главная обзор RSN блог библиотека закон бред форум dnet о проекте
bugtraq.ru / форум / programming
Имя Пароль
ФОРУМ
все доски
FAQ
IRC
новые сообщения
site updates
guestbook
beginners
sysadmin
programming
operating systems
theory
web building
software
hardware
networking
law
hacking
gadgets
job
dnet
humor
miscellaneous
scrap
регистрация





Легенда:
  новое сообщение
  закрытая нитка
  новое сообщение
  в закрытой нитке
  старое сообщение
  • Напоминаю, что масса вопросов по функционированию форума снимается после прочтения его описания.
  • Новичкам также крайне полезно ознакомиться с данным документом.
[C++] Маленькое уточнение 15.12.03 10:24  Число просмотров: 1975
Автор: void <Grebnev Valery> Статус: Elderman
<"чистая" ссылка> <обсуждение закрыто>
1.*РЕАЛИЗАЦИЯ*

Поскольку по-большому разобраться не получается, попробуем по-маленькому. Я попробую конкретизировать ещё, для дополнительной «затравки», некоторые аспекты. Хотя они и не основные, но разобраться хочется.

В реализации клиента и сервера всё как бы обыденно. Обсуждать особо нечего. Может только некоторые детали ниже. И сервер, и клиент - на блокирующих сокетах. Интерфейс Беркли. Асинхронные штучки в реализации сокетов в Windows здесь не использую (пока). Сервер – многопоточный (каждого клиента обслуживаем в отдельном потоке).

1.1 Потоки клиентов.
Зная свою слабость к большому числу ошибок в коде, решил не оставлять потоки клиентов «безнадзорными». Кое-что здесь уже сделано. Кое-что не стал делать (для «спортивного интереса» было б слишком). Есть пул потоков, который можно контролировать из специального потока-свиппера «менеджера клиентских потоков». Для описания клиентского потока используем структуру CLIENTINFO:

#define MAXCLIENTS 512 #define MAX_CLIENTQUERYES 10 typedef struct _iclientquery { _DWORD hQuery; // дескриптор объекта доступа // к набору данных DBMS _DWORD status; // статус оъекта (создан/открыт/закрыт...) IQueryInterface* IQuery; // указатель на интерфейс объекта … … } IQUERY; typedef struct _clientinfo { HANDLE hThread; // дескриптор потока _BYTE ThreadState; // статус потока SOCKET sock; // клиентский сокет _DWORD addr; // ip клиента _BYTE *stream_buf; // буфер ввода/вывода клиента _DWORD cur_bufsize; // текущий размер буфера IQUERY querylist[MAX_CLIENTQUERYES]; _WORD nqueryes; … … } CLIENTINFO; typedef CLIENTINFO* PCLIENTINFO;

Каждый клиентский поток регистрируется при его создании (спящим). После того, как данные, необходимые клиенту заполнены (структура CLIENTINFO, указатель на которую уже передан потоку при его создании) – будим поток.

while((client_socket=accept(srv_socket, (sockaddr *) &client_addr, &client_addr_size))) { PCLIENTINFO sockinfo = allocinfo(); if ( !sockinfo ) { printf("\n\aAllocation error. Close connection."); byeclient( client_socket ); continue; } … … unsigned thID; uintptr_t hThread = _beginthreadex( NULL, 0, &ClientThread, sockinfo, CREATE_SUSPENDED, &thID ); if (! hThread) { printf("\n\aCreating client thread error. Close connection."); byeclient( client_socket ); freeinfo( sockinfo); continue; } sockinfo->sock = client_socket; sockinfo->addr = client_addr.sin_addr.S_un.S_addr; sockinfo->hThread = (HANDLE) hThread; sockinfo->ThreadState |= THS_CREATED | THS_SUSPENDED; if (ResumeThread( (HANDLE) hThread ) != -1L) sockinfo->ThreadState &= ~THS_SUSPENDED; … … }

Доступ потока-свиппера к пулу быстрый, без существенной блокировки потоков клиентов. Свиппер будет искать и чистить подвисшие потоки по такому сценарию. В критической секции (сама по себе быстрая) можно выполняем «моментальный снимок» - копию пула. Затем, «отпустив» критическую секцию, можно уже в копии пула искать процессы-зомби по некоторому критерию (скорее всего – некоторый таймаут клиентского потока, который по типу «жив/не жив» контролируется из клиентского потока), и если таковые найдутся, то в свиппер-потоке: приостановить клиентский поток, почистить его ресурсы (буфер ввода/вывода, открытые наборы данных DBMS, объекты доступа к данным DBMS) и потом уж - убить подвисший поток. Останется быстро вторым вхождением в критическую секцию почистить реальный пул с CLIENTINFO уже задавленного потока-зомби. Взаимных продолжительных блокировок клиентских потоков не будет. Клиентский поток обращается к пулу в критической секции только один раз и на очень короткое время – для освобождения своего дескриптора и саморазрегистрации (удаления себя из пула потоков) непосредственно перед завершением функции потока.
Как думаете нужен ли свиппер, и какой?
Здесь было сказано о буфере ввода/вывода клиентского потока, см. stream_buf в структуре CLIENTINFO выше. Вообще, на мой взгляд, это вопрос – как выделять память потоку для буфера ввода/вывода? В той реализации, которая сделана из «спортивного интереса» – сделано дубово. Память выделяется в одной, общей куче процесса. Какие идеи в части памяти, господа?

1.2 Буфер ввода/вывода клиентского потока.
Неожиданно наткнулся на вопрос, на который раньше не обращал внимания. Выше, в п. 1.1 сказано, что память для буфера ввода/вывода клиентского потока выделяется из одной кучи процесса. Сам по себе вопрос. Другой вопрос связан с тем, как выделяется? Проще и быстрее всего, с точки зрения доступа – realloc (поэтому-то и realloc). Алгоритм здесь, казалось бы, может быть совсем простым – вычитываем из заголовка «сообщения» его размер (размер заголовка плюс данные), и делаем realloc требуемого размера. Сомнения здесь связаны с возможной фрагментацией памяти из большого числа клиентских потоков, и как следствие с общим кирдык. В случае, когда каждый из клиентских потоков сервера будет «шинковать» память процесса для маленьких «сообщений», мне кажется, что возможна ситуация, когда память будет нарезана, как рулетик. Пока я сделал тупо – память выделяется блоками кратными 1024 и в два раза больше, чем было запрошено, если текущий буфер мал. При следующем запросе вначале проверяем текущий размер буфера, и если «сообщение» помещается, то realloc не делаем вовсе. Какие идеи, господа?
У меня – никаких нормальных.

1.3 Клиент.
В работе клиента есть одна особенность – возможно большие таймауты, когда пользователь пошёл покурить. Т.е. соединились, прочитали что надо, а дальше юзвер ничего не делает. Просто рвать соединение по таймауту, пусть даже достаточно большому, на мой взгляд неразумно. Молчание клиента может быть связано как с тем, что клиент «курит», так и с тем, что клиентская сторона подвисла. Именно подвисла, а не разрушила сокет, разорвала соединение и т.д., что серверная сторона заметит по клиентскому сокету на своей стороне. Другие патологические ситуации, когда клиент исчез, оставив полуоткрытое соединение на конце сервера, а сервер ожидает каких-либо данных от клиента, здесь я не рассматриваю. Хотя и это может быть. Единственное решение в этой ситуации, на мой взгляд – это пинги пингать. Опция TCP keepalive вряд ли поможет. Да и не рекомендуют её гуру в Host Requirements RFC. Не гуру, но практики на форумах так же замечают, что реальное время таумаута keepalive в различных реализациях и конфигурациях TCP может быть различным - 1,5 – 2 и более часов. Мутно всё это.
Таким образом, мне кажется, что в любом мало-мальски нормальном прикладном протоколе, рассчитанном на длительные соединения (имеется ввиду только TCP, а не соединения с DBMS) обрабатывать длительные таймауты следует самостоятельно. Не проблема это включить в обсуждаемый протокол. Не очень ясно другое, как это сделать в реализации сервера и клиента. Например, на клиенте придётся или делать это в отдельном потоке (все данные и получаем и отсылаем в отдельном потоке) – изврат, или использовать асинхронные сокеты Windows в сочетании с таймерами (для организации обычных таймаутов чтения/записи в сокет).
Какие идеи, господа?

2.*ДОПОЛНИТЕЛЬНЫЕ ВОПРОСЫ*
Совсем простые вопросы. Хочется узнать ваше мнение, уважаемые.

2.1 Сетевой порядок байт.
В простейшем случае запрос/отклик состоят из заголовка в 12 байт и возможно данных (если таковые необходимы). Формат заголовка запроса и отклика одинаков:


msg_size (32 bit) – общий размер «сообщения» | command ( 8 bit ) – код команды запроса | param ( 8 bit ) – параметр команды (пока у меня всегда 0) | checksum ( 16 bit ) – контрольная сумма | error ( 32 bit ) – код результата запроса |

typedef struct _appheader { _DWORD msg_size; /* 32 bit */ _BYTE command; /* 8 bit */ _BYTE param; /* 8 bit */ _WORD checksum; /* 16 bit */ _DWORD error; /* 32 bit */ // _BYTE reserved[HDRPADDING]; /* HDRPADDING bytes of trailing padding */ } APPHEADER; /* 12 bytes */

ООО!!! Неоднократно встречал на форумах, в том числе и в faq-ах от гуру, что, дескать, не забывайте, ламеры, про сетевой порядок байт. Зачем?!!! Наивные. Да у них (у гуру) крыша поехала. Это ж прикладной протокол. Какая здесь разница. Всё равно клиентов под разные платформы придётся компилировать отдельно. Там (на платформах отличных от Intel) и учесть можно – делать htons, или нет. Другие мнения есть?

2.2 Выравнивание заголовка по границе слова.
Имеется в виду то, обстоятельство, что если в будущем пришлось бы убрать из заголовка, например, _BYTE param (за ненадобностью), то хорошо б было добавить закомментированное выше reserved, см.заголовок выше.
Не вижу большого смысла, ибо напоминает это мне «мастерство» типа замены операции « a / 2 » побитовым «a >> 1». Хотя сомнения, конечно, есть небольшие. Заголовок вычитывается из сокета «первым». Здесь не так важно сколько читать – 11 байт, или 12. Другое дело, насколько эффективным будет следующий шаг – вычитывание «невыровненных» данных (что за заголовком) из буфера сокета.
Какие будут мнения, товарищи?

2.3 Выравнивание полей в заголовке.
Похоже на предыдущий пункт. И здесь логики больше. Хотя, тоже с учётом возможностей современных компиллеров, сомнительно. Речь идёт о том, что слово выравниваем по границе слова, а двойное слово – по границе двойного слова. Придерживаюсь пока правил. А по правде – насколько это актуально сейчас (чай не 80-е годы на дворе)?

2.4 Контрольная сумма.
Есть некое противоречие между потоковой природой сокетов TCP и желанием программиста работать с потоком, как с набором «сообщений». «Ненадёжность» здесь может быть связана с ошибками в коде сервера или клиента, когда, грубо говоря, будем читать «заголовок» следующего сообщения, не дочитав из потока ещё данные предыдущего сообщения. Подчёркиваю, речь не о том, что данные придут не в том порядке, неполностью или искажёнными (это невозможно, поскольку контрольные суммы и IP, и, главным образом, TCP – гаранты надёжности), а о только том, чтобы программер правильно «вычитавал» заголовок, а потом данные. Чтобы он не считал мусор – заголовком.
Здесь для повышения надёжности протокола можно было б вычислять контрольную сумму по заголовочной информации (без данных). Поле checksum, см. структуру заголовка APPHEADER выше, – это контрольная сумма. Пока вычисляю и использую. На стадии отладки кое-где помогало. Хочу с вами посоветоваться о нужности его вообще в прикладных протоколах, и в обсуждаемом протоколе в частности. Алгоритм вычисления – стандартный, как в IP, или в TCP/UDP. Как и в IP при вычислении контрольной суммы данные не учитываются (считаем только заголовочные данные). Однако в отличие от протокола IP, расчёт ведётся с учётом псевдохедера (как в TCP/UDP). Алгоритм и структура псевдозаголовка соответствует таковым в RFC (но в отличие от TCP/UDP данные не учитываются). Структура псевдохедера:

typedef struct { _DWORD saddr; /* 32 bit */ _DWORD daddr; /* 32 bit */ _DWORD len; /* 32 bit */ _BYTE zero; /* 8 bit */ _BYTE proto; /* 8 bit */ } PSEUDOHDR; /* 14 bytes */

Здесь при вычислении контрольной суммы в поле len помещаю значение поля msg_size реального заголовка. Поле proto – порт, который «слушает» сервер.

Вставляется эта проверка движением руки. Потери производительности – никакой. А сомнения здесь такого рода – хорошо «вылезанный» код не требует проверки checksum. На форумах вообще не слышал, чтобы кто-то так делал. Отсюда, думаю, - а не усложняю ли я понапрасну здесь всё. Единственное, что могу привести в свою защиту – так и в первых реализациях UDP не вычисляли контрольную сумму.
Как думаете вы, уважаемые?
<programming> Поиск 






Rambler's Top100
Рейтинг@Mail.ru


  Copyright © 2001-2024 Dmitry Leonov   Page build time: 0 s   Design: Vadim Derkach