Вычислительные системы, сети и
телекоммуникации
Часть 1

2013
Учебно-методические материалы для лабораторных занятий по курсу
«Вычислительные системы, сети и телекоммуникации»
Содержание
Содержание........................................................................................... 2
Предисловие......................................................................................... 4
Разработка клиент-серверных приложений в. Net........ 6
Работа с потоками данных........................................................ 6
Синхронный ввод/вывод........................................................................... 6
Асинхронный ввод/вывод......................................................................... 7
Основные классы для работы с потоками............................................. 7
Члены класса Stream................................................................................ 8
Работа с объектом FileStream............................................................... 9
Работа с объектом MemoryStream........................................................ 9
Класс CryptoStream................................................................................ 10
Reader-ы и Writer-ы................................................................................ 10
Наиболее важные члены базового класса TextWriter............................ 11
Наиболее важные члены класса TextReader.......................................... 11
Работа с двоичными данными (классы BinaryReader и BinaryWriter) 12
Наиболее важные члены класса BinaryWriter....................................... 12
Наиболее важные члены класса BinaryReader...................................... 13
Пример................................................................................................... 13
Задание для самостоятельной работы............................................... 14
Сериализация.................................................................................... 15
Сериализация и десериализация в формат XML................................. 15
Сериализация с помощью объектов форматирования...................... 18
Лирическое отступление. Графы для отношений объектов.............. 19
Выбираем объект Formatter................................................................. 20
Сериализация в двоичном формате..................................................... 21
Сериализация и десериализация в формате SOAP.............................. 21
Задание для самостоятельной работы............................................... 22
Сетевое программирование в. NET........................................ 23
Сокеты................................................................................................... 23
Порты.................................................................................................... 23
Классы для работы с сокетами в .NET................................................ 24
Класс . Sockets. Socket.............................................................. 25
Создание сервера................................................................................... 26
Создание клиента.................................................................................. 27
Классы TcpListener и TcpClient.............................................................. 27
Класс TcpClient....................................................................................... 28
Задание для самостоятельной работы............................................... 29
Многопоточность........................................................................... 30
Пространство имен System. Threading.................................................. 31
Работа с классом Thread...................................................................... 32
Обычные члены класса Thread............................................................... 32
Пример................................................................................................... 33
Многопотоковый сервер....................................................................... 36
Что изменится на сервере.................................................................... 37
Что изменится на клиенте................................................................... 38
Задание для самостоятельной работы............................................... 38
Потоки на клиентской стороне клиент-серверных приложений....... 39
Проблемы с доступом между потоками............................................. 40
Базы данных в. NET......................................................................... 42
Управляемый провайдер OLE DB......................................................... 42
Наиболее важные типы пространства имен System. Data. OleDb...... 42
Установление соединения при помощи типа OleDbConnection.......... 43
Наиболее часто используемые провайдеры OLE DB:......................... 43
Члены класса OleDbConnection............................................................. 44
Построение команды SQL.................................................................... 44
Члены класса OleDbCommand............................................................... 45
Работа с OleDbDataReader.................................................................. 46
Подключение к базе данных Access........................................................ 46
Некоторые проблемы и способы их решения.................. 47
Литература............................................................................................ 49
Предисловие

