Создание проекта TCP-сервера — КиберПедия 

Общие условия выбора системы дренажа: Система дренажа выбирается в зависимости от характера защищаемого...

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

Создание проекта TCP-сервера

2019-11-19 184
Создание проекта TCP-сервера 0.00 из 5.00 0 оценок
Заказать работу

Создадим проект для сервера. Для этого зайдем в меню «Файл» и выберем пункт «Создать проект…». Выберем проект приложения «Консольное приложение» на языке C#. Рекомендуется назвать проект server и добавить его в решение lab2 (рис. 3.1).

Рисунок 3.5 Интерфейс окна создания проекта «Консольное приложение»

Нажмите правой кнопкой на имени проекта server в окне «Обозреватель решений» и выберите пункт «Назначить запускаемым проектом…».

Создание класса ChatServer

Создадим класс, который инкапсулирует работу с экземпляром класса TcpListener.

Рисунок 3.6 Добавление нового класса в проект серверного приложения

 

Для создания класса необходимо вызвать контекстное меню нажатием правой кнопки мыши на имени проекта server в окне «Окно классов» или «Обозреватель решений», выбрать пункт меню «Добавить» и подпункт «Класс…» (рис. 3.6).

В открывшемся диалоговом окне задайте имя файла, которое соответствует имени нового класса (рис. 3.7). Рекомендуется назвать класс ChatServer. После нажатия кнопки «Добавить» новый класс должен появиться в «Окне классов». Для дальнейшей работы необходимо открыть файл ChatServer.cs двойным щелчком по названию класса в «Обозревателе решений» или «Окне классов». В рабочей области VS возникнет автоматически сгенерированный код для класса ChatServer.

Рисунок 3.7 Диалоговое окно создания класса ChatServer

Цель создания данного класса — инкапсуляция работы с экземпляром класса TcpListener. Для достижения цели нам потребуется решить три задачи: создать метод для запуска сервера, метод для обслуживания клиентских подключений по TCP-протоколу и метод для передачи принятых сообщений другим клиентам.

Создание объекта listener

Для создания экземпляра класса TcpListener можно использовать строку кода:

System.Net.Sockets.TcpListener listener;

Запись упрощается, если применить директиву using к пространству имен System.Net.Sockets:

TcpListener listener;

Поэтому сразу после директивы using System.Threading.Tasks; в блок директив необходимо дописать:

using System.Net.Sockets;

Это позволит вызывать методы для работы с TcpListener в более короткой форме.

Закрытое поле данных listener объявляется в файле ChatServer.cs в области видимости ChatServer:

private TcpListener listener;

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

private AutoResetEvent clientAccepted = new AutoResetEvent(false);

private Hashtable clientTable = new Hashtable();

Класс AutoResetEvent поможет асинхронно обслуживать сразу несколько запросов от клиентов. Особенностью экземпляра данного класса является то, что он автоматически устанавливается в значение по-умолчанию после срабатывания метода WaitOne().

Класс Hashtable позволяет создать хэш-таблицу clientTable, которая будет использоваться для хранения информации о клиентах.

VS автоматически проверяет корректность вашего кода. Для получения подробной информации об ошибках наведите курсор на подчёркнутый текст.

Все методы, описанные в данном разделе, должны быть созданы в области видимости класса ChatServer.

Метод Start()

Для запуска сервера будет использоваться следующий метод:

public void Start()

{

listener = new TcpListener(IPAddress.Any, 48654);

listener.Start();

bool isListening = true;

Console.WriteLine("Сервер начал работу.");

Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e)

{

listener.Stop();

isListening = false;

Console.WriteLine("Сервер закончил работу.");

};

while (isListening)

{

listener.BeginAcceptTcpClient(new AsyncCallback(AcceptCallback), listener);

clientAccepted.WaitOne();

}

}

Сервер создается с помощью конструктора класса TcpListener, а IP-адрес IPAddress.Any и порт 5555 подаются в него в качестве аргументов. Метод listener.Start() позволяет запустить работу сервера. Булева переменная isListening определяет текущее состоянии сервера. При получении сигнала о завершении работы (сочетание клавиш Ctrl+C) программа присвоит булевой переменной isListening значение false и вызовет метод Stop(). Если значение isListening равно true, то программа вызывает асинхронный метод обработки клиентских подключений BeginAcceptTcpClient(). После этого вызывается метод WaitOne(), который блокирует текущий поток до получения сигнала от потока обработки нового клиента.

Метод AcceptCallback()

Каждый клиент, подключающийся к серверу по TCP-сокету обрабатывается в отдельном потоке с помощью следующего асинхронного метода:

public void AcceptCallback(IAsyncResult ar)

