Console. Write("Сообщение от: "+mes2.from);

Console. WriteLine(" к: " + mes2.to);

Console. WriteLine("текст: " + mes2.text);

Console. WriteLine("приоритет: " + mes2.priority);

. . .

Усложним структуру класса Message:

public class Message

{

public Person from;

public Person to;

public String text;

public int priority;

public Message() {}

public Message(Person person_from, Person person_to,

string txt_text, int num_priority)

{

from = person_from;

to = person_to;

text = txt_text;

priority=num_priority;

}

}

public class Person

{

public string name;

public string position;

public Person() { }

public Person(string txt_name, string txt_position)

{

name = txt_name;

position = txt_position;

}

}

Сериализованный объект будет выглядеть примерно следующим образом:

<?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>

<name>Юстас</name>

<position>шпион</position>

</from>

<to>

<name>Алекс</name>

<position>разведчик</position>

</to>

<text>Вторник. Новостей нет.</text>

<priority>1</priority>

</Message>

Сериализация с помощью объектов форматирования

Для сериализации объектов сложной структуры (например, содержащих вложенные объекты, в том числе и других классов) удобно применять так называемые объекты форматирования, или форматтеры. При использовании этой технологии каждый класс, который будет участвовать в сериализации, должен обладать атрибутом [Serializable]. Те элементы объекта, которые сериализовать не нужно, следует пометить атрибутом [NonSerialized].

НЕ нашли? Не то? Что вы ищете?

// Классы Message и Person могут быть сериализованы

[Serializable]

public class Person

{

public string name;

public string position;

public Person(string txt_name, string txt_position)

{

name = txt_name;

position = txt_position;

}

}

[Serializable]

public class Message

{

public Person from;

public Person to;

public String text;

public int priority;

[NonSerialized]

public string color;

public Message(Person person_from, Person person_to,

string txt_text, int num_priority, string txt_color)

{

from = person_from;

to = person_to;

text = txt_text;

priority=num_priority;

color = txt_color;

}

}

Лирическое отступление. Графы для отношений объектов

Службы сериализации в .NET — это довольно сложные программные модули. С их помощью выполняются многие неочевидные действия: когда объект сериализуется в поток, информация обо всех других объектах, на которые он так или иначе ссылается, также должна сериализоваться. Например, когда сериализуется объект производного класса, ссылки на другие классы, которые есть в базовых классах для этого производного класса, также должны отслеживаться и сохраняться.

Набор взаимосвязанных объектов, сериализованных в поток байтов, называется графом объектов (object graph). Графы позволяют фиксировать отношения объектов друг к другу, и они, вообще говоря, не соответствуют классическим моделям отношений классов в объектно-ориентированном программировании.

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

Выбираем объект Formatter

Пространство имен *****ntime. Serialization. Formatters включает в себя еще два пространства имен - *.Binary и *.Soap, каждому из которых соответствует один из двух классов Formatter.

·  Класс ВinаryFormatter сериализует объектный граф в компактном потоке двоичного формата,

·  а класс SoapFormatter представляет граф как текстовое сообщение протокола SOAP (Simple Object Access Protocol — это простой протокол доступа к объектам) в формате XML.

Класс BinaryFormatter находится в базовой библиотеке mscorlib. dll, поэтому единственное, что нам потребуется для сериализации при помощи такого объекта — указать использование этого пространства имен:

// Для сериализации объектов в двоичном формате

using *****ntime. Serialization. Formatters. Binary;

Класс SoapFormatter находится в отдельной сборке, поэтому для сохранения объекта в формате SOAP нам потребуется добавить ссылку на сборку *****ntime. Serializaton. Formatters. Soap.dll (в пункте Add Reference текущего проекта), а затем использовать аналогичную команду:

// Для сериализации объектов в формате SOAP

using *****ntime. Serialization. Formatters. Soap;

Сериализация в двоичном формате

using *****ntime. Serialization. Formatters. Binary;

. . .

Message mes1 = new Message(

new Person("Юстас","шпион"),

new Person("Алекс","разведчик"),

"Вторник. Новостей нет.", 1, "red");

