© Терехов А В. 2002
ВАЖНО. При работе с сокетами используется системная библиотека wsock32.dll.
Для обращения к функциям этой библиотеки существует модуль WinSock, входящий в поставку
Delphi. Поэтому не забудьте указать модуль WinSock в клаузе Uses.
unit SocketUnit;
interface
Uses Windows, SysUtils, WinSock;
Function MyClientSocket:String;
implementation
Function MyClientSocket:String;
Begin
//писать будем все сюда
End;
End.
Теперь можно приступать к сокетам.
Шаг 1. Инициализация сокета Windows
     Для инициализации сокета используется функция:
Function WSAStartUp(wVersionRequested:Word; WSAData:TWSAData):Integer
VersionRequested - это запрашиваемая версия сокетов
     В Windows есть две версии 1.1 и 2.0. Мы будем использовать версию 1.1 Младший байт переменной wVersionRequested должен содержать номер версии (major), старший байт - номер расширения версии (minor). Для того чтобы создать нужное нам значение, воспользуемся функцией:
wVersionRequested:=MakeWord(1,1);
     Второй аргумент функции несколько посложнее. При вызове функции WSAStartUp в этот аргумент будут возвращены параметры инициализации. На языке Си принято переменные типа запись (Record) называть структорой, будем придерживать такой же терминологии. Итак WSAData - это структура следующего типа:
TWSAData = Record
wVersion : Word;
wHighVersion : Word;
szDescription : Array [0..WSADESCRIPTION_LEN+1] Of Char;//где WSADESCRIPTION_LEN = 255
szSystemStatus : Array [0..WSASYS_STATUS_LEN+1] Of Char;//где WSASYS_STATUS_LEN+1 = 127
MaxSockets : Word;
MaxUdpDg : Word;
lpVendor : Word;
End;
     На самом деле аргумент WSAData нам не нужен, нам важен результат функции. Если результат функции
равен 0, значит все в порядке, иначе - надо искать ошибку с помощью функции WSAGetLastError, которая
вернет код ошибки. Затем код ошибки можно обработать используя Help Win32 Develoer's References
(входит в поставку Delphi).
     Напишем первые строчки кода (не забудем о включении в клаузу Uses
модуля WinSock):
Function MyClientSocket:String;
Var
     wVersionRequested:Word;
     WSAData:TWSAData;
