Опубликован: 05.08.2010 | Уровень: специалист | Доступ: платный
Лекция 6:

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

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

Пример 6. Сетевой обмен сериализованными объектами данных

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

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

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

До этого момента нашей главной задачей было определение типа пересылаемых данных, чтобы правильно использовать инструменты их чтения и записи. Теперь такая задача станет проще, поскольку тип данных будет назначаться при конструировании объекта транспортировки. А сама транспортировка будет выполняться оболочкой сериализации унифицированным образом.

Чтобы убедительно продемонстрировать сказанное, будем сериализовать и пересылать разнородные типы данных: текст, рисунки, звук и видео. Все это, в конечном итоге, будет представлять собой потоки байтов с хорошо распознаваемыми типами при десериализации. Работа предстоит большая, но чрезвычайно полезная, поэтому прошу ее не игнорировать, а выполнить внимательно. Постараемся код сделать интересным и разнообразным (да простят меня классики!). Итак, приступим...

Создание объекта транспортировки

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

  • Командой оболочки File/Add/New Project добавьте к решению NetworkStream новый проект с именем MyLibrary типа Class Library

  • Заполните созданный оболочкой файл Class1.cs следующим кодом (приводится полностью)
using System;
using System.IO;
    
namespace MyLibrary.MyDataSerialization
{
    /// <summary>
    /// Перечисление команд, передаваемых 
    /// серверу для исполнения на нем
    /// </summary>
    [Serializable()]    // Атрибут
    public enum Command
    {
        /// <summary>Добавить сообщение в журнал</summary>
        AddTextInLog,
        /// <summary>Прислать журнал</summary>
        Log,
        /// <summary>Удалить журнал</summary>
        DeleteLog,
        /// <summary>Прислать сообщение</summary>
        Text,
        /// <summary>Прислать рисунок</summary>
        Image,
        /// <summary>Прислать звук</summary>
        Sound,
        /// <summary>Прислать видео</summary>
        Video
    }
    
    /// <summary>
    /// Сериализуемый объект для транспортировки
    /// </summary>
    [Serializable()]    // Атрибут
    public class DataObject : ICloneable
    {
        // Поля с сохраняемыми данными
        Command command;        // Идентификатор команды
        string displayCommand;  // Представление команды для пользователя
        string text;            // Для передачи текстовых данных
        string logName;         // Имя файла с журналом
        string imageName;       // Имя файла с рисунком
        string soundName;       // Имя файла со звуком
        string videoName;       // Имя файла с видео
        Stream image;           // Сам рисунок
        Stream sound;           // Сам звук
        Stream video;           // Само видео
    
        // Свойства доступа к полям
        public Command Command
        {
            get { return command; }
            set { command = value; }
        }
        // Строковый тип
        public String DisplayCommand
        {
            get { return displayCommand; }
            set { displayCommand = value; }
        }
        public String Text
        {
            get { return text; }
            set { text = value; }
        }
        public String LogName
        {
            get { return logName; }
            set { logName = value; }
        }
        public String ImageName
        {
            get { return imageName; }
            set { imageName = value; }
        }
        public String SoundName
        {
            get { return soundName; }
            set { soundName = value; }
        }
        public String VideoName
        {
            get { return videoName; }
            set { videoName = value; }
        }
        // Потоковый тип
        public Stream Image
        {
            get { return image; }
            set { image = value; }
        }
        public Stream Sound
        {
            get { return sound; }
            set { sound = value; }
        }
        public Stream Video
        {
            get { return video; }
            set { video = value; }
        }
    
        /// <summary>
        /// Оригинальный метод C#
        /// Реализация интерфейса ICloneable для создания копии объекта
        /// </summary>
        public Object Clone()
        {
            // Создаем клон
            DataObject clone = new DataObject();
    
            // Копируем значимые свойства
            clone.command = this.command;
            clone.displayCommand = this.displayCommand;
            clone.text = this.text;
            clone.logName = this.logName;
            clone.imageName = this.imageName;
            clone.soundName = this.soundName;
            clone.videoName = this.videoName;
    
            // Значения ссылок не копируем
            clone.image = null;
            clone.sound = null;
            clone.video = null;
    
            return clone;
        }
    