// Создаем поток для записи

FileStream myStream = File. Create("Message. dat");

// Создаем форматтер

BinaryFormatter myBinaryFormat = new BinaryFormatter();

// Помещаем объектный граф в поток в двоичном формате

myBinaryFormat. Serialize(myStream, mes1);

// Закрываем поток

myStream. Close();

Сериализация и десериализация в формате SOAP

using *****ntime. Serialization. Formatters. Soap;

. . .

Message mes1 = new Message(

new Person("Юстас","шпион"),

new Person("Алекс","разведчик"),

"Вторник. Новостей нет.", 1, "red");

// Создаем поток для записи

FileStream myStream = File. Create("Message2.xml");

// Создаем форматтер

SoapFormatter myXMLFormat = new SoapFormatter();

// Помещаем объектный граф в поток в SOAP формате

myXMLFormat. Serialize(myStream, mes1);

// Закрываем поток

myStream. Close();

. . .

// Восстанавливаем объект из файла SOAP

myStream = File. OpenRead("Message2.xml");

Message mes2 = (Message)myXMLFormat. Deserialize(myStream);

Console. Write("Сообщение от: " + mes2.from. name +"," +mes2.from. position);

Console. WriteLine(" к: " + mes2.to. name + "," + mes2.to. position);

Console. WriteLine("текст: " + mes2.text);

Console. WriteLine("приоритет: " + mes2.priority);

myStream. Close();

. . .

В результате получим такой файл:

<SOAP-ENV:Envelope xmlns:xsi="http://www. w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www. w3.org/2001/XMLSchema" xmlns:SOAP-ENC="http://schemas. xmlsoap. org/soap/encoding/" xmlns:SOAP-ENV="http://schemas. xmlsoap. org/soap/envelope/" xmlns:clr="http://schemas. /soap/encoding/clr/1.0" SOAP-ENV:encodingStyle="http://schemas. xmlsoap. org/soap/encoding/">

<SOAP-ENV:Body>

<a1:Message id="ref-1" xmlns:a1="http://schemas. /clr/nsassem/ConsoleApplication3/ConsoleApplication3%2C%20Version%3D1.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">

<from href="#ref-3"/>

<to href="#ref-4"/>

<text id="ref-5">Вторник. Новостей нет.</text>

<priority>1</priority>

</a1:Message>

<a1:Person id="ref-3" xmlns:a1="http://schemas. /clr/nsassem/ConsoleApplication3/ConsoleApplication3%2C%20Version%3D1.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">

<name id="ref-6">Юстас</name>

<position id="ref-7">шпион</position>

</a1:Person>

<a1:Person id="ref-4" xmlns:a1="http://schemas. /clr/nsassem/ConsoleApplication3/ConsoleApplication3%2C%20Version%3D1.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">

<name id="ref-8">Алекс</name>

<position id="ref-9">разведчик</position>

</a1:Person>

</SOAP-ENV:Body>

</SOAP-ENV:Envelope>

Задание для самостоятельной работы

Напишите программу, которая сериализует и десериализует произвольный объект в файл. Используйте обычный формат XML и формат SOAP. Просмотрите полученные XML-файлы в любом текстовом редакторе или браузере.

Сетевое программирование в. NET

Наконец, мы приступаем к раcсмотрению базовых технологий для разработки клиент-серверных приложений.

Сокеты

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

Существуют два основных типа сокетов - потоковые сокеты и дейтаграммные.

Потоковый сокет – это сокет с установленным соединением, состоящий из потока байтов, который может быть двунаправленным, т. е. через эту конечную точку приложение может и передавать, и получать данные. Потоковый сокет гарантирует исправление ошибок, обрабатывает доставку и сохраняет последовательность данных. На него можно положиться в доставке упорядоченных, недублированных данных.

Потоки базируются на явных соединениях: сокет А запрашивает соединение с сокетом В, а сокет В либо соглашается с запросом на установление соединения, либо отвергает его.

Порты

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

