powered by simpleCommunicator - 2.0.49     © 2025 Programmizd 02
Форумы / WinForms, .Net Framework [игнор отключен] [закрыт для гостей] / Сниффер текста из потока
17 сообщений из 17, страница 1 из 1
Сниффер текста из потока
    #39967960
Фотография hVostt
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Такая задачка.

Нужно сделать сниффер потока, т.е. обёртка Stream, которая считывает текст из байтов, которые в него пишут и прочитанный текст пишет в другой поток.

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

Приведу пример:

Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
// обёртка для Stream, для перехвата текстовых данных в UTF-8
public class TextSnifferStream : Stream
{
   // input - оригинальный поток, куда будут писать
   // output - поток, куда надо перехватить данные
   // maxSize - максимальное количество байт для перехвата
   public TextSnifferStream(Stream input, Stream output, long maxSize)
   {
   }

   ...
}



Проблема в том, что в UTF-8 один символ не всегда равен одному байту. А нужно перехватить не больше указанного количество байт, в виде текста.

Что думаете?
...
Рейтинг: 0 / 0
Сниффер текста из потока
    #39967986
Фотография Shocker.Pro
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
значит реально ты не имеешь права перехватывать более maxSize-3 байт по определению.
...
Рейтинг: 0 / 0
Сниффер текста из потока
    #39967988
Фотография hVostt
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Shocker.Pro,

именно )
...
Рейтинг: 0 / 0
Сниффер текста из потока
    #39967989
Фотография Shocker.Pro
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
А проблема-то в чём?
Прочитал байты. Перевел в текст. Отбросил последний символ, так как он может быть некорректным, и обработал байты последнего символа вручную.
...
Рейтинг: 0 / 0
Сниффер текста из потока
    #39967994
Фотография hVostt
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Shocker.Pro,

Ну вот не выглядит как тривиальное решение, так как на входе байты пишутся, а не из потока читаем.
...
Рейтинг: 0 / 0
Сниффер текста из потока
    #39968001
Фотография Shocker.Pro
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Ну байты байтами, вопрос в том, начало первой порции всегда соответствует началу символа или это вообще могут быть вырезанные из контекта байты?

А так, можно не пытаться расшифровывать UTF-8. Получил maxSize байт, перевел в UTF-8, отрезал последний символ, определил длину строки в байтах. Оставшийся хвост байтов положил в буфер, дождался следующей порции, присоединил к предыдущему хвосту и далее сначала.

Ну это если не хочется возиться с UTF8, вообще, окружающие требования до конца неясны, что, к примеру, происходит с потоком, когда достигнуто нужное количество символов.
...
Рейтинг: 0 / 0
Сниффер текста из потока
    #39968014
Фотография hVostt
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Shocker.Pro,

начало всегда хорошее, т.е. сниффер подключается до записи первого байта.

вот пример:

Код: c#
1.
Encoding.UTF8.GetBytes("Привет!")



Получается 13 байт:

208
159
209
128
208
184
208
178
208
181
209
130
33

На вход записалась порция из первых 5 байт. Что делать? :)
...
Рейтинг: 0 / 0
Сниффер текста из потока
    #39968016
Фотография hVostt
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Вот болванка:

Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
    public class TextSnifferStream : Stream
    {
        private readonly Stream _input;
        private readonly Stream _sniffOutput;
        private readonly int _sniffMaxBytes;

        // в результате работы в sniffOutput должно оказаться не более sniffMaxBytes байт
        // корректного текста UTF-8
        public TextSnifferStream(Stream input, Stream sniffOutput, int sniffMaxBytes)
        {
            _input = input;
            _sniffOutput = sniffOutput;
            _sniffMaxBytes = sniffMaxBytes;
        }

        // нужно реализовать этот метод
        private void WriteToTarget(byte[] buffer, int offset, int count)
        {
            // ???
        }
        
        public override void Flush()
        {
            _input.Flush();
        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            throw new NotImplementedException();
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            return _input.Seek(offset, origin);
        }

        public override void SetLength(long value)
        {
            _input.SetLength(value);
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            // здесь мы снифаем запись
            WriteToTarget(buffer, offset, count);
            _input.Write(buffer, offset, count);
        }

        public override bool CanRead => _input.CanRead;

        public override bool CanSeek => _input.CanSeek;

        public override bool CanWrite => _input.CanWrite;

        public override long Length => _input.Length;

        public override long Position
        {
            get => _input.Position;
            set => _input.Position = value;
        }
    }



