Какие типы применить?
Я могу использовать структуру с битовыми полями, но тогда я теряю возможность битовых операций, нужно отдельно перечислять действия над каждым полем.
Я могу перечислить отдельные установленные биты в виде enum, но как указать общий тип, который является суперпозицией этих установленных битов?
Есть ли в C возможность нормальной работы с битовыми операциями?
Во-первых, сервер работает в одном потоке и не может использовать больше одного ядра. Да, можно написать аддоны, которые будут создавать потоки, может даже процессы и выносить туда свою логику, но речь идет о базовом функционале сервера, пока что он не распараллелен. Во-вторых, исследуя траффик общения сервера и клиента игры Left 4 Dead 2, удалось выявить особый и, разумеется, недокументированный тип запроса. Он предназначен для отображения списка серверов в главном меню и пакеты такого запроса содержат номер версии игры и текст InetSearchServerDetails. Стоит сказать, что для этого типа пакетов сервер уже реализует анти-ддос защиту: проверяет версию и следит за числом pps. Но ситуация усугубляется тем, что для данного типа запросов нет никакого мастер-сервера. Точнее он есть, и возвращает лишь список серверов которые привязаны к Steam-группам в которых ты состоишь. И при получении такого списка клиент сразу же шлет INET_SEARCH-пакеты всем серверам. Тем не менее, даже с кэшированием сервер тратит драгоценное время на проверку запроса и отправку ответа. Забегая вперед скажу, что полностью сняв с сервера эту обязанность мне удалось снизить задержки в игре (пинг) в 2, а то и 3 раза. Решение достаточно простое: сервер не должен отвечать на все эти запросы. Вообще. Эту задачу можно попробовать переложить на плечи какой-нибудь службы, которая бы отвечала на весь этот флуд, изредка запрашивала актуальные данные с основного сервера, а ОСь бы грамотно распределяла ресурсы. Но как отделить пакеты запросов инфы, от обычного обмена данными с сервером? Первой попыткой было использовать statefull firewall. Это обычный знакомый нам фаерволл, который следит за подключениями, может их маркеровать и тд. Не получилось. Вот так просто. Даже поймав пакет и отправив его куда надо (сторонней службе), фаерволл пометит соединение и все что придет с пары ip:port на пару ip:port будет отправлено туда же. PS: Многие просто не смогут подключиться, так как клиент как правило использует тот же сокет для обмена данными, что и для запроса информации. stateless firewall я даже не ковырял, и как я потом понял - он мне бы и не подошел. Остался самый очевидный и как следствие - простой - способ отделить один пакет и направить куда надо. Это модуль ядра. И это сработало! Я зарегистрировал обработчик пакетов самым первым (там можно выставлять приоритет), еще пока он обрабатывается в пределах RAW таблицы и никто его в глаза не видел. Нагло подменяю порт отправки, пересчитываю контрольную сумму и пропускаю его дальше! А дальше он проходит через воду, огонь и медные трубы и попадает прямо к службе, которая слушает нужный порт. Ответы от службы обратно транслируются и порт подменяется при отправлении. Для простоты и удобства порты имеют жесткую связку: 27015 -> 27915 27016 -> 27916 ... В итоге, все запросы приходящие на нужные порты в соответствии с правилами направляются к службе, которая знает на какой порт для какого сервера идет запрос. Службу я написал на языке Python, запросы обрабатывает асинхронно и работает очень шустро. Соответствия портов лежат в конфиге, ну а в модуле ядра они зашиты. Перспектив для развития достаточно много, можно отвечать не всем, проще управлять лимитами, детектировать атаки и собирать разного рода статистику. В общем, все что душе угодно. И главное - всё это не будет отнимать процессорного времени у сервера (игрового).
Хотелось бы сказать "по долгу службы", ан нет. Не в столь отдаленном прошлом необходимо было решить проблему с "DDoS". Это была не спланированная атака, никто не хотел причинить вред или что-то подобное. Нет. Просто слишком много людей хотело спросить сервер о его состоянии, но обо всем по порядку. Речь идет о source-based серверах (игровых, на движке Source). При запуске такой сервер отправляет информацию о себе мастер-серверу, и уже с мастер-серевером происходит всё общение клиентов: получение списка серверов и базовая информация. Подробная инфофрмация будет запрошена напрямую. Такое общение происходит по спец. протоколу, который описан тут https://developer.valvesoftware.com/wiki/Server_queries и тут https://developer.valvesoftware.com/wiki/Master_Server_Query_Protocol Атаковать сервер не так уж сложно, достаточно начать флудить запросами, а если еще провайдер позволяет ip-spoofing и на другой стороне нет даже rp-фильтра, то дело за малым. Проблему люди быстро научились решать кэшированием: https://forums.alliedmods.net/showthread.php?t=135543 Кэшируют конечно не всё, но ситуация стала определенно лучше. Проблема же кроется в другом, а для Left 4 Dead 2 их целых две.
С колен, ёбнрот.
Почему нельзя было сделать нормально и удобно???
За несколько часов я перепробовал почти все возможные и невозможные варианты, и когда провалилась бинарная запись в файл — я понял — надо что-то менять, не в данных, не во мне, а в подходе. До того момента я старался избегать "грязных" хаков, трюков, основанных на знании работы компилятора и задачи программы, за которые она /никогда/ не выйдет. Я старался абстрагироваться, делать все максимально прозрачно (насколько мне позволяла лень :Р) и грамотно. Но у меня не осталось выхода. И так, я знал три(?) вещи: 1. Итераторы не трогали данные, во всяком случае без моего приказа. 2. Итераторы предоставляли прямой доступ к данным 3. При создании итераторов я использовал метод std::basic_string c_str()/data() из описания которых следует: Modifying the character array accessed through data is *undefined behavior*. В дополнение ко всему этому я знал, что строка изменяться уже не будет, и взглотнув побольше воздуха я сделал это — получил прямой доступ к данным через итератор, нагло избавился от const модификатора, который имел место быть благодаря c_str()/data() и стал записывать туда данные. В исходном тексте замена пробельного символа на перенос стала выглядеть так: (*(xmlChar*)u8_word.base()) = '\n'; Дописав код я скомпилировал, запустил и... выдохнул. Оно работало! Данные отображались верно, без доп. копирования, проверок (ранее я пытался записывать по слову) и прочего. "Dirty hack" — то самое выражение, которое пришло в голову сразу после получения (ожидаемого?) результата. Будь у меня более свежая голова, мозг товарища и пицца, я думаю мы разделались бы с этим за 10 минут. Но это был веселый опыт разработки. Больше, пожалуй, мне добавить нечего. Пишите код правильно ребята, не давайте доводить себя до отчаянья. У меня не было ни времени ни возможности обратиьтся к товарищу, и я решил эту проблему иначе. Тем не менее, ссылки по теме: https://github.com/spumer/tenzor-test/ — исходный код, виновник торжества. http://en.cppreference.com/w/cpp/string/basic_string/data — ... through data is undefined behavior. http://utfcpp.sourceforge.net/ — utf8 C++ portable library http://www.xmlsoft.org/html/libxml-xmlstring.html#xmlChar
Сталось так, что при работе с библиотекой libxml2 понадобилось подсчитывать кол-во символов в строке, с целью ограничить длину строки в N символов и остальную ее часть перенести на новую(строку). Символы в этой строке естественно могут занимать от 1 до 6 байт, в соответствии с форматом UTF-8. Самолично парсить строку по стандарту не было никакого желания, а уж темболее проходиться по граблям желания было еще меньше. Благо это тема довольно изъезженная и решение в виде небольшой портабильной библиотеки уже было написано. Язык С++, и библиотека была написана в виде итераторов для прохода по последовательности байт и функций, которые осуществляют некоторые простые операции как с итераторами, так и с самими последовательностями. libxml2 для хранения данных использует свой собственный тип xmlChar, хотя на деле это ни что иное как unsigned char. Камнем преткновения стала запись в файл, ведь именно туда мне было необходимо выгрузить все отформатированные данные. unsigned char в принципе не хотел записываться в файл и каждый раз он получался пустым, запись же в бинарном виде либо полностью колечило данные, либо их non-ASCII составляющую. Все попытки воссоздать правильную последовательность xmlChar байт с разбивкой на N символов в строке заканчивались одним — полный бред на выходе. Немного о том, как работает UTF-8 библиотечка: созданные итераторы как и предполагается не трогают данные по которым следуют, но доступ к ним предоставляют. Доступ к символу в моем случае можно было осуществить двумя способами: operator* и метод base(), где первый возвращает данные типа unsigned int, второй же возвращает указатель на место в последовательности, в котором он в данный момент находится. Так, например, если *u8_it вернет нам 'c', то (*u8_it.base()) будет равен &("ascending"[2]). Если же вы(как и я) передавали при создании итератора константный указатель на данные (e.g. const xmlChar *) то при использовании base() для записи вам придется избавиться от const модификатора, но об этом далее.
Закинул на свой гитхаб бетку тестового задания, в состояние релиза должно перейти сегодня вечером ;) https://github.com/spumer/tenzor-test
Знаете, а libxml2 забавная штука. Например, пустое пространство определяет как тег text, и чтобы полноценно запарасить без фантомов приходится задавать флаг HTML_PARSE_NOBLANKS при обработке, а так же дополнительно проверять node->name на соответствие NULL или "text". void parseNode(htmlNodePtr node, int level) { while(node != NULL) { /* skip blank tags */ if( node->name != NULL && xmlStrcmp(node->name, (const xmlChar *)"text") ) { printf("TAG: %s. Level: %d\n", node->name, level); if(node->xmlChildrenNode) parseNode(node->xmlChildrenNode, level+1); } node = node->next; } return; }
Что более тру? uint32_t (*piVersusSurvivorCompletion)[4] = reinterpret_cast<uint32_t(*)[4]>((unsigned char *)(this) + 976); for(i = 0; i < 4; ++i) { piVersusSurvivorCompletion[flipped][i] = g_totalResult / 4; } piVersusSurvivorCompletion[flipped][0] += g_totalResult % 4; или же uint32_t *piVersusSurvivorCompletion = reinterpret_cast<uint32_t*>((unsigned char *)(this) + 976); for(i = 0; i < 4; ++i) { piVersusSurvivorCompletion[flipped*4 + i] = g_totalResult / 4; } piVersusSurvivorCompletion[flipped*4] += g_totalResult % 4;
Тестирую на своем каталоге с исходниками, размер которого 936 МБ в котором 40 217 файлов и 4146 каталогов. Программа обходит рекурсивно указанную папку и создает md5-хэш для каждого файла и каталога на основе его содержимого. В качестве данных для хэширования каталога, я решил использовать хэши его файлов/каталогов.
Для указанного мной ранее каталога, это заняло 15 мин. и 44 сек.
Компилировал без оптимизации, разве что -s :3
Для того же самого каталога, но с -O3 и -Os, это заняло 15 мин. 39 сек.
И наконец, просто с -O2 это заняло 15 мин. 27 сек
Пока без поддержки многоядерности. Посему наблюдается лишь 50% заргузки на моем ноуте.
Потребление памяти не замерял, но точно менее 10МБ.
Достаточно неплохие результаты на мой взгляд, буду продолжать работать в этом направлении :)
Написал енкриптер паролей для обновленного steamcommunity: https://github.com/spumer/Steam-Password-Encrypter
Даже в такой маленькой программе выигрыш во времени составил 0.07 сек. при использовании указателей. Хотя тут активно используется STL, что не делает программу такой уж маленькой =\ #include <fstream> #include <iostream> #include <string> #include <list> #include <unistd.h> // for readlink() #include <libgen.h> // for dirname() #include <time.h> using namespace std; string* getline(istream& input); int main(void){ clock_t start = clock(); char buf[96]; if (readlink("/proc/self/exe", buf, 96) == -1) //http://www.kernel.org/doc/man-pages/online/pages/man5/proc.5.html { cout << "Error! Can't get path to myself!"; return 2; } sprintf(buf, "%s/%s", dirname(buf), "words.txt"); ifstream ifs( buf ); if( !ifs.is_open() ) { cout << "Words file not found!"; return 1; } list<string> words; //read file while ( ifs.peek() != EOF ) words.push_back(*getline(ifs)); ifs.close(); //search patterns list<string>::iterator it; string *buffer; while( cin.peek() != EOF ){ buffer = getline(cin); for(it = words.begin(); it != words.end(); ++it) if( buffer->find( *it ) != string::npos) goto next; cout << *buffer << endl; next: ; } printf("Время работы = %Lf\n", (long double) (clock() - start) / CLOCKS_PER_SEC); return 0; } string* getline(istream& input){ static string str; register char c; str.clear(); while ( (c = input.get()) != '\n' ) str += c; return &str; }
— iProducts
— дорогие ноутбуки
Без Деннис Ритчи (9 сентября 1941 — 12 октября 2011 года) мы не имели бы:
— Windows
— Unix (а следовательно Linux, FreeBSD, AIX, Solaris, MacOS X)
— Язык программирования С (а следовательно C++, Java, C# и т.п.)
— программы
— прогресс в компьютерах
— обычный печатный текст на компьютерах — мы всё читали бы в бинарной форме
Они умерли в одном и том же году и в одном и том же месяце, но кажется, лишь немногие заметили смерть Денниса Ритчи по сравнению со смертью Стива Джобса. (с) vinxru.livejournal.com
PS. Добавлю еще свои 5 копеек — John McCarthy(September 4, 1927 – October 24, 2011) — Nuff Said
img.chan4chan.com
Без Стива Джобса (24 февраля 1955 — 5 октября 2011 года) мы не имели бы:
— iProducts
— дорогие ноутбуки
Без Деннис Ритчи (9 сентября 1941 — 12 октября 2011 года) мы не имели бы:
— Windows
— Unix (а там и Linux)
— С
— и много что еще
Они умерли в одном и том же году и в одном и том же месяце, но кажется, лишь немногие заметили смерть Денниса Ритчи по сравнению со смертью Стива Джобса.
Без Стива Джобса (24 февраля 1955 — 5 октября 2011 года) мы не имели бы:
— iProducts
— дорогие ноутбуки
Без Деннис Ритчи (9 сентября 1941 — 12 октября 2011 года) мы не имели бы:
— Windows
— Unix (а там и Linux)
— С
— и много что еще
Они умерли в одном и том же году и в одном и том же месяце, но кажется, лишь немногие заметили смерть Денниса Ритчи по сравнению со смертью Стива Джобса.
Бинарный поиск: #include <stdio.h> #define N 10 int main(){ int ie, n, start, end, mid; int a[N] = {1, 2, 3, 4, 5, 7, 12, 24, 67, 136}; n = 0; ie = start = 0; end = N - 1; do{ if( a[ie] != n ){ ie = mid = start + (end - start) / 2; if( n > a[mid] ) ie = start = mid + 1; else end = mid; } else { printf("I find (%d) for (%d), correct?", a[ie], n); return 0; } }while(start != end); printf("Sorry. I can't find this item for you!"); return 1; }
Для начала я родил небольшую проверку на деление по модулю на 10, ну а потом мы со знакомым стали извращаться и в итоге получилось вот это:
for(char c = 'A'; c <= 'Z'; printf( (c % 10) — 5 ? "%c" : "%c\n", c++ ));
labs.qt.nokia.com мне хочется бегать по кругу, кричать "ААА этот мир ебанулся!" и биться головой о стену. У меня такое впечатление, что C/C++ вменяемы, пока компилятор реализует только разумное подмножество стандарта, а не весь стандарт досконально.
Когда я вижу подобные статьи: typedef struct{
char* host;
char* port;
} ADDRESS;
ADDRESS* get_addr(char *address){
ADDRESS ptr = (ADDRESS)malloc(sizeof(ADDRESS));
ptr->host = address;
while(*(address-1) != ':')
ptr->port = ++address;
*(address — 1) = '\0';
return ptr;
}
So, I'd prefer:
#include <stdio.h>
#include <stdlib.h>
int main( void )
{ int ok = 1; int i = 0;
for( int looping = 1; looping; looping = ++i < 10 && ok )
ok = ok && 2 +( i < 9 )== printf( i < 9 ? "%2d " : "%2d", i );
if( 1 != printf( "\n" ))ok = 0;
return ok ? EXIT_SUCCESS : EXIT_FAILURE; }
This terminated the loop on error, but tries to terminate the line anyways.
Это я к тому, что не разберусь с typedef void *PVOID;
1. Я увидел, что прога падает.
2. В поисках ошибки нашел пару кривых memset'ов и исправил.
3. Много раз пересобирал, запускал, ловил падение.
4. Задолбался, решил сохранить работу и пойти отдохнуть.
5. Закоммитил изменения из текущего подпроекта в текущую ветку devel (баг был в старом коде, который с новым напрямую не связан).
6. Перешел в ветку master.
7. Проверил master — работает.
8. Закоммитил исправление memset'ов.
9. Вернулся в devel, стянул этот коммит из master.
10. Проверил — работает.
Остался один вопрос: что это, блять, было?!
codepad.org (путь до падения помечен воскл. знаками). Анализ кордампа в gdb: codepad.org Что делать, жуйк?
Жуйк, такие дела. Есть вариадическая функция, которая передает свой va_list на пару уровней вниз, где он скармливается в vsnprintf. На i686 системе все работает, на x86_64 падает с сегфолтом. Код: [code]http://paste.org.ru/?9hx2he[/code]