Сокет состоит из IP-адреса машины и номера порта, используемого приложением ТСР. Поскольку IP-адрес уникален в Интернете, а номера портов уникальны на отдельной машине, номера сокетов также уникальны во всем Интернете.

Эта характеристика позволяет процессу общаться через сеть с другим процессом исключительно на основании номера сокета.

За определенными службами номера портов зарезервированы (80 - Apache, 3306 - mySQL, 8080 и т. п.)

Классы для работы с сокетами в. NET

MulticastOption

Устанавливает значение IP aдpeca для присоединения к IР-rруппе или для выхода из нее.

TcpClient

строится на классе Socket, чтобы обеспечить ТСР обслуживание на более высоком уровне. TcpClient предоставляет несколько методов для отправки и получения данных через сеть.

TcpListener

также построен на низкоуровневом классе Socket. Ero основное назначение - серверные приложения. Он ожидает входящие запросы на соединения от клиентов и уведомляет приложение о любых соединениях.

NetworkStream

реализует базовый класс потока, из котоpoгo данные отправляются и в котором они получаются. Это абстракция высокого уровня, представляющая соединение с каналом связи TCP/IP.

UdpClient

UDP - это протокол, не организующий соединение, следовательно, для реализации UDР-обслуживания в. NEТ требуется друrая функциональность. Класс UdpClient предназначен для реализации UDР обслуживания.

SocketException

это исключение порождается, когда в сокете возникает ошибка.

Socket

обеспечивает базовую функциональность приложения сокета.

Класс . Sockets. Socket

Свойство

Описание

AddressFamlly

Возвращает семейство адресов сокета - значение из перечисления Socket. AddressFamily.

Available

Возвращает объем доступных для чтения данных.

Blocking

Возвращает или устанавливает значение, показывающее, находится ли сокет в блокирующем режиме.

Connected

Возвращает значение, информирующее, соединен ли сокет с удаленным хостом.

LocalEndPoint

Возвращает локальную конечную точку

ProtocolType

Возвращает тип протокола сокета.

RemoteEndPoint

Возвращает удаленную конечную точку сокета.

SocketType

Возвращает тип сокета

Метод

Описание

Accept()

Создает новый сокет для обработки входящего запроса на соединение

Bind()

Связывает сокет с локальной конечной точкой для ожидания входящих запросов на соединение

Close()

Закрывает сокет

Connect()

Устанавливает соединение с удаленным хостом

Listen()

Помещает сокет в режим прослушивания

Receive()

Получает данные от соединенного сокета

Poll()

Определяет статус сокета

Send()

Отправляет данные соединенному сокету

ShutDown()

Запрещает операции отправки и получения данных на сокете

Создание сервера

Не забудем подключить . Sockets и !

Для корректной пересылки русских букв или символов других национальных кодировок при преобразовании строки в массив байтов (и обратно) удобно использовать класс Encoding. UTF8.

// создаем конечную точку подключения

IPHostEntry ipHost = Dns. GetHostEntry("localhost");

IPAddress ipAddr = ipHost. AddressList[0];

// В новых версиях Framework следует использовать

// IPAddress ipAddr = ipHost. AddressList[1];

IPEndPoint ipEndPoint = new IPEndPoint(ipAddr, 11000);

// создаем сокет

Socket sListener = new Socket(AddressFamily. InterNetwork,

SocketType. Stream, ProtocolType. Tcp);

// и связываем его с точкой подключения

sListener. Bind(ipEndPoint);

// запускаем прослушивание

sListener. Listen(10);

while (true)

{

Console. WriteLine("Start new client");

Socket handler = sListener.Accept();

// подключился клиент

string data = null;

byte[] bytes = new byte[1024];

int bytesRec = handler.Receive(bytes);

// принимаем от него сообщение

data = Encoding. UTF8.GetString(bytes, 0, bytesRec);

Console. WriteLine(data);

string theReply = "Спасибо!";

byte[] msg = Encoding. UTF8.GetBytes(theReply);

handler.Send(msg);

// и отправляем ответ

handler.Shutdown(SocketShutdown. Both);

// завершаем сеанс

handler. Close();

}

