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

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

< Лекция 5 || Лекция 6: 12345678910111213

Пример 7. Использование базы данных с рисунками

Теперь создадим утилиту для просмотра рисунков из полученной ранее БД.

  • В файле Window1.xaml измените заголовок списка для элемента ' Пример 7 ' на ' Пример 7. Использование БД с рисунками '
  • В панели Solution Explorer добавьте к узлу App7 командой контекстного меню Add/Window новый файл с именем Window7.xaml
  • Заполните дескрипторную часть окна Window7 (файл Window7.xaml ) следующим кодом (приводится полностью)
<Window x:Class="App7.Window7"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Пример 7. Использование БД с рисунками" 
    Height="300"
    Width="470"
    MinHeight="300"
    MinWidth="450" 
    Name="_Window7" 
    Closing="Window_Closing"
    >
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="110" />
        </Grid.ColumnDefinitions>
        <Border Background="Black">
            <Image Name="image" Stretch="Fill" />
        </Border>
        <ListBox Name="listBox" Grid.Column="1" Padding="5,0"
                 ScrollViewer.VerticalScrollBarVisibility="Visible"
                 SelectionChanged="listBox_SelectionChanged">
        </ListBox>
    </Grid>
</Window>
  • Заполните процедурную часть окна Window7 (файл Window7.xaml.cs ) следующим кодом (приводится полностью)
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
    
// Дополнительные пространства имен
using FORMS = System.Windows.Forms; // Псевдоним
using System.IO;
using System.Configuration;         // Для SettingsProperty
using IO = System.IO;               // Псевдоним для адресации Path
    
// Дополнительные пространства имен для ADO.NET
using System.Data;
using System.Data.OleDb;
using System.Data.Common;
    
namespace App7
{
    public partial class Window7 : Window
    {
        String nameDB;     // Опорное поле для свойства с именем БД
        int count;              // Поле под число рисунков
        string[] names;         // Поле под имена рисунков
        Properties.Settings settings; // Параметры
    
        // Конструктор
        public Window7()
        {
            InitializeComponent();
    
            // Запускаем диалог выбора БД
            nameDB = SelectDB();
            // При отказе от выбора ничего не запускаем,
            // а в главном окне Window1 закрываем это окно 
            if (nameDB != String.Empty)
                ExecuteWindow();
        }
    
        // Извлекаем рисунки и управляем показом 
        private void ExecuteWindow()
        {
            // Читаем некоторые данные из БД
            OleDbConnection conn = new OleDbConnection(ConnectionString(nameDB));
            OleDbCommand cmd = new OleDbCommand("SELECT COUNT(*) FROM MyTable");
            cmd.Connection = conn;
            conn.Open();
            // Извлекаем число рисунков
            count = (int)cmd.ExecuteScalar();
            // Меняем команду и извлекаем имена рисунков
            cmd.CommandText = "SELECT FileName FROM MyTable";
            OleDbDataReader reader = cmd.ExecuteReader(CommandBehavior.CloseConnection);
            names = new string[count];
            int i = 0;
            while (reader.Read())// Равносильно foreach (DbDataRecord rec in reader)
                names[i++] = ((string)reader["FileName"]).Trim();// Сразу убираем пробелы
            // Соединение здесь закроет сам объект DataReader после прочтения всех данных
            // в соответствии с соглашением при его создании CommandBehavior.CloseConnection
    
            // Привязываем массив имен рисунков к списку
            listBox.ItemsSource = names;
            // Выделяем первый элемент списка, чтобы вызвать SelectionChanged
            listBox.SelectedIndex = 0;
            listBox.Focus();
        }
    
        // Строка соединения с БД 
        private String ConnectionString(String fileName)
        {
            //string pathToFile = Application.StartupPath.ToString();
            string JetEngineString = "Provider=Microsoft.Jet.OLEDB.4.0;" +
                "Jet OLEDB:Engine Type=5;Data Source=";
            return JetEngineString + fileName;
        }
    
        // При смене индекса списка показывает выбранный рисунок
        private void listBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            // Создаем и настраиваем инфраструктуру ADO.NET
            OleDbConnection conn = new OleDbConnection();
            conn.ConnectionString = ConnectionString(nameDB);
    