Begin{MyClientSocket}
     wVersionRequested:=MakeWord(1,1); //инициализируем сокет Windows
     FillChar(WSAData,SizeOf(WSAData),#0);//обнулим все значения WSAData
     If Not WSAStartup(wVersionRequested,WSAData)=0 Then //стартуем
     Begin {If Not = 0}
     //неудача при старте
          Result:='';
          WSACleanup; //уничтожим сокет см. шаг 10
          Exit;
     End; {If Not = 0}
     WSACleanup; //уничтожим сокет см. шаг 10
End;{MyClientSocket}
Шаг 2. Создание сокета
     Для создания сокета используется функция:
Function socket(af:Integer; struct:Integer; protocol:Integer):TSocket;
где (все константы определены в модуле WinSock):
af - спецификация формата адресов, всегда PF_INET
struct - тип спецификации для нового сокета
          SOCK_STREAM - для TCP-протокола,
          SOCK_DGRAM - для UDP-протокола
protocol - конкретный протокол или 0, если протокол не определен
          IPPROTO_TCP - TCP/IP протоколо
          IPPROTO_UDP - UDP/IP протокол
          ISOPROTO_TP4 - ISO протокол
          NSPROTO_IPX - IPX протокол
          NSPROTO_SPX - SPX протколо
          NSPROTO_SPXII - SPX II протокол
результат функции - в результате возвращается хэндл (описатель) нового сокета или
INVALID_SOCKET в случае неудачи.
Function MyClientSocket:String;
Var
     wVersionRequested:Word;
     WSAData:TWSAData;
     hSocket:TSocket;
Begin{MyClientSocket}
     wVersionRequested:=MakeWord(1,1); //инициализируем сокет Windows
     FillChar(WSAData,SizeOf(WSAData),#0);//обнулим все значения WSAData
     If Not WSAStartup(wVersionRequested,WSAData)=0 Then //стартуем
     Begin {If Not = 0}
     //неудача при старте
          Result:='';
          closesocket(hSocket);//закрыть сокет см. шаг 9
          WSACleanup; //уничтожим сокет
          Exit;
     End; {If Not = 0}
     hSocket:=socket(PF_INET,SOCK_STREAM,0);//создаем сокет
     If hSocket=INVALID_SOCKET Then
     //неудача при создании сокета
     Begin{INVALID_SOCKET}
          Result:='';
          closesocket(hSocket);//закрыть сокет см. шаг 9
          WSACleanup; //уничтожим сокет
          Exit;
     End; {INVALID_SOCKET}
     closesocket(hSocket);//закрыть сокет
     WSACleanup; //уничтожим сокет
End;{MyClientSocket}
Шаг 3. Настройка параметров сокета
     Для настройки параметров сокетов используется довольно сложная структура:
TSockAddrIn = Record
TInAddr = Record
SunB = Record
SunW = Record
sin_family : Word;
sin_port : Word;
sin_addr : TInAddr;
sin_zero : Array [0..7] of Char;
sa_family : Word;
sa_data : Array [0..13] Of Char;
End;
S_un_b : SunB;
S_un_w : SunW;
S_addr : Integer;
End;
s_b1 : Char;
s_b2 : Char;
s_b3 : Char;
s_b4 : Char;
End;
s_w1 : Word;
s_w2 : Word;
End;
1. TSockAddrIn.sin_addr.S_addr - IP-адрес сервера
2. TSockAddrIn.sin_family - используемый протокол
3. TSockAddrIn.sin_port - используемый порт
Все остальные параметры установим в 0.
Исходные параметры:
1. пока будем работать с сервером, находящимся на локальной машине, поэтому
IP-адрес у нас должен быть такого вида:
2. работать будем по IP-протоколу
3. порт для HTTP-протокола используется 80-й
Прежде всего объявим переменные
Var
name:TSockAddrIn;
IPAddress:String;
Function inet_addr(cp:PChar):Integer;
где cp - нужный IP-адрес
IPAddress:='127.0.0.1';
name.sin_port:=htons(80);
name.sin_addr.S_addr:=inet_addr(PChar(IPAddress));
name.sin_family:=AF_INET; //для сокетов TCP/IP надо использовать константу AF_INET
Function htons(hostshort:Word):Word;
Теперь наша функция будет выглядеть следующим образом:
Function MyClientSocket:String;
Var
     wVersionRequested:Word;
     WSAData:TWSAData;
     hSocket:TSocket;
     IPAddress:String;
Begin{MyClientSocket}
     wVersionRequested:=MakeWord(1,1); //инициализируем сокет Windows
     FillChar(WSAData,SizeOf(WSAData),#0);//обнулим все значения WSAData
     If Not WSAStartup(wVersionRequested,WSAData)=0 Then //стартуем
     Begin {If Not = 0}
     //неудача при старте
          Result:='';
          WSACleanup; //уничтожим сокет
          Exit;
     End; {If Not = 0}
     hSocket:=socket(PF_INET,SOCK_STREAM,0);//создаем сокет
     If hSocket=INVALID_SOCKET Then
     //неудача при создании сокета
     Begin{INVALID_SOCKET}
          Result:='';
          closesocket(hSocket);//закрыть сокет
          WSACleanup; //уничтожим сокет
          Exit;
     End; {INVALID_SOCKET}
     IPAddress:='127.0.0.1'; //будем использовать localhost
     name.sin_family:=AF_INET;//укажем тип протокола
     name.sin_addr.S_addr:=inet_addr(PChar(IPAddress));//укажем IP-адрес
     name.sin_port:=htons(80);//укажем 80-й порт для HTTP-протокола
     closesocket(hSocket);//закрыть сокет
     WSACleanup; //уничтожим сокет
End;{MyClientSocket}
Шаг 4. Соединение с сервером
     Для соединения вилки с розеткой используется следующая функция:
Function connect(hSocket:Integer; Var name:TSockAddrIn; namelen:Integer):Integer;
где:
hSocket - полученый нами ранее хэндл сокета;
name - полученная нами ранее структура с параметрами соединения;
namelen - размер этой струтуры;
результат функции - если все в порядке, то 0, проблемы - SOCKET_ERROR.
     Теперь посмотрим полученный код. К этому времени локальный сервер должен быть уже запущен.
Function MyClientSocket:String;
Var
     wVersionRequested:Word;
     WSAData:TWSAData;
     hSocket:TSocket;
     IPAddress:String;
Begin{MyClientSocket}
     wVersionRequested:=MakeWord(1,1); //инициализируем сокет Windows
     FillChar(WSAData,SizeOf(WSAData),#0);//обнулим все значения WSAData
     If Not WSAStartup(wVersionRequested,WSAData)=0 Then //стартуем
     Begin {If Not = 0}
     //неудача при старте
          Result:='';
          WSACleanup; //уничтожим сокет
          Exit;
     End; {If Not = 0}
     hSocket:=socket(PF_INET,SOCK_STREAM,0);//создаем сокет
     If hSocket=INVALID_SOCKET Then
     //неудача при создании сокета
     Begin{INVALID_SOCKET}
          Result:='';
          closesocket(hSocket);//закрыть сокет
          WSACleanup; //уничтожим сокет
          Exit;
     End; {INVALID_SOCKET}
     IPAddress:='127.0.0.1'; //будем использовать localhost
     name.sin_family:=AF_INET;//укажем тип протокола
     name.sin_addr.S_addr:=inet_addr(PChar(IPAddress));//укажем IP-адрес
     name.sin_port:=htons(80);//укажем 80-й порт для HTTP-протокола
     If connect(hSocket,name,SizeOf(name))=SOCKET_ERROR Then //соединяемся с сервером
     Begin {SOCKET_ERROR}
     //неудача при соедении с сервером
          Result:='';
          closesocket(hSocket);//закрыть сокет
          WSACleanup;
          Exit;
     End; {SOCKET_ERROR}
     closesocket(hSocket);//закрыть сокет
     WSACleanup; //уничтожим сокет
End;{MyClientSocket}
Шаг 5. Подготовка данных в соотвествии с протоколом HTTP
     Пакет с данными для протокола HTTP состоит из двух частей:
заголовка и собственно данных, размер всего пакета должен быть равен 1024 байтам.
В заголовке располагается служебная информация. Заголовок отделяется от данных
двойным #13#10 - конец строки и перевод каретки. Мы будем использовать команду HTTP-протокола
GET. Послав серверу команду
     Подготовим новые переменные:
Var
...
HTTPAddress:String;
GetingString:String;
Buf:Array [0..1023] Of Char;
FillChar(Buf,SizeOf(Buf),#0);//обнулим буфер
HTTPAddress:='/index.html';//укажем нужную страницу
GetingString:='GET '+HTTPAddress+' HTTP/1.1'#13#10#13#10;//сформируем HTTP-заголовок
StrPCopy(Buf, GetingString);//положим заголовок в буферШаг 6. Передача пакета данных серверу
     Для передачи данных используется функция:
Function send(hSocket:Integer; Var Buf:Untyped, len:Integer; Flags:Integer):Integer;
где:
hSocket - уже знакомый нам хэндл сокета;
Buf - буфер с данными - нетипизированная переменная, т.е. просто набор байтов;
len - длина буфера;
Flags - способ передачи данных, мы этот флаг установим в 0
результат функции - количество переданных байт в случае успеха или SOCKET_ERROR в случае неудачи.
Function MyClientSocket:String;
Var
     wVersionRequested:Word;
     WSAData:TWSAData;
     hSocket:TSocket;
     GetingString:String;
     Buf:Array [0..1023] Of Char;
     IPAddress,HTTPAddress:String
Begin{MyClientSocket}
     wVersionRequested:=MakeWord(1,1); //инициализируем сокет Windows
     FillChar(WSAData,SizeOf(WSAData),#0);//обнулим все значения WSAData
     If Not WSAStartup(wVersionRequested,WSAData)=0 Then //стартуем
     Begin {If Not = 0}
     //неудача при старте
          Result:='';
          WSACleanup; //уничтожим сокет
          Exit;
     End; {If Not = 0}
     hSocket:=socket(PF_INET,SOCK_STREAM,0);//создаем сокет
     If hSocket=INVALID_SOCKET Then
     //неудача при создании сокета
     Begin{INVALID_SOCKET}
          Result:='';
          closesocket(hSocket);//закрыть сокет
          WSACleanup; //уничтожим сокет
          Exit;
     End; {INVALID_SOCKET}
     IPAddress:='127.0.0.1'; //будем использовать localhost
     name.sin_family:=AF_INET;//укажем тип протокола
     name.sin_addr.S_addr:=inet_addr(PChar(IPAddress));//укажем IP-адрес
     name.sin_port:=htons(80);//укажем 80-й порт для HTTP-протокола
     If connect(hSocket,name,SizeOf(name))=SOCKET_ERROR Then //соединяемся с сервером
     Begin {SOCKET_ERROR}
     //неудача при соедении с сервером
          Result:='';
          closesocket(hSocket);//закрыть сокет
          WSACleanup;
          Exit;
     End; {SOCKET_ERROR}
     FillChar(Buf,SizeOf(Buf),#0);//обнулим буфер
     HTTPAddress:='/index.html';//укажем нужную страницу
     GetingString:='GET '+HTTPAddress+' HTTP/1.1'#13#10#13#10;//сформируем HTTP-заголовок
     StrPCopy(Buf, GetingString);//положим заголовок в буфер
     If send(hSocket,Buf,SizeOf(Buf), 0)=SOCKET_ERROR Then //передаем данные
     Begin {SOCKET_ERROR}
     //неудача при передаче данных
          Result:='';
          closesocket(hSocket);
          WSACleanup;
          Exit;
     End;{SOCKET_ERROR}
     closesocket(hSocket);//закрыть сокет
     WSACleanup; //уничтожим сокет
End;{MyClientSocket}
Шаг 7. Получение ответа от сервера
     Для приема данных используется следующая функция:
Function recv(hSocket:Integer; Var Buf:Untyped, len:Integer; Flags:Integer):Integer;
где:
hSocket - хэндл сокета;
Buf - буфер с данными;
len - длина буфера;
Flags - способ передачи данных, мы этот флаг установим в 0
результат функции - количество полученных байт в случае успеха или SOCKET_ERROR в случае неудачи.
     Для получения данных от сервера напишем небольшой код и определим
еще одну переменную.
Var
...
ReciveBytes:Integer;
ReciveBytes:=1; //инициализируем переменную
//будем принимать данные пока они не закончатся или возникнет ошибка
While Not((ReciveBytes=SOCKET_ERROR) OR (ReciveBytes=0)) Do
//ошибка при приеме данных или нет данных
Begin {SOCKET_ERROR OR нет данных}
     FillChar(Buf,SizeOf(Buf),#0); //очистим буфер
     ReciveBytes:=recv(hSocket,Buf,SizeOf(Buf),0); //примем данные
     Result:=Result+String(Buf); //сформируем результат
End; {SOCKET_ERROR OR нет данных}
Шаг 8. Обработка полученных данных
     Мы не будем писать свой Web-броузер, поэтому данные будем смотреть
именно в том виде, в котором они пришли. Прежде немного модернизируем функцию вынеся
две переменные IPAddress,HTTPAddress в ее аргументы (не забудьте сделать это и в
объявлении функции и в ее реализации, а также убрать
IPAddress:='127.0.0.1'; и
HTTPAddress:='/index.html'; из реализации функции). Обработку ошибок для наглядности оставим
без изменений.
Function MyClientSocket(IPAddress,HTTPAddress:String):String;
Var
     wVersionRequested:Word;
     WSAData:TWSAData;
     hSocket:TSocket;
     GetingString:String;
     Buf:Array [0..1023] Of Char;
     ReciveBytes:Integer;
Begin{MyClientSocket}
     wVersionRequested:=MakeWord(1,1); //инициализируем сокет Windows
     FillChar(WSAData,SizeOf(WSAData),#0);//обнулим все значения WSAData
     If Not WSAStartup(wVersionRequested,WSAData)=0 Then //стартуем
     Begin {If Not = 0}
     //неудача при старте
          Result:='';
          WSACleanup; //уничтожим сокет
          Exit;
     End; {If Not = 0}
     hSocket:=socket(PF_INET,SOCK_STREAM,0);//создаем сокет
     If hSocket=INVALID_SOCKET Then
     //неудача при создании сокета
     Begin{INVALID_SOCKET}
          Result:='';
          closesocket(hSocket);//закрыть сокет
          WSACleanup; //уничтожим сокет
     closesocket(hSocket);//закрыть сокет
          Exit;
     End; {INVALID_SOCKET}
     name.sin_family:=AF_INET;//укажем тип протокола
     name.sin_addr.S_addr:=inet_addr(PChar(IPAddress));//укажем IP-адрес
     name.sin_port:=htons(80);//укажем 80-й порт для HTTP-протокола
     If connect(hSocket,name,SizeOf(name))=SOCKET_ERROR Then //соединяемся с сервером
     Begin {SOCKET_ERROR}
     //неудача при соедении с сервером
          Result:='';
          closesocket(hSocket);//закрыть сокет
          WSACleanup;
          Exit;
     End; {SOCKET_ERROR}
     FillChar(Buf,SizeOf(Buf),#0);//обнулим буфер
     GetingString:='GET '+HTTPAddress+' HTTP/1.1'#13#10#13#10;//сформируем HTTP-заголовок
     StrPCopy(Buf, GetingString);//положим заголовок в буфер
     If send(hSocket,Buf,SizeOf(Buf), 0)=SOCKET_ERROR Then //передаем данные
     Begin {SOCKET_ERROR}
     //неудача при передаче данных
          Result:='';
          closesocket(hSocket);
          WSACleanup;
          Exit;
     End;{SOCKET_ERROR}
     ReciveBytes:=1; //инициализируем переменную
     //будем принимать данные пока они не закончатся или возникнет ошибка
     While Not((ReciveBytes=SOCKET_ERROR) OR (ReciveBytes=0)) Do
     //ошибка при приеме данных или нет данных
     Begin {SOCKET_ERROR OR нет данных}
          FillChar(Buf,SizeOf(Buf),#0); //очистим буфер
          ReciveBytes:=recv(hSocket,Buf,SizeOf(Buf),0); //примем данные
          Result:=Result+String(Buf); //сформируем результат
     End; {SOCKET_ERROR OR нет данных}
     closesocket(hSocket);//закрыть сокет
     WSACleanup; //уничтожим сокет
End;{MyClientSocket}
Теперь вернемся в главную форму и на обытие OnClick кнопки (TButton) укажем следующее:
Memo1.Text:=MyClientSocket(Edit1.Text,Edit2.Text);
Если локального сервера нет, можно использовать какой-нибудь Web-сервер в Интернет.
Чтобы посмотреть как перевести доменное имя в IP-адрес обратитесь к Приложению 1..
Шаг 9. Закрытие сокета
     Каждая попытка создать сокет
socket
должна быть завершена вызовом функции
closesocket(hSocket), где hSocket - хендл сокета.
Шаг 10. Уничтожение сокета
     Каждая попытка инициализировать сокет
WSAStartUp
должна быть завершена вызовом функции
WSACleanUp.
Приложение 1. Получение IP-адреса из доменного имени
Function DomaneNameToIP(DomaneName:String):String;
Type
TaPInAddr = Array [0..10] Of PInAddr;
PaPInAddr = ^TaPInAddr;
Var
     phe: PHostEnt;
     pptr: PaPInAddr;
     i: Integer;
     GInitData: TWSADATA;
     wVersionRequested : WORD;
Begin {DomaneNameToIP}
     wVersionRequested:=MakeWord(1,1);
     WSAStartup(wVersionRequested, GInitData);
     Result := '';
     phe :=GetHostByName(PChar(DomaneName));
     If phe = NIL Then Exit;
          pptr := PaPInAddr(Phe^.h_addr_list);
          i := 0;
          While pptr^[i] <> Nil Do
          Begin {While}
          result:=StrPas(inet_ntoa(pptr^[i]^));
          Inc(i);
     End; {While}
     WSACleanup;
End; {DomaneNameToIP}
Приложение 2. Получение доменного имени из IP-адреса
Function IpToDomaneName(IP:String):String;
Var
     wVersionRequested : WORD;
     wsaData : TWSAData;
     Addr:Integer;     
     p : PHostEnt;
Begin {IpToDomaneName}
     Result:='Can''t reslove host';
     wVersionRequested := MAKEWORD(1, 1);
     WSAStartup(wVersionRequested, wsaData);
     Addr:=inet_addr(PChar(IP));
     p := GetHostByAddr(@Addr,128,AF_INET);
     WSACleanup;
     If p<>Nil Then Result:=p^.h_Name;
End; {IpToDomaneName}