Лекции / РАЗРАБОТКА ПРИЛОЖЕНИЯ С КЛИЕНТ-СЕРВЕРНОЙ АРХИТЕКТУРОЙ
.pdf5. Класс EntityModelContainer, представляющий контекст данных,
используемый для взаимодействия с базой данных.
|
Листинг 24.7 – Класс контекста данных |
|
|
|
|
1 |
namespace MyLib |
|
2 |
{ |
|
3 |
public partial class EntityModelContainer : DbContext |
|
4 |
{ |
|
5 |
public EntityModelContainer() : |
|
base("name=EntityModelContainer") |
|
|
|
|
|
6 |
{} |
|
7 |
public virtual DbSet<User> UserSet { get; set; } |
|
8 |
public virtual DbSet<Student> StudentSet { get; set; |
|
} |
|
|
|
|
|
9 |
public virtual DbSet<Professor> ProfessorSet { get; |
|
set; } |
|
|
|
|
|
10 |
public virtual DbSet<Thesis> ThesisSet { get; set; } |
|
11 |
} |
|
12 |
} |
|
|
6. Класс SerializeAndDeserialize, хранящий методы для |
сериализации (Serialize) и десериализации (Deserialize) объектов.
|
Листинг 24.8 – Класс сериализации и десериализации |
|
|
|
|
1 |
namespace MyLib |
|
2 |
{ |
|
3 |
public class SerializeAndDeserialize |
|
4 |
{ |
|
5 |
public static Message Serialize(object |
|
anySerializableObject) |
|
|
|
|
|
6 |
{ |
|
7 |
IFormatter formatter = new BinaryFormatter(); |
|
8 |
formatter.Binder = new CustomBinder(); |
|
9 |
using (var memoryStream = new MemoryStream()) |
|
10 |
{ |
|
11 |
formatter.Serialize(memoryStream, |
|
|
anySerializableObject); |
|
12 |
return new Message { Data = memoryStream.ToArray() }; |
|
13 |
} |
|
14 |
} |
|
15 |
public static object Deserialize(Message message) |
|
16 |
{ |
|
|
11 |
|
17 |
IFormatter formatter = new BinaryFormatter(); |
|
|||
18 |
formatter.Binder = new CustomBinder(); |
|
|
||
19 |
using |
(var |
memoryStream |
= |
new |
MemoryStream(message.Data)) |
|
|
|||
20 |
{ |
|
|
|
|
21 |
return formatter.Deserialize(memoryStream); |
|
|||
22 |
} |
|
|
|
|
23 |
} |
|
|
|
|
24 |
} |
|
|
|
|
25 |
} |
|
|
|
|
Для корректной сериализации и десериализации в строке 8 и 18 свойству объекта formatter присваивается экземпляр биндера CustomBinder,
созданного классе 4.
7. Класс Message, представляющий в качестве объекта пакет данных,
передаваемый посредством TCP-протокола через компьютерную сеть от клиента к серверу и наоборот, от сервера к клиенту.
Листинг 24.9 – Класс пакетов данных
1 |
namespace MyLib |
2 |
{ |
3 |
[Serializable] |
4 |
public class Message |
5 |
{ |
6 |
public byte[] Data { get; set; } |
7 |
} |
8 |
[Serializable] |
9 |
public class ComplexMessage |
10 |
{ |
11 |
public Message First { get; set; } |
12 |
public Message Second { get; set; } |
13 |
public int NumberStatus { get; set; } |
14 |
} |
15 |
} |
Класс Message представляет собой пакет с данными об одном сериализованном объекте, а класс ComplexMessage – о двух сериализованных объектах (First и Second). Сериализованные данные об объекте хранятся в массиве байтов Data.
12
Свойство NumberStatus позволяет при отправке пакета от одной машины к другой отследить тип запроса, который необходимо выполнить после приема данных. Например, если значение свойства равно 0, то выполняется запрос на регистрации, а если равно 1, то выполняется запрос на авторизацию пользователя и т.д.
3. Серверная часть приложения
Рассмотрим реализацию серверной части приложения.
Для начала создаем отдельный проект (в нашем случае LR8_SERVER).
В новый проект добавляем ссылку на раннее созданную библиотеку классов
MyLib.
Сервер будет принимать от клиента пакеты данных, которые будут содержать сериализованные объекты и тип запроса. После приема данных необходимо организовать взаимодействие с базой данных – или для добавления, или для извлечения данных. Для этого необходимо создать базу данной с аналогичной структурой, как она была реализована в десктопной версии приложения (в лабораторной работе 7).
Добавляем в проект следующие классы:
1. Класс ServerObject – класс, описывающий свойства и метода,
требуемые для обеспечения работоспособности TCP-сервера, который будет осуществлять прием и отправку данных посредством TCP-протокола через компьютерную сеть.
Реализация класса сервера представлена в листинге 24.10.
|
|
|
|
Листинг 24.10 – Класс сервера |
|
|
|
||
1 |
public class ServerObject |
|
||
2 |
{ |
|
|
|
3 |
static public TcpListener |
tcpListener; // сервер для |
||
прослушивания |
|
|
||
|
|
|
||
4 |
List<ClientObject> clients = new List<ClientObject>(); |
|||
5 |
ClientObject clientObject = null; |
|||
6 |
protected |
internal |
void |
AddConnection(ClientObject |
clientObject) |
|
|
||
|
|
|
||
7 |
{ |
|
|
|
|
|
|
13 |
|
8 |
clients.Add(clientObject); |
|
|
|
|
9 |
} |
|
|
|
|
10 |
protected internal void Listen() |
|
|
||
11 |
{ |
|
|
|
|
12 |
tcpListener = new TcpListener(IPAddress.Any, 8888); |
||||
13 |
tcpListener.Start(); |
|
|
|
|
14 |
while (true) |
|
|
|
|
15 |
{ |
|
|
|
|
16 |
TcpClient tcpClient = tcpListener.AcceptTcpClient(); |
||||
17 |
clientObject = new ClientObject(tcpClient, this); |
||||
18 |
Thread |
clientThread |
= |
new |
Thread(new |
|
ThreadStart(clientObject.Process)); |
|
|
||
19 |
clientThread.Start(); |
|
|
|
|
20 |
} |
|
|
|
|
21 |
} |
|
|
|
|
22 |
} |
|
|
|
|
Все подключенные клиенты будут храниться в коллекции clients. С
помощью метода AddConnection мы можем добавлять объекты в эту коллекцию.
Основной метод - Listen(), в котором будет осуществляться прослушивание всех входящих подключений. При получении подключения будет запускаться новый поток, в котором будет выполняться метод Process
объекта ClientObject.
2. Класс ClientObject - класс, описывающий свойства и метода,
требуемые для обеспечения работоспособности TCP-клиента, который будет осуществлять отправку данных посредством TCP-протокола через компьютерную сеть и прием данных, отправленных сервером.
Реализация класса клиента представлена в листинге 24.11.
14
Листинг 24.11 – Класс клиента
|
1 |
public class ClientObject |
|
|
2 |
{ |
|
|
3 |
protected internal string Id { get; private set; } |
Идентификатор подключаемого к серверу |
|
клиента |
||
|
|
|
|
|
4 |
protected internal NetworkStream Stream { get; |
Поток, используемый для взаимодействия с |
|
private set; } |
клиентом |
|
|
|
||
|
5 |
TcpClient client; |
Объект TCP-клиента |
|
6 |
ServerObject server; |
Объект сервера |
|
7 |
MyLib.Message m, m1, m2; |
Экземпляры класса Message из библиотеки |
|
8 |
MyLib.ComplexMessage cm = new ComplexMessage(); |
MyLib, созданной на предыдущем этапе |
|
9 |
public ClientObject(TcpClient tcpClient, |
Конструктор класса клиента |
|
ServerObject serverObject) |
|
|
|
|
|
|
|
10 |
{ |
|
|
|
|
Инициализация свойства Id (метод NewGuid |
|
11 |
Id = Guid.NewGuid().ToString(); |
генерирует случайный уникальный |
|
|
|
идентификатор) |
|
12 |
client = tcpClient; |
Инициализация свойства tcpClient |
|
13 |
server = serverObject; |
Инициализация свойства serverObject |
|
14 |
serverObject.AddConnection(this); |
Добавление в коллекцию подключений класса |
|
ServerObject нового подключения |
||
|
|
|
|
|
15 |
Stream = client.GetStream(); |
Связывание потока Stream с TCP-клиентом |
|
client |
||
|
|
|
|
|
16 |
} |
|
|
17 |
public void Process() |
Метод, реализующий протокол обмена |
|
сообщениями с клиентом |
||
|
|
|
|
|
18 |
{ |
|
|
19 |
while (true) |
|
|
|
20 |
{ |
|
|
|
21 |
if (Stream.CanRead) |
CanRead возвращает значение true, если поток |
|
|
поддерживает чтение |
|
||
|
|
|
|
|
|
22 |
{ |
|
|
|
23 |
byte[] myReadBuffer = new byte[6297630]; |
Массив байтов для хранения данных, |
|
|
принимаемых от клиента |
|
||
|
|
|
|
|
|
24 |
do |
|
|
|
25 |
{ |
|
|
|
26 |
Stream.Read(myReadBuffer, 0, myReadBuffer.Length); |
Запись данных, считанных с потока Stream, в |
|
|
массив байтов myReadBuffer |
|
||
|
|
|
|
|
|
27 |
} |
|
|
|
28 |
while (Stream.DataAvailable); |
Пока данные есть в потоке Stream |
|
|
29 |
Student student; |
|
|
|
30 |
Professor professor; |
|
|
|
31 |
User user; |
|
|
|
32 |
MyLib.ComplexMessage complexMessage = new |
|
|
|
ComplexMessage(); |
|
|
|
|
33 |
MyLib.Message message = new MyLib.Message(); |
|
|
|
34 |
message.Data = myReadBuffer; |
Запись принятого пакета данных с клиента в |
|
|
свойство Data объекта message |
|
||
|
|
|
|
|
|
35 |
complexMessage = (ComplexMessage) |
Десериализация полученного пакета message в |
|
|
SerializeAndDeserialize.Deserialize(message); |
объект complexMessage |
|
|
|
|
|
|
|
|
36 |
if (complexMessage.NumberStatus == 0) |
Если NumberStatus, сообщающий о типе |
|
|
запроса, равен 0 |
|
||
|
|
|
|
|
|
37 |
{ |
…то выполняется запрос на добавление нового |
|
|
пользователя в базу данных |
|
||
|
|
|
|
|
|
|
16 |
|
|
|
38 |
try |
|
|
39 |
{ |
|
|
|
student = (Student)SerializeAndDeserialize. |
Десериализуется свойство First сложного |
|
40 |
пакета complexMessage и результат |
|
|
Deserialize(complexMessage.First); |
||
|
|
|
записывается в переменную student |
|
41 |
} |
|
|
42 |
catch |
|
|
43 |
{ |
|
|
|
|
Если в строке 40 произойдет ошибка при |
|
44 |
student = null; |
преобразовании типов, то переменной student |
|
|
|
присвоится нулевой объект null |
|
45 |
} |
|
|
46 |
try |
|
|
47 |
{ |
|
|
|
professor = (Professor)SerializeAndDeserialize. |
Десериализуется свойство First сложного |
|
48 |
пакета complexMessage и результат |
|
|
Deserialize(complexMessage.First); |
||
|
|
|
записывается в переменную professor |
|
49 |
} |
|
|
50 |
catch |
|
|
51 |
{ |
|
|
|
|
Если в строке 48 произойдет ошибка при |
|
52 |
professor = null; |
преобразовании типов, то переменной student |
|
|
|
присвоится нулевой объект null |
|
53 |
} |
|
|
|
user = (User)SerializeAndDeserialize. |
Десериализуется свойство Second сложного |
|
54 |
пакета complexMessage и результат |
|
|
Deserialize(complexMessage.Second); |
||
|
|
|
записывается в переменную user |
|
|
17 |
|
|
55 |
using (EntityModelContainer db = new |
Создается экземпляр контекста данных для |
|
EntityModelContainer()) |
взаимодействия с базой данных |
|
|
56 |
{ |
|
|
|
|
Обращение к коллекции UserSet для добавления |
|
57 |
db.UserSet.Add(user); |
нового объекта user в таблицу UserSet базы |
|
|
|
данных |
|
58 |
db.SaveChanges(); |
Сохранение изменений, применяемых к базе |
|
данных |
||
|
|
|
|
|
59 |
} |
|
|
60 |
} |
|
|
|
|
|
|
61 |
else if (complexMessage.NumberStatus == 1) |
Если NumberStatus, сообщающий о типе |
|
запроса, равен 1 |
||
|
|
|
|
|
62 |
{ |
…то выполняется запрос на авторизацию |
|
пользователя |
||
|
|
|
|
|
63 |
using (EntityModelContainer db = new |
Создается экземпляр контекста данных для |
|
EntityModelContainer()) |
взаимодействия с базой данных |
|
|
64 |
{ |
|
|
65 |
byte[] responseData; |
Массив байтов для хранения ответа, |
|
формируемого сервером на запрос клиента |
||
|
|
|
|
|
66 |
for (int i = 0; i < db.UserSet.ToList().Count; |
Циклический перебор всех пользователей из |
|
i++) |
таблицы UserSet |
|
|
67 |
{ |
|
|
|
if (db.UserSet.ToList()[i].Login == |
Условие проверки соответствия логина и пароля |
|
|
Convert.ToString(SerializeAndDeserialize. |
|
|
|
i-го пользователя из коллекции –UserSet логину |
|
|
68 |
Deserialize(complexMessage.First)) && |
|
|
и паролю, полученными в результате запроса от |
||
|
|
db.UserSet.ToList()[i].Password == |
|
|
|
клиента |
|
|
|
Convert.ToString(SerializeAndDeserialize. |
|
|
|
|
|
|
|
18 |
|
|
|
Deserialize(complexMessage.Second))) |
|
|
|
69 |
{ |
|
|
|
|
|
Создаем объект user1 и копируем в него i-й |
|
|
70 |
User user1 = db.UserSet.ToList()[i]; |
элемент коллекции UserSet, для которого |
|
|
|
|
выполнилось условие в строке 68 |
|
|
|
|
Создаем объект user2, вызываем для него |
|
|
|
|
конструктор по умолчанию и инициализируем |
|
|
71 |
User user2 = new User() { Login = user1.Login, |
его свойства, скопировав значения свойств |
|
|
Password = user1.Password, Role = user1.Role }; |
объекта user1. Данное действие необходимо для |
|
|
|
|
|
дальнейшей корректной десериализации объекта |
|
|
|
|
класса User со стороны клиента. |
|
|
72 |
m1 = SerializeAndDeserialize.Serialize(user2); |
Сохранение в переменную m1 сериализованного |
|
|
значения объекта user2 |
|
||
|
|
|
|
|
|
73 |
if (db.UserSet.ToList()[i].Role == "Студент") |
Если роль пользователя – студент |
|
|
74 |
{ |
|
|
|
|
|
Создаем объект student1 и копируем в него |
|
|
75 |
Student student1 = db.UserSet.ToList()[i].Student; |
значение свойства Student i-го элемента |
|
|
коллекции UserSet, для которого выполнилось |
|
||
|
|
|
|
|
|
|
|
условие в строке 73 |
|
|
|
|
Создаем объект student2, вызываем для него |
|
|
|
Student student2 = new Student() { Name = |
конструктор по умолчанию и инициализируем |
|
|
76 |
student1.Name, NumberGroup = student1.NumberGroup, |
его свойства, скопировав значения свойств |
|
|
|
PersonalData = student1.PersonalData, Photo = |
объекта student1. Данное действие необходимо |
|
|
|
student1.Photo, Specialty = student1.Specialty }; |
для дальнейшей корректной десериализации |
|
|
|
|
объекта класса Student со стороны клиента. |
|
|
77 |
m2 = SerializeAndDeserialize.Serialize(student2); |
Сохранение в переменную m2 сериализованного |
|
|
значения объекта student2 |
|
||
|
|
|
|
|
|
78 |
} |
|
|
|
|
19 |
|
|
|
79 |
else if (db.UserSet.ToList()[i].Role == |
Если роль пользователя – преподаватель |
|
|
"Преподаватель") |
|
|
|
|
80 |
{ |
|
|
|
81 |
Professor professor1 = |
|
|
|
db.UserSet.ToList()[i].Professor; |
Повторяем действия, аналогичные строкам 75-77, |
|
|
|
82 |
Professor professor2 = professor1; |
|
|
|
но только для объектов класса Professor |
|
||
|
83 |
m2 = |
|
|
|
|
|
||
|
SerializeAndDeserialize.Serialize(professor1); |
|
|
|
|
84 |
} |
|
|
|
85 |
cm.First = m1; |
Свойству First объекта cm присваиваем m1 |
|
|
86 |
cm.Second = m2; |
Свойству Second объекта cm присваиваем m2 |
|
|
|
|
Свойству NumberStatus объекта cm |
|
|
87 |
cm.NumberStatus = 2; |
присваиваем значение 2 (статус успешной |
|
|
|
|
авторизации) |
|
|
88 |
m = SerializeAndDeserialize.Serialize(cm); |
Сериализованное значение объекта cm |
|
|
присваиваем переменной m |
|
||
|
|
|
|
|
|
89 |
responseData = m.Data; |
В массив responseData копируем содержимое |
|
|
массива Data объекта m |
|
||
|
|
|
|
|
|
90 |
Stream.Write(responseData, 0, |
Отправляем массив байтов responseData |
|
|
|
responseData.Length); |
клиенту |
|
|
91 |
goto label; |
Прыжок к label (строка 98) |
|
|
92 |
} |
|
|
|
93 |
} |
|
|
|
|
|
|
|
|
|
|
Свойству NumberStatus объекта cm |
|
|
94 |
cm.NumberStatus = 3; |
присваиваем значение 3 (статус неудачной |
|
|
|
|
авторизации) |
|
20