Опубликован: 05.08.2010 | Доступ: свободный | Студентов: 2009 / 47 | Оценка: 4.50 / 4.40 | Длительность: 60:26:00
Лекция 6:

Работа с потоками данных

< Лекция 5 || Лекция 6: 12345678910111213
Аннотация: До сих пор мы работали с файлами и каталогами, преимущественно рассматривая их как единицы хранения информации в целом и не касаясь самого содержимого. Мы научились создавать файлы и каталоги, читать и задавать их атрибуты. Но само наполнение файлов информацией мы затронули лишь мимоходом. Для записи и чтения информации в файлах используются классы файловых потоков, а для передачи данных на другие устройства имеются свои специализированные классы потоков. В данной лекции на примере ряда упражнений мы и познакомимся с некоторыми из них (с файловыми, и не только)
Ключевые слова: поток, место, приложение, адресное пространство, процессор, ПО, абстракция, канал связи, входной, обмен данными, чередование, файл, операции, байт, консоль, вывод, класс, очередь, абстрактный класс, асинхронное чтение, объектная модель, управляющий символ, возврат каретки, функция, текстовый редактор, объект, дескриптор, поток объектов, Типовая, режим доступа к файлу, сериализация объектов, сериализуемость, deserialize, интерфейс, конструктор класса, decompress, буферизация, информация, базовый тип, writable, список, simultaneous, periphery, путь, БД, сервер, доступ, протокол обмена, ОЗУ, целостность, сеть, сокет, запрос, узел сети, компьютер, хост, единица, порт, системный ресурс, C, номер порта, очередь соединений, системный таймер, десериализация, шифрование, криптографические операции, Rijndael, асимметричные алгоритмы, ветвь, initialization vector, ECBS, CFB, OFB, decrypt, массив фиксированного размера

Полезные ссылки

Все необходимые для выполнения данной работы программы можно найти в http://old.intuit.ru/department/se/prcsharp08/19/Setup.zipприлагаемом каталоге.

  1. Работа с потоками слегка описана в intuit:
  2. Работа с альтернативными потоками:
  3. Динамическое создание классов в модели CodeDOM:
  4. Динамическое создание классов в модели System.Reflection.Emit:
  5. Внедрение WPF в Windows Forms
  6. Как в WPF управлять проигрыванием видео с помощью MediaElement
  7. Пример по криптографии
  8. Класс Type

Введение

В современной литературе термином "Поток" обозначают два совершенно разных понятия: Thread (трэд) и Stream (стрим), из-за чего имеет место некоторая путаница. Под Thread понимают код, реально выполняемый процессором в данный момент времени в рамках одного процесса. Процесс - это приложение, загруженное в изолированное адресное пространство компьютера. Процесс может состоять из нескольких изолированных копий - доменов. Копия может иметь несколько Thread и каждому процессор уделяет определенное количество квантов времени, обслуживая их последовательно по бесконечному циклу. Так эмулируются (имитируются) параллельные вычисления и концепция многозадачности на однопроцессорных (малопроцессорных) системах.

Под Stream понимают логическую абстракцию, с помощью которой можно подключаться к различным типам источников данных одинаковым образом. Stream - это модель, через которую последовательно считываются или записываются элементы данных. Для исключения разночтения некоторые авторы предлагают такие термины: Thread - нить (или поток выполнения ), Stream - поток (или поток данных ), чем мы с удовольствием и воспользуемся.

Абстракция Stream создает канал связи между приложением и устройством хранения информации (резервным хранилищем), а также предоставляет необходимые для манипулирования данными инструменты. Для потока характерно направление: входной поток - данные передаются от устройства в приложение, выходной поток - от приложения к устройству. Есть однонаправленные потоки - например, файловый поток может открываться только для чтения или только для записи, с клавиатуры символы можно только считывать, и т.д. Есть и двунаправленные потоки - например, сессия по протоколу telnet или обмен данными между компьютером и модемом, которые представляют собой чередование однонаправленных потоков. Файл тоже может быть открыт для чтения и записи одновременно.

