всем привет, у меня сейчас архитектурная проблема и хочу обратиться за советом к более опытным товарищам. В общем, я все работа. над организацией связи через pipe и столкнулся со следующей проблемой. Отправляя текст через свою программу, я могу получить тот же текст, но в виде трех сообщений. Например я отправляю "Привет", а получаю "П" "риве" "т". А мне хотелось бы получиться сообщение целиком. Я пришел к выводу, что надо делать некий накопительный буфер, и потом из него сообщения извлекать. Так вот, как архитектурно грамотно организовать такой буфер? Клиент по сути создает экземпляр класса pipe и ожидает, когда придет команда умереть через пайп, сам экземпляр pipe при инициализации создает поток слушатель, к которому повешен callback. Далее приходят данные, попадают в callback и должна произойти сборка какая то. Появилась мысль написать класс, который будет буферизировать в себя данные и по запросу отдавать полученные команды. Так вот приходят мне данные, я их скидываю в буфер, для этого есть идея написать класс. Далее, в другом потоке я вызываю из этого класса команду извлечь текущие команды и получаю несколько команд, которые потом обрабатываю. Вопрос отчасти связан с тем, что чтобы работать с одним экземпляром класса, надо его делать глобальным, или как то передать в класс работы с пайпом. Для разбора сообщений планирую сделать некие маркеры начала и конца сообщения. Как бы сделали это вы?
В классе пайпа создать коллбек или виртуальную функцию, которую он будет вызывать, когда соберет в своем буфере команду целиком.
Либо сделать отдельный метод setCallback, куда передавать функтор, когда сообщенице целиком было принято: Код (C++): class IPipeHandler { //... virtual void handle(void *data, size_t length) = 0; }; class Pipe { // ... IPipeHandler *pHandler; uint8_t *pBuffer; // буфер, куда собираем size_t nBytes; // Количество байт в буфере // ... void setCallback(IPipeHandler *handler) { pHandler = handler; } void process() { //... // Этот участок кода срабатывает, когда собрали сообщение в буфере if (pHandler != NULL) pHandler->handle(pBuffer, nBytes); // ... } // ... };
rmn, SadKo, спасибо, то есть вы считаете что лучше что бы сам класс пайпа и собирал данные? просто что ему тогда считать маркерами начала и конца сообщения?
drem1lin, Ну, обычно в потоковых протоколах первым передают размер всего сообщения, а затем данные указанного размера. Считываем слово (двойное слово), например 0x1234, затем выделяем буфер и читаем в него 0x1234 байт (возможно фрагментами). Как считали, заполненный буфер передаем в коллбек и затем освобождаем.
я пока на строках пробовал и приходит обычно сначала первая буква, таким образом это позволяет побить даже слово длины
Блин, курите мануалы по сетевым протоколам. Протокол обмена по пайпу ничем не отличается от протокола обмена по сети.
Но его размер фиксирован и ты также можешь собрать его по частям. Код (C): BOOL ReadFileEx (HANDLE handle, LPVOID buffer, DWORD size) { DWORD bytesRead; while (size != 0) { if (!ReadFile (handle, buffer, size, &bytesRead, NULL) || bytesRead == 0) return FALSE; buffer = (LPBYTE)buffer + bytesRead; size -= bytesRead; } return TRUE; } LPVOID ReadCommand (HANDLE handle, DWORD* size) { DWORD dataSize; LPVOID data; if (ReadFileEx (handle, &dataSize, sizeof(DWORD)) { data = malloc (dataSize); if (data) { if (ReadFileEx (handle, data, dataSize)) { *size = dataSize; return data; } free (data); } } return NULL; }
rmn, мы с вами не много не понимаем друг друга. У меня сообщение не известной длины, и сама длина этого сообщения может быть больше 255 байт, а как вы предлагаете, я вместо длинны получу только первый байт длинны, я выше писал, что может придти только первый байт вначале. И такое поведение не позволит мне вычитать корректно все остальное. Поэтому хочу сделать буферизацию
drem1lin, Вот возьми код выше и представив, что системная ReadFile() читает ровно один байт при каждом вызове, потрассируй его в уме и посмотри, что получается.
HTML: если допустимо, то самое простое разделять сообщения - символом перевода строки если нет - то элементы разметки (вплоть до xml или json). Т.к при обычной работе с пайпом следующие символы не будут прочитаны, пока не прочитаны все отправленные предыдущие, можно использовать только один достаточно большой буфер. Мною бы было сделано так: каждая итерация чтения пишет с начала буфера, не более чем его длина. Считанное разбирается, и исходя из прочитанного, считыватель переключает своё состояние (ДКА). Например: есть команды exec <name> <a> <b> kill <a> И соответствующий им простой ДКА (на рис. - неполный): e x e c -> q0 -> q1 -> q2 -> q3 -> q4* | | k i l l -->q5 -> q6 -> q7 -> q8* Состояние в начале q0. В буфер читается первые 8 символов. Если успешно прошли в состояние q4, то пытаемся считать аргументы a, b. Не вышло - возврат в q0. Полная диаграмма переходов: | e | x | c | k | i | l ---+---+---+---+---+---+---- q0 |q1 | | |q5 | | ---+---+---+---+---+---+---- q1 | |q2 | | | | ---+---+---+---+---+---+---- q2 |q3 | | | | | ---+---+---+---+---+---+---- q3 | | |q4*| | | ---+---+---+---+---+---+---- q4*| | | | | | ---+---+---+---+---+---+---- q5 | | | | |q6 | ---+---+---+---+---+---+---- q6 | | | | | |q7 ---+---+---+---+---+---+---- q7 | | | | | |q8* ---+---+---+---+---+---+---- q8*| | | | | | ---+---+---+---+---+---+---- ; Где пустым - возврат в начальное q0 (не подставлено в таблицу) И это же в виде регулярки: (exec) + (kill). Можно и не посимвольно, и с помощью strcmp в простых случаях. При достижении состояния, определяющего команду (напр., q4), создаяется новый объект внутри класса-считывателя из пайпа, имеющий идентификатор пришедшей команды, и начинается заполнение ожидаемых считываемых аргументов. В случае успешного считывания пакет команд, упакованный в объект, кладется в очередь или список, и ожидает новых команд из пайпа или запроса на считывание команд. ЗЫ: насколько мне изменяет память, в никсах нет пайпов, там локальные сокеты
я протрассировал, и получил, что максимум он считает длину 255 и после этого он будет читать пока буфер не закончится, а он закончится на 255 и получается что слишком длинные сообщения не примутся
drem1lin, Ты потрассировал неправильно, попробуй еще раз. Этот код может принимать сообщения длиной от 1 до 232-1 байт (перед данными идет дворд размера). Функция ReadFileEx() не возвращает управление, пока не прочитает ровно size байт или не произойдет ошибка чтения. Так что первым вызовом она прочитает из хендла дворд длины, а вторым - данные указанной длины целиком. Даже если системная ReadFile() будет при каждом вызове возвращать только один байт.
#define PIPE_MAX_BUFFER_SIZE 65536 - это в манах для МС край создаете ивент и делайте в отдельном потоке - выделяете хип и всё, ждете завершения потока по WaitForSingleObject, закрываете хэндл буфер полный, FlushFileBuffers желательно тоже