            // Создаем и настраиваем объект команды, параметризованной по имени рисунка
            OleDbCommand cmd = new OleDbCommand();
            cmd.Connection = conn;
            cmd.CommandType = CommandType.Text; // Необязательно! Установлено по умолчанию
            cmd.CommandText = "SELECT Picture FROM MyTable WHERE FileName=?";
            cmd.CommandTimeout = 1;// Пытаться X секунд, потом сгенерировать ошибку (=30 сек)
            cmd.Parameters.Add(new OleDbParameter());
            cmd.Parameters[0].Value = ((ListBox)sender).SelectedValue.ToString();// Имя рисунка
    
            OleDbDataAdapter adapter = new OleDbDataAdapter(cmd);
    
            // Извлекаем рисунок из БД 
            DataTable table = new DataTable();
            adapter.Fill(table);
            byte[] bytes = (byte[])table.Rows[0]["Picture"]; // Подключаемся к рисунку
    
            // Отображаем рисунок пользователю
            BitmapImage bi = new BitmapImage();
            bi.BeginInit();
            bi.StreamSource = new MemoryStream(bytes);// Обертываем извлеченный рисунок в поток
            bi.EndInit();
            image.Source = bi;
        }
    
        // Свойство доступа, проверяется в Window1
        public String NameDB
        {
            get { return nameDB; }
        }
    
        // Диалог выбора БД. Возвращает
        // пусто, если БД не выбрана
        private String SelectDB()
        {
            settings = new App7.Properties.Settings();
            String path = settings.dialogOpen;
            String fileName = IO.Path.Combine(path, "Pictures.my2.mdb");
    
            // Создаем и настраиваем диалог выбора файла
            FORMS.OpenFileDialog openFileDialog = new FORMS.OpenFileDialog();
            openFileDialog.InitialDirectory = path;
            openFileDialog.FileName = "";
            openFileDialog.Filter = "DB Files(*.my2.mdb)|*.my2.mdb";
            FORMS.DialogResult result = openFileDialog.ShowDialog();
            if (result != FORMS.DialogResult.Cancel)
                fileName = openFileDialog.FileName; // Полное имя
            else
                fileName = String.Empty;
    
            return fileName;
        }
    
        // Обработчик события Closing, срабатывает перед закрытием приложения
        private void Window_Closing(object sender,
            System.ComponentModel.CancelEventArgs e)
        {
            // Сохраняем путь в параметрах 
            if (nameDB != String.Empty)
            {
                settings.dialogOpen =
                    nameDB.Substring(0, nameDB.LastIndexOf('\\'));
                settings.Save();
            }
        }
    }
}

Свойство CommandTimeout объекта OleDbCommand получает или задает время ожидания (в секундах) перед завершением попытки выполнения команды и созданием ошибки. По умолчанию установлено 30 секунд, что соответствует большой сети и сложным запросам. Для нашей задачи и 1 секунды очень много, но меньше нельзя, поскольку это свойство типа int.

  • Заполните в файле Window1.xaml.cs функцию Example7() следующим кодом (окончательный, приводится полностью)
void Example7()
        {
            // Предотвращение повторного создания дочернего окна
            bool existsWindow = false;
            foreach (Window window in Application.Current.Windows)// Перебираем коллекцию
            {
                if (window.Name == "_Window7")
                {
                    // Нашли среди существующих 
                    existsWindow = true;
                    window.Activate();// Сдвинуть на передний план
                    break;// Прервать перебор
                }
            }
    
            if (!existsWindow)// Если не существует, то создать и показать
            {
                Window7 wnd = new Window7();
                // Перед показом проверяем, выбрана ли БД
                if (wnd.NameDB != String.Empty)
                    wnd.Show();    // Показываем окно Window7
                else
                    wnd.Close();   // Закрываем окно Window7
            }
        }
  • Разберитесь с кодом и запустите пример - результат для ранее созданной БД Pictures.my2.mdb соответствует ожидаемому и будет таким

Все, иллюстрацию потока MemoryStream мы закончили и попутно еще кое в чем потренировались. Остальные неиспользованные пункты списка главного окна в файле Window1.xaml можно удалить, как и соответствующий им процедурный код в файле Window1.xaml.cs. Или пусть так и болтаются.

Задание к упражнению