Потоки в библиотеке .NET Framework представлены классами ввода-вывода и инкапсулируют собой низкоуровневые операции, которые в конечном итоге заставляют операционную систему выполнять действия по чтению или записи информации на конкретные адресуемые устройства. Потоки можно трактовать и как последовательность байт, представляющих данные в буфере памяти, файле, или полученные по сетевому соединению, клавиатурному вводу, а также данные, выводимые на различные устройства, включая консоль.

Потоки, которые непосредственно связываются с устройствами, можно назвать рабочими (основными). На самом низком уровне система ввода-вывода .NET оперирует байтами, поэтому все рабочие потоки являются байтовыми. Однако есть несколько классов-надстроек (оболочек) к рабочим потокам, которые устанавливают свой формат данных при чтении/записи или облегчают пересылку данных определенного типа.

Создав основной поток и погрузив его в надстройку, можно значительно облегчить решение задачи пересылки некоторых типов данных. Так, для упрощения пересылки текстов имеются специализированные классы, автоматически преобразующие байтовые потоки в символьные и наоборот. Чтение информации с клавиатуры и вывод на консоль обеспечивают стандартные потоки, которые являются одновременно и символьными.

Базовым классом большей части потоков является класс Stream, который в свою очередь наследует от класса System.MarshalByRefObject


Несмотря на то, что Stream абстрактный класс, этим типом (для реализации принципа динамического полиморфизма) можно объявлять ссылки, адресующие экземпляры его потомков. Но такие ссылки будут адресовать только те члены экземпляров, которые определены в самом классе Stream, а для использования других членов требуется приведение типа ссылки к объекту-экземпляру. Однако большинство наследуемых членов в потомках Stream являются виртуальными, переопределяются в них и адресоваться ссылками типа Stream могут. Вот некоторые из этих наследуемых членов

Таблица 19.1. Члены класса Stream
Член Описание
BeginRead() Начинает операцию асинхронного чтения, возможно, в отдельно созданной ните Thread. Основная нить действий продолжает выполняться
BeginWrite() Начинает операцию асинхронной записи, возможно, в отдельно созданной ните Thread. Основная нить действий продолжает выполняться
Close() Закрывает поток, освобождая связанные с ним системные ресурсы
EndRead() Переводит систему в режим ожидания, пока не будет завершено чтение данных или не произойдет ошибка
EndWrite() Переводит систему в режим ожидания, пока не будет завершена запись данных или не произойдет ошибка
Flush() Сбрасывает (выталкивает, сливает) кэш буфера записи
Read() Читает в массив с нужного места потока заданное количество байт. Возвращает число фактически прочитанных байт
ReadByte() Читает очередной байт потока
Seek() Смещяет курсор потока на заданное количество байт относительно заданного начала отсчета (начала, конца или текущей позиции) для последующей работы
Write() Записывает в поток фрагмент данных из массива, характеризуемых начальным индексом и количеством элементов
WriteByte() Записывает в выходной поток один байт
CanRead CanSeek CanTimeout CanWrite Проверяются в экземплярах потомков для определения поддержки соответствующей операции
Length Длина потока в байтах. Не во всех потомках реализована и может вызвать исключение!
Position Определяет или устанавливает текущую позицию (курсор) потока
ReadTimeout Определяет или устанавливает время в миллисекундах, отпущенное для попыток чтения ожидаемых данных, по безрезультатному истечению которого нить Thread будет синхронизирована с основной нитью приложения
WriteTimeout Определяет или устанавливает время в миллисекундах, отпущенное для попыток записи, по безрезультатному истечению которого асинхронная нить Thread будет синхронизирована с основной нитью приложения

Упражнение 1. Работа с FileStream как с чисто байтовым потоком

Знакомство с потоками начнем с файлового потока FileStream как инструмента для чтения/записи информации в файлы. Нужно понимать, что объектная модель FileStream является высокоуровневой оболочкой API -функций операционной системы по работе с файлами. Но в то же время сам FileStream имеет несколько специализированных высокоуровневых оболочек для облегчения чтения/записи в файл данных различных форматов.

