WinSock: HTTP-запрос с помощью блокирующего сокета

Синхронный сокет winsock. GET запрос

Сегодня хочу начать цикл заметок о winsock. Около года назад я этим заинтересовался, потом незаслуженно забыл и забросил.
WinSock — достаточно мощный инструмент, основа всех основ. Если ты пишешь программу на чистом api, тебе важен конечный размер программы или работаешь с хитрым протоколом, то сокеты просто незаменимы. В этой заметке рассмотрим, как открыть сокет и послать GET запрос серверу.

Типы сокетов

Вкратце. Есть сокеты синхронные, с ними работать удобнее. Программа (поток) будет ждать, пока запрос посылается, или же ждать ответа. Такие сокеты также называются блокирующими, и речь сегодня пойдёт о них.
Второй тип — асинхронные или неблокирующие сокеты. Из названия вполне очевидно, что, посылая запрос, мы не будем ничего ждать. Нам достаточно будет или иногда проверять состояние, или назначить событие, в разных версиях winsock существует две возможные тактики. Также массив асинхронных сокетов вполне заменяет многопоточность.

Инициализируем winsock

Сперва подключим модуль winsock. Теперь нам необходимо инициализировать winsock, для этого нам потребуется функция WSAStartup. Я использую её следующим образом:

  if WSAStartup(MAKEWORD(2,2), WSAData1)<>0 then begin
    writeln('WinSock error');
    readln;
    Exit;
  end;

MAKEWORD(2,2) — просто константа, точнее возвращает константу, можешь писать $101. WSAData1 — структура типа TWSAData. Не забудь объявить такую :) Как ты уже заметил, в случае успешной инициализации функция возвращает 0, иначе ошибка. Структура TWSAData (WSAData) и коды ошибок есть в msdn, не будем углубляться в голую теорию.
Еще замечу, что я пишу консольное приложение, пусть writeln и readln не смущают читателя.

Создадим сокет

Сокет — как красиво звучит. А это всего лишь число, эх. Socket1:TSocket, Socket1:integer — так надо объявить (TSocket сам по себе integer). Создаём функцией Socket:

  Socket1:=Socket(AF_INET,SOCK_STREAM,0);
  if Socket1=INVALID_SOCKET then begin
    writeln('socket error');
    readln;
    Exit;
  end;

Всё достаточно ясно, примем как есть, нудной теорией займёмся как-нибудь потом.

Устанавливаем соединение.

Сокет — это тебе не Indy или Synapse, здесь запрос делается только после установления соединения (мы сейчас про TCP, UDP не будем затрагивать). Как же нам установить соединение? Объявим следующую переменную: SockAddr1:TSockAddr;. Это структура, в ней укажем протокол, адрес сервера и порт:

  SockAddr1.sin_family:=AF_INET;
  SockAddr1.sin_addr.S_addr:=inet_addr(PChar('62.109.19.221'));
  SockAddr1.sin_port:=htons(80);

Таким образом мы можем указать только ip-адрес. Получению dns будет посвящён отдельный пост. IP-адрес я указал своего блога :)
Теперь функция connect:

  if Connect(Socket1,SockAddr1,SizeOf(SockAddr1))<>0 then begin
    writeln('connection error #',WSAGetLastError);
    readln;
    exit;    
  end;

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

Send — посылаем запрос

Объявим две переменных:

  Buffer1:string;
  Buffer2:array[1..1024] of char;

В одной будем формировать запрос, в другую будем порциями писать ответ сервера.

Сначала, конечно, надо сформировать запрос. Если ты хотя бы поверхностно знаком с http протоколом, то это не составит труда:

  Buffer1:='GET / HTTP/1.1'#13#10+
           'host: parsers.info'#13#10+
           'Connection: close'#13#10+
           #13#10;

А теперь можно и послать этот запрос:

  if send(Socket1,Buffer1[1],Length(Buffer1),0)=SOCKET_ERROR then begin
    writeln('socket error #',WSAGetLastError);
    readln;
    exit;
  end;

Спасибо A.Truhin за замечание, код выше исправлен, и теперь проверяется возвращаемое значение функции send. Она возвращает количество переданных байт, а в случае ошибки -1, то есть SOCKET_ERROR.
Мы передали начало буфера и его длину. Так как наш сокет блокирующий, то всё, что написано после send, будет выполнено после того, как пройдёт запрос. А после этого нам предстоит ждать ответа :)

Reсv — получаем ответ