Не настаиваю на полноценной реализации, хотя было бы неплохо :)
...
Рейтинг: 0 / 0
Сниффер текста из потока
    #39968022
Фотография Shocker.Pro
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Это задание на собеседование?
Что должно произойти, когда порог достигнут? Просто в пустоту байты будут уходить?
TDD? Тест есть?
Можно ли писать в Output с задержкой (буферизовать)?
...
Рейтинг: 0 / 0
Сниффер текста из потока
    #39968026
Фотография hVostt
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Shocker.Pro,

Почему на собеседование? Задание прикольное, намного интересней УГ "расположите цифры пирамидкой в консоли".

Если порог достигнут, снифать больше не нужно, обёртка просто пробрасывает вызовы в input.
Задержки, буферизация, что угодно, нужно чтобы надёжно работало.

Тест, ну вот такой для примера:

Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
var input = new MemoryStream();
var sniffedOutput = new MemoryStream();

var sniffedInput = new TextSnifferStream(input, sniffedOutput, 5);

var buffer = Encoding.UTF8.GetBytes("Привет!");

sniffedInput.Write(buffer, 0, buffer.Length);
sniffedInput.Flush();

Assert.Equals("Пр", Encoding.UTF8.GetString(sniffedOutput.ToArray());



т.е. в буфере окажется столько текста, сколько вместилось с учётом лимита
...
Рейтинг: 0 / 0
Сниффер текста из потока
    #39968029
Фотография Shocker.Pro
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Ок, ковырну попозже )
то, что ты задаешь такой вопрос, какбэ намекает на подводные камни ))
PS. Что за пирамидки? )
...
Рейтинг: 0 / 0
Сниффер текста из потока
    #39968032
Фотография hVostt
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Shocker.Pro
PS. Что за пирамидки? )


Ну там на входе N, нужно нарисовать пирамидку цифрами от 1 до N :)
Такие и подобные мне попадались


Shocker.Pro
то, что ты задаешь такой вопрос, какбэ намекает на подводные камни ))


Да не, подводных камней нет. Из реальной задачи, думал может кто сталкивался.
...
Рейтинг: 0 / 0
Сниффер текста из потока
    #39968084
Фотография Shocker.Pro
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Блин, набросал по упрощенному варианту и понял, что от анализа на уровне кодировки не уйти.
Иначе, если отбрасывать последний символ, то может возникнуть ситуация, если общая длина исходного потока на пару байтов больше, чем sniffMaxBytes, ты можешь упустить последний допустимый символ, то есть длина sniffa в символах может оказаться на один символ меньше, чем можно было бы запихнуть при заданном sniffMaxBytes.
Но это очень пограничная и редкая ситуация, если она не критична для этого ТЗ, то тогда можно не заморачиваться с анализом.
...
Рейтинг: 0 / 0
Сниффер текста из потока
    #39968087
Фотография hVostt
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Shocker.Pro
и понял, что от анализа на уровне кодировки не уйти


Да вот как раз и хотелось бы уйти, не изобретать же на ровном месте велосипедов )


Короч, мне стало лень и я глянул реализацию StreamReader ))

Решение кроется здесь: Encoding.UTF8. GetDecoder()

Если интересно, рабочий варик завтра скину.
...
Рейтинг: 0 / 0
Сниффер текста из потока
    #39968088
