Программирование на C++ с использованием сокетов в Linux |
||||||||||||
Содержание
1. ВведениеСокеты -- это механизм обмена данными между процессами. Эти процессы могут быть запущены на одной и той же машине, или на разных машинах, объединенных в компьютерную сеть. Будучи созданным, соединение между сокетами позволяет передавать данные в обеих направлениях, т.е. "туда и обратно", пока соединение не будет закрыто одной из сторон. Мне нужно было использовать сокеты в проекте, над которым я работал, и я написал и отладил несколько С++ классов, инкапсулирующих вызовы функций API для сокетов. Обычно приложение, запрашивающее данные, называется клиентом, а приложение, обслуживающее такие запросы -- сервером. Я создал два главных класса ClientSocket и ServerSocket, чтобы приложения, клиенты и серверы, могли их использовать для организации обмена данными. Цель данной статьи -- научить вас пользоваться классами ClientSocket и ServerSocket в собственных приложениях. Сначала мы коротко обсудим создание клиент-серверного соединения, а затем, в качестве примера, создадим простые приложения -- серверное и клиентское -- использующих два этих класса. 2. Обзор соединений клиент-сервер.Перед тем как перейти к рассмотрению кода, нам необходимо коротко рассмотреть по шагам создание типичного соединения между клиентом и сервером. Следующая таблица показывает эти шаги:
В основном все. Сначала сервер создает сокет, слушающий определенный порт, и ожидает попытку соединиться от клиентов. Клиент, со своей стороны, создает сокет и делает попытку соединиться с сервером. Затем сервер разрешает соединение, после чего может начинаться обмен данными. Когда все данные будут переданы, любая сторона может может закрыть соединение. 3. Реализация простого сервера и простого клиентаТеперь время погрузиться в код. В следующем разделе мы создадим и сервер и клиент, которые будут выполнять все описанные выше шаги. Мы реализуем эти операции в типичном порядке -- т.е. сначала создадим ожидающую соединения серверную часть, а затем создадим клиентскую часть, которая подключается к серверу и т.д. Полный код этого раздела можно найти в файлах simple_server_main.cpp и simple_client_main.cpp. Если вы хотите просто проверить, как работает этот код или поэкспериментировать с ним -- перейдите прямо в этот раздел. Там перечислены составляющие проект файлы, и обсуждается, как его скомпилировать и тестировать.3.1 Сервер -- создание ожидающего сокетаПервая вещь, которую мы должны сделать -- создать простой сервер, ожидающего входящих запросов от клиентов. Вот код, необходимый для создания серверного сокета: листинг 1 : создание серверного сокета (фрагмент файла simple_server_main.cpp ) #include "ServerSocket.h" #include "SocketException.h" #include <string> int main ( int argc, int argv[] ) { try { // Создание серверного сокета ServerSocket server ( 30000 ); // оставшийся код - // разрешение соединения, обработка запроса, и т.д... } catch ( SocketException& e ) { std::cout << "Обнаружено исключение:" << e.description() << "\nВыход.\n"; } return 0; } Вот и все. Конструктор класса ServerSocket вызывает необходимые API-функции для сокетов, и создает "слушающий" сокет, ожидающий соединения. Конструктор скрывает от вас детали, так что все, что вам нужно сделать, чтобы начать "слушать" локальный порт -- создать экземпляр этого класса. Обратите внимание на блок try/catch. ServerSocket и ClientSocket используют обработку исключений в стиле С++. Если выполнение метода класса по какой-либо причине завершается неудачно -- возбуждается исключение типа SocketException, описанное в SocketException.h. Не заканчивайте работу программы при возникновении исключения, лучше обработать его. Описание ошибки можно получить, вызвав метод description() класса SocketException, как показано выше. 3.2 Клиент -- подключение к серверуЗа второй шаг создания типичного клинт-серверного соединения -- попытку подключения к серверу -- отвечает клиент. Код похож на только что виденный вами код сервера: листинг 2 : создание клиентского сокета (фрагмент simple_client_main.cpp ) #include "ClientSocket.h" #include "SocketException.h" #include <iostream> #include <string> int main ( int argc, int argv[] ) { try { // создание клиентского сокета ClientSocket client_socket ( "localhost", 30000 ); // оставшийся код - // посылка запроса, получение ответа, и т.д. ... } catch ( SocketException& e ) { std::cout << "Обнаружено исключение:" << e.description() << "\n"; } return 0; } Просто создавая экземляр класса ClientSocket, вы создаете linux сокет, подключенный к хосту и порту, указанным в параметрах конструктора. Как и в классе ServerSocket, если конструктор по какой-либо причине не срабатывает, возбуждается исключение. 3.3 Сервер -- разрешение попытки соединения для клиентаСледующий шаг клиент-серверного соединения происходит внутри сервера. Сервер отвечает за реализацию попытки соединения со стороны клиента, который открывает канал сообщения между сокетами. Мы добавим эту функциональность в наш простой сервер. Вот дополненная версия: листинг 3 : разрешение соединения (фрагмент simple_server_main.cpp ) #include "ServerSocket.h" #include "SocketException.h" #include <string> int main ( int argc, int argv[] ) { try { // Создание сокета ServerSocket server ( 30000 ); while ( true ) { ServerSocket new_sock; server.accept ( new_sock ); // оставшийся код - // чтение запроса, передача ответа, т.д. ... } } catch ( SocketException& e ) { std::cout << "Обнаружено исключение:" << e.description() << "\nВыход.\n"; } return 0; } Разрешение соединиения достигается просто вызовом метода accept. Этот метод допускает попытку соединения и заполняет new_sock информацией о сокете, с которого производится попытка соединения. Как используется new_sock мы увидим в следующей разделе. 3.4 Клиент и сервер -- передача и прием данныхТеперь, когда сервер разрешил запрос клиента на соединение, самое время через соединенные сокеты посылать и принимать данные "туда и обратно". Продвинутая осбенность С++ -- возможность перегружать операторы, или, иными словами, заставить оператор выполнять определенные действия. В ClientSocket и ServerSocket я перегрузил операторы << и >>, и теперь они используются для записи данных в сокет и чтения их оттуда. Здесь дополненная версия простого сервера: листинг 4 : простая реализация сервера (simple_server_main.cpp) #include "ServerSocket.h" #include "SocketException.h" #include <string> int main ( int argc, int argv[] ) { try { // Создать сокет ServerSocket server ( 30000 ); while ( true ) { ServerSocket new_sock; server.accept ( new_sock ); try { while ( true ) { std::string data; new_sock >> data; new_sock << data; } } catch ( SocketException& ) {} } } catch ( SocketException& e ) { std::cout << "Обнаружено исключение:" << e.description() << "\nВыход.\n"; } return 0; } Переменная new_sock содержит всю информацию о нашем сокете, так что мы используем ее для обмена данными с клиентом. Строка "new_sock >> data;" должна читаться как "прочитать данные из new_sock, и поместить их в нашу строковую переменную 'data'." Таким же образом следующая строка посылает данные в 'data' назад через сокет клиенту. Если вы внимательны, вы обратите внимание, что здесь мы создали эхо-сервер. Каждая порция данных, которая отправлена от клиента на сервер, посылается назад клиенту, как есть. Мы можем написать клиент так, чтобы он отправлял порцию данных и затем выдавал на печать ответ сервера: листинг 5 : простая реализация клиента (simple_client_main.cpp)#include "ClientSocket.h" #include "SocketException.h" #include <iostream> #include <string> int main ( int argc, int argv[] ) { try { ClientSocket client_socket ( "localhost", 30000 ); std::string reply; try { client_socket << "Test message."; client_socket >> reply; } catch ( SocketException& ) {} std::cout << "Мы получили от сервера сообщение:\n\"" << reply << "\"\n";; } catch ( SocketException& e ) { std::cout << "Обнаружено исключение:" << e.description() << "\n"; } return 0; } Мы посылаем строку "Test Message." на сервер, читаем ответ с сервера и печатаем его на стандартный вывод. 4. Компиляция и тестирование наших клиента и сервераТеперь, когда мы разобрали основы использования классов ClientSocket и ServerSocket, можно построить законченный проект и протестировать его. 4.1 Список файловСледующие файлы составляют наш проект:
4.2 Компиляция и тестированиеКомпиляция проста. Сначала сохраните все файлы проекта в одной под-директории, а потом введите команду в ответ на приглашение системы: prompt$ cd directory_you_just_created prompt$ make Эта команда скомпилирует все файлы в проекте и создаст выходные файлы simple_server и simple_client. Чтобы проверить эти два выходных файла, запустите сервер в одной консоли, а затем в другой консоли запустите клиент: первая консоль: prompt$ ./simple_server running.... вторая консоль: prompt$ ./simple_client We received this response from the server: "Test message." prompt$ Клиент будет посылать данные серверу, читать ответ и печатать его на стандартный вывод, как показано выше. Мы можем запустить клиент столько раз, сколько захотим, сервер будет отвечать на каждый запрос. 5. ЗаключениеСокеты -- простой и эффкетивный путь пересылки данных между процессами. В этой статье мы рассмотрели сокет-коммуникации и разработали пример сервера и клиента. Теперь у вас должна быть возможность добавить организацию связи с помощью сокетов в ваши собственные приложения! Rob TougherРоб работает программистом на C++ в Нью Йорке. Когда он не пишет код, его можно найти прогуливающимся по пляжу вместе с подружкой Николь и ее собакой Холли. Copyright (C) 2002, Rob Tougher.
|
||||||||||||
Вернуться на главную страницу |