        /// <summary>
        /// Альтернативный метод создания копии объекта 
        /// на основе шаблона, потока памяти и сериализации.
        /// А он мне нравится, нравится, нравится...!!!!!!!
        /// </summary>
        public T BinaryClone<T>()
        {
            using (var stream = new System.IO.MemoryStream())
            {
                var binaryFormatter = new System.Runtime.Serialization.
                    Formatters.Binary.BinaryFormatter();
                binaryFormatter.Serialize(stream, this);
                stream.Position = 0;
    
                return (T)binaryFormatter.Deserialize(stream);
            }
        }
    }
}

Метод Clone() этого класса является реализацией наследуемого библиотечного интерфейса ICloneable. Этот метод нам понадобится в коде сервера для создания объекта отправки как копии присланного объекта. Интерфейс ICloneable языка C# является альтернативой конструктора копий языка C++. Типизированный метод BinaryClone() является еще более мощной этому заменой (предложен в ' http://weblogs.asp.net/esanchez/archive/2008/05/18/cloning-objects-in-net.aspx'). Он создает копии не только значимых данных, но и копии ссылок вместе с копиями адресуемых этими ссылками данных. И все это - с использованием рассматриваемых нами интруменов. Именно его (но попробуйте и Clone ) мы включим в код сервера.

Ключевое слово var в описании метода BinaryClone() предназначено для ленивых и означает объявление типа ссылки, определяемого типом адресуемого объекта. На более превычном нам синтаксисе метод должен выглядеть так

public T BinaryClone<T>()
        {
            using (MemoryStream stream = new System.IO.MemoryStream())
            {
                BinaryFormatter binaryFormatter = new System.Runtime.Serialization.
                    Formatters.Binary.BinaryFormatter();
                binaryFormatter.Serialize(stream, this);
                stream.Position = 0;
    
                return (T)binaryFormatter.Deserialize(stream);
            }
        }
  • Откомпилируйте проект MyLibrary командой Build - теперь это полноправный библиотечный объект и его можно подключать к любому проекту в узле References
Создание клиента транспортировки

Клиента построим с пользовательским интерфейсом по технике Windows Forms. Сервер должен 'сидеть' на компьютере с данными и по запросам клиентов отправлять им эти данные. Клиенты должны иметь интерфейс для формирования запроса и отображения присланных данных. Для разнообразия, видеоданные будем отображать с помощью плеера из WPF, хотя можно было бы воспользоваться COM -объектом " Windows Media Player ", как мы это делали в лабораторной работе №45. Поскольку в пределах одного окна интерфейсные элементы двух техник ( Windows Forms и WPF ) несовместимы без специального приема, используем для совместимости переходник ElementHost из пространства имен System.Windows.Forms.Integration 'http://msdn.microsoft.com/ru-ru/library/system.windows.forms.integration.elementhost.aspx'

  • Добавьте к решению NetworkStream новый проект с именем SerializeClient и назначьте его стартовым

  • В панели Solution Explorer выделите узел проекта SerializeClient и командой Project/Add Reference добавьте к проекту ссылки на библиотеки:
    • из вкладки .NET для видеоплеера WPF:
      • PresentationCore.dll
      • PresentationFramework.dll
      • WindowsBase.dll
      • WindowsFormsIntegration.dll
    • из вкладки Projects для нашего объекта транспортировки:
      • MyLibrary.dll
  • Настройте элементы формы в соответствии с таблицей свойств
Таблица 19.8.
Элемент Свойство Значение
Form Text Simple SerializeClient
Size 300; 300
StartPosition CenterScreen
MaximizeBox False
Label Text Command:
Location 3; 16
Size 75; 17
AutoSize True
ComboBox (Name) cbCommand
Location 79; 13
Size 201; 24
Label Text Message:
Location 9; 53
Size 69; 17
AutoSize True
TextBox (Name) txtMessage
Location 79; 50
Size 201; 22
Label Text Display:
Location 20; 144
Size 58; 17
AutoSize True
ElementHost Переходник WPF->WindowsForms. Ищите в панели Toolbox на вкладке 'WPF Interoperability', если там нет, добавьте в нее контекстной командой Choose Items или создайте объект динамически в процедурном коде
(Name) videoHost
Location 79; 85
Size 201; 127
PictureBox (Name) pictureBox
Location 79; 85
Size 201; 127
TextBox (Name) txtReceived
Location 79; 85
Size 201; 127
Multiline True
Button (Name) btnSend
AutoSize True
Location 142; 228
Size 75; 27
Text Send

