Гость
Целевая тема:
Создать новую тему:
Автор:
Форумы / WinForms, .Net Framework [игнор отключен] [закрыт для гостей] / Сниффер текста из потока / 17 сообщений из 17, страница 1 из 1
10.06.2020, 17:23
    #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
10.06.2020, 17:50
    #39967986
Shocker.Pro
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Сниффер текста из потока
значит реально ты не имеешь права перехватывать более maxSize-3 байт по определению.
...
Рейтинг: 0 / 0
10.06.2020, 17:54
    #39967988
hVostt
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Сниффер текста из потока
Shocker.Pro,

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

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

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

Ну это если не хочется возиться с UTF8, вообще, окружающие требования до конца неясны, что, к примеру, происходит с потоком, когда достигнуто нужное количество символов.
...
Рейтинг: 0 / 0
10.06.2020, 18:59
    #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
10.06.2020, 19:12
    #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
10.06.2020, 19:27
    #39968022
Shocker.Pro
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Сниффер текста из потока
Это задание на собеседование?
Что должно произойти, когда порог достигнут? Просто в пустоту байты будут уходить?
TDD? Тест есть?
Можно ли писать в Output с задержкой (буферизовать)?
...
Рейтинг: 0 / 0
10.06.2020, 19:39
    #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
10.06.2020, 19:43
    #39968029
Shocker.Pro
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Сниффер текста из потока
Ок, ковырну попозже )
то, что ты задаешь такой вопрос, какбэ намекает на подводные камни ))
PS. Что за пирамидки? )
...
Рейтинг: 0 / 0
10.06.2020, 19:55
    #39968032
hVostt
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Сниффер текста из потока
Shocker.Pro
PS. Что за пирамидки? )


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


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


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


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


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

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

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


Собственно Decoder хранит состояние, поэтому в него можно запихивать данные порциями, на выходе получать готовые чары, которые можно прочитать.
...
Рейтинг: 0 / 0
11.06.2020, 10:05
    #39968131
Shocker.Pro
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Сниффер текста из потока
hVostt
Если интересно, рабочий варик завтра скину.
да, конечно, тесты тоже скинь плиз
...
Рейтинг: 0 / 0
13.06.2020, 03:02
    #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
Форумы / WinForms, .Net Framework [игнор отключен] [закрыт для гостей] / Сниффер текста из потока / 17 сообщений из 17, страница 1 из 1
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


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