Данное учебное пособие разрабатывается для поддержки компьютерных лабораторных занятий и самостоятельной работы студентов по курсу «Вычислительные системы, сети и телекоммуникации».
В рамках данного курса каждый студент разрабатывает отдельный проект, представляющий собой клиент-серверное приложение. Результат работы оценивается в диапазоне от 0 до 50 баллов.
Обязательные требования к проекту (25 баллов):
· наличие серверного модуля в виде консольного или оконного приложения (10 баллов);
· наличие клиентского модуля в виде оконного приложения (10 баллов);
· совместное функционирование двух вышеуказанных модулей, заключающееся в обмене сообщениями между ними (5 баллов).
Обратите внимание, что для допуска к зачету выполнения обязательных требований недостаточно, следует добавить к проекту какие-либо (можно все) функциональные возможности из дополнительного списка.
Дополнительные требования к проекту:
· применение сериализации и десериализации для обмена информацией между клиентом и сервером (5 баллов);
· разработка специального класса (или нескольких классов) для обмена информацией между клиентом и сервером (5 баллов);
· применение многопотоковости на сервере (5 баллов) и/или на клиенте (5 баллов);
· использование базы данных в серверном приложении (5 баллов).
Бонус:
· применение технологий симметричного и/или асимметричного шифрования и/или цифровой подписи при передаче данных от клиента к серверу и/или от сервера клиенту (10 баллов).
Примерные темы проектов:
1. чат (система обмена текстовыми сообщениями) с авторизацией участников и, возможно, шифрованием всех сообщений;
2. система для идентификации сотрудников по личным идентификаторам;
3. система для поиска информации о товаре по штрих-коду;
4. платежный терминал для проведения операций с банковским счётом;
5. корпоративная система обмена сообщениями, в которой не требуется секретности, но обязательно обеспечить целостность сообщений с помощью технологии цифровой подписи.
Следует отметить, что в клиентском приложении предполагается только ввод данных пользователем через текстовые поля и другие элементы интерфейса – флажки, радиокнопки, списки и т. п. Считывание штрих-кодов, отпечатков пальцев и других нестандартных данных требует специальных периферийных устройств и не рассматривается в нашем курсе J.
Предполагается, что читатель:
· успешно изучил язык C# как основу технологии .Net в рамках курса «Объектно-ориентированное программи-рование» и имеет опыт разработки консольных и оконных. Net-приложений;
· успешно изучил курс «Базы данных» и имеет представление о проектировании баз данных, языке SQL и СУБД SQL server.
Наше пособие разбито на две части. Первая часть «Разработка клиент-серверных приложений в.Net» содержит всю необходимую информацию для разработки основы клиент-серверного приложения, а вторая часть «Криптографические возможности» содержит описания технологий и примеры применения криптографических классов в. Net.
Разработка клиент-серверных приложений в. Net
Работа с потоками данных
Поскольку для обмена информацией в клиент-серверных приложениях, разработан-ных на платформе. Net, широко применяются потоки, вспомним, что это такое.
Поток (stream) - это абстрактное представление последовательного устройства, для которого сохранение и считывание данных выполняется побайтно. Базовым устройством для потока может быть, например, файл, принтер или сетевой сокет. Через эту абстракцию можно из одного и того же процесса обращаться к разным устройствам, и аналогичный программный код может использоваться для чтения данных из файлового и сетевого входных потоков. Тем самым программисту не требуется беспокоиться о реальном физическом механизме такого устройства.
Синхронный ввод/вывод
По умолчанию все операции с потоками выполняются синхронно, и это простейший способ для операций ввода/вывода. Недостаток синхронного ввода/вывода состоит в том, что обработка блокируется до завершения операции ввода/вывода и лишь затем приложению разрешается продолжить обработку.
Синхронный ввод/вывод бывает полезен при небольших размерах файлов, но для больших файлов из-за блокирования выполнения текущего потока производительность приложения может оказаться слишком низкой.
Синхронный ввод/вывод не подходит для выполнения операций в сети, где слабо влияние на время, необходимое для завершения операции. Следовательно, синхронный ввод/вывод был бы неудачным выбором для передачи больших потоков через сеть с низкой пропускной способностью или скоростью.
Вводя многопоточную обработку (threading) в синхронные методы, можно имитировать асинхронный ввод/вывод.
Асинхронный ввод/вывод
При асинхронном вводе/выводе до завершения операции ввода/вывода могут выполняться другие задачи. Когда операция ввода/вывода завершается, операционная система уведомляет об этом вызывающую программу.
Следовательно, для асинхронного ввода/вывода требуется отдельный механизм уведомления.
Этот метод полезен, когда одновременно с передачей больших объемов данных из потока приложению требуется продолжать выполнение других задач или работать с медленными устройствами, чья скорость могла бы в противном случае замедлить работу приложения.
При асинхронном вводе/выводе для каждого запроса ввода/вывода создается отдельный поток выполнения, и это может привести к повышению накладных расходов для операционной системы.
Основные классы для работы с потоками
Большинство классов, связанных с вводом-выводом информации, находятся в пространстве имен System. IO.
Все классы, производные от базового класса Stream, предназначены для работы с блоками двоичных данных.

