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



Передача данных при использовании TCP
19 марта 2009

При программировании TCP используются те же функции, что и при программировании UDP, но их поведение при этом иное. Для передачи данных с помощью TCP необходимо сначала установить соединение, и после этого возможен обмен данными только с тем адресом, с которым это соединение установлено. Функция SendTo может использоваться для TCP-сокетов, но её параметры, задающие адрес получателя, игнорируются, а данные отправляются на тот адрес, с которым соединён сокет. Поэтому при отправке данных через TCP обычно используют функцию Send, которая даёт тот же результат. По тем же причинам обычно используется Recv, а не RecvFrom.
В TCP существует разделение ролей взаимодействующих сторон на клиент и сервер. Мы начнём изучение передачи данных в TCP с изучения действий клиента.
Для начала взаимодействия клиент должен соединится с сервером с помощью функции Connect. Мы уже знакомы с этой функцией, но в случае TCP она выполняет несколько иные действия. В данном случае она устанавливает реальное соединение, поэтому её действия начинаются с проверки того, существует ли по указанному адресу серверный сокет, находящийся в режиме ожидания подключения. Функция Connect завершается успешно только в том случае, если соединение установлено, и серверная сторона выполнила все необходимые для этого действия. При использовании Connect в TCP предварительный явный вызов функции Bind также не обязателен.
В отличие от UDP, сокет в TCP нельзя отсоединить или соединить с другим адресом, если он уже соединён. Для нового соединения необходимо использовать новый сокет.
Выше мы говорили, что TCP является надёжным протоколом, т.е. в том случае, если пакет не доставлен, отправляющая сторона уведомляется об этом. Тем не менее, успешное завершение Send, как и в случае UDP, не является гарантией того, что пакет был отослан и дошёл до получателя, а говорит только о том, что данные скопированы в выходной буфер сокета, и на момент копирования сокет был соединён. Если в дальнейшем библиотека сокетов не сможет отправить эти данные или не получит подтверждения об их доставке, соединение будет закрыто, и следующая операция с этим сокетом завершится с ошибкой.
Если выходной буфер сокета равен нулю, данные сразу копируются в сеть, но успешное завершение функции и в этом случае не гарантирует успешную доставку. Использовать нулевой выходной буфер для TCP-сокетов не рекомендуется, т.к. это снижает производительность при последовательной отправке данных небольшими порциями. При буферизации эти порции накапливаются в буфере, а потом отправляются одним большим пакетом, требующим одного подтверждения от клиента. Если же буферизация не используется, будет отправлено несколько мелких пакетов, каждый со своим заголовком и своим подтверждением от клиента, что приведёт к снижению производительности.
Функция Recv копирует пришедшие данные из входного буфера сокета в буфер, заданный параметром Buf, но не более BufLen байт. Скопированные данные удаляются из буфера сокета. При этом все полученные данные сливаются в один поток, поэтому получатель может самостоятельно выбирать, какой объём данных считывать за один раз. Если за один раз была скопирована только часть пришедшего пакета, оставшаяся часть не пропадает, а будет скопирована при следующем вызове Recv. Функция Recv возвращает количество байт, скопированных в буфер. Если на момент её вызова входной буфер сокета пуст, она ждёт, когда там что-то появится, затем копирует полученные данные и лишь после этого возвращает управление вызвавшей её программе. Если Recv возвращает 0, это значит, что удалённый сокет корректно завершил соединение. Если соединение завершено некорректно (например, из-за обрыва кабеля или сбоя удалённого компьютера), функция завершается с ошибкой (т.е. возвращает Socket_Error).
Теперь рассмотрим, какие действия должен выполнить сервер при использовании TCP. Как мы уже говорили выше, сервер должен перевести сокет в режим ожидания соединения. Это делается с помощью функции Listen, имеющей следующий прототип:
function Listen(S:TSocket;BackLog:Integer):Integer;
Параметр S задаёт сокет, который переводится в режим ожидания подключения. Этот сокет должен быть привязан к адресу, т.е. функция Bind должна быть вызвана для него явно. Для сокета, находящегося в режиме ожидания, создаётся очередь подключений. Размер этой очереди определяется параметром BackLog . Если этот параметр равен SoMaxConn , очередь будет иметь максимально возможный размер. В MSDN'е отмечается, что узнать максимально допустимый размер очереди стандартными средствами нельзя. Функция возвращает ноль в случае успешного завершения и Socket_Error в случае ошибки.
Когда клиент вызывает функцию Connect , и по указанному в ней адресу имеется сокет, находящийся в режиме ожидания подключения, то информация о клиенте помещается в очередь подключений этого сокета. Успешное завершение Connect говорит о том, что на стороне сервера подключение добавлено в очередь. Однако для того, чтобы соединение было действительно установлено, сервер должен выполнить ещё некоторые действия, а именно: извлечь из очереди соединений информацию о соединении и создать сокет для его обслуживания. Эти действия выполняются с помощью функции Accept , имеющей следующий прототип:
function Accept(S:TSocket;Addr:PSockAddr;AddrLen:PInteger):TSocket;
Параметр S задаёт сокет, который находится в режиме ожидания соединения и из очереди которого извлекается информация о соединении. Выходной параметр Addr позволяет получить адрес клиента, установившего соединение. Здесь должен быть передан указатель на буфер, в который этот адрес будет помещён. Параметр AddrLen содержит указатель на переменную, в которой хранится длина этого буфера: до вызова функции эта переменная должна содержать фактическую длину буфера, задаваемого параметром Addr , после вызова - количество байт буфера, реально понадобившихся для хранения адреса клиента. Очевидно, что при использовании TCP и входное, и выходное значение этой переменной должно быть равно SizeOf(TSockAddr). Эти параметры передаются как указатели, а не как параметры-переменные, что было бы более естественно для Delphi, потому что библиотека сокетов допускает для этих указателей нулевые значения, если сервер не интересует адрес клиента. В данном случае разработчики модуля WinSock сохранили полную функциональность, предоставляемую данной библиотекой.
В случае ошибки функция Accept возвращает значение Invalid_Socket . В случае успешного завершения возвращается дескриптор сокета, созданного библиотекой сокетов и предназначенного для обслуживания данного соединения. Этот сокет уже привязан к адресу и соединён с сокетом клиента, установившего соединение, и его можно использовать в функциях Recv и Send без предварительного вызова каких-либо других функций. Уничтожается этот сокет обычным образом, с помощью CloseSocket .
Исходный сокет, определяемый параметром S , остаётся в режиме прослушивания. Если сервер поддерживает одновременное соединение с несколькими клиентами, функция Accept может быть вызвана многократно. Каждый раз при этом будет создаваться новый сокет, обслуживающий одно конкретное соединение: протокол TCP и библиотека сокетов гарантируют, что данные, посланные клиентами, попадут в буферы соответствующих сокетов и не будут перемешаны.
Для получения целостной картины кратко повторим вышесказанное. Для установления соединения сервер должен, во-первых, создать сокет с помощью функции Socket , а во-вторых, привязать его к адресу с помощью функции Bind . Далее сокет должен быть переведён в режим ожидания с помощью функции Listen , а потом с помощью функции Accept создаётся новый сокет, обслуживающий соединение, установленное клиентом. После этого сервер может обмениваться данными с клиентом. Клиент же должен создать сокет, при необходимости привязки к конкретному порту вызвать Bind , и затем вызвать Connect для установления соединения. После успешного завершения этой функции клиент может обмениваться данными с сервером. Это иллюстрируется приведёнными ниже примерами.
Код сервера:
var S,AcceptedSock:TSocket;
Addr:TSockAddr;
Data:TWSAData;
Len:Integer;
begin
WSAStartup($101,Data);
S:=Socket(AF_Inet,Sock_Stream,0);
Addr.sin_family:=PF_Inet;
Addr.sin_port:=HToNS(3030);
Addr.sin_addr.S_addr:=InAddr_Any;
FillChar(Addr.Sin_Zero,SizeOf(Addr.Sin_Zero),0);
Bind(S,Addr,SizeOf(TSockAddr));
Listen(S,SoMaxConn);
Len:=SizeOf(TSockAddr);
AcceptedSock:=Accept(S,@Addr,@Len);
{ Теперь Addr содержит адрес клиента, с которым установлено
соединение, а AcceptedSock - дескриптор, обслуживающий это
соединение. Допустимы следующие действия:
Send(AcceptedSock,…) - отправить данные клиенту
Recv(AcceptedSock,…) - получить данные от клиента
Accept(…) - установить соединение с новым клиентом }
Здесь сокет сервера привязывается к порту с номером 3030. В общем случае разработчик сервера сам должен выбрать порт из диапазона 1024-65535.
Код клиента:
var S:TSocket;
Addr:TSockAddr;
Data:TWSAData;
begin
WSAStartup($101,Data);
S:=Socket(AF_Inet,Sock_Stream,0);
Addr.sin_family:=AF_Inet;
Addr.sin_port:=HToNS(3030);
Addr.sin_addr.S_addr:=Inet_Addr(…);
FillChar(Addr.Sin_Zero,SizeOf(Addr.Sin_Zero),0);
Connect(S,Addr,SizeOf(TSockAddr));
{ Теперь соединение установлено. Допустимы следующие действия:
Send(S,…) - отправить данные серверу
Recv(S,…) - получить данные от сервера }
В приведённом выше коде для краткости опущены проверки результатов функций с целью обнаружения ошибок. При написании серьёзных программ этим пренебрегать нельзя.
Если на момент вызова функции Accept очередь соединений пуста, то нить, вызвавшая её, блокируется до тех пор, пока какой-либо клиент не подключится к серверу. С одной стороны, это удобно: сервер может не вызывать функцию Accept в цикле до тех пор, пока она не завершится успехом, а вызвать её один раз и ждать, когда подключится клиент. С другой стороны, это создаёт проблемы тем серверам, которые должны взаимодействовать с несколькими клиентами. Действительно, пусть функция Accept успешно завершилась и в распоряжении программы оказались два сокета: находящийся в режиме ожидания новых подключений и созданный для обслуживания уже существующего подключения. Если вызвать Accept, то программа не сможет продолжать работу до тех пор, пока не подключится ещё один клиент, а это может произойти через очень длительный промежуток времени или вообще никогда не произойти. Из-за этого программа не сможет обрабатывать вызовы уже подключившегося клиента. С другой стороны, если функцию Accept не вызывать, сервер не сможет обнаружить подключение новых клиентов. Для решения этой проблемы библиотека сокетов предлагает средства, которые мы рассмотрим ниже (а библиотека Windows Sockets предлагает для этого свои средства, которые будут рассмотрены в следующей статье). Здесь же я хочу предложить довольно популярный способ её решения, использующий средства не библиотеки сокетов, а операционной системы. Он заключается в использовании отдельной нити для обслуживания каждого из клиентов. Каждый раз, когда клиент подключается, функция Accept передаёт управление программе, возвращая новый сокет. Здесь сервер может породить новую нить, которая предназначена исключительно для обмена данными с новым клиентом. Старая нить после этого снова вызывает Accept для старого сокета, а новая - функции Recv и Send для нового сокета. Такой метод решает заодно и проблемы, связанные с тем, что функции Send и Recv также могут блокировать работу программы и помешать обмену данными с другими клиентами. В данном случае будет блокирована только одна нить, обменивающаяся данными с одним из клиентов, а остальные нити продолжат свою работу.


Теги: asus socket, IP, borland delphi, User Datagram Protocol, Unix, SQL-сервер Borland Delphi

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

Компонент TQRChildBand
TryExclusive
ППП автоматизированного проектирования
Электронная доска объявлений
DbfFile
ClearCalcFields
OpenMode
Метод NewColumn
Протокол UDP
Свойство ShowProgress
Дополнительные функции
MasterFields
Свойство Bands для TQRSubDetail
Свойство Device
Перекрытый ввод-вывод
| 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 | Передача данных при использовании TCP. Регион сайта: Москва и Санкт-Петербург