Напоследок можно сделать еще одно замечание к приведенному коду Упражнения 7. В окнах просмотра Window3, Window5 и Window7 мы создавали обработчик события Closing и старательно сохраняли переменную dialogOpen в параметрах приложения. Переменную dialogFolder мы тоже сохраняем, но только при закрытии главного окна. Отсюда дизгармония (физгармоника): пока не закрыли главное окно, при выполнении четных примеров диалог выбора каталогов всегда стартует с того места, которое было сохранено при закрытии главного окна в последнем запуске.

  • Исправьте этот недостаток, чтобы пока длится сеанс, приемственность диалога выбора папки в четных примерах соблюдалась

Сохранять путь и переменную dialogFolder можно сразу после выдачи сообщения MessageBox.Show() об удачном создании файловой библиотеки или БД.

Упражнение 8. Сетевые приложения на базе потока NetworkStream

Еще одним потомком абстрактного класса Stream является сетевой поток NetworkStream пространства имен System.Net.Sockets библиотеки System.dll. Можно использовать класс NetworkStream для выполнения как синхронной, так и асинхронной передачи данных. В синхронном режиме перед возвращением управления вызывающей программе вызовы функций, которые выполняют сетевые операции, ожидают окончания выполнения этих операции. В асинхронном режиме вызовы немедленно возвращаются инициировавшему их коду.

Рассмотрим применение NetworkStream на примере совместной работы нескольких запущенных копий клиентского приложения и одной копии серверного. Работу построим на основе созданной ранее БД Pictures.my2.mdb с рисунками. Сервер будет иметь доступ к этой базе и посылать все необходимые данные клиентам по их запросу. Интерфейсное окно клиента реализуем по технологии Windows Forms. А вот сервер создадим как резидентную программу ( Службу Windows ) и запустим в фоновом режиме. Протокол обмена для контроля будем выводить в текстовый файл. Так мы максимально разнообразим демонстрацию применения потоков.

Шаги выполнения упражнения оформим в виде примеров. Вначале познакомимся с некоторыми понятиями, взятыми из Википедии - свободной энциклопедии.

Сокет ( Socket ) - программный интерфейс ( API - набор программ) для обеспечения информационного обмена между процессами (загрущенными в ОЗУ приложениями, которые уже выполняются процессором). Потоковые сокеты работают с установкой соединения. Они обеспечивают надежную идентификацию обеих сторон, гарантируя целостность и успешность доставки данных, и опираются на протокол TCP. Условно можно представить себе сокеты в виде двух разъемов, в которые включен канал связи, предназначенный для передачи данных через сеть (поток данных). При передачи данных сокет можно считать резервным хранилищем - аналогом файла. Данные между сокетами перекачивает поток.


TCP ( Transmission Control Protocol - протокол управления передачей) - один из основных сетевых протоколов Интернета, предназначенный для управления передачей данных в сетях и подсетях TCP/IP. Это транспортный механизм, предоставляющий поток данных, с предварительной установкой соединения, за счёт этого дающий уверенность в достоверности получаемых данных. TCP осуществляет повторный запрос данных в случае потери данных и устраняет дублирование при получении двух копий одного пакета. TCP гарантирует, что приложение получит данные точно в такой же последовательности, в какой они были отправлены, и без потерь. TCP устанавливает соединение, передает данные и завершает соединение.

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

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

Порт - в протоколах TCP и UDP (семейства TCP/IP ) порт представляет собой идентифицируемый номером системный ресурс, выделяемый приложению, выполняемому на некотором сетевом хосте, для связи с приложениями, выполняемыми на других сетевых хостах (в том числе c другими приложениями на этом же хосте).

Для каждого из протоколов TCP и UDP стандарт определяет возможность одновременного выделения на хосте до 65536 уникальных портов, идентифицирующихся номерами от 0 до 65535. При передаче по сети номер порта в заголовке пакета используется (вместе с IP -адресом хоста) для адресации конкретного приложения (и конкретного, принадлежащего ему, сетевого соединения).

В обычной клиент-серверной модели одно приложение ожидает входящих данных или запроса на соединение (сервер "слушает порт"), другое посылает данные или запрос на соединение в известный порт, открытый приложением-сервером (клиент).

По умолчанию приложению выдается порт с произвольным (например, ближайшим свободным и большим 1024) номером. При необходимости приложение может запросить конкретный (предопределенный) номер порта. Так, веб-серверы обычно открывают для ожидания соединения предопределенный порт 80 протокола TCP.