Фотография hVostt
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Shocker.Pro
Иначе, если отбрасывать последний символ, то может возникнуть ситуация, если общая длина исходного потока на пару байтов больше, чем sniffMaxBytes, ты можешь упустить последний допустимый символ, то есть длина sniffa в символах может оказаться на один символ меньше, чем можно было бы запихнуть при заданном sniffMaxBytes.
Но это очень пограничная и редкая ситуация, если она не критична для этого ТЗ, то тогда можно не заморачиваться с анализом.


Собственно Decoder хранит состояние, поэтому в него можно запихивать данные порциями, на выходе получать готовые чары, которые можно прочитать.
...
Рейтинг: 0 / 0
Сниффер текста из потока
    #39968131
Фотография Shocker.Pro
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
hVostt
Если интересно, рабочий варик завтра скину.
да, конечно, тесты тоже скинь плиз
...
Рейтинг: 0 / 0
Сниффер текста из потока
    #39968738
Фотография hVostt
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Shocker.Pro,

выдалось время, накропал )
немного условие изменилось, на выходе мне нужна заснифанная строка, но ограничения также -- в байтах

метод представляющий интерес, отдельно выделен (Sniff)

Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
183.
184.
185.
186.
187.
188.
189.
190.
191.
192.
193.
194.
195.
196.
197.
198.
199.
200.
201.
202.
203.
204.
205.
206.
207.
208.
209.
210.
211.
212.
213.
214.
215.
216.
217.
218.
219.
220.
221.
222.
223.
224.
225.
226.
227.
228.
229.
230.
231.
232.
233.
234.
235.
236.
237.
238.
239.
240.
241.
242.
243.
244.
245.
246.
247.
248.
249.
250.
251.
252.
253.
254.
255.
256.
257.
258.
259.
260.
261.
262.
263.
264.
265.
266.
267.
268.
269.
270.
271.
272.
273.
274.
275.
276.
277.
278.
279.
280.
281.
282.
283.
284.
285.
286.
    /// <summary>
    ///     Обёртка потока для перехвата записи или чтения текстовых данных в указанной кодировке.
    /// </summary>
    public class StringSnifferStream : Stream
    {
        private const int CharBufferLen = 128;

        private readonly Stream _source;
        private readonly long _sniffMaxBytes;
        private readonly Encoding _encoding;
        private readonly Decoder _decoder;
        private readonly StringBuilder _stringBuilder;

        private bool _checkPreamble;
        private bool _sniffRead;
        private bool _sniffWrite;
        private long _sniffBytes;

        /// <summary>
        ///     Создать экземпляр перехватчика потока.
        /// </summary>
        /// <param name="source">Исходный поток.</param>
        /// <param name="snifferWay">Направление перехвата.</param>
        /// <param name="sniffMaxBytes">Максимальное количество байт, которые будут перехвачены.</param>
        /// <param name="encoding">Кодировка для извлечения текста (по умолчанию UTF-8).</param>
        /// <exception cref="ArgumentException"><paramref name="sniffMaxBytes"/> должно быть больше нуля.</exception>
        public StringSnifferStream(
            Stream source,
            SnifferWay snifferWay,
            long sniffMaxBytes,
            Encoding? encoding = null)
        {
            if (sniffMaxBytes <= 0)
                throw new ArgumentException("Value must be greater than zero.", nameof(sniffMaxBytes));
            _source = source;
            _sniffMaxBytes = sniffMaxBytes;
            _encoding = encoding ?? Encoding.UTF8;
            _decoder = _encoding.GetDecoder();
            _stringBuilder = new StringBuilder();
            
            _sniffRead = snifferWay == SnifferWay.Read || snifferWay == SnifferWay.Both;
            _sniffWrite = snifferWay == SnifferWay.Write || snifferWay == SnifferWay.Both;
            
            var preamble = encoding.Preamble;
            _checkPreamble = preamble.Length > 0 && preamble.Length <= sniffMaxBytes;
        }

        // перехват данных
        private void Sniff(ReadOnlySpan<byte> buffer)
        {
            _sniffBytes += buffer.Length;

            // следим за превышением лимита
            if (_sniffBytes >= _sniffMaxBytes)
            {
                if (_sniffBytes > _sniffMaxBytes)
                    buffer = buffer.Slice(0, buffer.Length - (int) (_sniffBytes - _sniffMaxBytes));
                // отключаем перехват
                _sniffRead = false;
                _sniffWrite = false;
            }

            // на всякий случай чекаем маркер юникода (BOM)
            if (_checkPreamble)
            {
                _checkPreamble = false;
                var offset = GetPreambleOffset(buffer);
                if (offset > 0)
                {
                    if(offset == buffer.Length) 
                        return;
                    buffer = buffer.Slice(offset);
                }
            }

            // собственно декодируем байты в символы
            Span<char> charBuffer = stackalloc char[CharBufferLen];
            for (var offset = 0; offset < buffer.Length; offset += CharBufferLen)
            {
                var readBuffer = buffer.Slice(offset, Math.Min(buffer.Length - offset, CharBufferLen));
                var charsCount = _decoder.GetChars(readBuffer, charBuffer, false);
                if (charsCount > 0)
                    _stringBuilder.Append(charsCount == CharBufferLen ? charBuffer : charBuffer.Slice(0, charsCount));
            }
        }

        // реальное смещение в байтах заголовка кодировки
        private int GetPreambleOffset(ReadOnlySpan<byte> buffer)
        {
            var preamble = _encoding.Preamble;
            if (preamble.IsEmpty || preamble.Length > buffer.Length) return 0;
            for (var i = 0; i < preamble.Length; i++)
            {
                if (buffer[i] != preamble[i]) return 0;
            }
            return preamble.Length;
        }

        /// <summary>
        ///     Получить строку, содержащую перехваченный текст.
        /// </summary>
        public string GetStringContent() => _stringBuilder.ToString();

        #region Overrides of Stream

        /// <inheritdoc />
        public override void Close() => _source.Close();

        /// <inheritdoc />
        public override ValueTask DisposeAsync() => _source.DisposeAsync();

        /// <inheritdoc />
        protected override void Dispose(bool disposing)
        {
            if (disposing) _source.Dispose();
        }

        /// <inheritdoc />
        public override void Flush() => _source.Flush();

        /// <inheritdoc />
        public override Task FlushAsync(CancellationToken cancellationToken) => _source.FlushAsync(cancellationToken);

        /// <inheritdoc />
        public override int Read(byte[] buffer, int offset, int count)
        {
            var readCount = _source.Read(buffer, offset, count);
            if (_sniffRead)
            {
                Sniff(new ReadOnlySpan<byte>(buffer, offset, readCount));
            }

            return readCount;
        }

        /// <inheritdoc />
        public override int Read(Span<byte> buffer)
        {
            var readCount = _source.Read(buffer);
            if (_sniffRead)
            {
                Sniff(buffer);
            }

            return readCount;
        }

        /// <inheritdoc />
        public override async Task<int> ReadAsync(byte[] buffer, int offset, int count,
            CancellationToken cancellationToken)
        {
            var readCount = await _source.ReadAsync(buffer, offset, count, cancellationToken);
            if (_sniffRead)
            {
                Sniff(new Span<byte>(buffer, offset, readCount));
            }

            return readCount;
        }

        /// <inheritdoc />
        public override async ValueTask<int> ReadAsync(Memory<byte> buffer,
            CancellationToken cancellationToken = new CancellationToken())
        {
            var readCount = await _source.ReadAsync(buffer, cancellationToken);
            if (_sniffRead)
            {
                Sniff(buffer.Length == readCount
                    ? buffer.Span
                    : buffer.Span.Slice(0, readCount));
            }

            return readCount;
        }

        /// <inheritdoc />
        public override long Seek(long offset, SeekOrigin origin) =>
            throw new NotSupportedException("Seek not supported for sniffed streams.");

        /// <inheritdoc />
        public override void SetLength(long value) => _source.SetLength(value);

        /// <inheritdoc />
        public override void Write(byte[] buffer, int offset, int count)
        {
            if (_sniffWrite)
            {
                Sniff(new ReadOnlySpan<byte>(buffer, offset, count));
            }

            _source.Write(buffer, offset, count);
        }

        /// <inheritdoc />
        public override void Write(ReadOnlySpan<byte> buffer)
        {
            if (_sniffWrite)
            {
                Sniff(buffer);
            }

            _source.Write(buffer);
        }

        /// <inheritdoc />
        public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
        {
            if (_sniffWrite)
            {
                Sniff(new ReadOnlySpan<byte>(buffer, offset, count));
            }

            return _source.WriteAsync(buffer, offset, count, cancellationToken);
        }

        /// <inheritdoc />
        public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer,
            CancellationToken cancellationToken = new CancellationToken())
        {
            if (_sniffWrite)
            {
                Sniff(buffer.Span);
            }

            return _source.WriteAsync(buffer, cancellationToken);
        }

        /// <inheritdoc />
        public override bool CanRead => _source.CanRead;

        /// <inheritdoc />
        public override bool CanSeek => false;

        /// <inheritdoc />
        public override bool CanTimeout => _source.CanTimeout;

        /// <inheritdoc />
        public override bool CanWrite => _source.CanWrite;

        /// <inheritdoc />
        public override long Length => _source.Length;

        /// <inheritdoc />
        public override long Position
        {
            get => _source.Position;
            set => throw new NotSupportedException("Change position not supported for sniffed streams.");
        }

        /// <inheritdoc />
        public override int ReadTimeout
        {
            get => _source.ReadTimeout;
            set => _source.ReadTimeout = value;
        }

        /// <inheritdoc />
        public override int WriteTimeout
        {
            get => _source.WriteTimeout;
            set => _source.WriteTimeout = value;
        }

        #endregion
    }

    /// <summary>
    ///     Направление перехвата записи.
    /// </summary>
    public enum SnifferWay
    {
        /// <summary>
        ///     Чтение и запись.
        /// </summary>
        Both,
        
        /// <summary>
        ///     Только чтение.
        /// </summary>
        Read,
        
        /// <summary>
        ///     Только запись.
        /// </summary>
        Write
    }