Вначале рассмотрим использование только одного FileStream в чистом виде. Начнем с упражнения, иллюстрирующего пример из MSDN, который попутно немного 'подкрутим'.

  • Создайте новый проект App1 консольного типа в решении Stream

Стартовым его назначать нет смысла, поскольку в решении он пока будет единственным.

  • Заполните файл Program.cs следующим кодом
using System;
using System.Text;
    
using System.IO;
    
namespace App1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.ForegroundColor = ConsoleColor.White;
            string newLine = System.Environment.NewLine;// Пара "\r\n"
    
            string path = @"e:\temp1\MyTest.txt";// Задаем полное имя файла
            string dir = Path.GetDirectoryName(path);   // Выделяем каталог
    
            // Если каталог не существует, создаем его
            if (!Directory.Exists(dir))
            {
                Directory.CreateDirectory(dir);
            }
    
            // Создаем новый или усекаем существующий файл
            // и сразу открываем его для записи
            using (FileStream fs = File.Create(path))
            {
                AddText(fs, "Буря мглою ");
                AddText(fs, "небо кроет,");
                AddText(fs, "\r\nВихри снежные крутя.");
                AddText(fs, "\r\nТо как зверь она завоет,\r\nТо заплачет как дитя.\r\n");
    
                AddText(fs, newLine + "Сгенерированные символы:" + newLine);
                for (int i = 32; i < 123; i++)  // Начинаем с пробела ASCII=32
                {
                    // Добавляем символ в поток
                    AddText(fs, Convert.ToChar(i).ToString());
    
                    // Через каждые десять символов вставляем новую строку,
                    // когда i делится без остатка
                    //if (i % 10 == 0)  // Вариант
                    if (Math.IEEERemainder(Convert.ToDouble(i), 10) == 0)
                    {
                        // Вариант с библиотечным свойством
                        //AddText(fs, newLine);
                        // Вариант в управляющих символах
                        //AddText(fs, "\r\n");  
                        // Вариант в шестнадцатиричных кодах ASCII
                        //AddText(fs, Convert.ToChar(0x0D).ToString() +
                        //    Convert.ToChar(0x0A).ToString());
                        // Вариант в целых кодах ASCII
                        AddText(fs, 
                            Convert.ToChar(13).ToString() + // Попробуйте закомментировать эту строку!!!
                            Convert.ToChar(10).ToString());
                    }
                }
            } // Здесь, по окончании инструкции using поток автоматически закрывается
    
            // Открываем файловый поток для чтения и обрабатываем его
            //using (FileStream fs = File.OpenRead(path))   // Вариант
            using(Stream fs = new FileInfo(path).OpenRead())    // Вариант для разнообразия
            {
                byte[] bytes = new byte[fs.Length]; // Заготавливаем байтовый массив
                int count = fs.Read(bytes, 0, bytes.Length);// Заполняем массив из файла
    
                UTF8Encoding tmp = new UTF8Encoding(true);// Объект кодировки символов
                String str = tmp.GetString(bytes);// Преобразуем массив в строку
                Console.WriteLine(str);// Печатаем на консоль
    
                Console.WriteLine("\nВсего прочитано байт: {0}", count);
                Console.WriteLine("Из них отображаемых символов: {0}", str.Length);
            } // Здесь, по окончании инструкции using поток автоматически закрывается
    
            // Печатаем ASCII 'перевод строки' - 'возврат каретки'
            int a = '\r', b = '\n';
            String ascii = String.Format("Управляющие символы: ");
            ascii += String.Format("\\r={0}, \\n={1}", a, b);
            Console.WriteLine(ascii);
    
            // Проверяем строку NewLine
            byte[] bts = new ASCIIEncoding().GetBytes(newLine);
            a = bts[0]; b = bts[1];
            ascii = String.Format("Управляющие символы NewLine: ");
            ascii += String.Format("\\r={0}, \\n={1}", a, b);
            Console.WriteLine(ascii);
    
            Console.ReadLine();
        }
    
        private static void AddText(FileStream fs, string value)
        {
            // Преобразуем строку в массив байтов через объект кодировки символов
            byte[] bytes = new UTF8Encoding(true).GetBytes(value);
            // Записываем в открытый поток
            fs.Write(bytes, 0, bytes.Length);
        }
    }
}
  • Запустите приложение - результат должен быть таким

  • Разберитесь с кодом

