Проект 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 - в случае ошибки. Пример организации приёмки данных в цикле: Code: { Пытаемся взять память для буфера } 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 Exit; { Если данные были приняты - то дописываем данные к уже принятым и увеличиваем счётчик всего принятых байт } 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, а также всем, кто помогал мне в освоении темы сетевого программирования. Исходный код модуля: Code: 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.
Кода немного, и это хорошо. Маленький модуль, который справляеться с поставленной целью. Для новичков сойдет! Настоятельно рекомендую избавиться от SysUtils в uses, он здесь ни к чему.