* При перепечатке материалов ссылка на www.SeoLiga.ru обязательна! RSS



Создание сокета
23 февраля 2009

До сих пор мы обсуждали только теоретические аспекты использования сокетов. С этого раздела начинается практическая часть статьи: будут рассматриваться конкретные функции, позволяющие осуществлять те или иные операции с сокетами. Эти функции экспортируются системной библиотекой wsock32.dll (а также библиотекой ws2_32.dll; взаимоотношение этих библиотек будет обсуждаться в следующей статье цикла), для их использования в Delphi в раздел uses нужно добавить стандартный модуль WinSock. Я не буду приводить здесь исчерпывающего описания функций, так как лучше, чем это сделано в MSDN'е, мне не написать, но буду давать описание, достаточно полное для понимания назначения функции, а также обращать внимание на некоторые моменты, которые в MSDN'е найти трудно. Поэтому я настоятельно рекомендую в дополнение к этой статье внимательно прочитать то, что написано в MSDN'е о каждой из упомянутых мною функций.
В самом начале статьи говорилось, что здесь мы будем обсуждать только функции стандартной библиотеки сокетов, а функции, специфичные для Windows, оставим для следующей статьи. Тем не менее, есть три функции, отсутствующие в стандартной библиотеке, знать которые необходимо. Это функции WSAStartup, WSACleanup и WSAGetLastError (префикс WSA означает Windows Sockets API и используется для именования большинства функций, относящихся к Windows-расширению библиотеки сокетов).
Функция WSAStartup используется для инициализации библиотеки сокетов. Эту функцию необходимо вызвать до вызова любой другой функции из этой библиотеки. Её прототип имеет вид:
function WSAStartup(wVersionRequired:Word;var WSData:TWSAData): Integer;
Параметр wVersionRequired задаёт требуемую версию библиотеки сокетов. Младший байт задаёт основную версию, старший - дополнительную. Допустимы версии 1.0 ($0001), 1.1 ($0101), 2.0 ($0002) и 2.2 ($0202). Пока мы используем стандартные сокеты, принципиальной разницы между этими версиями нет, но версии 2.0 и выше лучше не использовать, т.к. модуль WinSock не рассчитан на их поддержку. Вопросы взаимоотношения библиотек и версий будут рассматриваться в следующей статье данного цикла, а пока остановимся на том, что будем всегда использовать версию 1.1.
Параметр WSData является выходным параметром, т.е. значение, которое имела переменная до вызова функции, игнорируется, а имеет смысл только то значение, которая эта переменная получит после вызова функции. Через этот параметр передаётся дополнительная информация о библиотеке сокетов. В большинстве случаев эта информация не представляет никакого интереса, поэтому её можно игнорировать.
Нулевое значение, возвращаемое функцией, говорит об успешном завершении, в противном случае возвращается код ошибки.
Функция WSACle anup завершает работу с библиотекой сокетов. Эта функция не имеет параметров и возвращает ноль в случае успешного завершения или код ошибки в противном случае.
Функцию WSAStartup достаточно вызвать один раз, даже в многонитевом приложении. В этом её отличие от таких функций, как, например, CoInitialize , которая должна быть вызвана в каждой нити, использующей COM. Функцию можно вызывать повторно - в этом случае её вызов не даёт никакого эффекта, но для завершения работы с библиотекой сокетов функция WSACleanup должна быть вызвана столько же раз, сколько была вызвана WSAStartup.
Большинство функций библиотеки сокетов возвращают значение, позволяющее судить только об успешном или неуспешном завершении операции, но не дающее информации о том, какая именно ошибка произошла (если она произошла). Для получения информации об ошибке служит функция WSAGetLastError , не имеющая параметров и возвращающая целочисленный код последней ошибки, произошедшей в библиотеке сокетов в данной нити. После неудачного завершения функции из библиотеки сокетов следует вызывать функцию WSAGetLastError , чтобы выяснить причину неудачи.
Забегая чуть вперёд, отмечу, что библиотека сокетов содержит стандартную функцию GetSockOpt , которая, кроме всего прочего, также позволяет получить информацию об ошибке. Однако она менее удобна в использовании, поэтому в тех случаях, когда не требуется совместимость с другими платформами, лучше использовать WSAGetLastError . К тому же, GetSockOpt возвращает ошибку, связанную с указанным сокетом, поэтому с её помощью нельзя получить код ошибки, не связанной с конкретным сокетом.
Для создания сокета используется стандартная функция Socket со следующим прототипом:
Function Socket(AF,SocketType,Protocol:Integer):TSocket;
Параметр AF задаёт семейство адресов (address family). Этот параметр определяет, какой способ адресации (т.е., по сути дела, какой стек протоколов) будет использоваться для данного сокета. При использовании TCP/IP этот параметр должен быть равен AF_Inet, для других стеков также есть соответствующие константы, которые можно посмотреть в файле WinSock.pas.
Параметр SocketType указывает на тип сокета и может принимать одно из двух значений: Sock_Stream (сокет используется для потоковых протоколов) и Sock_Dgram (сокет используется для дейтаграммных протоколов).
Параметр Protocol позволяет указать, какой именно протокол будет использоваться сокетом. Этот параметр можно оставить равным нулю - тогда будет выбран протокол по умолчанию, отвечающий заданным первыми двумя параметрами. Для стека TCP/IP потоковым протоколом по умолчанию является TCP, дейтаграммным - UDP. В некоторых примерах можно увидеть, что значение третьего параметра равно IPProto_IP . Значение этой константы равно 0, и её использование только повышает читабельность кода, но приводит к тому же результату: будет выбран протокол по умолчанию. Если нужно использовать протокол, отличный от протокола по умолчанию (например, на базе IP существует протокол RDP - Reliable Datagram Protocol, надёжный дейтаграммный протокол), следует указать здесь соответствующую константу (для RDP это будет IPProto_RDP ). Можно также явно указать на использование TCP или UDP с помощью констант IPProto_TCP и IPProto_UDP соответственно.
Тип TSocket предназначен для хранения дескриптора сокета. Формально он совпадает с 32-битным беззнаковым целым типом, но об этом лучше не вспоминать, т.к. любые операции над значениями типа TSocket бессмысленны. Значение, возвращаемое функцией Socket, следует сохранить в переменной соответствующего типа и затем использовать для идентификации сокета при вызове других функций. Если по каким-то причинам создание сокета невозможно, функция вернёт значение Invalid_Socket . Причину ошибки можно узнать с помощью функции WSAGetLastError .
Сокет, созданный с помощью функции Socket, не привязан ни к какому адресу. Привязка осуществляется с помощью функции Bind, имеющей следующий прототип:
function Bind(S:TSocket;var Addr:TSockAddr;NameLen:Integer):Integer;
Первый параметр этой функции - дескриптор сокета, который привязывается к адресу. Здесь, как и в остальных подобных случаях, требуется передать значение, которое вернула функция Socket. Второй параметр содержит адрес, к которому требуется привязать сокет, а третий - длину структуры, содержащей адрес.
Функция Bind предназначена для сокетов, реализующих разные протоколы из разных стеков, поэтому кодирование адреса в ней сделано достаточно универсальным. Впрочем, надо отметить, что разработчики модуля WinSock для Delphi выбрали не лучший способ перевода прототипа этой функции на Паскаль, поэтому универсальность в значительной мере утрачена. В оригинале прототип функции Bind имеет следующий вид:
int bind(SOCKET s,const struct sockaddr FAR* name,int namelen);
Видно, что второй параметр - это указатель на структуру sockaddr. Однако компилятор C/C++ позволяет при вызове функции в качестве параметра использовать указатель на любую другую структуру, если будет выполнено явное приведение типов. Для каждого семейства адресов используется своя структура, и в качестве фактического параметра передаётся указатель на эту структуру. Если бы авторы модуля WinSock описали второй параметр как параметр-значение типа указатель, можно было бы поступать точно так же. Однако они описали этот параметр как параметр-переменную. В результате на двоичном уровне ничего не изменилось: и там, и там в стек помещается указатель. Однако компилятор при вызове функции Bind не допустит использование никакой другой структуры, кроме TSockAddr , а эта структура не универсальна и удобна, по сути дела, только при использовании стека TCP/IP. В других случаях наилучшим решением будет самостоятельно импортировать функцию Bind из wsock32.dll с нужным прототипом. При этом придётся импортировать и некоторые другие функции, работающие с адресами. Впрочем, мы в данной статье ограничиваемся только протоколами TCP и UDP, поэтому больше останавливаться на этом вопросе не будем.
В стандартной библиотеке сокетов (т.е. в заголовочных файлах для этой библиотеки, входящих в SDK) полагается, что адрес кодируется структурой sockaddr длиной 16 байт, причём первые два байта этой структуры кодируют семейство протоколов, а смысл остальных зависит от этого семейства. В частности, для стека TCP/IP семейство протоколов задаётся константой PF_Inet. (Выше мы уже встречались с термином "семейство адресов" и константой AF_Inet. В ранних версиях библиотеки сокетов семейства протоколов и семейства адресов были разными понятиями, но затем эти понятия слились в одно, и константы AF_XXX и PF_XXX стали взаимозаменяемыми.) Остальные 14 байт структуры sockaddr занимает массив типа char (тип char в C/C++ соответствует одновременно двум типам Delphi: Char и ShortInt). В принципе, в стандартной библиотеке сокетов предполагается, что структура, задающая адрес, всегда имеет длину 16 байт, но на всякий случай предусмотрен третий параметр функции Bind, который хранит длину структуры. В Windows Sockets длина структуры может быть любой (это зависит от протокола), так что этот параметр, в принципе, может пригодится.
Выше мы уже касались вопроса о том, что неструктурированное представление адреса в виде массива из 14 байт бывает неудобно, и поэтому для каждого семейства протоколов предусмотрена своя структура, учитывающая особенности адреса. В частности, для протоколов стека TCP/IP используется структура sockaddr_in, размер которой также составляет 16 байт. Из них используется только восемь: два для кодирования семейства протоколов, четыре для IP-адреса и два - для порта. Оставшиеся 8 байт не используются и должны содержать нули.
Можно было бы предположить, что типы TSockAddr и TSockAddrIn , описанные в модуле WinSock, соответствуют структурам sockaddr и sockaddr_in , однако это не так. На самом деле эти типы описаны следующим образом:
type SunB=packed record
s_b1,s_b2,s_b3,s_b4:u_char;
end;

