|
При выполнении в лабораторной работе упражнения №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" или один из зависимых от них компонентов. Не удается найти указанный файл. Делаю все пунктуально. В чем может быть проблема? |
Работа с потоками данных
Пример 4. Создание сервера БД как Console Application
Хоть консольное окно и будет считаться пользовательским интерфейсом, но для простоты мы его никак не станем задействовать.
-
Добавьте к решению командой File/Add/New Project консольное приложение с именем ConsoleServerDB
-
В панели Solution Explorer в корень нового проекта скопируйте из предыдущего проекта PicturesServerDB файл App.config и папку Data. На предложение мастера создать типизированный DataSet скажите Cancel
-
В панели Solution Explorer через контекстное меню для узла References добавьте к проекту ссылку на System.Configuration.dll
-
Откройте файл Program.cs проекта консольного приложения и наполните его следующим кодом (приводится полностью)
using System;
using System.Collections.Generic;
using System.Text;
// Дополнительные пространства имен для ADO.NET
using System.Data;
using System.Data.OleDb;
using System.Data.Common;
// Дополнительные пространства имен
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Collections;
using System.Configuration;
namespace ConsoleServerDB
{
class Program
{
int port = 12000;
String hostName = "127.0.0.1"; // local
IPAddress localAddr;
TcpListener server = null; // Ссылка на сервер
String separator = "#"; // Разделитель имен в строке ответа
String connectionString; // Строка соединения с БД
// Добавили конструктор
public Program()
{
// Извлекаем в поле строку соединения с БД из файла App.config
connectionString = System.Configuration.ConfigurationManager.
ConnectionStrings["PicturesDB"].ConnectionString;
// Конвертируем 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();// Держим основной поток (нить) приложения
}
private void ExecuteLoop()
{
try
{
server = new TcpListener(localAddr, port);// Создаем сервер-слушатель
server.Start();// Запускаем сервер
// Бесконечный цикл прослушивания клиентов
while (true)
{
// Проверяем очередь соединений
if (!server.Pending())// Очередь запросов пуста
continue;
TcpClient client = server.AcceptTcpClient();// Текущий клиент
// Создаем потоки сетевых соединений
StreamReader readerStream = new StreamReader(client.GetStream());
NetworkStream streamOut = client.GetStream();
StreamWriter writerStream = new StreamWriter(streamOut);
// Читаем команду клиента
String receiverData = readerStream.ReadLine();
// Распознаем и исполняем
switch (receiverData)
{
case "!!!GetNames!!!":// Посылаем имена, разделенные сепаратором
String names = GetNames();
writerStream.WriteLine(names); // Используем через оболочку
writerStream.Flush();
break;
default:// Посылаем рисунок
Byte[] bytes = GetPicture(receiverData);
streamOut.Write(bytes, 0, bytes.Length);// Используем напрямую
streamOut.Flush();
break;
}
// Закрываем соединение и потоки, порядок неважен
client.Close();
readerStream.Close();
writerStream.Close();
}
}
finally
{
// Останавливаем сервер
server.Stop();
}
}
// Извлечение из БД имен рисунков и упаковка их в одну строку для пересылки клиенту
string GetNames()
{
// Создаем и настраиваем инфраструктуру ADO.NET
OleDbConnection conn = new OleDbConnection(connectionString);
OleDbCommand cmd = new OleDbCommand("SELECT FileName FROM MyTable");
cmd.Connection = conn;
conn.Open();
// Извлекаем имена рисунков
OleDbDataReader reader = cmd.ExecuteReader(CommandBehavior.CloseConnection);
// Формируем строку исходящих данных
StringBuilder sb = new StringBuilder();
foreach (DbDataRecord record in reader)// Равносильно чтению reader.Read()
sb.Append(((string)record["FileName"]).Trim() + separator);
// Соединение здесь закроет сам объект DataReader после прочтения всех данных
// в соответствии с соглашением при его создании CommandBehavior.CloseConnection
// Удаляем лишний последний символ сепаратора
sb.Replace(separator, String.Empty, sb.ToString().
LastIndexOf(separator), separator.Length);
return sb.ToString();
}
// Извлечение из БД самого рисунка для отправки клиенту
byte[] GetPicture(String name)
{
// Создаем и настраиваем инфраструктуру ADO.NET
OleDbConnection conn = new OleDbConnection();
conn.ConnectionString = connectionString;
// Создаем и настраиваем объект команды, параметризованной по имени рисунка
OleDbCommand cmd = new OleDbCommand();
cmd.Connection = conn;
cmd.CommandType = CommandType.Text; // Необязательно! Установлено по умолчанию
cmd.CommandText = "SELECT Picture FROM MyTable WHERE FileName=?";
cmd.Parameters.Add(new OleDbParameter());
cmd.Parameters[0].Value = name;// Имя рисунка
OleDbDataAdapter adapter = new OleDbDataAdapter(cmd);
// Извлекаем рисунок из БД
DataTable table = new DataTable();
adapter.Fill(table);
byte[] bytes = (byte[])table.Rows[0]["Picture"]; // Подключаемся к рисунку
return bytes;
}
}
}Приведенный код в точности соответствует серверу, созданному нами ранее как сервис, только дополнительно добавилась точка входа Main() и класс стал называться по другому.
-
Откомпилируйте проект командой Build/Build Solution
-
Через Проводник Windows зайдите в каталоги bin/Debug проектов ConsoleServerDB и PicturesClientDB
-
Для удобства запуска создайте для исполнимых сборок ConsoleServerDB.exe и PicturesClientDB.exe ярлыки на рабочем столе компьютера, попробуйте запустить несколько копий каждого из приложений
Мы видим, что копии клиентских приложений запускаются нормально, а для консольного сервера дублирующие копии вылетают с выбросом исключения операционной системы. А если запустить ранее созданный сервисный сервер (в Службах Windows ), то консольный сервер будет вылетать даже при первом запуске.
-
Поэкспериментируйте с построенными приложениями, осмыслите происходящее и разберитесь с кодом
Пример 5. Создание сервера БД как Windows Forms
Чтобы показать, что сервером может выступать приложение любого типа, повторим предыдущую задачу на приложении с оконным интерфейсом. Учитывая, что в сети прослушивать клиентов по одному и тому же адресу может только один запущенный экземпляр сервера, заодно покажем, как блокировать повторный его запуск. Нового здесь будет мало, только создадим оконную заготовку приложения Windows Forms и просто скопируем код сервера в это приложение, изменив самую малость. А потом еще добавим в файл Program.cs код блокирования повторного запуска, - вот и все.
Создание оконного сервера
-
Добавьте к проекту командой File/Add/New Project оболочки новый проект с именем WindowServerDB (стартовым его не делайте)
-
В панели Solution Explorer вызовите для узла WindowServerDB контекстное меню и выполните команду Build
-
В Проводнике Windows зайдите в каталог исполнимой сборки проекта bin\Debug и отправьте ярлык исполнимой сборки WindowServerDB.exe на рабочий стол контекстной командой Отправить
-
Заполните файл Form1.cs следующим кодом (приводится полностью и почти в точности повторяет код файла Program.cs проекта ConsoleServerDB )
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
// Дополнительные пространства имен для ADO.NET
using System.Data.OleDb;
using System.Data.Common;
// Дополнительные пространства имен
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Collections;
using System.Configuration;
namespace WindowServerDB
{
public partial class Form1 : Form
{
int port = 12000;
String hostName = "127.0.0.1"; // local
IPAddress localAddr;
TcpListener server = null; // Ссылка на сервер
String separator = "#"; // Разделитель имен в строке ответа
String connectionString; // Строка соединения с БД
public Form1()
{
InitializeComponent();
// Извлекаем в поле строку соединения с БД из файла App.config
connectionString = System.Configuration.ConfigurationManager.
ConnectionStrings["PicturesDB"].ConnectionString;
// Конвертируем IP в другой формат
localAddr = IPAddress.Parse(hostName);
// Запускаем в новом потоке (ните)
Thread thread = new Thread(ExecuteLoop);
thread.IsBackground = true;
thread.Start();
}
private void ExecuteLoop()
{
try
{
server = new TcpListener(localAddr, port);// Создаем сервер-слушатель
server.Start();// Запускаем сервер
// Бесконечный цикл прослушивания клиентов
while (true)
{
// Проверяем очередь соединений
if (!server.Pending())// Очередь запросов пуста
continue;
TcpClient client = server.AcceptTcpClient();// Текущий клиент
// Создаем потоки сетевых соединений
StreamReader readerStream = new StreamReader(client.GetStream());
NetworkStream streamOut = client.GetStream();
StreamWriter writerStream = new StreamWriter(streamOut);
// Читаем команду клиента
String receiverData = readerStream.ReadLine();
// Распознаем и исполняем
switch (receiverData)
{
case "!!!GetNames!!!":// Посылаем имена, разделенные сепаратором
String names = GetNames();
writerStream.WriteLine(names); // Используем через оболочку
writerStream.Flush();
break;
default:// Посылаем рисунок
Byte[] bytes = GetPicture(receiverData);
streamOut.Write(bytes, 0, bytes.Length);// Используем напрямую
streamOut.Flush();
break;
}
// Закрываем соединение и потоки, порядок неважен
client.Close();
readerStream.Close();
writerStream.Close();
}
}
finally
{
// Останавливаем сервер
server.Stop();
}
}
// Извлечение из БД имен рисунков и упаковка их в одну строку для пересылки клиенту
string GetNames()
{
// Создаем и настраиваем инфраструктуру ADO.NET
OleDbConnection conn = new OleDbConnection(connectionString);
OleDbCommand cmd = new OleDbCommand("SELECT FileName FROM MyTable");
cmd.Connection = conn;
conn.Open();
// Извлекаем имена рисунков
OleDbDataReader reader = cmd.ExecuteReader(CommandBehavior.CloseConnection);
// Формируем строку исходящих данных
StringBuilder sb = new StringBuilder();
foreach (DbDataRecord record in reader)// Равносильно чтению reader.Read()
sb.Append(((string)record["FileName"]).Trim() + separator);
// Соединение здесь закроет сам объект DataReader после прочтения всех данных
// в соответствии с соглашением при его создании CommandBehavior.CloseConnection
// Удаляем лишний последний символ сепаратора
sb.Replace(separator, String.Empty, sb.ToString().
LastIndexOf(separator), separator.Length);
return sb.ToString();
}
// Извлечение из БД самого рисунка для отправки клиенту
byte[] GetPicture(String name)
{
// Создаем и настраиваем инфраструктуру ADO.NET
OleDbConnection conn = new OleDbConnection();
conn.ConnectionString = connectionString;
// Создаем и настраиваем объект команды, параметризованной по имени рисунка
OleDbCommand cmd = new OleDbCommand();
cmd.Connection = conn;
cmd.CommandType = CommandType.Text; // Необязательно! Установлено по умолчанию
cmd.CommandText = "SELECT Picture FROM MyTable WHERE FileName=?";
cmd.Parameters.Add(new OleDbParameter());
cmd.Parameters[0].Value = name;// Имя рисунка
OleDbDataAdapter adapter = new OleDbDataAdapter(cmd);
// Извлекаем рисунок из БД
DataTable table = new DataTable();
adapter.Fill(table);
byte[] bytes = (byte[])table.Rows[0]["Picture"]; // Подключаемся к рисунку
return bytes;
}
}
}-
В панели Solution Explorer раскройте узел проекта ConsoleServerDB и, удерживая клавишу Ctrl, выделите папку Data и файл App.config, затем перетащите их курсором в корень проекта WindowServerDB. На предложение мастера создать набор данных скажите Cancel
-
Убедитесь, чтобы скопированная база данных Data/Pictures.my2.mdb имела в панели Properties значение:- Copy to Output Directory=Copy if newer
-
В панели Solution Explorer выделите любой элемент проекта WindowServerDB (например, корень), откройте через меню оболочки панель View/Object Browser, задайте в ней режим Browse: All Components, а в поле поиска - пространство имен System.Configuration.
Для найденного пространства имен выделите родительскую сборку System.configuration.dll и кнопкой с пиктограммой Add to References in Selected Project in Solution Explorer добавьте ссылку в папку References текущего проекта (таким способом можно добавлять сразу несколько ссылок) для видимости компилятором класса ConfigurationManager
-
Откомпилируйте проект командой Rebuild и запустите этот оконный сервер через ярлык рабочего стола компьютера - сервер хоть и с пустым окном, но работает -
Проверьте работу сервера WindowServerDB.exe совместно с клиентской сборкой PicturesClientDB.exe - все должно работать исправно!
Блокирование дублирующего запуска оконного сервера
Не очень хорошо, когда пользователь случайно попытается запустить второй экземпляр оконного сервера и получит системное исключение (попробуйте!). Хоть это ни к чему страшному не приведет, но может вызвать замешательство (или даже инфаркт с миокардом и кариесом) у бедного пользователя, поэтому пощадим миокард и заблокируем эту возможность.
Не всегда у управляемой среды исполнения CLR хватает своих средств для решения любых задач и в некоторых случаях приходится напрямую обращаться к библиотекам и функциям операционной системы. Для таких целей предусмотрен атрибут DllImport, с помощью которого можно вызвать любую функцию API Windows. Это относится и к нашей задаче - когда требуется блокировать повторный запуск приложения, то можно воспользоваться таблицей глобальных атомов самой операционной системы. А к ней из управляемого кода C# доступ невозможен.
-
Измените код файла Program.cs проекта WindowServerDB на следующий (приводится полностью)
using System;
using System.Collections.Generic;
using System.Windows.Forms;
// Дополнительное пространство имен импорта типов API
using System.Runtime.InteropServices;
namespace WindowServerDB
{
static class Program
{
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern ushort GlobalAddAtom(string lpString);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern ushort GlobalFindAtom(string lpString);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern ushort GlobalDeleteAtom(ushort atom);
[STAThread]
static void Main()
{
string strAtom = Application.ProductName
+ Application.ProductVersion;
ushort atom = GlobalFindAtom(strAtom);
if (atom != 0)
{
MessageBox.Show("Прежде чем создать новый\n"
+ "экземпляр этого приложения,\n"
+ "сначала следует закрыть текущий!");
}
else
{
// Атом не найден в таблице атомов, создаем его
atom = GlobalAddAtom(strAtom);
// Настройка приложения
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
try
{
// Рабочий цикл Windows
Application.Run(new Form1());
}
finally
{
// Удаляем атом в любом случае
GlobalDeleteAtom(atom);
}
}
}
}
}Вопросы взаимодействия CLR с операционной системой, потоки выполнения Thread, атрибуты и многое другое прекрасно описаны в:
Дубовцев А.В. Microsoft .NET в подлиннике. - СПб.: БХВ-Петербург, 2004. - 704 с.
-
Запустите оконный сервер WindowServerDB и убедитесь, что дублирующий запуск приложения теперь заблокирован
Сокрытие окна сервера
Сейчас нам стыдно смотреть на окно сервера - оно просто пустое. В дальнейшем мы можем его задействовать. Например, для регистрации администратором клиентов, редактирования настроек сервера и другими задачами, связанными с нашим приложением. А пока для тренировки его отображение на экране можно просто отключить: оно есть и работает, просто его нигде не видно (почти нигде). Для этого воспользуемся событием Shown объекта окна.
-
Дополните класс формы в файле Form1.cs следующим кодом
namespace WindowServerDB
{
public partial class Form1 : Form
{
................................................
public Form1()
{
................................................
// Подписываемся на событие для сокрытия окна
this.Shown += Form1_Shown;
}
void Form1_Shown(object sender, EventArgs e)
{
this.Hide();
}
................................................
}
}-
Откомпилируйте сервер WindowServerDB и запустите через ярлыки его и клиента PicturesClientDB - все работает, но сам сервер невидно и как его остановить! -
Вызовите жестом Ctrl-Alt-Del Диспетчер задач Windows, откройте вкладку Процессы - сервер работает и его можно даже закрыть
Замечания
Если сейчас закрыть невидимый сервер через Диспетчер задач Windows, то он повторно не запустится - сработает наша блокировка. Причина в том, что в данном случае приложение завершится нестандартно, а именно: не через среду CLR, а напрямую через Диспетчер задач Windows. Соответственно, и атом (наша метка блокировки) в таблице глобальных атомов останется. Он будет удален только при перезагрузке Windows. Здесь нужно выбирать что-то одно: либо оставить блокировку и не скрывать сервер, либо убрать блокировку и сделать сервер невидимым для пользователя.
Теперь мы знаем, что необязательно делать сервер как Службу Windows ( Windows Service ), чтобы скрыть его от пользователя. Но можно сделать сервер как приложение Windows Forms, пусть даже с пустой формой, скрыть его и включить в список программ, автоматически запускаемых при загрузке Windows. Это уже дело вкуса - наша задача состояла в том, чтобы проверить такую возможность.
Кстати, консольное приложение не имеет события, подобного событию Shown объекта окна. Оно показывается самой CLR при запуске и скрыть его программно невозможно, хотя мы его и применяли при создании сервера.
Есть еще один вариант: установить состояние окна при запуске свернутым, тогда его можно завершить через среду CLR естественным образом.
-
Внесите в конструктор формы следующие очевидные изменения
namespace WindowServerDB
{
public partial class Form1 : Form
{
................................................
public Form1()
{
................................................
// Подписываемся на событие для сокрытия окна
//this.Shown += Form1_Shown;
// Минимизируем окно при запуске
this.WindowState = FormWindowState.Minimized;
}
void Form1_Shown(object sender, EventArgs e)
{
this.Hide();
}
................................................
}
}-
Запустите сервер - теперь и блокировка работает, и сервер можно закрыть, и миокард целый!