Члены класса Stream
CanRead CanSeek CanWrite | Определяют, будет ли данный поток поддерживать чтение, поиск и (или) запись |
Close() | Закрывает текущий поток и освобождает связанные с ним ресурсы (сокеты, указатели на файлы и т. п.) |
Flush() | Записывает данные из буфера в связанный с потоком источник данных и очищает буфер. Если для данного потока буфер не используется, то этот метод ничего не делает |
Length | Возвращает длину потока в байтах |
Position | Определяет указатель на местонахождение (позицию) в текущем потоке |
Read() ReadByte() | Считывают последовательность байтов (или единственный байт) в текущем потоке и перемещают указатель в потоке на количество считанных байтов |
Seek() | Устанавливает указатель на местонахождение (позицию) в текущем потоке |
SetLength() | Устанавливает длину текущего потока |
Write() WriteByte() | Записывают последовательность байтов (или единственный байт) в текущий поток и перемещают указатель в потоке на количество записанных байтов |
Работа с объектом FileStream
Класс FileStream предназначен для работы с внешними носителями – информация пишется и читается на/с винчестера, дискеты или флеш-накопителя. Рассмотрим пример записи/чтения целых чисел. После создания файла можете открыть его в текстовом редакторе и посмотреть, что получилось. Объясните результат.
// Создаем файл в текущем каталоге
FileStream myFStream = new FileStream("test. dat",
FileMode. OpenOrCreate, FileAccess. ReadWrite);
// Записываем байты в файл *.dat
for (int i = 0; i < 256; i++)
{ myFStream. WriteByte((byte)i); }
// Переставляем внутренний указатель на начало
myFStream. Position = 0;
// Считываем байты из файла *.dat
for (int i = 0; i < 256; i++)
{ Console. Write(myFStream. ReadByte()); }
myFStream. Close();
Работа с объектом MemoryStream
Основой для потоков может служить не только внешнее устройство, но и оперативная память. Разумеется, для долгосрочного хранения информации такой подход не годится.
Обратите внимание, как в этом примере происходит преобразование строки в массив байтов и обратно.
// Создаем пустой поток в памяти
MemoryStream ms = new MemoryStream();
byte[] memData = Encoding. ASCII. GetBytes
("This will go in Memory!");
// Записываем данные в память
ms. Write(memData, 0, memData. Length);
// Устанавливаем указатель на начало
ms. Position=0;
byte[] inData = new byte[100];
// Читаем из памяти и выводим на экран
ms. Read(inData, 0, 100);
Console. WriteLine(Encoding. ASCII. GetString(inData));
// А также переписываем содержимое памяти в файл
Stream strm =new FileStream("Memoutput. txt",
FileMode. OpenOrCreate, FileAccess. Write);
ms. WriteTo(strm);
Класс CryptoStream
В некоторых случаях защита данных при передаче и хранении является очень важным требованием.
Для защиты данных, как правило, применяется их шифрование секретным либо публичным ключом. В зависимости от алгоритма для расшифровки может использоваться тот же секретный ключ, что и для шифрования (при симметричном шифровании), или другой ключ (при асимметричном шифровании).
Платформа. NET предоставляет класс CryptoStream, связывающий потоки с криптографическими преобразованиями. Хотя CryptoStream не принадлежит на самом деле пространству имен System. IO, он все же порожден от класса Stream.
Класс CryptoStream может использоваться для выполнения криптографических операций на объекте Stream.
В нашем распоряжении имеются самые разные криптографические преобразования - можно использовать любой провайдер службы криптографии, реализующий интерфейс ICryptoTransform.
Reader-ы и Writer-ы
Классы StreamReader и StreamWriter пригодятся нам в тех ситуациях, когда необходимо считать или записать символьные данные (данные в формате string). По умолчанию оба этих типа работают с кодировкой Unicode. Классы StringReader и StringWriter выполняют те же функции, но запись и чтение ведётся в символьную строку (в оперативной памяти).

