Проект mySockLib
Краткое предисловие:
Сегодня я решил опубликовать одну из своих сетевых библиотек, написанную ещё давно для облегчённой работы с сокетами.
Эта библиотека довольно сильно облегчает создание различных клиентских приложений и не подгружает много лишнего кода.
Описание функционала:
function mySock_Startup: Integer; - инициализирует сетевую библиотеку WinSock2 для дальнейшей работы.
Даже в многопоточном приложении вполне достаточно одного вызова инициализации библиотеки.
После завершения работы с библиотекой должна быть вызвана функция деинциализации
mySock_Cleanup столько же раз,
сколько была вызвана функция инициализации.
function mySock_Open(Host: String; Port: Word): TSocket; - создаёт сокет и выполняет соединение.
В случае успешного вызова возвращает дескриптор сокета, в случае ошибки возвращает
0.
В параметре
Host может быть передано как и доменное имя, так и IP адрес.
function mySock_SetTimeout(Sock: TSocket; millisec: Integer): Integer; - задаёт время жизни сокета в миллисекундах. Например, если вы хотите задать сокету время жизни равное
10 секундам - то параметр
millisec должен быть равен
10000.
После истечения таймаута вы уже не сможете работать с этим сокетом и вам придётся его закрыть.
function mySock_SetBlockMode(Sock: TSocket; NoneBlock: Boolean): Integer; - Несложно догадатьсе, что данная функция задаёт режим работы сокета.
Соответственно, если параметр
NoneBlock будет равен
True - то сокет будет переведён в неблокирующий, в случае с
False - сокет будет переведён в блокирующий режим.
Различия блокирующего и неблокирующего режимов состоят в том, что в блокирующем режиме сокет будет ждать, пока данные отправятся/примуться, что вызывает так сказать блокирование и подвисание программы на этом этапе.
В неблокирующем режиме сокет не будет ждать данных и в случае ошибки сразу же вернёт
-1.
Так как данные всёже не приходят и не уходят мгновенно - то рекомендую перед приёмом/передачей использовать функции
mySock_WaitRecv/mySock_WaitSend соответственно.
function mySock_WaitSend(Sock: TSocket; sec: Integer = 15; millisec: Integer = 0): Boolean; - как я уже сказал выше, данная функция проверяет готовность сокета к отправке данных втечении определённого таймаута.
В случае вызова без параметров
mySock_WaitSend(mySock) - интервал ожидания булет равен
15 секундам.
В случае, если за указанный интервал сокет будет готов к передаче данных - то функция вернёт
True иначе
False
При использовании данной функции вам следует учитывать, что если процесс затянется - то это вызовет блокировку (подвисание) программы на указанный интервал.
Пример вызова для ожидания в
10.450 секунд:
mySock_WaitSend(mySock, 10, 450)
function mySock_WaitRecv(Sock: TSocket; sec: Integer = 15; millisec: Integer = 0): Boolean; - вызов данной функции аналогичен предыдущей, заисключением что она используется для ожидания готовности принять данные.
function mySock_Send(Sock: TSocket; Buff: Pointer; Size: Integer): Integer; - функция предназначена для передачи данных.
В случае успешной передачи вернёт количество переданных
байт.
В случае ошибки вернёт
-1.
Buff - указатель на данные, которые нужно передать.
Size - объём переданных данных в
байтах.
Пример передачи текстового заголовка
req: String:
mySock_Send(mySock, PChar(req), Length(req));
Важно понимать, что если предстоит передать/принять некоторый объём данных - то не обязательно передавать/принимать всё сразу.
Это можно делать порциями, используя промежуточный буффер, равный например
1024 байта
function mySock_Recv(Sock: TSocket; Buff: Pointer; Size: Integer): Integer; - вызов этой функции немного отличается от вызова предыдущей.
Часто мы заранее не знаем, какой объём данных нам предстоит принять.
Поэтому оптимальным решением будет выделить некоторый объём памяти под временный буффер, а после получения всех данных его освободить.
Функция может вернуть целое положительное число в случае удачной приёмки данных,
0 - в случае закрытия сервером сокета и отсутствии данных для приёма и
соответственно
-1 - в случае ошибки.
Пример организации приёмки данных в цикле:
Код:
{ Пытаемся взять память для буфера }
Buff := GetMemory(BuffSize);
if (Buff = nil) then raise Exception.Create('Error! Cannot get memory!');
Size := 0;
Stream.Position := 0;
Resp := '';
try
Repeat
{ В случае таймаута ожидания данных - выходим }
if not mySock_WaitRecv(Sock, TmSec, TmMillisec) then Exit;
{ Чистим память буфера }
ZeroMemory(Buff, BuffSize);
{ Принимаем данные }
iSize := mySock_Recv(Sock, Buff, BuffSize);
{ В случае ошибки - выходим }
if (iSize 0) then
begin
{ Пример записи принятых данных в поток }
Stream.Write(Buff^, iSize);
{ Пример добавления принятых данных в строку }
Resp := Resp + Copy(Buff, 1, iSize);
{ Увеличиваем счётчик всего принятых байт }
Inc(Size, iSize);
end;
Until (iSize = 0);
finally
FreeMemory(Buff);
end;
function mySock_Close(Sock: TSocket): Integer; - закрывает сокет и освобождает все ресурсы, занимаемые им.
function mySock_Cleanup: Integer; - как я уже говорил, эта функция предназначена для инициализации сокетной библиотеки.
Не забывайте, вызывать деинициализацию после окончания пользования библиотекой.
procedure mySock_GetLastError(var ErrCode: Integer; var ErrStr: String); - очень полезная процедура для отладочных целей.
Я постарался собрать наиболее частые сетевые ошибки и составить их описания.
В переменную
ErrCode запишется код последней возникшей ошибки, а в переменную
ErrStr - запишется её описание.
Послесловие:
Ну вот собственно и подошла к логическому финалу моя первая статья.
Надеюсь, что кому-то это окажется полезным.
Я постарался сделать исходник и описание наиболее простыми и понятными. Вы можете дополнить этот исходный код по своему усмотрению. Если что-то будет непонятно - пишите в этой теме, я постараюсь объяснить.
Особую благодарность я хотел бы выразить пользователю под ником Slesh, а также всем, кто помогал мне в освоении темы сетевого программирования.
Исходный код модуля:
Код:
unit mySockLib;
interface
uses
Windows, WinSock2;
type
TSocket = DWORD;
function mySock_Startup: Integer;
function mySock_Open(Host: String; Port: Word): TSocket;
function mySock_SetTimeout(Sock: TSocket; millisec: Integer): Integer;
function mySock_SetBlockMode(Sock: TSocket; NoneBlock: Boolean): Integer;
function mySock_WaitSend(Sock: TSocket; sec: Integer = 15; millisec: Integer = 0): Boolean;
function mySock_WaitRecv(Sock: TSocket; sec: Integer = 15; millisec: Integer = 0): Boolean;
function mySock_Send(Sock: TSocket; Buff: Pointer; Size: Integer): Integer;
function mySock_Recv(Sock: TSocket; Buff: Pointer; Size: Integer): Integer;
function mySock_Close(Sock: TSocket): Integer;
function mySock_Cleanup: Integer;
procedure mySock_GetLastError(var ErrCode: Integer; var ErrStr: String);
implementation
function mySock_Startup: Integer;
var
wData: TWSAData;
begin
Result := WSAStartup($0202, wData);
end;
function mySock_Open(Host: String; Port: Word): TSocket;
var
Sock: TSocket;
HostEnt: PHostEnt;
SockAddr: TSockAddrIn;
begin
Result := 0;
HostEnt := GetHostByName(PChar(Host));
if (HostEnt = nil) then Exit;
Sock := Socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
if (Sock = INVALID_SOCKET) then Exit;
ZeroMemory(@SockAddr, SizeOf(SockAddr));
With SockAddr do
begin
sin_family := AF_INET;
sin_port := HToNS(Port);
With sin_addr, HostEnt^ do
s_addr := PDWORD(h_addr^)^;
end;
if (Connect(Sock, @SockAddr, SizeOf(SockAddr)) <> 0) then
begin
CloseSocket(Sock);
Exit;
end;
Result := Sock;
end;
function mySock_SetTimeout(Sock: TSocket; millisec: Integer): Integer;
var
tv: TTimeVal;
begin
tv.tv_sec := 0;
tv.tv_usec := millisec;
Result := setsockopt(Sock, SOL_SOCKET, SO_RCVTIMEO, @tv, SizeOf(tv));
end;
function mySock_SetBlockMode(Sock: TSocket; NoneBlock: Boolean): Integer;
var
iMode: DWORD;
begin
iMode := DWORD(NoneBlock);
Result := IOCtlSocket(Sock, FIONBIO, iMode);
end;
function mySock_WaitSend(Sock: TSocket; sec: Integer = 15; millisec: Integer = 0): Boolean;
var
FDSet: TFDSet;
tv: TTimeVal;
begin
Result := False;
FD_ZERO(FDSet);
FD_SET(Sock, FDSet);
tv.tv_sec := sec;
tv.tv_usec := millisec * 1000;
if (select(0, nil, @FDSet, nil, @tv) = 1) then Result := True;
end;
function mySock_WaitRecv(Sock: TSocket; sec: Integer = 15; millisec: Integer = 0): Boolean;
var
FDSet: TFDSet;
tv: TTimeVal;
begin
Result := False;
FD_ZERO(FDSet);
FD_SET(Sock, FDSet);
tv.tv_sec := sec;
tv.tv_usec := millisec * 1000;
if (select(0, @FDSet, nil, nil, @tv) = 1) then Result := True;
end;
function mySock_Send(Sock: TSocket; Buff: Pointer; Size: Integer): Integer;
begin
Result := send(Sock, Buff^, Size, 0);
end;
function mySock_Recv(Sock: TSocket; Buff: Pointer; Size: Integer): Integer;
begin
Result := recv(Sock, Buff^, Size, 0);
end;
function mySock_Close(Sock: TSocket): Integer;
begin
Result := CloseSocket(Sock);
end;
procedure mySock_GetLastError(var ErrCode: Integer; var ErrStr: String);
begin
ErrCode := WSAGetLastError;
Case ErrCode of
0: ErrStr := 'Ошибки нет';
WSANOTINITIALISED: ErrStr := 'Нужно сначала вызвать функцию WSASturtup';
WSAENETDOWN: ErrStr := 'Проблема с сетью';
WSAEADDRINUSE: ErrStr := 'Указанный адрес уже используется';
WSAEPROTONOSUPPORT: ErrStr := 'Протокол не поддерживается';
WSAEPROTOTYPE: ErrStr := 'Некорректный протокол для данного типа сокета';
WSAEFAULT: ErrStr := 'Параметры name и namelen не соответствуют данной адресации';
WSAEINPROGRESS: ErrStr := 'Уже выполняется операция в блокирующем режиме';
WSAEINVAL: ErrStr := 'Сокет уже связан с адресом';
WSAESOCKTNOSUPPORT: ErrStr := 'Некорректный сокет для данной адресации';
WSAENOBUFS: ErrStr := 'Недостаточно памяти';
WSAENOTSOCK: ErrStr := 'Неверный дескриптор сокета';
WSAEISCONN: ErrStr := 'Сокет уже подключён';
WSAEMFILE: ErrStr := 'Нет больше доступных дескрипторов';
else ErrStr := 'Неизвестная ошибка';
end;
end;
function mySock_Cleanup: Integer;
begin
Result := WSACleanup;
end;
end.