В итоге, пользовательский интерфейс формы должен выглядеть так


Обратите внимание, что мы совместили позиции элементов ElementHost, PictureBox и TextBox, причем видимым на верхнем слое будет элемент TextBox, добавленный на форму последним. Естественно, что из них одновременно мы будем показывать только один элемент, делая в коде остальные невидимыми.

  • Заполните файл Form1.cs клиента SerializeClient следующим кодом (приводится полностью)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
    
// Дополнительные пространства имен
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Runtime.Serialization.Formatters.Binary;
using System.Media; // Для проигрывателя wav-файлов SoundPlayer
    
// Своя библиотека
using Data = MyLibrary.MyDataSerialization.DataObject;// Псевдоним класса
using MyLibrary.MyDataSerialization;
    
namespace SerializeClient
{
    public partial class Form1 : Form
    {
        int port = 12000;
        String hostName = "127.0.0.1";  // local
        TcpClient client;               // Ссылка на клиента
        NetworkStream readerStream, writerStream;// Объявили ссылки на транспортный поток
        DataTable table;// Для привязки к списку ComboBox
    
        System.Windows.Controls.MediaElement player;// Для просмотра видео
        bool playerReady = true;// Блокировка повторного запуска на время проигрывания
    
        public Form1()
        {
            InitializeComponent();
    
            // Формируем объектную таблицу и привязываем к списку
            cbCommand.DataSource = DataCommand();
            cbCommand.DisplayMember = "CommandName";
            cbCommand.ValueMember = "CommandValue";
            //cbCommand.SelectedIndex = 0;// Первый
            // Устанавливаем курсор списка в конец
            cbCommand.SelectedIndex = cbCommand.Items.Count - 1;// Последний
        }
    
        DataTable DataCommand()
        {
            table = new DataTable();// Таблица, привязываемая к ComboBox
    
            // Задаем структуру таблицы
            DataColumn[] cols = new DataColumn[2];
            cols[0] = new DataColumn("CommandName", typeof(String));
            cols[1] = new DataColumn("CommandValue", typeof(Command));
            table.Columns.AddRange(cols);
    
            // Формируем строки таблицы
            DataRow row;
            String[] names ={   // Видимое в ComboBox
                                "Добавить в журнал",
                                "Прислать журнал",
                                "Удалить журнал",
                                "Прислать сообщение",
                                "Прислать рисунок",
                                "Прислать звук",
                                "Прислать видео"
                            };
            //String[] commands = Enum.GetNames(typeof(Command));   // Сортированы по значению
            int[] values = (int[])Enum.GetValues(typeof(Command));  // Сортированы по значению
            for (int i = 0; i < values.Length; i++)
            {
                row = table.NewRow();
                row[0] = names[i];
                row[1] = (Command)values[i];
                table.Rows.Add(row);
            }
    
            return table;// Возвращаем заполненную таблицу для привязки к ComboBox
        }
    