Наиболее важные члены базового класса TextWriter
Close() | Закрывает соответствующий объект Writer и освобождает связанные с ним ресурсы. Если в процессе записи используется буфер, он будет автоматически очищен |
Flush() | Очищает все буферы для текущего объекта Writer и записывает накопленные в них данные в место постоянного их хранения, но при этом сам объект Writer не закрывается |
NewLine | Используется для определения последовательности символов, означающих начало новой строки. По умолчанию используется последовательность “возврат каретки” — “перевод строки” (\r\n) |
Write() | Записывает новый отрезок текста в поток без применения последовательности начала новой строки |
WriteLine() | Записывает новую строку в поток (с применением последовательности начала новой строки) |
Наиболее важные члены класса TextReader
Peek() | Возвращает следующий символ, не изменяя позицию указателя в файле |
Read() | Считывает данные из потока на входе |
ReadBlock() | Считывает указанное пользователем количество символов, начиная с определенной позиции, и записывает считанные данные в буфер |
ReadLine() | Считывает строку данных из текущего потока и возвращает ее как значение типа string. Пустая строка (null string) означает конец файла (EOF) |
ReadToEnd() | Считывает все символы, начиная с текущей позиции и до конца потока, и возвращает считанные данные как единое значение типа string |
В следующем примере мы записываем в файл строки, введенные с консоли, а затем читаем содержимое файла и выводим на экран. Обратите внимание, что открытие файла происходит с помощью статического метода класса File.
string mythought;
// создаем файл StreamWriter и заполняем его умными мыслями c консоли:
StreamWriter sw=File. CreateText("Thoughts. txt");
while ((mythought = Console. ReadLine()) != "End")
{
sw. WriteLine(mythought);
}
sw. Close();
// выводим информацию из файла на консоль при помощи
// StreamReader
Console. WriteLine("Here are your thoughts:\n");
StreamReader sr = File. OpenText("Thoughts. txt");
string input = null;
while ((input = sr. ReadLine()) !=null)
{
Console. WriteLine(input);
}
sr. Close();
Работа с двоичными данными (классы
BinaryReader и BinaryWriter)