Бытовой пример: На вокзале касса с тремя окошками (для поездов дальнего следования, ближнего и военных) и тремя кассирами, каждый из которых обслуживает свое окно. Кассиры пьют кофе или играют в карты, но когда к какому-нибудь окну подойдет пассажир, его обслужит соответствующий кассир. Вокзал - сеть, касса - хост, кассиры - серверы, окна - порты, пассажиры - клиенты.

Очень хорошо вопросы сетевого программирования описаны в книге (скорее, единственной такой):

".NET - Сетевое программирование для профессионалов"

Авторы: Эндрю Кровчик, Винод Кумар, Номан Лагари, Аджит Мунгале, Кристиан Нагел, Тим Паркер,

Шриниваса Шивакумар

Издательство: Лори

Год: 2005


Пример 1. Создание простой тестовой Службы Windows

Сервером в C# может служить приложение любого из существующих исполнимых типов: Console Application, Windows Forms Application, WPF Application или Windows Service. Для разнообразия, вначале построим сервер на основе Windows Service. Такие программы не имеют пользовательского интерфейса (он им и не нужен - висят в оперативной памяти и делают свое дело) и раньше назывались резидентными, а теперь Службами.

Позже мы создадим сервер и как Console Application, в котором консольное окно может быть использовано для запуска, настроек и контроля состояния сервера. А пока немного отвлечемся и на простом примере научимся создавать Службу Windows как прототип нашего будущего сервера, затем опять продолжим рассмотрение темы потоков.

  • Командой File/New/Project создайте в каталоге решений Stream новое решение с именем NetworkStream и новый проект с именем TestService типа Windows Service

Оболочка создаст новый проект и покажет окно графического конструктора сервисов.

  • Закройте окно конструктора сервисов и через панель Solution Explorer в узле Service1.cs удалите подчиненный файл Service1.Designer.cs за ненадобностью, поскольку компоненты в этом примере мы использовать не будем
  • В панели Solution Explorer вызовите контекстное меню для узла Service1.cs и выполните команду View Code
  • Заполните файл Service1.cs следующим кодом ('http://msdn.microsoft.com/ru-ru/library/9k985bc9(VS.90).aspx')
using System;
using System.ServiceProcess;
using System.Timers;
using System.IO;
    
namespace TestService
{
    public partial class Service1 : ServiceBase
    {
        Timer timer;// Системный таймер
        String logFile = "C:\\Report.txt";
    
        // Конструктор
        public Service1()
        {
            // Создаем и настраиваем системный таймер
            timer = new Timer();
            timer.Elapsed += timer_Elapsed;// Регистрируем обработчик
            timer.Interval = 5000; // 5 секунд
        }
    
        // Обработчик события таймера
        void timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            StreamWriter writer = File.AppendText(logFile);
            writer.WriteLine(DateTime.Now.ToString());
            writer.Close(); // Закрываем поток
        }
    
        // Метод срабатывает при запуске Службы
        protected override void OnStart(string[] args)
        {
            timer.Start();
        }
    
        // Метод срабатывает при остановке Службы
        protected override void OnStop()
        {
            timer.Stop();
        }
    }
}

Запускать на выполнение через оболочку эту Службу нельзя. Она запускается операционной системой через Диспетчер Служб (или через Диспетчер задач Windows по жесту Ctrl-Alt-Del). Мы ее можем пока только откомпилировать, но и это преждевременно. Еще нужно зарегистрировать Службу на текущем компьютере, чтобы она была видна диспетчеру Служб, а для этого нужно к проекту добавить класс настроек регистрации Службы.

  • В панели Solution Explorer вызовите на узле TestService проекта контекстное меню и командой Add/New Item в появишемся окне мастера выберите элемент Installer Class

  • Закройте окно графического конструктора и через панель Solution Explorer в узле Installer1.cs удалите подчиненный файл Installer1.Designer.cs за ненадобностью
  • В панели Solution Explorer вызовите контекстное меню для узла Installer1.cs и выполните команду View Code
  • Заполните файл Installer1.cs следующим кодом настроек регистрации Службы ('http://msdn.microsoft.com/ru-ru/library/ddhy0byf(v=VS.90).aspx')
using System;
using System.ComponentModel;
using System.Configuration.Install;
using System.ServiceProcess;
    