Создание клиента

byte[] bytes = new byte[1024];

// таким же образом создаем конечную точку подключения

IPHostEntry ipHost = Dns. GetHostEntry("127.0.0.1");

IPAddress ipAddr = ipHost. AddressList[0];

IPEndPoint ipEndPoint = new IPEndPoint(ipAddr, 11000);

// создаем сокет

Socket sender = new Socket(AddressFamily. InterNetwork,

SocketType. Stream, ProtocolType. Tcp);

// устанавливаем соединение

sender.Connect(ipEndPoint);

// отправляем сообщение

string message = "Привет!";

int bytesSend =

sender.Send(Encoding. UTF8.GetBytes(message));

// получаем ответ от сервера

int bytesRec = sender.Receive(bytes);

Console. WriteLine("Server:" +

Encoding. UTF8.GetString(bytes, 0, bytesRec));

// завершаем работу

sender.Shutdown(SocketShutdown. Both);

sender. Close();

Console. ReadKey();

Не забудьте в приложениях обработать исключения! Подсказка: чтобы определить, где и какие исключения следует обработать, создавайте нарочно ошибочные ситуации. Например, можно запустить клиент без сервера и посмотреть, что получится.

Классы TcpListener и TcpClient

В отличие от класса Socket, в котором для отправки и получения данных применяется побайтовый подход, классы TcpClient и TcpListener придерживаются потоковой модели.

В этих классах все взаимодействие между клиентом и сервером базируется на потоке с использованием класса NetworkStream. Однако при необходимости можно работать и с байтами.

// Создать объект класса TcpListener и запустить

// его на прослушивание порта 65125

IPHostEntry ipHost = Dns. GetHostEntry("127.0.0.1");

IPAddress ipAddr = ipHost. AddressList[0];

TcpListener tcpListener = new TcpListener(ipAddr, 65125);

tcpListener.Start();

Console. WriteLine("Start of listening");

while (true) // Ожидание запроса приемника

{

// По запросу приемника установить соединение и вернуть

// новый объект класса сокета, который используется для

// установки связи с приемником; тем временем tcpListener

// продолжает прослушивание

Socket clientSocket = tcpListener.AcceptSocket();

// теперь можно создать сетевой поток данных и

// объект для работы с ним – например, бинарный поток

// вывода

NetworkStream netStream = new NetworkStream(clientSocket);

BinaryWriter binWriter = new BinaryWriter(netStream);

// . . . обмен данными. . .

// в конце работы с клиентом не забудем закрыть сокет:

clientSocket.Close();

}

Класс TcpClient

// создать объект TcpClient для работы с сервером

TcpClient serverSocket;

serverSocket = new TcpClient("localhost", 65125);

// создать сетевой поток данных и объект для чтения из

// него

NetworkStream netStream = serverSocket. GetStream();

BinaryReader binReader = new BinaryReader(netStream);

// . . . обмен данными. . .

serverSocket.Close();

Не забывайте в приложениях обработать исключения! Подсказка: чтобы определить, где и какие исключения следует обработать, создавайте нарочно ошибочные ситуации. Например, можно запустить клиент без сервера и посмотреть, что получится.

Задание для самостоятельной работы

Создайте простые приложения клиент и сервер на основе классов TcpListener и TcpClient.

Пусть, например, клиент отправляет сообщение, сервер его принимает и в ответ тоже отправляет сообщение, и на этом сеанс клиента завершается.

Разработанное приложение представляет собой шаблон, «скелет» для вашего проекта.

Многопоточность

В клиент-серверных приложениях особое внимание уделяется понятию многопоточности.

Каждому традиционному приложению Win32 соответствует один (обычно) или несколько процессов (process).

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

Для каждого файла ЕХЕ операционная система создает отдельную изолированную область в оперативной памяти, которой процесс пользуется в течение всего своего жизненного цикла.

Каждому процессу соответствует один (по крайней мере) или несколько потоков.

Поток (thread) можно представить как специфический путь выполнения внутри процесса Win32 (thread в буквальном переводе с английского означает «нить»).