Мы знаем, что ответ должен быть, так как мы работаем с http протоколом. Поэтому сразу претендуем на ответ от сервера. Наш помощник — функция recv.

  repeat
    FillChar(Buffer2,SizeOf(Buffer2),0);
    d:=recv(Socket1,Buffer2,SizeOf(Buffer2),0);
    for i:=1 to d do write(Buffer2[i]);
  until d<=0;

Небольшое пояснение. Recv читает из буфера сокета, в который он получает ответ. То есть мы ждём ответа, а потом читаем из буфера сокета. Если мы не прочитаем, то данные там и останутся, и при следующем вызове recv мы их и получим. Функция recv возвращает нам количество полученных байт, если это ноль, то всё, больше ничего нет. Моё цикл просто выводит на экран ответ сервера, он содержит тело ответа и html-код моего сайта.

Закроем сокет и подведём итог.

if CloseSocket(Socket1)<>0 then
  writeln('error closing socket');

Итог: winsock - это просто!
Скачать исходник!
И в заключение хочу отметить, что вес нашей программы всего 17Кб! Неплохо для работы с интернетом? :)

(c) crystalbit, http://parsers.info
Подпишись на RSS ленту, и я обещаю тебе много интересного материала по сокетам!

23 ответа к «WinSock: HTTP-запрос с помощью блокирующего сокета»

  1. А по-моему использовать блокирующие соккеты в основном потоке программы неудобно — лишняя заморочка с «замиранием», ожиданиями и т.д. и т.п. По-моему с ними работать лучше в отдельном потоке.

    1. Всё зависит от целей программы, если это просто какой-нибудь консольный пингователь, то я бы не стал выделять отдельный поток, разве что если управление делать. Да и у консоли замирать особо нечему.
      А в остальных случаях согласен, спасибо за замечание

  2. Именно на сокетах построен мой компонент для отправки почты по SMTP — Light SMTP. Очень удобный инструмент в руках кодера. При чем не соглашусь про использование блокирующих совекетов — можно сразу узнать ответ получателя и вывести необходимую инфу, а не ждать тупо. Хотя в принципе соглашусь, что все зависит от ситуации.

    1. Спасибо за отзыв, простые парсеры, где надо получить только одну страницу, а потом с ней работать, — практически идеальная ситуация.
      Light SMTP — отличный компонент, удивительно, что у тебя на блоге его нет :)
      Я по крайней мере не нашел, думаю, если ты напишешь о нём в своём блоге, то это однозначно прибавит популярность

      1. Спасибо, как до конца обновлю и все подправлю — выложу и отпишу о нем, а заодно и программирование под SMTP рассмотрю, думаю кому-нибудь да пригодится, тем более что отправка аттача и авторизация нигде не рассмотрена особо.

    2. Тоже не нашел компонент…В блоге есть раздел по кодингу на Delphi — добавьте ленту раздела в DelphiFeeds.ru — будем отслеживать движения в вашем блоге, чаще заходить.
      З.Ы. Димас, ето я на правах рекламы зазываю новые блоги в сообщество, плз не ругай :)

  3. А есть пример реализации работы в асинхронном неблокирующем режиме, в несколько потоков работать с HTTP протоклом?
    Ни разу вообще не работал с сокетами, вообще сам http запрос особо не важен, это не сложно, а вот с сокетами не отказался бы от помощи.
    сенкс.

    1. Когда у нас асинхронные сокеты, то несколько потоков не имеют смысла, всё происходит в одном потоке. Но асинхронно :)
      Статья на подходе :)

  4. Функция recv возвращает вместо html кода какието китайские иероглифе. Как я понял это проблемы с кодировкой. Не подскажете как сделать чтобы было читабельно. Компилировал на Delphi 2009

    1. Затрудняюсь ответить, как это всё выглядит? Знаки вопроса или просто не так кириллица отображается? Во втором случае виноват вывод в консоль, так как в таком случае всё нормально в буфере :)

      1. Поробуйте к крякозябрам применить UTF8ToAnsi() перед выводом. А если заголовок нормальный, а в теле сообщения бред, запретите(не разрешайте) GZIP в отправляемом хидере

  5. Спасибо тебе дорогой за эту статью!

    Три дня парился и вот теперь благодаря тебе наконец написал свой первый правильный запрос.

    В общем три раза «ку» тебе :-)

  6. а будет продолжение про не блокируещие, асинхронные прокси?
    и как подставлять кукизы для сайтов?

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *