Зарег. на сайте Всего: 35071 Новых за месяц: 0 Новых за неделю: 0 Новых вчера: 0 Новых сегодня: 0 Из них Администраторов: 1 Модераторов: 2 Журналистов: 54 Обычных юзеров: 35013 Из них Парней: 34467 Девушек: 256
Структура сетевого приложения на примере загрузки файла с веб-сервера
Структура сетевого приложения на примере загрузки файла с веб-сервера
В статье рассматривается соответствие сетевых программных инетерфейсов модели OSI. Также подробно разбирается структура клиентского HTTP-приложения, написанного с помощью интерфейса WinSock. Статья будет полезна как программистам, так и обычным пользователям, интересующимся работой компьютерных сетей
В настоящее время существует много библиотек высокого уровня, которые позволяют программистам манипулировать сетевыми функциями. Приложения, которые их используют, в основном обращаются к интерфейсам прикладного уровня ОС (например WinHTPP в MS Windows). Однако для понимания принципов работы сети необходимо знать как все работает и на более низких уровнях. Канальный и сетевой уровни обычно реализуются аппаратно, и особого интереса не представляют. В данной статье я опишу сетевое приложение, обращающееся непосредственно к модулям транспортного уровня ОС. Транспортный уровень - уровень модели OSI, который отвечает за доставку данных от одного приложения к другому с заданным уровнем надежности. Этот уровень особенно интересует программистов. Существует две модели транспортного уровня - датаграммнаяпередача и передача с установлением соединения. Датаграммы - небольшого размера сообщения (обычно служебные), которыми обмениваются приложения. Например протокол DNS использует датаграммы. И протокол DHT (в файлообменных сетях) тоже их использует. Но при передаче датаграмм невозможно восстановление поврежденных данных, поэтому при передаче файлов их не используют. Техника передачи с установлением соединения используется в протоколах HTTP, FTP, BitTorrent и др. Суть ее в том, что узлы перед процессом передачи данных устанавливают соединение, а в процессе передачи потерянные пакеты восстанавливаются. Вся прелесть этой техники в том, что программист может не париться, как именно пакеты восстанавливаются: важно то, что все пакеты дойдут именно в нужной последовательности (кроме случаев разрыва соединения). В стеке TCP/IP протокол TCP реализует эту технику. Программный интерфейс транспортного уровня называется sockets (Он есть в разных системах, конкретно для Windows это Winsock). Этот интерфейс предоставляет программистам функции, которые работают независимо от протоколов и сетевого оборудования (даже от системы адресации). И это круто! Например функции getaddrinfo можно передать как доменное имя, так и NetBIOS имя компьютера - и все будет одинаково работать. В принципе процесс передачи данных через протоколы разной технологии почти не отличается в Winsock - например в протоколе UDP можно вызвать функцию connect , хотя реально соединение не устанавливается. Вместе с тем можно писать приложения ориентируясь и на конкрентный протокол. Словом, этот интерфейс очень гибкий. Но написание приложений в его среде довольно сложно. Поэтому на практике часто применяются другие интерфейсы. Но мы рассмотрим его с теоретической точки зрения, чтобы увидеть "внутренности" работы сетевых приложений. Приложение WinSock тебует подключения библиотеки импорта ws2_32.lib (Это в среде разработки VisualStudio). Функции WinSock хранятся в библитеке ws2_32.dll .
Далее разбирается приложение, загружающее файл с веб-сервера по протоколу HTTP и отображающее его в экране консоли. Точнее оно отображает полный ответ сервера, включая заголовок. Интерес этого приложения также в том, что оно позволяет увидеть "своими глазами" ответы сервера, которые обычно скрыты. В начале приложения мы включаем заголовочные файлы.
WSADATA wsaData; //для инициализации сетевой подсистемы char rq[]=//строка с запросом "GET / HTTP/1.1\r\n\ Host: ya.ru\r\n\ User-Agent: Mozilla/4.05 (WinNT; 1)\r\n\ Accept: */*\r\n\r\n\ "; char buf[5000]="";//переменная для хранения ответа сервера
Здесь в строковой константе задан HTTP-запрос GET. Путь к документу обозначен "/", что значит главную страницу сайта. В качестве параметров задается версия протокола, имя узла, браузер - на всякий случай говорим что мы Mozilla Firefox. Параметр Accept: */* задает что мы принимаем любой тип данных.
Теперь сама точка входа:
Code
int main() { int iResult; struct addrinfo *result = NULL; struct addrinfo hints; SOCKET ConnectSocket = INVALID_SOCKET;
В данной части происходит инициализация сетевой подсистемы, а затем разрешение доменного имени узла - в нашем случае "ya.ru" - в сетевой адрес. На этом этапе происходит запрос к DNS-серверу. В структуре hints мы задаем желаемые требования - семейство адресов Ipv4, Ipv6 или любое; тип сокета - для нашего случая это сокет-поток, который соответствует протоколу TCP (для других протколов мог быть тип датаграмного сокета). Ну и задаем непосредственно протокол. Хотя это параметры необязательные - и так наверно все будет работать! У функции getaddrinfo второй параметр - номер порта или проткола, у нас "http", стандартный поpт 80. От DNS-сервера нам приходит IP-адрес сервера ( вс перемнной result), и теперь мы готовы соединится с ним. Для соединения мы создаем специальный объект - сокет. Фактически это набор ресурсов, связанных с сетевым соединением.
if (ConnectSocket == INVALID_SOCKET) { printf("Error at socket(): %ld\n", WSAGetLastError()); system("PAUSE"); freeaddrinfo(result); WSACleanup(); return 1; } iResult=connect(ConnectSocket,result->ai_addr,(int)result->ai_addrlen); //соединение с сервером if (iResult == SOCKET_ERROR) { iResult=WSAGetLastError(); printf("\nConnect error: %i\n",iResult); system("PAUSE"); closesocket(ConnectSocket); ConnectSocket = INVALID_SOCKET; } freeaddrinfo(result);//информация об адресе нам больше не нужна
Вот мы соединились с сервером. Теперь посылаем запрос.
Code
// Send an initial buffer iResult = send(ConnectSocket, rq, (int) strlen(rq), 0); if (iResult == SOCKET_ERROR) { printf("send failed: %d\n", WSAGetLastError()); system("PAUSE"); closesocket(ConnectSocket); WSACleanup(); return 1; } printf("\nSending a request - bytes Sent: %ld\n", iResult); // shutdown the connection for sending since no more data will be sent // the client can still use the ConnectSocket for receiving data iResult = shutdown(ConnectSocket, SD_SEND); if (iResult == SOCKET_ERROR) { printf("shutdown failed: %d\n", WSAGetLastError()); system("PAUSE"); closesocket(ConnectSocket); WSACleanup(); return 1; }
Функцией shutdown мы сигнализируем серверу, что посылка данных завершена, и мы ждем ответа. Ее вызов можно опустить (иногда он даже вреден). Теперь мы получаем ответ порциями по 5 кб и выводим на экран. Когда сервер закрывает соединение, выходим из цикла.
Code
do { iResult = recv(ConnectSocket, buf, 5000, 0);//прием данных if (iResult > 0){ //данные успешно приняты //Start receiving answer printf("Server Answered.Bytes received: %d\n\n", iResult);
Вот пример ответа сервера (для mail.ru). Обратите внимание на странность - сервер возвращает код ошибки 404, но описание - ОК.
Вот и все. Попробуйте усовершенствовать это приложение, чтобы оно сохраняло данные на жесткий диск. На основе него можно написать целый менеджер закачек. Ах да, еще - отключите сетевой экран и брендмауэр перед тестированием приложения, а то они почему-то могут его блокировать. Видимо считается хакерские технологии... Таким образом, прочитав эту статью, вы знаете как устроено простейшее клиент-серверное сетевоe приложение, надеюсь это вам поможет в понимании архитектуры современных информационных систем.