простенький тест:

Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
    public class StringSnifferStreamTests
    {
        [Theory]
        [InlineData("х_эй", 1, "")]
        [InlineData("х_эй", 2, "х")]
        [InlineData("х_эй", 3, "х_")]
        [InlineData("х_эй", 4, "х_")]
        [InlineData("х_эй", 5, "х_э")]
        [InlineData("х_эй", 6, "х_э")]
        [InlineData("х_эй", 7, "х_эй")]
        [InlineData("х_эй", 8, "х_эй")]
        public void Test1(string source, int sniffMaxCount, string expected)
        {
            var stream = new MemoryStream();
            var sniffer = new StringSnifferStream(stream, SnifferWay.Both, sniffMaxCount, Encoding.UTF8);
            var bytes = Encoding.UTF8.GetBytes(source);
            sniffer.Write(bytes);

            // ожидаемый вывод
            var output = sniffer.GetStringContent();
            Assert.Equal(expected, output);

            // в оригинальный поток должны попасть все байты
            var writeBytes = stream.ToArray();
            Assert.Equal(bytes, writeBytes);
        }
    }
...
Рейтинг: 0 / 0
17 сообщений из 17, страница 1 из 1
Форумы / WinForms, .Net Framework [игнор отключен] [закрыт для гостей] / Сниффер текста из потока
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]