Интересно отметить следующее... Если не записывать в файл управляющий символ ' \r ' - возврат каретки, а только ' \n ' - новая строка, то при чтении файла на консоль функция Console.WriteLine() этого не заметит, а текстовый редактор Notepad заметит (попробуйте). Это значит, что при побайтовой записи нужно полностью обеспечивать документ всей необходимой информацией, не надеясь на умные устройства вывода. Альтернативной заменой строки " \r\n " может служить статическое свойство System.Environment.NewLine, которое означает то же самое и в приведенном коде частично используется.

Всякий раз, когда мы открываем файл через объект FileStream, следует помнить, что по завершении работы этот файл следует закрыть, чтобы освободить дескриптор файла и вернуть его операционной системе. В противном случае файл будет считаться занятым и будет освобожден только после завершения приложения. Закрыть файл можно методом Close() объекта FileStream, либо открывать в инструкции using(), по завершению работы которой файл автоматически будет закрыт. Последний прием и был использован нами в приведенном выше коде.

Упражнение 2. Подробности использования байтового потока FileStream

В данном упражнении рассмотрим файловый поток более подробно. Класс FileStream обеспечивает практически все мыслимые потребности программного управления чтением/записью информации в файлы. После создания экземпляра потока (объекта) связанный с ним файл будет представлен этим объектом и все дальнейшие манипуляции с файлом будут выполняться через этот объект.

Для создания экземпляра класс FileStream имеется множество перегрузок его конструктора, с которыми можно ознакомиться в MSDN. Наиболее типовая из них следующая:

FileStream(string path, System.IO.FileMode mode, 
   System.IO.FileAccess access, System.IO.FileShare share)
  • path - относительный или абсолютный путь для файла, который будет представлен потоком
  • mode - значение перечисления FileMode, устанавливающее режим открытия или создания файла
    • Append - открывает файл, если он существует, и переходит в его конец, либо создает новый файл. Этот режим может быть использован только в сочетании с доступом FileAccess.Write. Попытка чтения или перемещения указателя на другую позицию генерирует исключение
    • Create - создает новый файл и открывает его для записи. Если файл уже существует, то он будет перезаписан на пустой
    • CreateNew - создает новый файл и открывает его для записи. Если файл уже существует, генерируется исключение IOException
    • Open - открывает существующий файл в режиме, определяемом перечислением FileAccess. Если указанный файл не существует, генерируется исключение FileNotFoundException
    • OpenOrCreate - открывает существующий или создает новый файл с режимом доступа, определяемым перечислением FileAccess
    • Truncate - открывает существующий файл для записи и усекает его длину до нуля байт. При попытке чтения генерируется исключение
  • access - значение перечисления FileAccess, устанавливающее режим доступа к файлу. Оно влияет на значения флагов CanRead - возможно чтение, CanWrite - возможна запись, CanSeek - возможен параллельный доступ к дисковому файлу
    • Read - только для чтения
    • ReadWrite - для чтения и записи
    • Write - только для записи
  • share (разделять, делить, совместно использовать) - значение перечисления FileShare, устанавливающее режим доступа к тому же самому файлу других процессов или объектов FileStream
    • Delete - разрешает последующее удаление файла
    • Inheritable - делает дескриптор файла наследуемым дочерними процессами. Этот режим непосредственно не поддерживается Win32
    • None - отключает режим разделения и переводит файл в монопольный доступ только текущего процесса
    • Read - открывает совместный доступ только для чтения
    • ReadWrite - открывает совместный доступ для чтения и записи
    • Write - открывает совместный доступ только для записи