{

TcpClient client = listener.EndAcceptTcpClient(ar);

clientAccepted.Set();

IPEndPoint clientAddr = (IPEndPoint) client.Client.RemoteEndPoint;

Console.WriteLine("Подключился клиент {0}", clientAddr.ToString());

clientTable.Add(clientAddr, client);

NetworkStream stream = client.GetStream();

byte[] dataRead = new byte[client.ReceiveBufferSize];

stream.BeginRead(dataRead, 0, client.ReceiveBufferSize, new AsyncCallback(BeginReadCallback), new MyAsyncInfo(dataRead, clientAddr));

}

В результате работы метода EndAcceptTcpClient() класса TcpListener содается экземпляр класса TcpClient. Связь с каждым клиентом поддерживается с помощью отдельного потокового сокета и отдельного объекта client. Объект clientAccepted с помощью метода Set() отправляет сигнал о продолжении работы в метод Start(). Тем временем, в данном потоке продолжается работа с текущим клиентом. В объект clientAddr класса IPEndPoint записывается IP-адрес и портэтого клиента. Данный объект будет выполнять роль ключа в хэш-таблице clientTable. С помощью метода Add() в таблицу добавляется объект client и соответствующий ему ключ clientAddr. Объект stream позволяет обмениваться информацией с текущим клиентом. Байтовый массив dataRead используется для приема данных и имеет размер равный client.ReceiveBufferSize. С помощью метода BeginRead() осуществляется асинхронный прием данных от текущего клиента. Для дальнейшей обработки полученной информации в вызываемый метод BeginReadCallback() необходимо подать указатель на массив с полученной информацией dataRead и ключ для хэш-таблицы clientAddr. Для этого используется подкласс:

public class MyAsyncInfo

{

public Byte[] Data { get; set; }

public IPEndPoint Address { get; set; }

 

public MyAsyncInfo(Byte[] data, IPEndPoint address)

{

Data = data;

Address = address;

}

}

Этот подкласс играет роль структуры и инкапсулирует байтовый массив Data и экземпляр класса IPEndPoint в один объект класса MyAsyncInfo. Ключевые слова get и set описывают стандартные методы работы с полями данных этого класса. В конструкторе MyAsyncInfo() полям присваиваются начальные значения data и address. Подкласс должен быть объявлен в области видимости класса ChatServer.

Метод BeginReadCallback()

Для обработки информации, поступившей от одного из клиентов используется следующий метод:

public void BeginReadCallback(IAsyncResult ar)

{

MyAsyncInfo info = ar.AsyncState as MyAsyncInfo;

IPEndPoint clientAddr = info.Address;

byte[] dataRead = info.Data;

TcpClient client = (TcpClient)clientTable[clientAddr];

NetworkStream stream = client.GetStream();

int bytesRead = stream.EndRead(ar);

if (bytesRead < 1)

{

stream.Close();

client.Close();

clientTable.Remove(clientAddr);

Console.WriteLine("Отключился клиент {0}", clientAddr.ToString());

}

else

{

string recvMessage = Encoding.UTF8.GetString(dataRead, 0, bytesRead);

Broadcast(recvMessage);

stream.BeginRead(dataRead, 0, client.ReceiveBufferSize, new AsyncCallback(BeginReadCallback), new MyAsyncInfo(dataRead, clientAddr));

}

}

В объекты clientAddr и dataRead записывается информация, полученная в виде экземпляра класса MyAsyncInfo. По ключу clientAddr из хэш-таблицы clientTable извлекается объект client класса TcpClient. Объект stream позволяет обмениваться данными с текущим клиентом client. С помощью метода EndRead() данные, полученные от клиента, записываются в байтовый массив dataRead. Если было получено сообщение о разрыве соединения, то работа с объектами stream и client заканчивается с помощью соответствующих методов, а объект, соответствующий данному клиенту, удаляется из хэш-таблицы с помощью метода Remove() по ключу clientAddr. В противоположном случае, байтовый массив преобразуется в строковую переменную recvMessage с помощью метода Encoding.UTF8.GetString(). Строковая переменная recvMessage передается в метод Broadcast() в качестве аргумента. Работа с текущим продолжается с помощью асинхронного метода BeginRead().

Метод Broadcast()

Любое сообщение может быть доставлено до всех клиентов, записи о которых существуют в хэш-таблице clientTable, с помощью следующего метода:

private void Broadcast(string sendMessage)

{

foreach (DictionaryEntry entry in clientTable)

{

TcpClient client = (TcpClient)(entry.Value);

NetworkStream stream = client.GetStream();

byte[] dataSend = new byte[client.SendBufferSize];

dataSend = Encoding.UTF8.GetBytes(sendMessage);

stream.BeginWrite(dataSend, 0, dataSend.Length, new AsyncCallback(BeginWriteCallback), stream);

}

}