SunW=packed record
s_w1,s_w2:u_short;
end;

in_addr=record
case Integer of
0:(S_un_b:SunB);
1:(S_un_w:SunW);
2:(S_addr:u_long);
end;
TInAddr=in_addr;

sockaddr_in=record
case Integer of
0:(sin_family:u_short;
sin_port:u_short;
sin_addr:TInAddr;
sin_zero:array[0..7] of Char);
1:(sa_family:u_short;
sa_data: array[0..13] of Char)
end;

TSockAddrIn=sockaddr_in;
TSockAddr=sockaddr_in;
Таким образом, типы TSockAddr и TSockAddrIn являются синонимами типа sockaddr_in, но не того sockaddr_in, который имеется в стандартной библиотеке сокетов, а типа sockaddr_in, описанного в модуле WinSock. А sockaddr_in из WinSock является вариантной записью, и в случае 0 соответствует типу sockaddr_in из стандартной библиотеки сокетов, а в случае 1 - типу sockaddr из этой же библиотеки. Вот такая несколько запутанная ситуация, хотя на практике всё выглядит не так страшно.
Перейдём, наконец, к более жизненному вопросу: какими значениями нужно заполнять переменную типа TSockAddr, чтобы при передаче её в функцию Bind сокет был привязан к нужному адресу. Так как мы ограничиваемся рассмотрением протоколов TCP и UDP, нас не интересует та часть вариантной записи sockaddr_in, которая соответствует случаю 1, т.е. мы будем рассматривать только те поля этой структуры, которые имеют префикс sin.
Поле sin_zero, очевидно, должно содержать массив нулей. Это то самое поле, которое не несёт никакой смысловой нагрузки и служит только для увеличения размера структуры до стандартных 16 байт. Поле sin_family должно иметь значение PF_Inet. В поле sin_port записывается номер порта, к которому привязывается сокет. Номер порта должен быть записан в сетевом формате, т.е. здесь нужно использовать функцию HtoNS, чтобы из привычной нам записи номера порта получить число в нужном формате. Номер порта можно оставить нулевым - тогда система выберет для сокета свободный порт с номером от 1024 до 5000.
IP-адрес для привязки сокета задаётся полем sin_addr, которое имеет тип TInAddr. Этот тип сам является вариантной записью, которая отражает три способа задания IP-адреса: в виде 32-битного числа, в виде двух 16-битных чисел или в виде четырёх 8-битных чисел. На практике чаще всего используется формат в виде четырёх 8-битных чисел, реже - в виде 32-битного числа. Случаи использования формата из двух 16-битных чисел мне неизвестны.
Пусть у нас есть переменная Addr типа TSockAddr , и нам требуется в её поле sin_addr записать адрес 192.168.200.217. Это можно сделать следующим образом:
Addr.sin_addr.S_un_b.s_b1:=192;
Addr.sin_addr.S_un_b.s_b2:=168;
Addr.sin_addr.S_un_b.s_b3:=200;
Addr.sin_addr.S_un_b.s_b4:=217;
Существует альтернатива такому присвоению четырёх полей по отдельности - функция Inet_Addr . Эта функция в качестве входного параметра принимает строку, в которой записан IP-адрес, и возвращает этот IP-адрес в формате 32-битного числа. С использованием функции Inet_Addr вышеприведённый код можно переписать так:
Addr.sin_addr.S_addr:=Inet_Addr('192.168.200.217');
Функция Inet_Addr выполняет простой парсинг строки и не проверяет, существует ли такой адрес на самом деле. Поля адреса можно задавать в десятичном, в восьмеричном и в шестнадцатеричном форматах. Восьмеричное поле должно начинаться с нуля, шестнадцатеричное - с "0x". Приведённый выше адрес можно записать в виде "0300.0250.0310.0331" (восьмеричный) или "0xC0.0xA8.0xC8.0xD9" (шестнадцатеричный). Допускается также смешанный формат записи, в котором разные поля заданы в разных системах исчисления. Функция Inet_Addr поддерживает также менее распространённые форматы записи IP-адреса в виде трёх полей. Подробнее об этом можно прочитать в MSDN'е.
В библиотеке сокетов предусмотрена константа InAddr_Any , позволяющая не указывать явно адрес в программе, а оставить его выбор на усмотрение системы. Для этого надо полю sin_addr.S_addr присвоить значение InAddr_Any . Если IP-адрес компьютеру не назначен, при использовании этой константы сокет будет привязан к локальному адресу 127.0.0.1. Если компьютеру назначен один IP-адрес, сокет будет привязан к этому адресу. Если компьютеру назначено несколько IP-адресов, то будет выбран один из них, причем сама привязка при этом отложится до установления соединения (в случае TCP) или до первой отправки данных через сокет (в случае UDP). Выбор конкретного адреса при этом зависит от того, какой адрес имеет удалённая сторона.
Итак, резюмируем вышесказанное. Пусть у нас есть сокет S, который надо привязать к адресу 192.168.200.217 и порту 3320. Для этого нужно выполнить следующий код:
Addr.sin_family:=PF_Inet;
Addr.sin_addr.S_addr:=Inet_Addr('192.168.200.217');
Addr.sin_port:=HtoNS(3320);
FillChar(Addr.sin_zero,SizeOf(Addr.sin_zero),0);
if Bind(S,Addr,SizeOf(Addr))=Socket_Error then
begin
// какая-то ошибка, анализируем с помощью WSAGetLastError
end;
Процедура FillChar - это стандартная процедура Паскаля, заполняющая некоторую область памяти заданным значением. В данном случае мы используем её для заполнения нулями поля sin_zero. Для этой же цели можно было бы использовать функцию WinAPI ZeroMemory . В примерах на C/C++ для этой же цели нередко используется функция memset.
Теперь рассмотрим другой случай: пусть выбор адреса и порта можно оставить на усмотрение системы. Тогда код будет выглядеть следующим образом:
Addr.sin_family:=PF_Inet;
Addr.sin_addr.S_addr:=InAddr_Any;
Addr.sin_port:=0;
FillChar(Addr.sin_zero,SizeOf(Addr.sin_zero),0);
if Bind(S,Addr,SizeOf(Addr))=Socket_Error then
begin
// какая-то ошибка, анализируем с помощью WSAGetLastError
end;
При использовании TCP сервер сам не является инициатором подключения, но может работать с любым подключившимся клиентом, какой бы у него ни был адрес. Для сервера принципиально, какой порт он будет использовать - если порт не определён заранее, клиент не будет знать, куда подключаться. Поэтому номер порта является важным признаком для сервера. (Иногда, впрочем, встречаются серверы, порт которых заранее неизвестен, но в таких случаях всегда существует другой канал передачи данных, позволяющий клиенту до подключения узнать, какой порт используется в данный момент сервером.) С другой стороны, клиенту обычно непринципиально, какой порт будет у его сокета, поэтому чаще всего сервер использует фиксированный порт, а клиент оставляет выбор системе.
Протокол UDP не поддерживает соединение, но при его использовании часто одно приложение тоже можно условно назвать сервером, а другое - клиентом. Сервер создаёт сокет и ждёт, когда кто-нибудь что-нибудь пришлёт и высылает что-то в ответ, а клиент сам отправляет что-то куда-то. Поэтому, как и в случае TCP, сервер должен использовать фиксированный порт, а клиент может выбирать любой свободный.
Если у компьютера только один IP-адрес, то выбор адреса для сокета и клиент, и сервер могут доверить системе. Если компьютер имеет несколько интерфейсов к одной сети, и каждый имеет свой IP-адрес, выбор конкретного адреса в большинстве случаев также непринципиален и может быть оставлен на усмотрение системы. Проблемы возникают, когда у компьютера несколько сетевых интерфейсов, каждый из которых включен в свою сеть. В этом случае выбор того или иного IP-адреса для сокета привязывает его к одной из сетей, и только к одной. Поэтому нужно принять меры для того, чтобы сокет оказался привязан к той сети, в которой находится его адресат.
Выше я уже говорил, что в системах с несколькими сетевыми картами привязка сокета к адресу в том случае, когда его выбор доверен системе, может осуществляться не во время выполнения функции Bind, а позже, когда системе станет понятно, зачем используется этот сокет. Например, когда TCP-клиент осуществляет подключение к серверу, система по адресу этого сервера определяет, через какую карту должен идти обмен, и выбирает соответствующий адрес. То же самое происходит с UDP-клиентом: когда он отправляет первую дейтаграмму, система по адресу получателя определяет, к какой карте следует привязать сокет. Поэтому клиент и в данном случае может оставить выбор адреса на усмотрение системы. С серверами всё несколько сложнее. Система привязывает сокет UDP-сервера к адресу, он ожидает получения пакета. В этот момент система не имеет никакой информации о том, с какими узлами будет вестись обмен через данный сокет, и может выбрать не тот адрес, который нужен. Поэтому сокеты UDP-серверов, работающих в подобных системах, должны явно привязываться к нужному адресу. Сокеты TCP-серверов, находящиеся в режиме ожидания и имеющие адрес InAddr_Any, допускают подключение к ним по любому сетевому интерфейсу, который имеется в системе. Сокет, который создаётся таким сервером при подключении клиента, будет автоматически привязан к IP-адресу того сетевого интерфейса, через который осуществляется взаимодействие с подключившимся клиентом. Таким образом, сокеты, созданные для взаимодействия с разными клиентами, могут оказаться привязанными к разным адресам.
После успешного завершения функций Socket и Bind сокет создан и готов к работе. Дальнейшие действия с ним зависят от того, какой протокол он реализует и для какой роли предназначен. Мы разберём эти операции в разделах, посвящённых соответствующим протоколам. Там же мы увидим, что в некоторых случаях можно обойтись без вызова функции Bind - она будет неявно вызвана при вызове других функций библиотеки сокетов.
Когда сокет больше не нужен, необходимо освободить связанные с ним ресурсы. Это выполняется в два этапа: сначала сокет "выключается", а потом закрывается.
Для выключения сокета используется функция Shutdown , имеющая следующий прототип:
function Shutdown(S:TSocket;How:Integer):Integer;
Параметр S определяет сокет, который необходимо выключить, параметр How может принимать значения SD_Receive , SD_Send или SD_Both . Функция возвращает ноль в случае успешного выполнения и Socket_Error в случае ошибки.
Вызов функции с параметром SD_Receive запрещает чтение данных из входного буфера сокета. Однако на уровне протокола вызов этой функции игнорируется: дейтаграммы UDP и пакеты TCP, посланные данному сокету, продолжают помещаться в буфер, хотя программа уже не может их оттуда забрать.
При использовании параметра SD_Send функция запрещает отправку данных через сокет. При использовании протокола TCP при этом удалённый сокет получает специальный сигнал, предусмотренный данным протоколом, уведомляющий о том, что больше данные посылаться не будут. Если на момент вызова Shutdown в буфере для исходящих остаются данные, сначала посылаются они, а потом только сигнал о завершении. Протокол UDP подобных сигналов не предусматривает, поэтому при использовании этого протокола Shutdown просто запрещает библиотеке сокетов использовать указанный сокет для отправки данных.
Параметр SD_Both позволяет одновременно запретить и приём, и передачу данных через сокет.
Примечание: модуль WinSock до пятой версии Delphi включительно содержит ошибку - в нём не определены константы SD_XXX. Чтобы использовать их в своей программе, надо объявить их следующим образом:
const SD_Receive=0;
SD_Send=1;
SD_Both=2;
Для освобождения ресурсов, связанных с сокетом, используется функция CloseSocket . Эта функция освобождает память, выделенную для буферов, и порт. Её единственный параметр задаёт сокет, который требуется закрыть, а возвращаемое значение - ноль или Socket_Error. После вызова этой функции соответствующий дескриптор сокета перестаёт иметь смысл, и использовать его больше нельзя.
По умолчанию функция CloseSocket немедленно возвращает управление вызвавшей её программе, а процесс закрытия сокета начинает выполняться на заднем плане. Под закрытием подразумевается не только освобождение ресурсов, но и отправка данных, которые остались в выходном буфере сокета. Вопрос о том, как изменить поведение функции CloseSocket , будет обсуждаться в разделе "Параметры сокета". Если сокет закрывается одной нитью в тот момент, когда другая нить пытается выполнить какую-либо операцию с этим сокетом, то эта операция завершается с ошибкой.
Функция Shutdown нужна в первую очередь для того, чтобы заранее сообщить партнёру по связи о намерении завершить связь, причём это имеет смысл только для протоколов, поддерживающих соединение. При использовании UDP функцию Shutdown вызывать практически бессмысленно, можно сразу вызывать CloseSocket. При использовании TCP удалённая сторона получает сигнал о выключении партнёра, но стандартная библиотека сокетов не позволяет программе обнаружить его получение (такие функции есть в Windows Sockets, о чём мы будем говорить в следующей статье). Но этот сигнал может быть важен для внутрисистемных функций, реализующих сокеты. Windows-версия библиотеки сокетов относится к отсутствию данного сигнала достаточно либерально, поэтому вызов Shutdown в том случае, когда и клиент, и сервер работают под управлением Windows, не обязателен. Но реализации TCP в других системах не всегда столь же снисходительно относятся к подобной небрежности. Результатом может стать долгое (до двух часов) "подвешенное" состояние сокета в той системе, когда с ним и работать уже нельзя, и информации об ошибке программа не получает. Поэтому при использовании TCP лучше не пренебрегать вызовом Shutdown, чтобы сокет на другой стороне не имел проблем.
MSDN рекомендует следующий порядок закрытия TCP-сокета. Во-первых, сервер не должен закрывать свой сокет по собственной инициативе, он может это делать только после того, как был закрыт связанный с ним клиентский сокет. Клиент начинает закрытие сокета с вызова Shutdown с параметром SD_Send . Сервер после этого сначала получает все данные, которые оставались в буфере сокета клиента, а затем получает от клиента сигнал о завершении передачи. Тем не менее, сокет клиента продолжает работать на приём, поэтому сервер при необходимости может на этом этапе послать клиенту какие-либо данные, если это необходимо. Затем сервер вызывает Shutdown с параметром SD_Send , и сразу после этого - CloseSocket . Клиент продолжает читать данные из входящего буфера сокета до тех пор, пока не будет получен сигнал о завершении передачи сервером. После этого клиент также вызывает CloseSocket . Такая последовательность гарантирует, что данные не будут потеряны, но, как мы уже обсуждали выше, она не может быть реализована в рамках стандартных сокетов из-за невозможности получить сигнал о завершении передачи, посланный удалённой стороной. Поэтому следует использовать упрощённый способ завершения связи: клиент вызывает Shutdown с параметром SD_Send или SD_Both , и сразу после этого - CloseSocket . Сервер при попытке выполнить операцию с сокетом получает ошибку, после которой также вызывает CloseSocket . Вызов Shutdown на стороне сервера при этом не нужен, т.к. в этот момент соединение уже потеряно, и высылать данные из буфера вместе с сигналом завершения уже некуда.


Теги: CloseSocket, Shutdown, Создание сокета Borland Delphi

Статьи по теме:

Электронная почта. Структура электронной почты, её возможности
Компонент TQRBand
Свойство PaperSize
Структура системы электронного документооборота
Простая функция Паскаля
Протокол TCP
Свойство PixelsPerX
Свойство AutoStretch
Технологии открытых систем
Компонент TQRLabel
Использование выражений
RepageIndexFile
Version
Компонент TQRImage
Метод AddPrintable
| Borland Delphi | Alex |
 


Пн Вт Ср Чт Пт Сб Вс
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30


     



Rambler's Top100

Данный сайт или домен продается ICQ: 403-353-727

© 2009 Seoliga.ru | Borland Delphi | Создание сокета. Регион сайта: Москва и Санкт-Петербург