Не стоит забывать, что класс FileStream имеет в своем распоряжении и весь набор членов, наследуемых от базового класса Stream.

  • В панели Solution Explorer вызовите контекстное меню для узла решения (не проекта!), командой Add/New Project добавьте новый проект с именем App2 типа консольного приложения и назначьте его стартовым

  • Заполните файл Program.cs следующим кодом, иллюстрирующим работу с байтовым потоком FileStream
using System;
using System.Collections.Generic;
using System.Text;
    
// Дополнительные пространства имен
using System.IO;// Для потоков
    
namespace App2
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.ForegroundColor = ConsoleColor.White;
            Console.Title = "Упражнение 2. Файловые потоки";
    
            new MyClass().Foo();
    
            Console.ReadLine();// Задержка консоли
        }
    }
}
    
namespace App2
{
    class MyClass
    {
        public void Foo()
        {
            byte[] bytes;   // Только объявили
    
            // Сохраняемые данные
            string sData = "Привет всем!";  // В Unicode займет 12*2 байт
            int iData1 = (int)12345;        // 4 байта
            int iData2 = int.MaxValue;      // 4 байта
            float fData = 67F;              // 4 байта
            double dData = 89D;             // 8 байт
    
            // Сохраняем значения в файле
            String fileName = "Data.dat";// Без пути - будет создан в каталоге сборки
            FileStream fileStream = null;
            try
            {
                // Создаем или усекаем
                fileStream = new FileStream(fileName, FileMode.Create);
    
                // Сохраняем строку в универсальной кодировке, 
                // занимающей 2 байта на каждый символ
                bytes = new UnicodeEncoding().GetBytes(sData);  // Кодировка UTF-16
                for (int i = 0; i < bytes.Length; i++)
                    fileStream.WriteByte(bytes[i]); // Побайтно
    
                // Сохраняем первое целое число
                bytes = new byte[sizeof(int)];
                for (int i = 0; i < sizeof(int); i++)
                    bytes[i] = (byte)(iData1 >> 8 * i);  // Делим на байты, начиная с младшего
                fileStream.Write(bytes, 0, sizeof(int));// Нулевой сдвиг относительно текущего
    
                // Сохраняем второе целое число
                bytes = new byte[sizeof(int)];
                bytes = BitConverter.GetBytes(iData2);
                fileStream.Write(bytes, 0, sizeof(int));// Нулевой сдвиг относительно текущего
    
                // Сохраняем внутренне представление вещественного float (Single)
                bytes = BitConverter.GetBytes(fData);// Массив той же длины int
                fileStream.Write(bytes, 0, sizeof(float));// Нулевой сдвиг относительно текущего
    
                // Сохраняем внутренне представление удвоенного double
                bytes = new byte[sizeof(double)];
                bytes = BitConverter.GetBytes(dData);
                fileStream.Write(bytes, 0, sizeof(double));// Нулевой сдвиг относительно текущего
    
                fileStream.Flush(); // Слить кэш (необязательно!)
            }
            finally
            {
                if (fileStream != null)
                    fileStream.Close();
            }
    
            // Читаем значения из файла и печатаем их на консоль
            try
            {
                // Открываем только для чтения
                fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read);
                if (fileStream.CanSeek) // Поддерживает ли такую возможность
                    fileStream.Seek(0L, SeekOrigin.Begin);  // Необязательно!
    
                // Читаем строку, зная, сколько нужно прочитать
                bytes = new byte[sData.Length * 2];// Для UTF-16 это 2 байта на каждый символ
                fileStream.Read(bytes, 0, sData.Length * 2);
                Console.WriteLine((new UnicodeEncoding()).GetString(bytes));
    
                // Далее идут байты для представления первого целого числа
                bytes = new byte[sizeof(int)];
                fileStream.Read(bytes, 0, sizeof(int));
                int a = 0;
                for (int i = 0; i < sizeof(int); i++)
                    a |= bytes[i] << 8 * i; // Заполняем все байты, начиная с младшего
                Console.WriteLine(a);
    
                // Далее идут байты для представления второго целого числа
                fileStream.Read(bytes, 0, sizeof(int));
                a = BitConverter.ToInt32(bytes, 0);
                Console.WriteLine(a);
    
                // Считываем и печатаем вещественное float 
                // Побитовые операции к типам с плавающей точкой применять нельзя
                bytes = new byte[sizeof(float)];
                fileStream.Read(bytes, 0, sizeof(float));
                float b = BitConverter.ToSingle(bytes, 0);
                Console.WriteLine(b);
    
                // Считываем и печатаем double 
                bytes = new byte[sizeof(Double)];
                fileStream.Read(bytes, 0, sizeof(double));
                double c = BitConverter.ToDouble(bytes, 0);
                Console.WriteLine(c);
    
                // Измеряем длину потока, пока открыт
                Console.WriteLine("==============\nВсего байт: {0}", fileStream.Length);
            }
            finally
            {
                fileStream.Close();
            }
    