Эти классы позволяют считывать и записывать определенные двоичные типы данных в поток. Класс BinaryWriter определяет многократно перегруженный метод Write() для помещения в поток объектов самых разных типов данных.
Наиболее важные члены класса BinaryWriter
BaseStream | Представляет поток, с которым работает объект BinaryWriter |
Close() | Закрывает поток |
Flush() | Очищает буфер |
Seek() | Устанавливает позицию в текущем потоке |
Write() | Записывает значение в текущий поток |
Наиболее важные члены класса BinaryReader
BaseStream | Представляет поток, с которым работает объект BinaryReader |
Close() | Закрывает объект BinaryReader |
PeekChar() | Возвращает следующий символ без перемещения внутреннего указателя в потоке |
Read() | Считывает поток байтов или символов и сохраняет в массиве (передаваемом как входящий параметр) |
ReadXXXX() | Считывает данные определенного типа из потока (например, ReadBoolean(), ReadByte(), Readlnt32() и т. д.) |
Пример
Console. WriteLine("Creating a file and writing binary data...");
FileStream myFStream = new FileStream("temp. dat",
FileMode. OpenOrCreate,
FileAccess. ReadWrite);
// Записываем двоичные данные
BinaryWriter binWrit = new BinaryWriter(myFStream);
binWrit. Write("Hello as binary info...");
int myInt = 99;
float myFloat = 9984.82343F;
bool myBool = false;
char[] myCharArray = {'H', 'e', 'l', 'l', 'o'};
binWrit. Write(myInt);
binWrit. Write(myFloat);
binWrit. Write(myBool);
binWrit. Write(myCharArray);
// Устанавливаем внутренний указатель на начало
binWrit. BaseStream. Position = 0;
// Считываем двоичную информацию как поток байтов
Console. WriteLine("Reading binary data...");
BinaryReader binRead = new BinaryReader(myFStream);
int temp = 0;
while(binRead. PeekChar()!=-1)
{
Console. Write(binRead. ReadByte());
temp = temp + 1;
if(temp == 5)
{ // Добавляем пустую строку через каждые 5 байтов
temp = 0;
Console. WriteLine();
}
}
// Все закрываем
binWrit. Close();
binRead. Close();
myFStream. Close();
Console. ReadKey();
Задание для самостоятельной работы
Пусть текстовый файл содержит пары «логин-пароль». Напишите программу, которая по заданному логину находит и печатает пароль или сообщение о том, что логин не найден.
Сериализация
Для передачи информации между сервером и клиентом очень удобно применять механизм сериализации.
Сериализацией (serialization) обычно называют процесс преобразования объекта в линейную последовательность байтов. Обратное действие, при котором из потока байтов объект восстанавливается в исходном виде, называется десериализацией (deserialization).
Сериализация имеет следующие «плюсы»:
· Доступность. Объект можно сохранить в файле и обращаться к нему в любое время.
· Время жизни. Сохранение состояния объекта в файл продлевает ему жизнь. Обычно объекты просто хранятся в оперативной памяти компьютера и автоматически уничтожаются по окончании работы программы.
· Использование в сетевых приложениях. Объекты сложной формы преобразуются в формат, который легко передается через сеть.
· Надежность. Сохраненный объект можно воссоздать в его первоначальной форме.
Сериализация и десериализация в формат XML
Для сериализации объектов часто используется формат XML. Этот формат удобен как для автоматической передачи информации между программами, так и для чтения человеком. Сериализованные объекты в виде текста можно переслать по сети или сохранить на диск для дальнейшего использования.
Класс XmlSerializer содержится в пространстве имен System. Xml. Serialization.
Для того чтобы объект некоторого класса можно было сериализовать с использованием класса XmlSerializer, нужно выполнить следующие требования:
· Класс, подлежащий сериализации, должен содержать используемый по умолчанию открытый конструктор без параметров. Это условие возникло потому, что при восстановлении объекта в процессе десериализации сначала объект создается конструктором по умолчанию, а затем из входного потока данных читаются открытые свойства. Если конструктор по умолчанию отсутствует, .NET Framework не будет знать, как создать объект.
· При сериализации сохраняются только открытые свойства, поддерживающие операции get и set, и открытые члены данных. Это объясняется тем, что процесс сериализации не может обращаться к закрытым и доступным только для чтения элементам данных.
Предположим, у нас есть класс Message.
public class Message
{
public string from;
public string to;
public string text;
public int priority;
public Message() {}
public Message (string txt_from, string txt_to,
string txt_text, int num_priority)
{
from = txt_from;
to = txt_to;
text = txt_text;
priority=num_priority;
}
}
Создадим объект этого класса и сериализуем его.
. . .
Message mes1 = new Message
("Юстас", "Алекс", "Вторник. Новостей нет.", 1);
// создаем поток для записи
StreamWriter writer = new StreamWriter("Message. xml");
// создаем сериализатор
XmlSerializer serializer = new XmlSerializer(typeof(Message));
// и сериализуем объект
serializer. Serialize(writer, mes1);
writer. Close();
. . .
Получим файл Message.xml со следующим содержимым:
<?xml version="1.0" encoding="utf-8"?>
<Message xmlns:xsi="http://www. w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www. w3.org/2001/XMLSchema">
<from>Юстас</from>
<to>Алекс</to>
<text>Вторник. Новостей нет.</text>
<priority>1</priority>
</Message>
Теперь выполним обратное действие:
. . .
// создаем поток для чтения
FileStream reader= new FileStream("Message. xml", FileMode. Open,
FileAccess. Read);
// создаем десериализатор
XmlSerializer deserializer = new XmlSerializer(typeof(Message));
// и десериализуем объект
Message mes2 = (Message)deserializer. Deserialize(reader);
|
Из за большого объема этот материал размещен на нескольких страницах:
1 2 3 |