        // По щелчку кнопки выполняет обмен данными с сервером
        private void btnSend_Click(object sender, EventArgs e)
        {
            try
            {
                // Создать клиента
                client = new TcpClient(hostName, port);
            }
            catch
            {
                MessageBox.Show("Сервер не готов!");
                return;
            }
    
            //
            //********************* Отправка *********************
            //
            // Собираем данные с элементов управления для отправки через объект
            Data dataSend = new Data();
            dataSend.Command = (Command)cbCommand.SelectedValue;
            dataSend.DisplayCommand =
                table.Rows[cbCommand.SelectedIndex]["CommandName"].ToString(); ;
            dataSend.Text = txtMessage.Text.Trim();// Берем из текстового поля Message
            // Обнуляем ссылки на потоки
            dataSend.Image = null;
            dataSend.Sound = null;
            dataSend.Video = null;
            // Имена зададим жестко в коде, хотя можно 
            // сделать диалог опроса пользователя
            dataSend.LogName = "Log.txt";               // Имя журнала, локализация на сервере C:\server
            dataSend.ImageName = @"Data\flower2.jpg";   // Разместить в каталоге сборки сервера
            dataSend.SoundName = @"Data\Trumpet1.wav";  // Разместить в каталоге сборки сервера
            dataSend.VideoName = @"Data\tits.mpe";      // Разместить в каталоге сборки сервера
    
            // Подключаем транспортный поток для отправки
            writerStream = client.GetStream();
            // Сериализуем объект в транспортный поток и отправляем 
            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Serialize(writerStream, dataSend);// Отправленный объект
    
            //
            //********************* Получение *********************
            //
            // Подключаем транспортный поток для получения данных
            readerStream = client.GetStream();
            // Прочитать и десериализовать в объект Data
            formatter = new BinaryFormatter();
            Data dataReceived = (Data)formatter.Deserialize(readerStream);// Полученный объект
    
            readerStream.Close();// Освобождаем поток
            writerStream.Close();// Освобождаем поток
            client.Close();// Освобождаем сокет клиента
    
            // Анализ и отображение присланного
            // Сервер может поменять команду, поэтому проверяем
            // не по cbCommand.SelectedIndex, а по присланному факту
            String dir = @"c:\Client";
            String path = Path.Combine(dir, dataReceived.LogName);
            if (!Directory.Exists(dir))
                Directory.CreateDirectory(dir);// Создаем каталог, если не существует
    
            // Разбор и исполнение вариантов
            switch (dataReceived.Command)
            {
                // Прислали текст, покажем на форме
                case Command.Text:
                    if (!String.IsNullOrEmpty(dataReceived.Text))
                    {
                        txtReceived.Visible = true;
                        pictureBox.Visible = false;
                        videoHost.Visible = false;
                        txtReceived.Text = dataReceived.Text;
                    }
                    break;
                // Прислали журнал, покажем через Блокнот
                case Command.Log:
                    if (!String.IsNullOrEmpty(dataReceived.Text))
                    {
                        // Сохраним содержимое журнала в файл
                        FileStream fs = File.Create(path);
                        StreamWriter sw = new StreamWriter(fs);
                        sw.Write(dataReceived.Text);
                        sw.Close();
    
                        // Покажем журнал через блокнот
                        System.Diagnostics.Process exe = new System.Diagnostics.Process();
                        exe.StartInfo.FileName = "Notepad.exe"; //Имя программы для запуска
                        exe.StartInfo.Arguments = path;         //Аргументы
                        exe.Start();        // Запускаем внешний процесс
                        //exe.WaitForExit();// Останавливаем наш процесс до закрытия блокнота
                        exe.Close();        // Освобождаем связанные с процессом ресурсы
                    }
                    break;
                // Прислали рисунок, покажем на форме
                case Command.Image:
                    if (dataReceived.Image != null)
                    {
                        txtReceived.Visible = false;
                        pictureBox.Visible = true;
                        videoHost.Visible = false;
                        pictureBox.SizeMode = PictureBoxSizeMode.StretchImage;
                        txtMessage.Text = dataReceived.Text;// Сообщение
                        pictureBox.Image = new Bitmap(dataReceived.Image);
                    }
                    break;
                // Прислали звук, проиграем (можно и в карты!)
                case Command.Sound:
                    if (dataReceived.Sound != null)
                    {
                        txtReceived.Visible = true;
                        pictureBox.Visible = false;
                        videoHost.Visible = false;
                        txtReceived.Text = dataReceived.Text;
                        txtMessage.Text = dataReceived.Text;// Сообщение
                        SoundPlayer soundPlayer = new SoundPlayer(dataReceived.Sound);
                        //soundPlayer.LoadAsync(); //Играть фоново (Необязательно!)
                        soundPlayer.Play();
                    }
                    break;
                // Прислали видео, посмотрим
                case Command.Video:
                    if (dataReceived.Video != null && playerReady)
                    {
                        txtReceived.Visible = false;
                        pictureBox.Visible = false;
                        videoHost.Visible = true;
                        txtMessage.Text = dataReceived.Text;// Сообщение
    
                        // Создаем WPF-проигрыватель
                        player = new System.Windows.Controls.MediaElement();
                        playerReady = false;// Затычка от повторного запуска, пока играет
                        // Передаем переходнику как контейнеру
                        videoHost.Child = player;
                        // player.SetStream(dataReceived.Video); // Не работает, хотя есть в MSDN
    
                        // Поэтому по-турецки: загружаем видео через файл на клиенте
                        // Получили поток памяти и пишем его в файл на диске клиента
                        String[] tmp = dataReceived.VideoName.Split('\\');
                        string shortName = tmp[tmp.Length - 1];// Короткое имя видеофайла  
                        string fullName = Path.Combine(dir, shortName);// Имя с путем
                        FileStream fs = File.Create(fullName);// Создаем или усекаем
                        MemoryStream ms = dataReceived.Video as MemoryStream;// Приводим к исходному
                        ms.WriteTo(fs);// Сливаем в файл
                        ms.Flush();// Необязательно! Сбросится при закрытии потока
                        fs.Close();
                        ms.Close();
    
                        // Настраиваем WPF-проигрыватель
                        player.Source = new Uri(fullName, UriKind.Relative);// Держит файл!!!
                        player.Stretch = System.Windows.Media.Stretch.Fill;// На весь контейнер
                        // Сработает, когда проигрывание закончится
                        player.MediaEnded += new System.Windows.RoutedEventHandler(player_MediaEnded);
                        // Запускать вручную - только после команды Play()
                        player.LoadedBehavior = System.Windows.Controls.MediaState.Manual;
                        // Останавливать вручную - только после команды Stop()
                        player.UnloadedBehavior = System.Windows.Controls.MediaState.Stop;
                        player.IsMuted = true; // Проигрывать с отлюченным звуком
                        player.Play();// Запустить проигрывание
    
                        /*******************************************************************
                        // Это альтернативный код. Он только для WPF и не конвертируется в Windows Forms
                        System.Windows.Media.MediaPlayer player =
                            new System.Windows.Media.MediaPlayer();
                        player.Open(new Uri("WellWait - 02.AVI", UriKind.Relative));
                        System.Windows.Media.VideoDrawing aVideoDrawing =
                            new System.Windows.Media.VideoDrawing();
                        aVideoDrawing.Player = player;
                        System.Windows.Rect rect = new System.Windows.Rect(0, 0, 100, 100);
                        aVideoDrawing.Rect = rect;// Здесь несоответствие типов!!!
                        player.Play();    
                        //******************************************************************/
                    }
    
                    break;
            }
        }
    