            // Измеряем длину файла
            Console.WriteLine("Длина файла: {0}", new FileInfo(fileName).Length);
        }
    }
}

Приведенный код иллюстрирует важный вывод о том, что байтовый поток может хранить любой тип информации, все равно это будет последовательность байт. Главная задача состоит в том, чтобы эту информацию правильно разбить при записи, а затем правильно извлечь, соединить и интерпретировать при чтении.

  • Запустите приложение App2 - результат будет таким

< Лекция 5 || Лекция 6: 12345678910111213
Алексей Бабушкин
Алексей Бабушкин

При выполнении в лабораторной работе упражнения №1 , а именно при выполнении нижеследующего кода:

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Text;

using System.Windows.Forms;

using Microsoft.Xna.Framework.Graphics;

   

namespace Application1

{

    public partial class MainForm : Form

    {

        // Объявим поле графического устройства для видимости в методах

        GraphicsDevice device;

   

        public MainForm()

        {

            InitializeComponent();

   

            // Подпишемся на событие Load формы

            this.Load += new EventHandler(MainForm_Load);

   

            // Попишемся на событие FormClosed формы

            this.FormClosed += new FormClosedEventHandler(MainForm_FormClosed);

        }

   

        void MainForm_FormClosed(object sender, FormClosedEventArgs e)

        {

            //  Удаляем (освобождаем) устройство

            device.Dispose();

            // На всякий случай присваиваем ссылке на устройство значение null

            device = null;       

        }

   

        void MainForm_Load(object sender, EventArgs e)

        {

            // Создаем объект представления для настройки графического устройства

            PresentationParameters presentParams = new PresentationParameters();

            // Настраиваем объект представления через его свойства

            presentParams.IsFullScreen = false; // Включаем оконный режим

            presentParams.BackBufferCount = 1;  // Включаем задний буфер

                                                // для двойной буферизации

            // Переключение переднего и заднего буферов

            // должно осуществляться с максимальной эффективностью

            presentParams.SwapEffect = SwapEffect.Discard;

            // Устанавливаем размеры заднего буфера по клиентской области окна формы

            presentParams.BackBufferWidth = this.ClientSize.Width;

            presentParams.BackBufferHeight = this.ClientSize.Height;

   

            // Создадим графическое устройство с заданными настройками

            device = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, DeviceType.Hardware,

                this.Handle, presentParams);

        }

   

        protected override void OnPaint(PaintEventArgs e)

        {

            device.Clear(Microsoft.Xna.Framework.Graphics.Color.CornflowerBlue);

   

            base.OnPaint(e);

        }

    }

}

Выбрасывается исключение:

Невозможно загрузить файл или сборку "Microsoft.Xna.Framework, Version=3.0.0.0, Culture=neutral, PublicKeyToken=6d5c3888ef60e27d" или один из зависимых от них компонентов. Не удается найти указанный файл.

Делаю все пунктуально. В чем может быть проблема?