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 |