        // Плеер блокирует файл на время жизни, поэтому ждем 
        // окончания проигрывания и полность уничтожаем объект
        void player_MediaEnded(object sender, System.Windows.RoutedEventArgs e)
        {
            player.Close();
            player = null;// Необязательно, итак работает!
            GC.WaitForFullGCComplete();// Остановить основной процесс до конца уборки
            GC.Collect();// Запускаем сборщик мусора
            // Здесь уже плеер полность удален из памяти и отпустил файл
            playerReady = true; // Открываем доступ к коду создания нового проигрывателя
        }
    }
}
  • Запустите проект на выполнение - ошибок компиляции быть не должно и клиент выдаст сообщение, что сервер не готов
Создание сервера транспортировки
  • Добавьте к решению NetworkStream новый проект с именем SerializeServer типа консольного приложения (стартовым не назначайте, стартовым будет приложение SerializeClient )

  • В панели Solution Explorer для узла References проекта SerializeServer вызовите контекстное меню и командой Add Reference через вкладку Projects добавьте к проекту ссылку на библиотеку MyLibrary.dll для нашего объекта транспортировки
  • В панели Solution Explorer вызовите контекстное меню для корня проекта SerializeServer и командой Add/New Folder создайте папку Data
  • В панели Solution Explorer вызовите контекстное меню для папки Data и командой Add/Existing Item добавьте к проекту из прилагаемого каталога Source файлы:
    • flower2.jpg
    • Trumpet1.wav
    • tits.mpe
  • Выделите все три скопированных в проект файла и через панель Properties установите для них свойства:
  • Build Action=None
    • Copy to Output Directory=Copy if newer
  • Откройте файл Program.cs проекта SerializeServer и заполните его следующим процедурным кодом (приводится полностью)
using System;
using System.Collections.Generic;
using System.Text;
    