Первый поток, создаваемый в процессе, называется первичным процессом (primary thread).

В любом процессе существует, по крайней мере, один поток, который играет роль точки входа для приложения. В традиционных графических приложениях Windows такой точкой входа является метод WinMain (), а в консольных приложениях — метод main().

Основная цель, для которой создаются многопоточные приложения (вместо использования единственного потока), — повышение производительности и сокращение времени отклика приложения.

Многопоточные приложения на однопроцессорном компьютере создают иллюзию одновременного выполнения сразу нескольких дел.

Управление потоками производится на уровне операционной системы.

Компьютеры с единственным центральным процессором в действительности не могут одновременно обрабатывать более одного потока.

Иллюзия многозадачности достигается тем, что каждому потоку выдаются (в соответствии с его приоритетом) специальные кванты времени (time-slice).

При исчерпании потоком выделенного ему кванта времени ресурсы центрального процессора передаются другим потокам, а первый поток вновь ждет своей очереди.

Для того чтобы поток мог продолжить выполнение с того места, на котором его работа была остановлена, он обеспечивается возможностью записи в локальную память потока (Thread Local Storage) и отдельным стеком вызовов.

Примечание. К сожалению, в русском языке и для понятия Stream, и для понятия Thread используется один и тот же термин – поток. Постарайтесь не путать эти два понятия. Обычно из контекста бывает ясно, о чем именно идет речь.

Пространство имен System. Threading

Класс

Назначение

Interlocked

Для синхронизированного доступа к общим данным

Monitor

Обеспечивает синхронизацию потоковых объектов при помощи блокировок и управления ожиданием

Mutex

Примитив синхронизации, используемый для синхронизации разных процессов

Thread

Представляет поток, работающий в среде выполнения. NET. При помощи этого типа можно порождать в текущем домене приложения новые потоки

ThreadPool

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

Timer

Определяет делегат, который будет вызван в указанное время. Операция ожидания выполняется потоком в пуле потоков

WaitHandle

Представляет во время выполнения все объекты синхронизации (которые позволяют многократное ожидание)

ThreadStart

Представляет делегат со ссылкой на метод, который должен быть выполнен перед запуском потока

TimerCallback

Делегат для объектов Timer

WaitCallback

Делегат, который представляет метод обратного вызова для рабочих элементов ThreadPool

Работа с классом Thread

Самый простой тип в пространстве имен System. Threading — это класс Thread. Этот класс в. NET — не более чем объектная оболочка вокруг некоторого этапа выполнения программы внутри домена приложения.

Статические переменные и методы

Назначение

CurrentThread

Это свойство «только для чтения» возвращает ссылку на поток, выполняемый в настоящее время

GetData() SetData()

Возвращает/устанавливает значение для указанного слота в текущем потоке

GetDomain()

GetDomainlD()

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

Sleep()

Приостанавливает выполнение текущего потока на указанное пользователем время

Обычные члены класса Thread

Переменные и методы

Назначение

IsAlive

Это свойство возвращает «true» или «false» в зависимости от того, запущен поток или нет

IsBackground

Свойство предназначено для получения или установки значения, которое показывает, является ли этот поток фоновым

Name

Свойство для установки дружественного текстового имени потока

Priority

Позволяет получить/установить приоритет потока (используются значения из перечисления ThreadPriority)

ThreadState

Возвращает информацию о состоянии потока (используются значения из перечисления ThreadState)

Interrupt()

Прерывает работу текущего потока

Join()

Ждет появления другого потока (или указанный промежуток времени) и завершается

Resume()

Продолжает работу после приостановки работы потока

Start()

Начинает выполнение потока, определенного делегатом ThreadStart

Suspend()

Приостанавливает выполнение потока.

Пример

Создается класс Car (автомобиль). Метод Pusk данного класса содержит цикл из 100 итераций, на каждой из которых скорость автомобиля изменяется на случайную величину и печатается информация о состоянии автомобиля.

Из за большого объема этот материал размещен на нескольких страницах:
1 2 3