Delphi→ Создаем компонент TCP на Winsock (Часть 1)
Окт 8, 2010

При написании программы, работающей через протокол TCP, я столкнулся с рядом проблем в стандартных компонентах IDE Delphi. В каждом компоненте свой недостаток.
- TCPClient (uses Sockets): нет возможности установить таймаут соединения;
- TIdTCPClient (uses IdTCPClient): принимаемые данные приходили не полностью;
- ClientSocket (uses ScktComp): ничем не отличается от первого претендента.
Выход только один — использовать Winsock, предварительно создав на основе его компонент. И как оказалось, это совсем не сложно.
Приступим.
Для начала создадим новый Unit (File->New->Unit), в окне редактора появится готовый шаблон:
unit Unit1; interface implementation end.
Свой компонент я назвал WinTCP, поэтому название модуля Unit 1 изменяем на WinTCP. После директивы interface добавляем директиву uses с модулем Winsock. Теперь необходимо создать класс на основе TObject, назовем его, например, TWinTCP:
unit WinTCP;
interface
uses Winsock;
type
TWinTCP = class(TObject)
private
public
end;
implementation
end.
Директивы private и public являются областями видимости, в которые мы помещаем переменные и функции. Если размещать функции после private, то к функции нельзя будет обратиться вне класса, а если в public, то можно.
Говоря простым языком, в приват мы будем размещать функции для своих нужд, а в паблик те функции, которыми сможет воспользоваться наш потенциальный пользователь компонента.
В область public добавляем конструктор и деструктор класса, соответственно они будут запускаться при создании и уничтожении экземпляра нашего класса.
Примечание: чтобы автоматически создавались функции/процедуры вы можете зажать CTRL + SHIFT + C, когда курсор находится на функции в public/private.
unit WinTCP;
interface
uses Winsock;
type
TWinTCP = class(TObject)
private
public
constructor Create;
destructor Destroy; override;
end;
implementation
{ TWinTCP }
constructor TWinTCP.Create;
begin
end;
destructor TWinTCP.Destroy; override;
begin
inherited;
end;
end.
Здесь мы используем директиву override, которая переписывает уже имеющийся деструктор у класса TObject. Так надо.
В конструктор добавляем команду инициализации сокетов WSAStartup($202, FWSData), в деструктор команду очищения WSACleanup. Мы используем переменную FWSData, её нужно объявить в области private, т.к. никому не нужно знать, что она есть — только для внутреннего пользования.
Примечание: хорошим стилем написания своих классов является именование внутренних переменных и функций с заглавной буквы F.
unit WinTCP;
interface
uses Winsock;
type
TWinTCP = class(TObject)
private
FWSData: TWSAData;
public
constructor Create;
destructor Destroy; override;
end;
implementation
{ TWinTCP }
constructor TWinTCP.Create;
begin
WSAStartup($202, FWSData);
end;
destructor TWinTCP.Destroy;
begin
inherited;
WSACleanup;
end;
end.
Прежде чем создавать процедуры подключения/отключения, добавим нашему классу возможность задать адрес и порт удаленного узла. Для этого будем использовать свойства — property. В область public пишем property Host: string write FSetHost; Таким образом мы добавили свойство Host, который имеет тип данных string, при этом в свойство можно только записывать (write) данные. Записывая в эту переменную значение, оно будет сохранятся в переменную (или вызываться функция, как в нашем случае).
Теперь нужно объявить внутреннюю функцию FSetHost, которая будет принимать аргумент с типом данных string (Host же у нас string) :
unit WinTCP;
interface
uses Winsock;
type
TWinTCP = class(TObject)
private
FWSData: TWSAData;
FHostname: PHostEnt;
FAddress: TSockAddrIn;
procedure FSetHost(AHost: string);
public
constructor Create;
destructor Destroy; override;
property Host: string write FSetHost;
end;
implementation
{ TWinTCP }
constructor TWinTCP.Create;
begin
WSAStartup($202, FWSData);
end;
destructor TWinTCP.Destroy;
begin
inherited;
WSACleanup;
end;
procedure TWinTCP.FSetHost(AHost: string);
begin
FHostname := GetHostByName(PChar(AHost));
FAddress.sin_addr := PInAddr(FHostname^.h_addr^)^;
end;
end.
В области private объявляем переменные FHostname (необходима для преобразования Hostname в IP) и FAddress (переменная, которая содержит ip-адрес, порт и тип соединения). GetHostByName() как раз таки преобразует name в ip-адрес (DNS-Resolve). После этого все заносится в переменную FAddress хитроумным способом, через указатели.
Тоже самое делаем и для свойства Port (integer):
unit WinTCP;
interface
uses Winsock;
type
TWinTCP = class(TObject)
private
FWSData: TWSAData;
FHostname: PHostEnt;
FAddress: TSockAddrIn;
procedure FSetHost(AHost: string);
procedure FSetPort(APort: integer);
public
constructor Create;
destructor Destroy; override;
property Host: string write FSetHost;
property Port: integer write FSetPort;
end;
implementation
{ TWinTCP }
constructor TWinTCP.Create;
begin
WSAStartup($202, FWSData);
end;
destructor TWinTCP.Destroy;
begin
inherited;
WSACleanup;
end;
procedure TWinTCP.FSetHost(AHost: string);
begin
FHostname := GetHostByName(PChar(AHost));
FAddress.sin_addr := PInAddr(FHostname^.h_addr^)^;
end;
procedure TWinTCP.FSetPort(APort: integer);
begin
FAddress.sin_family := AF_INET;
FAddress.sin_port := htons(APort);
end;
end.
Необходимые свойства заданны, приступаем к коннекту/дисконнекту.
Объявляем переменную FSocket (TSocket), после чего создаем две процедуры Connect и Disconnect. В коннекте создаем сокет и присваиваем его FSocket’у. После чего соединяемся.
Здесь очень важный момент: нужно явно указать модуль из которого используется функция Connect т.к. по умолчанию будет взята свежесозданная процедура Connect.
Закрытие сокета CloseSocket() добавляем в деструктор.
unit WinTCP;
interface
uses Winsock;
type
TWinTCP = class(TObject)
private
FSocket: TSocket;
FWSData: TWSAData;
FHostname: PHostEnt;
FAddress: TSockAddrIn;
procedure FSetHost(AHost: string);
procedure FSetPort(APort: integer);
public
constructor Create;
destructor Destroy; override;
procedure Connect;
procedure Disconnect;
property Host: string write FSetHost;
property Port: integer write FSetPort;
end;
implementation
{ TWinTCP }
constructor TWinTCP.Create;
begin
WSAStartup($202, FWSData);
end;
destructor TWinTCP.Destroy;
begin
inherited;
WSACleanup;
end;
procedure TWinTCP.FSetHost(AHost: string);
begin
FHostname := GetHostByName(PChar(AHost));
FAddress.sin_addr := PInAddr(FHostname^.h_addr^)^;
end;
procedure TWinTCP.FSetPort(APort: integer);
begin
FAddress.sin_family := AF_INET;
FAddress.sin_port := htons(APort);
end;
procedure TWinTCP.Connect;
begin
FSocket := Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
Winsock.connect(FSocket, FAddress, SizeOf(FAddress))
end;
procedure TWinTCP.Disconnect;
begin
CloseSocket(FSocket);
end;
end.
Сохраняем наш Unit под именем WinTCP.pas. Для проверки закинем его в папку с каким-нибудь проектом, добавляем в uses WinTCP, после объявляем переменную TCP (TWinTCP) и создаем экземпляр:
unit main;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, WinTCP;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
TCP: TWinTCP;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
TCP := TWinTCP.Create;
TCP.Host := 'ya.ru';
TCP.Port := 80;
TCP.Connect;
TCP.Disconnect;
TCP.Free;
end;
end.
Теперь у нас есть полноценный компонент, который можно добавлять в свои проекты, не переписывая каждый раз по новой один и тот же код. Правда пока мы не добавили функции отправки/приемки данных он мало чем полезен, но об этом мы поговорим в следующей части.Некоторые вебмастеры недооценивают важность формы обратной связи. А зря, так как я не раз наблюдал, когда сайт имеет уязвимость, но уведомить автора нет возможности. Что уж говорить о клиентах, которые захотят приобрести товары или услуги «без заморочек». Проблема решается, если на вашем портале установлена ajax контактная форма обратной связи, которую, кстати говоря, может установить любой человек, не обладающий какими-то особыми знаниями в компьютерах.
Похожие статьи:





