Разработка приложений клиент сервер tcp ip

Клиент-серверное приложение на потоковом сокете TCP

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

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

Сервер TCP

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

Схема .NET-сервера

Вот полный код программы SocketServer.cs:

// SocketServer.cs using System; using System.Text; using System.Net; using System.Net.Sockets; namespace SocketServer < class Program < static void Main(string[] args) < // Устанавливаем для сокета локальную конечную точку IPHostEntry ipHost = Dns.GetHostEntry("localhost"); IPAddress ipAddr = ipHost.AddressList[0]; IPEndPoint ipEndPoint = new IPEndPoint(ipAddr, 11000); // Создаем сокет Tcp/Ip Socket sListener = new Socket(ipAddr.AddressFamily, SocketType.Stream, ProtocolType.Tcp); // Назначаем сокет локальной конечной точке и слушаем входящие сокеты try < sListener.Bind(ipEndPoint); sListener.Listen(10); // Начинаем слушать соединения while (true) < Console.WriteLine("Ожидаем соединение через порт ", ipEndPoint); // Программа приостанавливается, ожидая входящее соединение 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.Write("Полученный текст: " + data + "\n\n"); // Отправляем ответ клиенту\ string reply = "Спасибо за запрос в " + data.Length.ToString() + " символов"; byte[] msg = Encoding.UTF8.GetBytes(reply); handler.Send(msg); if (data.IndexOf("") > -1) < Console.WriteLine("Сервер завершил соединение с клиентом."); break; >handler.Shutdown(SocketShutdown.Both); handler.Close(); > > catch (Exception ex) < Console.WriteLine(ex.ToString()); >finally < Console.ReadLine(); >> > > 

Давайте рассмотрим структуру данной программы.

Читайте также:  Структура программного обеспечения системы программирования

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

Класс Dns предоставляет методы, возвращающие информацию о сетевых адресах, поддерживаемых устройством в локальной сети. Если у устройства локальной сети имеется более одного сетевого адреса, класс Dns возвращает информацию обо всех сетевых адресах, и приложение должно выбрать из массива подходящий адрес для обслуживания.

Создадим IPEndPoint для сервера, комбинируя первый IP-адрес хост-компьютера, полученный от метода Dns.Resolve(), с номером порта:

IPHostEntry ipHost = Dns.GetHostEntry("localhost"); IPAddress ipAddr = ipHost.AddressList[0]; IPEndPoint ipEndPoint = new IPEndPoint(ipAddr, 11000);

Здесь класс IPEndPoint представляет localhost на порте 11000. Далее новым экземпляром класса Socket создаем потоковый сокет. Установив локальную конечную точку для ожидания соединений, можно создать сокет:

Socket sListener = new Socket(ipAddr.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

Перечисление AddressFamily указывает схемы адресации, которые экземпляр класса Socket может использовать для разрешения адреса.

В параметре SocketType различаются сокеты TCP и UDP. В нем можно определить в том числе следующие значения:

Поддерживает дейтаграммы. Значение Dgram требует указать Udp для типа протокола и InterNetwork в параметре семейства адресов.

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

Поддерживает потоковые сокеты. Значение Stream требует указать Tcp для типа протокола.

Третий и последний параметр определяет тип протокола, требуемый для сокета. В параметре РrotocolType можно указать следующие наиболее важные значения — Tcp, Udp, Ip, Raw.

Следующим шагом должно быть назначение сокета с помощью метода Bind(). Когда сокет открывается конструктором, ему не назначается имя, а только резервируется дескриптор. Для назначения имени сокету сервера вызывается метод Bind(). Чтобы сокет клиента мог идентифицировать потоковый сокет TCP, серверная программа должна дать имя своему сокету:

Метод Bind() связывает сокет с локальной конечной точкой. Вызывать метод Bind() надо до любых попыток обращения к методам Listen() и Accept().

Теперь, создав сокет и связав с ним имя, можно слушать входящие сообщения, воспользовавшись методом Listen(). В состоянии прослушивания сокет будет ожидать входящие попытки соединения:

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

В состоянии прослушивания надо быть готовым дать согласие на соединение с клиентом, для чего используется метод Accept(). С помощью этого метода получается соединение клиента и завершается установление связи имен клиента и сервера. Метод Accept() блокирует поток вызывающей программы до поступления соединения.

Метод Accept() извлекает из очереди ожидающих запросов первый запрос на соединение и создает для его обработки новый сокет. Хотя новый сокет создан, первоначальный сокет продолжает слушать и может использоваться с многопоточной обработкой для приема нескольких запросов на соединение от клиентов. Никакое серверное приложение не должно закрывать слушающий сокет. Он должен продолжать работать наряду с сокетами, созданными методом Accept для обработки входящих запросов клиентов.

while (true) < Console.WriteLine("Ожидаем соединение через порт ", ipEndPoint); // Программа приостанавливается, ожидая входящее соединение Socket handler = sListener.Accept();

Как только клиент и сервер установили между собой соединение, можно отправлять и получать сообщения, используя методы Send() и Receive() класса Socket.

Метод Send() записывает исходящие данные сокету, с которым установлено соединение. Метод Receive() считывает входящие данные в потоковый сокет. При использовании системы, основанной на TCP, перед выполнением методов Send() и Receive () между сокетами должно быть установлено соединение. Точный протокол между двумя взаимодействующими сущностями должен быть определен заблаговременно, чтобы клиентское и серверное приложения не блокировали друг друга, не зная, кто должен отправить свои данные первым.

Когда обмен данными между сервером и клиентом завершается, нужно закрыть соединение используя методы Shutdown() и Close():

handler.Shutdown(SocketShutdown.Both); handler.Close();

SocketShutdown — это перечисление, содержащее три значения для остановки: Both - останавливает отправку и получение данных сокетом, Receive - останавливает получение данных сокетом и Send - останавливает отправку данных сокетом.

Сокет закрывается при вызове метода Close(), который также устанавливает в свойстве Connected сокета значение false.

Клиент на TCP

Функции, которые используются для создания приложения-клиента, более или менее напоминают серверное приложение. Как и для сервера, используются те же методы для определения конечной точки, создания экземпляра сокета, отправки и получения данных и закрытия сокета:

Клиентское приложение, использующее сокеты

Вот полный код для SocketClient.cs и его объяснение:

// SocketClient.cs using System; using System.Text; using System.Net; using System.Net.Sockets; namespace SocketClient < class Program < static void Main(string[] args) < try < SendMessageFromSocket(11000); >catch (Exception ex) < Console.WriteLine(ex.ToString()); >finally < Console.ReadLine(); >> static void SendMessageFromSocket(int port) < // Буфер для входящих данных byte[] bytes = new byte[1024]; // Соединяемся с удаленным устройством // Устанавливаем удаленную точку для сокета IPHostEntry ipHost = Dns.GetHostEntry("localhost"); IPAddress ipAddr = ipHost.AddressList[0]; IPEndPoint ipEndPoint = new IPEndPoint(ipAddr, port); Socket sender = new Socket(ipAddr.AddressFamily, SocketType.Stream, ProtocolType.Tcp); // Соединяем сокет с удаленной точкой sender.Connect(ipEndPoint); Console.Write("Введите сообщение: "); string message = Console.ReadLine(); Console.WriteLine("Сокет соединяется с ", sender.RemoteEndPoint.ToString()); byte[] msg = Encoding.UTF8.GetBytes(message); // Отправляем данные через сокет int bytesSent = sender.Send(msg); // Получаем ответ от сервера int bytesRec = sender.Receive(bytes); Console.WriteLine("\nОтвет от сервера: \n\n", Encoding.UTF8.GetString(bytes, 0, bytesRec)); // Используем рекурсию для неоднократного вызова SendMessageFromSocket() if (message.IndexOf("") == -1) SendMessageFromSocket(port); // Освобождаем сокет sender.Shutdown(SocketShutdown.Both); sender.Close(); > > > 

Единственный новый метод - метод Connect(), используется для соединения с удаленным сервером. На рисунке ниже показаны клиент и сервер в действии:

Источник

Лабораторная работа № 3 Создание приложений «клиент-сервер» на основе протокола tcp/ip в среде ос Windows

Для выполнения лабораторной работы требуется написать две программы (клиент и сервер), которые выполняются под управлением ОС Windows и реализуют некоторый специальный прикладной протокол сетевого взаимодействия. Для разработки программы рекомендуется использовать среду Microsoft Visual C++ версии 6.0 или выше под управлением ОС типа Windows 95/98 или Windows NT/2000.

Краткие теоретические сведения Программирование сетевых приложений с использованием mfc и Winsock.

В среде Windows интерфейсом для программирования сетевых взаимодействий по протоколу TCP/IP служит Windows Sockets (Winsock). Это Windows-реализация интерфейса "сокетов" ("гнезд", далее будем использовать слово "сокет" без кавычек), начало которой было положено в 1991 г., когда группа поставщиков ПО объединилась для разработки стандарта на интерфейс для сетевых Windows-приложений. За образец был взят Berkeley Sockets, вошедший в употребление вместе с версией UNIX 4.2 BSD и сыгравший немалую роль в развитии и распространении Интернета. Стандарт оказался чрезвычайно удачным и сейчас считается классическим благодаря хорошо продуманной архитектуре. Последняя версия стандарта - Winsock 2.0 - была значительно расширена и позволяет создавать не зависящие от транспортных протоколов приложения, работающие с TCP/IP, IPX/SPX, DECnet, OSI, NetBEUI, AppleTalk, ATM, ISDN и другими сетевыми средами.

Но, несмотря на то, что Winsock API был реализован программистами очень высокой квалификации и имеет стройную и логичную структуру, все-таки проще воспользоваться библиотекой классов MFC. Это позволит максимально упростить задачу.

Для реализации той части программы, которая, собственно, и отвечает за все взаимодействие с сетью, нам понадобится три класса - CClientSocket, CListeningSocket и CPool. Первый создается для каждого нового клиента, подсоединяющегося к серверу. CListeningSocket, как видно из названия, - "слушающий" сокет. Его основная и единственная задача - отвечать на запросы о подключении новых клиентов. CPool хранит в себе все создаваемые сокеты, управляет ими и осуществляет связь с остальной частью программы. Теперь рассмотрим каждый класс подробно.

class CClientSocket : public CSocket

virtual void OnReceive(int nErrorCode);

virtual void OnClose(int nErrorCode);

Этот класс - наследник CSocket, который в свою очередь является наследником CAsyncSocket. Последние два класса принадлежат библиотеке MFC. В них выполнена вся черная работа по упаковке WinSock API в объектно-ориентированную обертку. Заголовки классов находятся в файле afxsock.h. Строго говоря, основная нагрузка лежит на CAsyncSocket, и можно было бы свой класс наследовать от него, а не от CSocket, но, несмотря на то, что классы выглядят очень похожими, в их устройстве, а значит, и использовании есть существенные различия.

Некоторые функции, принадлежащие CAsyncSocket, такие, как Receive, Send, Accept, всегда возвращают ошибку с кодом WSAEWOULDBLOCK. Это связано с тем, что они не дожидаются своего полного выполнения. И поэтому результат их вызова приходится определять в других местах программы. В CSocket те же самые функции ожидают, пока все необходимые действия не завершатся, и лишь тогда возвращают управление программе. Сокет с такими свойствами называется блокирующим. Для взаимодействия в локальной сети такие задержки в большинстве случаев не будут заметны. Но при использовании Интернета время ожидания может оказаться слишком большим. Тогда существует два варианта: один - воспользоваться CAsyncSocket и самостоятельно создавать механизм обработки подтверждений. Его можно реализовать следующим образом: после вызова упомянутых функций устанавливаем таймер и, если по прошествии некоторого времени не получаем уведомление об удачном выполнении затребованных действий, констатируем неудовлетворительный результат. Другой - выделить для CSocket отдельный поток.

Теперь вернемся к нашему классу. Единственная необходимая его переменная - указатель на объект типа CPool, т. е. указатель на хозяина. Его инициализация совершенно необходима, поэтому конструктор по умолчанию заменяем на следующий.

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

void CClientSocket::OnReceive(int nErrorCode)

Источник

Оцените статью