namespace TestService
{
    [RunInstaller(true)]// Во время установки сборки следует вызвать установщик
    public partial class Installer1 : Installer
    {
        private ServiceInstaller serviceInstaller;
        private ServiceProcessInstaller serviceProcessInstaller;
    
        public Installer1()
        {
            // Создаем объекты настройки для Службы
            serviceInstaller = new ServiceInstaller();
            serviceProcessInstaller = new ServiceProcessInstaller();
    
            // Имя Службы для машины и пользователя
            serviceInstaller.ServiceName = "TestServiceServiceName";
            serviceInstaller.DisplayName = "TestServiceDisplayName";
    
            // Контекст безопасности - как будет запускаться Служба
            this.serviceProcessInstaller.Account = ServiceAccount.LocalSystem;
            this.serviceProcessInstaller.Password = null;
            this.serviceProcessInstaller.Username = null;
    
            // Добавляем настройки в коллекцию текущего объекта
            this.Installers.AddRange(
                new Installer[]         
                {
                    serviceInstaller,
                    serviceProcessInstaller
                });
        }
    }
}
  • Откомпилируйте проект командой Build/Build Solution

Мы создали Службу, которая должна быть зарегистрирована в операционной системе. Как это делается, описано в MSDN ('http://msdn.microsoft.com/ru-ru/library/sd8zc8ha(VS.90).aspx') и мы это сейчас выполним. Для регистрации Служб приложений .NET используется утилита InstallUtil.exe, которая находится в каталоге " C:\Windows\Microsoft.NET\Framework\v2.0.50727 ". Для удаления регистрации Службы используется та же самая утилита, но с опцией /u.

  • В меню Пуск/Выполнить введете в текстовое поле имя запускаемой программы cmd и откройте окно команд Windows
  • Следующей командой перейдите в каталог с утилитой InstallUtil.exe >cd C:\Windows\Microsoft.NET\Framework\v2.0.50727
  • В панели Solution Explorer щелкните на пиктограмме Show All Files, раскройте узел bin/Debug и скопируйте полное имя сборки E:\Tmp\Stream\NetworkStream\TestService\bin\Debug\TestService.exe
  • В окне команд Windows введите команду >InstallUtil.exe и добавьте мышью скопированное имя сборки, должно получиться >InstallUtil.exe E:\Tmp\Stream\NetworkStream\TestService\bin\Debug\TestService.exe
  • После выполнения этой команды откройте окно Пуск/Настройка/Панель управления/Администрирование/Службы и запустите нашу Службу (или жестом Ctrl-Alt-Del запустите Диспетчер задач и откройте вкладку Службы)

  • Убедитесь, что в файле Report.txt стала накапливаться информация, остановите Службу командой Остановить и удалите этот пробный файл

Замечания

  1. Вызвать окно со списком Служб можно и через Диспетчер задач Windows комбинацией клавиш Ctrl-Alt-Del (когда-то заветная комбинация для программистов DOS!)
  2. Созданная нами Служба запускается вручную, но через команду Свойства контекстного меню ее можно сделать запускаемой автоматически при загрузке компьютера
  3. После регистрации Службы в системе нельзя менять место расположения созданной нами сборки, иначе при запуске система эту Службу просто не найдет. При необходимости Службу нужно вначале разрегистрировать, перенести и вновь зарегистрировать для нового места
  4. В каталоге сборки утилита инсталляции InstallUtil.exe создала два протокольных текстовых файла (без расширения): TestService.InstallLog и TestService.InstallState, которые не влияют на дальнейшую работу Службы и их можно спокойно удалить
  • В окне команд Windows разрегистрируйте Службу командой >InstallUtil.exe /u E:\Tmp\Stream\NetworkStream\TestService\bin\Debug\TestService.exe
  • Перейдите в окно Службы, обновите его командой Действие/Обновить и убедитесь, что Служба была удалена

Итак, на этом простом примере мы научились создавать Службы Windows (не путайте с Web -Службами) и теперь можно бы приступить к созданию серверной программы, которая по запросу клиентов будет извлекать рисунки из БД и посылать им (их) для просмотра. Но не будем торопиться и рассмотрим еще один тестовый пример, теперь уже иллюстрирующий взаимодействие клиента и сервера.

< Лекция 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" или один из зависимых от них компонентов. Не удается найти указанный файл.

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