С помощью оператора цикла foreach осуществляется перебор всех записей entry класса DictionaryEntry в хэш-таблице clientTable. Каждая запись представляет собой объект client класса TcpClient. Объект stream позволяет обмениваться информацией с текущим клиентом. Создается байтовый массив dataSend, в который записывается строковая переменная sendMessage, преобразованная методом Encoding.UTF8.GetBytes(). Процедура записи осуществляется в асинхронном режиме методом BeginWrite(). В другой поток передается только объект stream.

Метод BeginWriteCallback()

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

private void BeginWriteCallback(IAsyncResult ar)

{

NetworkStream stream = (NetworkStream)ar.AsyncState;

stream.EndWrite(ar);

}

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

Запустите сервер с помощью кнопки . В качестве IP-адреса используйте IP-адрес вашего ПК в локальной сети (определите его с помощью команды ipconfig). В качестве порта используйте значение 48654 + номер_варианта, номера портов указаны в Приложении A. Ошибки, возникшие при выполнении кода, будут показаны в окне «Список ошибок». Если сервер успешно начал работу, попробуйте подключиться к нему с помощью ранее созданного клиентского приложения (exe-файл можно найти в client/bin/debug/). Запустите еще одну копию клиентского приложения и проведите передачу нескольких сообщений. С помощью оператора ветвления if и метода Broadcast() сделайте так, чтобы сервер рассылал сообщения всем кроме отправителя. Проверьте работу сервера и клиентов соответственно вашему варианту, указанному в Приложении A. Добавьте новый функционал в работу сервера согласно вашему варианту с помощью метода Broadcast(). Проведите обмен сообщениями между сервером и количеством клиентом, указанным в Приложении A. Сделайте скриншот окна клиента после обмена сообщениями с сервером.

Опишите в отчете принцип работы TCP-сервера.

Выводы

Сделайте выводы по проделанной работе. С помощью какого класса был реализован TCP-клиент в данной ЛР? Опишите задачи, которые необходимо решить для создания простейшего клиента. С помощью каких классов был реализован TCP-сервер? Опишите задачи, которые необходимо решить при создании сервера. Каким образом осуществляется взаимодействие между клиентом и сервером по протоколу TCP? Какие ошибки могут произойти в данных приложениях и чем они могут быть вызваны?

Вопросы

· Что такое TCP-сервер?

· Что такое TCP-клиент?

· На каком уровне модели OSI происходит взаимодействие по TCP-протоколу?

· Каким образом был реализован TCP-сервер на языке C# в данной ЛР?

· Можно ли реализовать сервер на языке C# как-то иначе?

· Какие ошибки могут возникнуть при работе по протоколу TCP?

· В чем различие между клиентом и сервером, работающими по протоколу TCP?

· В чем главная особенность протокола TCP?


·

Приложение A

Варианты

Номер варианта Изменение функционала сервера Номер порта Количество клиентов
1 Сервер сообщает о новом клиенте всем остальным 48655 2
2 Сервер сообщает об отключении клиента всем остальным 48656 2
3 Сервер приветствует каждого клиента 48657 2
4 Сервер возвращает текущее время новому клиенту 48658 2
5 Сервер возвращает текущую дату новому клиенту 48659 2
6 Сервер сообщает о новом клиенте всем остальным 48660 3
7 Сервер сообщает об отключении клиента всем остальным 48661 3
8 Сервер приветствует каждого клиента 48662 3
9 Сервер возвращает клиенту текущее время новому клиенту 48663 3
10 Сервер возвращает клиенту текущую дату новому клиенту 48664 3
11 Сервер сообщает о новом клиенте всем остальным 48665 4
12 Сервер сообщает об отключении клиента всем остальным 48666 4
13 Сервер приветствует каждого клиента 48667 4
14 Сервер возвращает клиенту текущее время новому клиенту 48668 4
15 Сервер возвращает клиенту текущую дату новому клиенту 48669 4
16 Сервер сообщает о новом клиенте всем остальным 48670 5
17 Сервер сообщает об отключении клиента всем остальным 48671 5
18 Сервер приветствует каждого клиента 48672 5
19 Сервер возвращает клиенту текущее время новому клиенту 48673 5
20 Сервер возвращает клиенту текущую дату новому клиенту 48674 5

 


Поделиться с друзьями:

История развития хранилищ для нефти: Первые склады нефти появились в XVII веке. Они представляли собой землянные ямы-амбара глубиной 4…5 м...

Автоматическое растормаживание колес: Тормозные устройства колес предназначены для уменьше­ния длины пробега и улучшения маневрирования ВС при...

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

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



© cyberpedia.su 2017-2024 - Не является автором материалов. Исключительное право сохранено за автором текста.
Если вы не хотите, чтобы данный материал был у нас на сайте, перейдите по ссылке: Нарушение авторских прав. Мы поможем в написании вашей работы!

0.055 с.