Я хотел бы использовать регулярные выражения для анализа данных, полученных из сокета.
Я написал собственный итератор сокетов, чтобы я мог передавать данные функциям регулярных выражений std.
Имейте в виду, что данные теоретически могут никогда не закончиться, сокет не закрывается после отправки полного запроса, поскольку клиент ожидает ответа и, возможно, будущего взаимодействия.
Представим, что у нас очень простой протокол, запросы состоят либо из START, либо из STOP.
Настоящий протокол, конечно, намного сложнее, но для примера этого достаточно.
// A simple regular expression to parse this could be defined like so:
static const std::regex re("^(START|STOP)");
// And parsed using:
std::regex_match(begin, end, result, re); // 1
// or using regex_search
std::regex_search(begin, end, result, re); // 2
Допустим, клиент отправляет слово START, ждет 5 секунд, затем отправляет другой символ, например X. В этом случае метод №1 будет ждать 5 секунд, прежде чем вернет false. Теперь представьте, что клиент ничего не отправляет после исходного сообщения START, метод №1 никогда не вернется.
Что касается метода № 2: предположим, что ваш ввод - XSTART, синтаксический анализатор, похоже, не понимает, что действительное совпадение никогда не будет найдено, потому что регулярное выражение начинается с ^, и поскольку ввод бесконечен, он никогда не завершится.
Таким образом, в конечном итоге метод №1 правильно определяет недопустимые запросы, в то время как метод №2 правильно определяет допустимые запросы, но метод №1 застревает в бесконечном цикле при правильном запросе, а метод №2 застревает при недопустимом запросе.
Этот Минимальный, полный и проверяемый пример демонстрирует проблему:
#include <stdio.h>
#include <stdint.h>
#include <iterator>
#include <vector>
#include <regex>
// stdin iterator that goes against all good
// programming practices for the sake of simplicity
class stdin_iter : public std::iterator<std::bidirectional_iterator_tag, char> {
static std::vector<char> buf;
size_t i;
public:
stdin_iter() : i(SIZE_MAX) {}
stdin_iter(size_t i) : i(i) {}
bool operator==(const stdin_iter& o) const { return i == o.i; }
bool operator!=(const stdin_iter& o) const { return i != o.i; }
value_type operator*() const {
while (i >= buf.size()) buf.push_back(getc(stdin));
return buf[i];
}
stdin_iter& operator++() { i++; return *this; }
stdin_iter operator++(int) { stdin_iter r = *this; i++; return r; }
stdin_iter& operator--() { i--; return *this; }
stdin_iter operator--(int) { stdin_iter r = *this; i--; return r; }
};
std::vector<char> stdin_iter::buf;
int main() {
stdin_iter begin(0), end;
std::regex re("^(START|STOP)");
std::match_results<stdin_iter> result;
//bool valid = std::regex_match(begin, end, result, re); // stuck on valid input
//bool valid = std::regex_search(begin, end, result, re); // stuck on invalid input
bool valid = std::regex_search(begin, end, result, re, std::regex_constants::match_continuous); // mostly works
if (valid) printf("valid: %s\n", result[1].str().c_str());
else printf("invalid\n");
}
Одно из решений - добавить искусственный конец к данным после, например, секунды бездействия. Но это значительно увеличивает время отклика и кажется неправильным. Еще одно решение - написать собственный синтаксический анализатор регулярных выражений, но изобретать колесо для такой простой проблемы кажется излишним. Есть ли лучший способ заставить эту работу работать?
Никакой синтаксический анализатор не может принимать бесконечно длинный ввод, и ожидается, что он завершится через конечное время. Вероятно, вам лучше сбросить свое состояние после новой строки, поскольку вы ищете вхождения в начале строки.
@SamiHult Разбор бесконечно длинного ввода отлично подходит, если регулярное выражение соответствует только конечному количеству символов. Я ищу совпадения не в начале строки, а в начале сообщения.
Да, если вы можете рассчитывать на завершение алгоритма (контролируемый ввод) или не заботитесь о том, завершится он или нет. Ваша практическая проблема может заключаться в том, что regex_search попытается сопоставить все вхождения в вашем вводе и для вечного ввода, который не будет завершен. Вы пробовали с флагом match_any?
match_any, похоже, не помогает, но, просматривая некоторые другие флаги, я обнаружил match_continuous, который делает именно то, что я хочу.
Не бывает бесконечного ввода. Со временем это закончится. Вопрос в том, возьмете ли вы под свой контроль этот финал или позволите судьбе решать?
Однако я не получаю информации о бесконечном цикле. минимальный воспроизводимый пример действительно поможет объяснить вашу проблему.
@LightnessRacesinOrbit Теоретически ввод может продолжаться вечно, хотя это недопустимый запрос, надежная реализация должна обрабатывать все случаи. Мой вопрос больше касается определения того, действителен ли ввод, даже если вы не уверены, что получили его все, STAXдолжен немедленно будет определен как недопустимый без чтения остальной части ввода, а STARTдолжен будет считаться действительным без проверки того, после него идет еще один символ, поэтому он не может быть ни START, ни STOP. Оба вышеуказанных метода не работают по крайней мере в одном из этих случаев.
На самом деле, если подумать, я полагаю, вы могли бы передать некоторые итераторы, которые представляют бесконечный диапазон. Если вы это делаете, это одна из вещей, которые я хочу видеть в вашем минимальный воспроизводимый пример. Обратите внимание, что это не удовлетворяет определению «диапазона» (если begin никогда не достигнет end) и, таким образом, вероятно, в любом случае имеет UB
Это действительно пользовательский итератор, о котором я говорю, хотя я не могу придумать пример, который был бы минимальным, полным и проверяемым. Он будет состоять из буферизованного сокета и итератора по буферизованным данным, моя реализация этого составляет более 300 строк.
Каким-то образом вам нужно будет найти способ минимизировать его. Я не совсем понимаю, как вы могли отладить проблему, не сделав этого еще
@LightnessRacesinOrbit Я добавил минимальный, полный и проверяемый пример. Я чувствую, что действительно превзошел себя с этим.
Кстати, std::istream_iterator уже есть
Используйте одно регулярное выражение, соответствующее всем вашим сценариям. Поместите каждого в чередование, заключенное в группу захвата. Одновременно будет соответствовать только 1 группа, что дает вам логику определения совпадений с помощью индекса итератора.
@LightnessRacesinOrbit std::istream_iterator - это InputIterator, для регулярных выражений требуется BidirectionalIterator.
@ DutChen18 О, так и есть. Что ж, это уже должна быть подсказка;)
@LightnessRacesinOrbit намек на что? Моя реализация работает с std::regex_constants::match_continuous, так что вы хотите сказать?
Этот std::regex не предназначен для такой тесной связи с входным потоком. Обратите внимание, что ваша реализация в конечном итоге буферизует все входные данные.
Мне пришлось использовать Create a new program, adding in only what is needed to see the problem. для моего минимального, полного и проверяемого примера, моя фактическая реализация очищает буфер после сопоставления регулярного выражения. И, честно говоря, меня не волнует, что std::regex должен делать предназначена, поскольку возможность синтаксического анализа запросов с использованием регулярных выражений приводит к получению чистого кода.





Используйте флаг std::regex_constants::match_continuous, Люк.
Непонятно, что именно вам нужно. Чтобы соответствовать строке, которая является либо
Start, либоStop, подойдет регулярное выражение"Start|Stop"сregex_match.