// Дополнительные пространства имен для ADO.NET
using System.Data;
    
// Дополнительные пространства имен
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Collections;
using System.Configuration;
using System.Runtime.Serialization.Formatters.Binary;
    
// Своя библиотека
using Data = MyLibrary.MyDataSerialization.DataObject;// Псевдоним класса
using MyLibrary.MyDataSerialization;// Пространство имен в библиотеке
    
namespace SerializeServer
{
    class Program
    {
        int port = 12000;
        String hostName = "127.0.0.1";  // local
        IPAddress localAddr;
        TcpListener server = null;      // Ссылка на сервер
        NetworkStream readerStream, writerStream;// Объявили ссылку на транспортный поток
    
        // Добавили конструктор
        public Program()
        {
            // Конвертируем IP в другой формат
            localAddr = IPAddress.Parse(hostName);
    
            // Запускаем в новом потоке (ните)
            Thread thread = new Thread(ExecuteLoop);
            thread.IsBackground = true;
            thread.Start();
        }
    
        static void Main(string[] args)
        {
            Console.WindowWidth = 20;
            Console.WindowHeight = 5;
            new Program();
    
            Console.ReadLine();// Держим основной поток (Thread) приложения
        }
    
        private void ExecuteLoop()
        {
            try
            {
                server = new TcpListener(localAddr, port);// Создаем сервер-слушатель
                server.Start();// Запускаем сервер
    
                // Бесконечный цикл прослушивания очереди клиентов
                while (true)
                {
                    // Проверяем очередь соединений
                    if (!server.Pending())// Очередь запросов пуста
                        continue;
                    TcpClient client = server.AcceptTcpClient();// Текущий клиент
    
                    //
                    //********************* Получение *********************
                    //
                    // Подключаемся к сокету для чтения
                    readerStream = client.GetStream();
    
                    // Получить объект данных и десериализовать
                    BinaryFormatter formatter = new BinaryFormatter();
                    Data dataReceived = (Data)formatter.Deserialize(readerStream);
    
                    //
                    //************ Выполнение действий и отправка **********
                    //
                    // Создаем объект-клон для отправки и постепенно заполняем данными
                    //Data dataSend = (Data)dataReceived.Clone();    // Оригинальный метод
                    Data dataSend = dataReceived.BinaryClone<Data>();// Альтернативный метод
    
                    // Распознаем присланное, исполняем и формируем данные для отправки
                    String dir = @"c:\Server";
                    String path = Path.Combine(dir, dataReceived.LogName);
                    if (!Directory.Exists(dir))
                        Directory.CreateDirectory(dir);
                    String message = "";
                    FileStream fs;
                    Byte[] bytes;
                    switch (dataReceived.Command)
                    {
                        case Command.AddTextInLog:// Клиент хочет сообщение добавить в журнал
                            fs = new FileStream(path, FileMode.Append);
                            StreamWriter sw = new StreamWriter(fs);
                            sw.WriteLine("Сервер: " + DateTime.Now.ToString());
                            message = "Получил команду: \"" + dataReceived.DisplayCommand;
                            sw.WriteLine(message + "\"");// Три конкатенации сразу нельзя
                            sw.WriteLine("Идентификатор команды: " +
                                Enum.GetName(typeof(Command), dataReceived.Command));
                            sw.WriteLine("Получил сообщение: " + dataReceived.Text);
                            message = "Добавлено сообщение в журнал!";
                            sw.WriteLine(message);
                            sw.WriteLine();
                            sw.Close();
                            dataSend.Text = message;// Отчет для отправки
                            dataSend.Command = Command.Text;// Изменили команду
                            break;
                        case Command.DeleteLog:// Клиент хочет удалить журнал с диска сервера
                            if (File.Exists(path))
                                File.Delete(path);
                            dataSend.Text = "Журнал удален";// Отчет для отправки
                            dataSend.Command = Command.Text;// Изменили команду
                            break;
                        case Command.Text:// Клиент хочет получить сообщение 
                            message = String.Format(
                                "Получил сообщение \"{0}\"\r\nОчень рад!", dataReceived.Text);
                            dataSend.Text = message;// Отчет для отправки
                            break;
                        case Command.Image:// Клиент хочет получить рисунок
                            // Файл должен быть в каталоге сборки, иначе надо дополнить путем
                            if (!File.Exists(dataReceived.ImageName))
                            {
                                dataSend.Text = "Рисунок не существует";// Отчет для отправки
                                dataSend.Command = Command.Text;// Изменили команду
                                break;
                            }
                            // Читаем файл с рисунком
                            bytes = File.ReadAllBytes(dataReceived.ImageName);
                            // Подключаем поток к свойству объекта
                            dataSend.Image = new MemoryStream(bytes);
                            dataSend.Text = "Посылаю рисунок";// Отчет для отправки
                            break;
                        case Command.Log:// Клиент требует содержимое журнала
                            if (!File.Exists(path))
                            {
                                dataSend.Text = "Журнал еще не создан";// Отчет для отправки
                                dataSend.Command = Command.Text;// Изменили команду
                                break;
                            }
                            fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
                            StreamReader sr = new StreamReader(fs);
                            dataSend.Text = sr.ReadToEnd();
                            sr.Close();
                            break;
                        case Command.Sound:// Клиент хочет музычку
                            // Файл должен быть в каталоге сборки, иначе надо дополнить путем
                            if (!File.Exists(dataReceived.SoundName))
                            {
                                dataSend.Text = "Файл с музычкой не существует";// Отчет для отправки
                                dataSend.Command = Command.Text;// Изменили команду
                                break;
                            }
                            // Читаем файл с музычкой
                            bytes = File.ReadAllBytes(dataReceived.SoundName);
                            // Подключаем поток к свойству объекта
                            dataSend.Sound = new MemoryStream(bytes);
                            dataSend.Text = "Посылаю музычку";// Отчет для отправки
                            break;
                        case Command.Video:// Клиент хочет видео
                            // Файл должен быть в каталоге сборки, иначе надо дополнить путем
                            if (!File.Exists(dataReceived.VideoName))
                            {
                                dataSend.Text = "Файл с video не существует";// Отчет для отправки
                                dataSend.Command = Command.Text;// Изменили команду
                                break;
                            }
                            // Читаем файл с video
                            bytes = File.ReadAllBytes(dataReceived.VideoName);
                            // Подключаем поток к свойству объекта
                            dataSend.Video = new MemoryStream(bytes);
                            dataSend.Text = "Там у них другие мерки...";// Отчет для отправки
                            break;
                    }
    
                    // Отправляем данные
                    writerStream = client.GetStream();
                    formatter = new BinaryFormatter();
                    formatter.Serialize(writerStream, dataSend);// Отправленный объект
    
                    readerStream.Close();// Освобождаем поток
                    writerStream.Close();// Освобождаем поток
                    client.Close();// Освобождаем сокет текущего клиента
                }
            }
            finally
            {
                // Останавливаем сервер
                server.Stop();
            }
        }
    }
}
  • Откомпилируйте проект SerializeServer и через Windows Explorer выведите на рабочий стол ярлык для запуска консольной сборки сервера
  • Запустите оба созданных приложения и испытайте их работу

Если запустить сразу несколько клиентов (не через оболочку, а через сборку), то наша защелка playerReady помогает только тогда, когда видеопроигрыватель в данный момент активен только на одном клиенте. При одновременном запуске в нескольких сборках WPF -проигрыватель MediaElement дает системное исключение. Да к тому же MediaElement еще требует наличия на компьютере установленного проигрывателя Windows Media версии 10 и выше.

На сегодняшний день только Windows Vista поставляется вместе с проигрывателем Windows Media 11 (у меня установлен и пример работает). Если на вашем компьютере такого проигрывателя нет (хотя и установлен WPF ), замените его на COM -объект, как в работе №45. Суть примера не в проигрывании видео, а в транспортировке данных, упакованных в сериализованный объект.

  • Попытайтесь осмыслить код примера - в нем много поучительного и полезного (как и в других предыдущих примерах!)

Клиент SerializeClient при запущенном сервере SerializeServer может выглядеть так


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

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

Dmitriy Ivanchenko
Dmitriy Ivanchenko
Украина, Кировоград, Виктория-П, 2011
Татьяна Ковалюк
Татьяна Ковалюк
Украина, Киев, Киевский политехнический институт, 1974