Синхронный сокет 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 ленту, и я обещаю тебе много интересного материала по сокетам!