Текст книги "Операционная система UNIX"
Автор книги: Андрей Робачевский
Жанр:
ОС и Сети
сообщить о нарушении
Текущая страница: 33 (всего у книги 39 страниц)
После создания виртуального канала взаимодействующие процессы получают возможность обмениваться данными в дуплексном режиме.
Хотя фактически передача данных осуществляется в виде сегментов, ее логический вид представляет собой последовательный поток октетов, каждый из которых адресуется порядковым номером. Каждый сегмент хранит в заголовке порядковый номер первого октета данных. Данные буферизуются обоими коммуникационными узлами TCP-канала. Как правило, модуль TCP самостоятельно принимает решение, когда именно сформировать сегмент для отправки и когда передать полученные данные процессу– адресату.
В случае, когда требуется немедленная передача данных, без ожидания заполнения сегмента определенного размера, протокол верхнего уровня (приложение) устанавливает флаг PSH
, который указывает модулю TCP на необходимость немедленной доставки данных, находящихся в очереди на отправление. Это может потребоваться, например, при передаче пользовательского ввода при удаленном доступе (протокол Telnet).
Как уже говорилось, протокол TCP обеспечивает надежный последовательный виртуальный канал передачи данных между приложениями. Поскольку нижележащий сетевой протокол IP является по определению ненадежным, а среда передачи вносит дополнительные ошибки, переданные данные могут быть утеряны, продублированы или испорчены, при этом порядок их доставки может быть нарушен. В случае ошибочности полученного сегмента модуль TCP узнает об этом, проверив контрольную сумму. Другие ошибки являются более сложными, и TCP должен обеспечить их определение и исправление.
Рассмотренные выше порядковый номер и номер подтверждения играют ключевую роль в обеспечении надежности доставки. По существу порядковый номер адресует каждый октет логического потока данных между источником и получателем, позволяя последнему определить правильность доставки (порядок доставки и потерю отдельных октетов). TCP является протоколом с позитивным подтверждением и повторной передачей (Positive Acknowledgement and Retransmission, PAR). Это означает, что если данные доставлены без ошибок, получатель подтверждает это сегментом ACK
. Если отправитель не получает подтверждения в течение некоторого времени, он повторно посылает данные. В любом случае отсутствует негативное подтверждение (NAK).
В качестве примера рассмотрим передачу данных между двумя хостами сети А и В, проиллюстрированную на рис. 6.13. Для простоты предположим симплексную передачу большого количества данных от хоста А к B. Начиная с SEQ=100
хост А посылает хосту В 200 октетов. Первый посланный сегмент (SEQ=300
) доставлен без ошибок и подтвержден хостом В (ACK=301
). Следующий сегмент передан с ошибкой и не доставлен получателю. Таким образом, хост А не получает подтверждения на второй сегмент и повторно посылает его после определенного тайм-аута.[75]75
На самом деле ситуация, скорее всего, окажется более печальной, поскольку хост А продолжит отправку последующих сегментов в пределах окна отправки, не дожидаясь подтверждений. Не получив подтверждения на второй сегмент, хост А по таймауту вынужден будет повторно передать все сегменты, начиная со второго. Более подробно мы рассмотрим этот аспект в разделе «Стратегии реализации TCP» далее в этой главе.
[Закрыть] В конечном итоге все данные, переданные хостом А будут получены и подтверждены хостом В.
Рис. 6.13. Повторная передача
Говоря об управлении потоком данных, следует отметить, что TCP представляет собой протокол со скользящим окном. Окно определяет объем данных, который может быть послан (send window – окно передачи) или получен (receive window – окно приема) TCP-модулем. Размеры окон фактически отражают состояние буферов приема коммуникационных узлов. Так окно приема свидетельствует о количестве данных, которое принимающая сторона готова получить, а окно передачи определяет количество данных, которое отправителю позволяется послать, не ожидая подтверждения о получении. Несомненно, между этими двумя параметрами существует связь – окно передачи одного узла отражает состояние буферов другого (его окно приема) и наоборот. Принимающая сторона имеет возможность изменять окно передачи отправителя (с помощью подтверждения или явного обновления значения окна в поле Window заголовка передаваемого сегмента), и, таким образом, регулировать трафик.
Интерпретация отправителем окна передачи показана на рис. 6.14. Размер окна передачи отправителя в данном случае покрывает с 4 по 8 байт. Это означает, что отправитель получил подтверждения на все байты, включая 3, а получатель анонсировал размер окна равным 5 байтам. Это также означает, что отправитель может еще передать 2 байта (7 и 8). По мере подтверждения получения данных окно будет смещаться вправо, открывая новые "горизонты" для передачи. Однако окно может изменять свои размеры, при этом имеет значение, смещение какого края окна (правого или левого) приводит к изменению размера.
□ Окно закрывается по мере смещения левого края вправо. Это происходит при отправлении данных.
□ Окно открывается по мере смещения правого края вправо. Это происходит в соответствии с освобождением буфера приема получателя данных.
□ Окно сжимается, когда правый край смещается влево. Хотя такое поведение не рекомендуется, модуль TCP должен быть готов к обработке этой ситуации.
Рис. 6.14. Окно передачи TCP
Если левый край окна достигает правого, размер окна становится равным нулю, что запрещает дальнейшую передачу данных.
Суммируя вышесказанное, можно отметить, что размер окна, сообщаемый получателем данных отправителю, является предлагаемым окном (offered window), которое в простейшем случае равно размеру свободного места в буфере приема. При получении этого значения отправитель данных вычисляет фактическое, доступное для использования окно (usable window), которое равно предлагаемому за вычетом объема отправленных, но не подтвержденных данных. Таким образом, доступное для использования, или просто доступное, окно меньше или равно предлагаемому. Неэффективная стратегия подтверждений может привести к чрезвычайно малым значениям доступного окна и, как следствие, к низкой производительности передачи данных. Это явление, известное под названием синдром «глупого окна» (Silly Window Syndrome, SWS), будет рассмотрено ниже.
Рассмотренный стандарт протокола TCP определяет взаимодействие между удаленными объектами, достаточное для обеспечения совместимых реализаций. Другими словами, модуль протокола, в точности следующий спецификации стандарта, является гарантированно совместимым с модулями TCP, разработанными другими производителями. Тем не менее ряд вопросов функционирования протокола остается за рамками стандарта и допускает различные реализации, в конечном итоге влияющие не на совместимость, а на производительность приложений, использующих этот протокол. В данном разделе мы рассмотрим различные подходы к реализации TCP, направленные на повышение его производительности.
Механизм подтверждения получения данных является ключевым в протоколе TCP. Стандарт указывает, что подтверждение должно быть передано без задержки, но не определяет конкретно, насколько быстро данные должны быть подтверждены, и объем подтверждаемых данных. К сожалению, корректная с точки зрения спецификации протокола, но неоптимальная реализация стратегии подтверждения приводит к неудовлетворительной работе механизма управления потоком данных (оконного механизма), что приводит к синдрому «глупого окна» (SWS).
Для иллюстрации этого явления рассмотрим передачу файла большого размера между двумя приложениями, использующими протокол TCP. Допустим, что модуль протокола осуществляет передачу сегментами, размер которых составляет 200 октетов. В начале передачи предлагаемое окно отправителя – 1000 октетов. Он полностью использует этот кредит, послав пять сегментов по 200 октетов каждый. После обработки первого полученного сегмента адресат отправляет подтверждение (сегмент ACK
), которое также содержит обновленное значение предлагаемого окна. Предположим, что адресат передал полученные данные приложению, и таким образом его буфер приема вновь содержит 1000 байтов свободного места. Поэтому обновленное значение окна будет также равным 1000 октетов. Эта ситуация показана на рис. 6.15.
Рис. 6.15. Возникновение SWS
При получении подтверждения отправитель вычисляет доступное окно. Поскольку получение 800 октетов данных еще не подтверждено, значение доступного окна получается равным 200.
Рассмотрим теперь процесс возникновения SWS. Предположим, что отправитель вынужден передать сегмент размером 50 октетов (например, если приложение указало флаг PSH
). Таким образом, он отправляет 50 байтов, и вслед за этим следующий сегмент, размером 150 октетов (поскольку размер доступного окна равен 200). Через некоторое время адресат получит 50 байтов, обработает их и подтвердит получение, не изменяя значения предлагаемого окна (1000 октетов). Однако теперь при вычислении доступного окна, отправитель обнаружит, что не подтверждены 950 байтов, и, таким образом, его окно равняется всего 50 октетам. В результате отправитель вновь вынужден будет передать всего 50 байтов, хотя приложение этого уже не требует.
Если мы продолжим анализировать передачу данных, то заметим, что рисунок транзакций будет периодически повторяться, т.е. отправитель будет вынужден периодически передавать сегмент необоснованно малого размера. Этот порочный круг не может быть разорван естественным образом. Происхождение сегментов малого размера очевидно: периодически у отправителя возникает необходимость разделить доступное окно на несколько мелких сегментов. При непрерывной передаче больших объемов данных такие ситуации будут время от времени возникать, оставляя неизгладимый след на характере транзакций. В результате это может привести к "засорению" сети множеством мелких пакетов в одну сторону и множеством подтверждений в другую.
Описанный синдром может также порождаться и принимающей стороной, которая анонсирует чересчур маленькие окна. Таким образом, для преодоления этих ситуаций, необходима модификация алгоритмов TCP как для отправления, так и для приема данных. К счастью, SWS легко избежать, обязав модули выполнять следующие правила:
1. Принимающая сторона не должна анонсировать маленькие окна. Говоря более конкретно, адресат не должен анонсировать размер окна, больший текущего (который скорее всего равен 0), пока последний не может быть увеличен либо на размер максимального сегмента (Maximum Segment Size, MSS), либо на ½ размера буфера приема, в зависимости от того, какое значение окажется меньшим.
2. Отправитель должен воздержаться от передачи, пока он не сможет передать сегмент максимального размера или сегмент, размер которого больше половины максимального размера окна, который когда-либо анонсировался принимающей стороной.
Однако как мы уже заметили, анализируя причины возникновения SWS, поспешные подтверждения полученных данных сыграли не последнюю роль в этом процессе. С одной стороны, немедленное подтверждение позволяет постоянно держать отправителя "в курсе дела", тем самым избегая ненужных повторных передач. Подтверждение также приводит к смещению окна, и таким образом, позволяет отправителю продолжить передачу данных. С другой стороны, немедленное подтверждение может привести к возникновению SWS и дополнительным накладным расходам.
Хорошим компромиссом между немедленным и отложенным подтверждением можно считать следующую схему. При получении сегмента адресат не отправляет подтверждение, если, во-первых, сегмент не содержит флага PSH
(дающего основание полагать, что вслед за полученным сегментом вскоре последуют дополнительные данные), и, во-вторых, отсутствует необходимость отправки обновленного значения окна.
Тем не менее получатель должен установить таймер, который позволит послать подтверждение, если в передаче данных произошел определенный перерыв, что может быть вызвано, например, потерей сегментов.
Старые реализации TCP начинали передачу, отправляя сегменты в пределах предлагаемого окна, не дожидаясь подтверждения. Это вызывало взрывообразный рост трафика в сети и могло привести к переполнению, в результате которого часть сегментов отбрасывалась и требовалась повторная передача.
Алгоритм, направленный на избежание подобной ситуации, получил название медленного старта (slow start). Основная идея, лежащая в основе этого алгоритма, заключается в том, что на начальном этапе передачи сегменты должны отправляться со скоростью, пропорциональной скорости получения подтверждений.
Реализация этого алгоритма предусматривает использование дополнительного к рассмотренным ранее окна отправителя – окна переполнения (congestion window). При установлении связи с адресатом значение этого окна cwnd
устанавливается равным одному сегменту (значению MSS, анонсированному адресатом, или некоторому значению по умолчанию, обычно 536 или 512 байтов). При вычислении доступного окна отправитель использует меньшее из предлагаемого окна и окна переполнения. Каждый раз, когда отправитель получает подтверждение полученного сегмента, его окно переполнения увеличивается на величину этого сегмента.
Легко заметить, что предлагаемое окно служит для управления потоком со стороны получателя, в то время как окно переполнения служит для управления со стороны отправителя. Если первое из них связано с наличием свободного места в буфере приема адресата, то второе – с представлением о загрузке сети у отправителя данных.
Обычно предлагаемое окно больше одного сегмента, поэтому отправитель передает один сегмент и ожидает подтверждения. Когда подтверждение приходит, он увеличивает значение окна переполнения до двух сегментов, таким образом, два сегмента разрешены к передаче. После того как получение каждого из этих сегментов подтверждено, размер окна переполнения становится равным четырем сегментам. Можно показать, что по мере отправления сегментов и получения подтверждений размер окна переполнения растет экспоненциально, соответственно растет и эффективная скорость передачи.[76]76
Легко вывести формулу изменения размера окна, предполагая, что время передачи данных от отправителя к получателю и обратно (Round Trip Time, RTT) гораздо больше времени передачи сегмента отправителем. Здесь параметр sz
равен размеру сегмента (например, MSS):
cwnd0 = sz
cwnd1 = cwnd0 + (cwnd0/sz)*sz = 2*cwnd0
...
cwndn = 2*cwndn-1 = 2n*sz
[Закрыть]
Начиная с некоторого значения скорость передачи достигнет эффективной пропускной способности виртуального канала между источником и получателем, и ее дальнейший рост приведет к потере данных. Начиная с этого момента, включается механизм устранения заторов, который будет обсужден ниже.
Переполнение, или затор, может возникнуть в сети по многим причинам. Например, если данные поступают к шлюзу по высокоскоростному каналу и должны быть переданы в низкоскоростной канал. Или данные нескольких каналов мультиплексируются в один канал, пропускная способность которого меньше суммы входящих. Во всех этих случаях неизбежна потеря пакетов.
Алгоритмы, позволяющие избежать заторов, основываются на предположении, что потеря данных, вызванная ошибками передачи по физической среде, пренебрежимо мала (гораздо меньше 1%). Следовательно, потеря данных свидетельствует о заторе, произошедшем где-то на пути следования пакета. В свою очередь, о потере данных отправитель может судить по двум событиям: значительной паузе в получении подтверждения или получении дубликата(ов) подтверждения.
Хотя устранение затора и медленный старт являются независимыми механизмами, каждый из которых имеет свою цель, обычно они реализуются совместно. Для их работы необходимо два дополнительных параметра виртуального канала;[77]77
Для простоты мы рассматриваем несимметричный виртуальный канал, в котором данные передаются в одну сторону, а управляющие сообщения (подтверждения, обновления окна и т.д.) передаются в обратную сторону. Эти рассуждения легко могут быть распространены и на случай полнодуплексного канала, когда каждая из сторон одновременно является и получателем и отправителем данных.
[Закрыть] окно переполнения cwnd
и порог медленного старта ssthresh
. Работа комбинированного алгоритма определяется следующим правилам:
1. Начальные значения cwnd
и ssthresh
инициализируются равными размеру одного сегмента и 65535 байтов соответственно.
2. Максимальное количество данных, которое может передать отправитель, не превышает меньшего из значений окна переполнения и предлагаемого окна.
3. При возникновении затора (что определяется по тайм-ауту или получению дубликатов подтверждений) параметр ssthresh
устанавливается равным половине текущего окна, но не меньше размера двух сегментов. Если же свидетельством затора является тайм-аут, то дополнительно размер cwnd
устанавливается равным одному сегменту, или, другими словами, включается медленный старт.
4. Когда отправитель получает подтверждение, он увеличивает размер cwnd
, однако новый размер зависит от того, выполняет ли модуль медленный старт или устранение затора.
Если значение cwnd
меньше или равно ssthresh
, то TCP находится в фазе медленного старта, в противном случае производится устранение затора. Таким образом, режим медленного старта продолжается до тех пор, пока эффективная скорость передачи не достигнет половины скорости, при которой был обнаружен затор.[78]78
Поскольку скорость передачи определяется текущим окном, половина размера окна, сохраненная в ssthresh
, определяет 1/2 скорости, при которой произошел затор.
[Закрыть] После этого включается процедура устранения затора.
Как мы только что видели, медленный старт начинается с отправления одного сегмента, затем двух, затем четырех и т.д., что порождает экспоненциальный рост размера окна. В фазе устранения затора вычисление нового значения cwnd
производится по следующей формуле при каждом подтверждении сегмента:[79]79
Предполагается, что получатель подтверждает каждый сегмент. На самом деле это не так, и в этом случае приращение производится фактически на число подтвержденных сегментов.
[Закрыть]
cwndn+1 = cwndn + 1/cwndn
Таким образом, формула дает зависимость роста размера окна, при которой максимальная скорость приращения составит не более одного сегмента за время передачи данных туда и обратно (Round Trip Time, RTT), независимо от того, сколько подтверждений было получено. Это утверждение легко доказать. Допустим, в какой-то момент времени размер окна составлял cwndn
. Тогда отправитель может передать максимум cwndn/sz
сегментов размером sz
, на которые он получит такое же число подтверждений. Можно показать, что
cwndn+1 ≤ cwndn + (cwndn/sz)×(1/cwndn) = cwndn + sz
На рис. 6.16 показан рост окна переполнения при медленном старте и последующем устранении затора. Заметим, что переход в фазу устранения затора происходит при превышении размером окна порогового значения ssthresh
.
Рис. 6.16. Рост окна переполнения при медленном старте и устранении затора
До сих пор рассматривалось получение дублированных подтверждений как свидетельство потери сегментов и затора в сети. Однако согласно RFC 1122 «Requirements for Internet Hosts – Communication Layers», модуль TCP может отправить немедленное подтверждение при получении неупорядоченных сегментов. Цель такого подтверждения – уведомить отправителя, что был получен неупорядоченный сегмент, и указать порядковый номер ожидаемых данных. Поскольку ожидаемый порядковый номер остался прежним (получение неупорядоченного сегмента не изменит его значение), данное подтверждение может явиться дубликатом уже отправленного ранее.
Таким образом, получение дублированных подтверждений может быть вызвано двумя причинами: потерей сегмента, как следствием затора в сети, и получением неупорядоченного сегмента. Чтобы установить истинную причину, модуль TCP ждет получения еще нескольких дублированных подтверждений. Если причина в получении неупорядоченного сегмента, вызванном буферизацией на промежуточных шлюзах или различными путями передачи датаграмм, то, вероятнее всего, вскоре ожидаемый сегмент будет получен и порядок будет восстановлен, что выразится в получении нового (уже не дубликата) подтверждения. Если получено три или более дубликатов, следует полагать, что произошла потеря данных. В этом случае отправитель совершает повторную передачу утраченного сегмента. Эта процедура получила название быстрой повторной передачи (fast retransmit). При этом, включается механизм устранения затора, но не медленный старт. Причиной такого поведения является то, что получение сегмента, хотя и не упорядоченного, свидетельствует об относительно невысоком уровне переполнения в сети, и необходимость в столь радикальных мерах, как медленный старт, отсутствует.
Однако потеря данных может вызвать ответное молчание. Для обработки подобной ситуации отправитель должен установить таймер и повторно передать данные по тайм-ауту, начиная с последнего подтверждения. Данный механизм является запасным и гарантирует повторную передачу, хотя и вызывает довольно большие задержки.
Программные интерфейсы
Программный интерфейс сокетовВы уже познакомились с интерфейсом сокетов при обсуждении реализации межпроцессного взаимодействия в BSD UNIX. Поскольку сетевая поддержка впервые была разработана именно для BSD UNIX, интерфейс сокетов и сегодня является весьма распространенным при создании сетевых приложений. В разделе «Поддержка сети в BSD UNIX» мы вновь вернемся к сокетам, когда будем рассматривать внутреннюю архитектуру сетевой подсистемы в UNIX ветви BSD. Сейчас же рассмотрим простой пример приложения клиент-сервер, который демонстрирует возможности сокетов при обеспечении взаимодействия между удаленными процессами. Несмотря на то что взаимодействие затрагивает передачу данных по сети, приведенная программа мало отличается от примера, рассмотренного в разделе «Межпроцессное взаимодействие в BSD UNIX. Сокеты» главы 3. Логика приложения сохранена – клиент отправляет серверу сообщение, сервер передает его обратно, а клиент, в свою очередь, выводит полученное сообщение на экран. Наиболее существенным отличием является коммуникационный домен сокетов – в данном случае AF_INET
. Соответственно изменилась и схема адресации коммуникационного узла. Согласно схеме адресации TCP/IP, коммуникационный узел однозначно идентифицируется двумя значениями: адресом хоста (IP-адрес) и адресом процесса (адрес порта). Это отражает и структура sockaddr_in
, которая является конкретным видом общей структуры адреса сокета sockaddr
. Структура sockaddr_in
имеет следующий вид:
struct sockaddr_in {
short sin_family;
Коммуникационный домен – AF_INET
u_short sin_port;
Номер порта
struct in_addr sin_addr;
IP-адрес хоста
char sin_zero[8];
};
Адрес порта должен быть предварительно оговорен между клиентом и сервером.
В заключение, прежде чем перейти непосредственно к текстам программы, заметим, что интерфейс сокетов также поддерживается и в UNIX System V, наряду с другим программным интерфейсом – TLI, который будет рассмотрен в следующем разделе.
Приведенный пример в качестве транспортного протокола использует TCP. Это значит, что перед передачей прикладных данных клиент должен установить соединение с сервером. Эта схема, приведенная на рис. 6.17, несколько отличается от рассмотренной в разделе "Межпроцессное взаимодействие в BSD UNIX. Сокеты", где передача данных осуществлялась без предварительного установления связи и в данном случае соответствовала бы использованию протокола UDP.
Рис. 6.17. Схема установления связи и передачи данных между клиентом и сервером
В соответствии с этой схемой сервер производит связывание с портом, номер которого предполагается известным для клиентов bind(2), и сообщает о готовности приема запросов listen(2)). При получении запроса он с помощью функции accept(2) создает новый сокет, который и обслуживает обмен данными между клиентом и сервером. Для того чтобы сервер мог продолжать обрабатывать поступающие запросы, он порождает отдельный процесс на каждый поступивший запрос. Дочерний процесс, в свою очередь, принимает сообщения от клиента (recv(2)) и передает их обратно (send(2)).
Клиент не выполняет связывания, поскольку ему безразлично, какой адрес будет иметь его коммуникационный узел. Эту операцию выполняет система, выбирая свободный адрес порта и установленный адрес хоста. Далее клиент направляет запрос на установление соединения (connect(2)), указывая адрес сервера (IP-адрес и номер порта). После установления соединения («тройное рукопожатие») клиент передает сообщение (send(2)), принимает от сервера ответ recv(2)) и выводит его на экран.
В программе используются несколько функций, которые не рассматривались. Эти функции значительно облегчают жизнь программисту, выполняя, например, такие действия, как трансляцию доменного имени хоста в его IP-адрес (gethostbyname(3N)), приведение в соответствие порядка следования байтов в структурах данных, который может различаться для хоста и сети (htons(3N)), а также преобразование IP-адресов и их составных частей в соответствии с привычной «человеческой» нотацией, например 127.0.0.1 (inet_ntoa(3N)). Мы не будем подробнее останавливаться на этих функциях, предоставляя читателю самостоятельно обратиться к соответствующим разделам электронного справочника man(1).
Ниже приведены тексты программ сервера и клиента.
Сервер
#include
#include
#include
#include
#include
#include
#include
/* Номер порта сервера, известный клиентам */
#define PORTNUM 1500
main(argc, argv)
int argc;
char *argv[];
{
int s, ns;
int pid;
int nport;
struct sockaddr_in serv_addr, clnt_addr;
struct hostent* hp;
char buf[80], hname[80];
/* Преобразуем порядок следования байтов
к сетевому формату */
nport = PORTNUM;
nport = htons((u_short)nport);
/* Создадим сокет, использующий протокол TCP */
if ((s=socket(AF_INET, SOCK_STREAM, 0))==-1) {
perror(«Ошибка вызова socket()»);
exit(1);
}
/* Зададим адрес коммуникационного узла */
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv.addr.sin_port = nport;
/* Свяжем сокет с этим адресом */
if (bind(s, struct sockaddr*)&serv_addr,
sizeof(serv_addr))==-1) {
perror(«Ошибка вызова bind()»);
exit(1);
}
/* Выведем сообщение с указанием адреса сервера */
fprintf(stderr, «Сервер готов: %sn»,
inet_ntoa(serv_addr.sin_addr));
/* Сервер готов принимать запросы
на установление соединения.
Максимальное число запросов, ожидающих обработки – 5.
Как правило, этого числа достаточно, чтобы успеть
выполнить accept(2) и породить дочерний процесс */
if (listen(s, 5)==-1) {
perror(«Ошибка вызова listen()»);
exit(1);
}
/* Бесконечный цикл получения запросов и их обработки */
while (1) {
int addrlen;
bzero(&clnt_addr, sizeof(clnt_addr));
addrlen = sizeof(clnt_addr);
/* Примем запрос. Новый сокет ns становится
коммуникационным узлом созданного виртуального канала */
if ((ns=accept(s, (struct sockaddr*)&clnt_addr,
&addrlen))==-1) {
perror(«Ошибка вызова accept()»);
exit(1);
}
/* Выведем информацию о клиенте */
fprintf(stderr, «Клиент = %sn»,
inet_ntoa(clnt_addr.sin_addr));
/* Создадим процесс для работы с клиентом */
if ((pid=fork())==-1) {
perror(«Ошибка вызова fork()»);
exit(1);
}
if (pid==0) {
int nbytes;
int fout;
/* Дочерний процесс: этот сокет нам не нужен. Он
по-прежнему используется для получения запросов */
close(s);
/* Получим сообщение от клиента и передадим его обратно */
while ((nbytes = recv(ns, buf, sizeof(buf), 0)) !=0) {
send(ns, buf, sizeof(buf), 0);
}
close(ns);
exit(0);
}
/* Родительский процесс: этот сокет нам не нужен. Он
используется дочерним процессом для обмена данными */
close(ns);
}
}
Клиент
#include
#include
#include
#include
#include
#include
#include
/* Номер порта, который обслуживается сервером */
#define PORTNUM 1500
main (argc, argv)
char *argv[];
int argc;
{
int s;
int pid;
int i, j;
struct sockaddr_in serv_addr;
struct hostent *hp;
char buf[80]="Hello, World!";
/* В качестве аргумента клиенту передается доменное имя
хоста, на котором запущен сервер. Произведем трансляцию
доменного имени в адрес */
if ((hp = gethostbyname(argv[1])) == 0) {
perror(«Ошибка вызова gethostbyname()»);
exit(3);
}
bzero(&serv_addr, sizeof(serv_addr));
bcopy(hp->h_addr, &serv_addr.sin_addr, hp->h_length);
serv_addr.sin_family = hp->h_addrtype;
serv_addr.sin_port = htons(PORTNUM);
/* Создадим сокет */
if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror(«Ошибка вызова socket!)»);
exit(1);
}
fprintf(stderr, «Адрес клиента: %sn»,
inet_ntoa(serv_addr.sin_addr));
/* Создадим виртуальный канал */
if (connect (s, (struct sockaddr*)&serv_addr,
sizeof(serv_addr)) == -1) {
perror(«Ошибка вызова connect()»);
exit(1);
}
/* Отправим серверу сообщение и получим его обратно */
send(s, buf, sizeof(buf), 0);
if (recv(s, buf, sizeof(buf) , 0) < 0) {
perror(«Ошибка вызова recv()»);
exit(1);
}
/* Выведем полученное сообщение на экран */
printf(«Получено от сервера: %sn», buf);
close(s);
printf(«Клиент завершил работу nn»);
}