sonyps4.ru

Что такое сокеты c. Программирование сокетов на Java

Следует сказать, что изучение стиля руководства ведется психологами уже более полувека. Так что исследователями накоплен к настоящему времени немалый эмпирический материал по этой проблеме.

Стиль руководства – типичная для руководителя система приемов деятельности, используемая в работе с людьми. Стиль руководства проявляется и в манере руководителя говорить, слушать других людей, и в том, как он организует подготовку, принятие и реализацию решений. О нем можно судить и по тому, как организованы личное рабочее место руководителя и труд возглавляемого им коллектива. Изучение стиля руководства и само возникновение этого понятия связаны прежде всего с именем выдающего немецкого психолога К. Левина. В 30-е годы вместе с группой своих сотрудников он провел в США, куда вынужден был эмигрировать из фашистской Германии, серию экспериментов, в ходе которых выявил три ставших классическими стиля руководства: авторитарный, демократический, либеральный. В чем же различия между ними?

АВТОРИТАРНЫЙ (директивный) стиль руководства характеризуется централизацией власти в руках одного руководителя. Он единолично принимает решения, жестко определяет всю деятельность подчиненных, не давая им возможности проявить инициативу. Подчиненные должны лишь исполнять то, что им приказано, при этом они получают минимум необходимой информации. Контроль за их деятельностью целиком основан на силе власти руководителя. Он обычно ориентирован на решение чисто производственных задач. Не доверяет подчиненным, пресекает всякую критику в свой адрес.

Руководитель, придерживающийся ДЕМОКРАТИЧЕСКОГО стиля, децентрализует свою управленческую власть. Он консультируется с подчиненными, которые также принимают участие в выработке решений и получают достаточно информации, чтобы иметь представление о перспективах своей работы. Практикуется делегирование функций и полномочий от руководителя подчиненным. Инициатива с их стороны всячески стимулируется. Деятельность подчиненных контролируется не только властью руководителя, но и посредством сил, имеющихся в коллективе (его активной частью).

ЛИБЕРАЛЬНЫЙ (попустительский) стиль руководства характеризуется минимальным вмешательством руководителя в деятельность подчиненных.

Руководитель выступает в роли посредника при осуществлении контактов между занятыми в производстве группами людей, обеспечивая своих подчиненных информацией и материалами, необходимыми для выполнения работы. Обычно дела он пускает на самотек, действует, когда оказывают давление – либо сверху, либо снизу.

Консервативен. Никогда не критикует начальство, удобен в качестве подчиненного. Склонен воздействовать уговариванием, налаживанием личных контактов. Критику выслушивает, соглашается с ней, но ничего не предпринимает. Как правило, из числа подчиненных выдвигаются один-два человека, которые фактически управляют группой и «спасают» дело. В некоторых ситуациях (творческий коллектив, творческие периоды в работе группы) такой стиль может оказаться оптимальным, но в течение непродолжительного времени.

Стиль руководства определяется многими факторами: социально-историческими, моральными качествами руководителя, его психологическими особенностями, конкретной ситуацией и т.д. На стиле руководства сказывается вся личность, ее установки и отношения в особенности ее характер. Само собой понятно, что нельзя абсолютизировать психологию личности и игнорировать другие влияния. На стиле руководства отражается и требование времени, школа, которую проходит в своем становлении руководитель. Вот почему могут быть два руководителя с приблизительно одинаковыми характерами, но проявлять различные стили работы и, наоборот, при идентичности стилей иметь различные характеры.

Авторитарность руководства, как показывают наблюдения, нередко связана с особенностями воли и темперамента . Авторитарные руководители – это в большинстве случаев властные люди, обладающие большим упорством и настойчивостью. Вместе с тем они самолюбивы и с повышенным чувством престижа. Среди авторитарных встречаются «безудержные» холерики, люди неуравновешенные, вспыльчивые, склонные к увлечению и субъективизму в оценках. Наблюдения показывают, что такие люди, особенно в период возбуждения, не терпят возражений, замечаний и склонны к проявлению резкости в обращении. Среди изученных авторитарных руководителей большинство оказались холериками . Однако из этого нельзя делать вывод, что представители других типов темперамента не могут быть авторитарными. Само собой понятно также, что и холерики, у которых выработано волевое торможение, могут оставаться на высоте человеческих отношений даже при тяжелых обстоятельствах и переживаниях. Они умеют держать себя в руках.

Так что темперамент только при некоторых условиях определяет тенденции к определенному поведению, но не может считаться роковым, исключительно предопределяющим стиль жизни и руководства. Очевидно, что тяготеют к авторитарному стилю руководства и люди с болезненным тщеславием и эгоистическим складом личности. Иногда авторитарность служит маскировкой профессиональной некомпетентности или неспособности личности к руководящей деятельности. Нельзя отрицать и то, что высокий авторитет и постоянное преклонение перед деятелем иногда может служить причиной формирования авторитарного типа руководителя.

Таким образом, нет однозначной корреляции между авторитарностью и каким-то одним психическим свойством личности. Могут быть различные комбинации свойств, определяющие этот стиль деятельности. Вот почему в каждом конкретном случае требуется выяснить источники авторитарности.

Что касается демократического руководителя, то его психология ясна. Это человек, который любит дело и любит людей, которыми руководит. Доброта характера и ответственное отношение к делу служат предпосылкой требовательности к людям и уважительного к ним отношения. Личности демократического руководителя чуждо тщеславие, хотя он и не лишен уверенности в себе. Духовное богатство, обилие знаний и творческих возможностей позволяют такому руководителю щедро делиться с людьми, которыми он руководит. Демократические руководители отличаются также гибкостью мышления, а отсюда и находчивостью. Что касается темперамента, то среди представителей этого типа чаще встречаются сангвиники.

Что касается либерального руководителя, то можно считать одной из причин такого отношения к делу поглощение своими делами и забвение коллективных обязанностей, недостаточность сознания своего долга перед коллективом. Среди либеральных встречаются люди добродушные, а вместе с тем нерешительные и мягкотелые. Они предпочитают собственную ответственность перекладывать на плечи помощников, боятся совершить ошибку в оценке людей. Бывают либеральные и другой природы – это люди равнодушные ко всему: и к коллективу, и к его делам; они ленивы, и для них дороже всего свой покой, им «тяжело» и распоряжаться, и контролировать, и заботиться о других. Эти люди не карьеристы. Они готовы уступить свое место любому, так как занимают его случайно, по ошибке. Кому-то показалось, что человек покладист, скромен, не вступает в пререкания с вышестоящими руководителями, следовательно, он вполне подходит для руководства нижестоящей организацией, а в результате получается «ничто» для дела.

Следует отметить, что в чистом виде тот или иной стиль руководства в каком-то конкретном эпизоде организационной жизни может себя и не обнаружить, он обусловлен рядом социально-психологических факторов, которые с неизбежностью приходится иметь в виду руководителю, а именно: специфику наличной ситуации, своеобразие решаемых задач, квалификацию и сработанность членов коллектива, их личностные особенности и т.д.

Совершенно очевидно, например, что ситуации, характеризующиеся экстремальностью условий, даже от очень демократичного руководителя потребуют жесткости в управлении группой.

В то же время спокойное течение событий способно побудить и самого отъявленного автократа к более «мягким» формам руководства. Ясно также, что для управления людьми, имеющими высокую квалификацию, большой опыт совместной работы и достаточный уровень сплочения, потребуются иные методы и формы управления, нежели в том случае, когда руководитель взаимодействует с людьми, чьи индивидуальные и групповые показатели носят диаметрально противоположный характер.

Конечно, демократический стиль руководства чаще всего приводит организацию к успеху и процветанию: в большей степени отвечает интересам человека и производства. Содержание этого понятия раскрывается, детализируется в приводимых ниже рекомендациях.

Научитесь управлять собой в любых условиях. Будьте приветливым, вежливым. Улыбайтесь людям. Стремитесь поддерживать хорошее настроение и у себя, и у окружающих. Придя на работу, приветствуйте своих коллег. Дайте им понять, что вы рады с ними начать новый рабочий день, что они могут рассчитывать на вашу помощь и поддержку. Обращение к подчиненным на «Вы» – необходимый инструмент для поддержания нормальных служебных отношений и трудовой дисциплины. Проявляйте максимум доброжелательности к участникам совещаний: люди приходят для того, чтобы выработать управленческие решения, в которых, в первую очередь, заинтересован руководитель. Научитесь терпеливо слушать других, не отвергайте высказанные предложения только потому, что вы с ними не согласны, дайте высказаться другим, поощряйте активность участников совещания. В любых ситуациях сохраняйте самообладание, будьте вежливы. Отдавая поручения подчиненным, помните, что подчиненные лучше воспринимают поручения в форме «прошу», чем «приказываю». Старайтесь вместо повелительного наклонения чаще употреблять согласительное («Я был бы Вам очень признателен, если бы Вы...»). Не унижайте достоинство людей, не допускайте перерастания критики в элементарную ссору. Не преследуйте за критику снизу. Будьте самокритичны – это только укрепит ваш авторитет. Цените самостоятельных людей, избегайте навязчивых услуг подхалимов. Не злоупотребляйте правом единоначалия, чаще советуйтесь с коллегами.

“Как я работаю?” Это вопрос - особенно для работников умственного труда - не менее важен, чем вопрос о способностях и сильных сторонах. А возможно, он даже важнее.

На удивление мало людей знают, как они добиваются требуемого результата.

Большинство даже не задумываются о том, что разные люди работают и достигают своих целей по-разному. Поэтому многие ошибочно выбирают “чужой" стиль работы, а это почти стопроцентная гарантия неудачи.

Как и сильные стороны, стиль работы у каждого свой. Он составляет один из аспектов личности человека. Личность человека, несомненно, формируется еще задолго до того, как этот он начинает свою трудовую деятельность. Стиль работы является “врожденным” качеством, точно так же, как способности в одних сферах и полное отсутствие таковых - в других. Стиль работы можно скорректировать, но изменить кардинальным образом - нельзя. Люди добиваются успеха, если они выполняют работу, к которой у них есть склонности, привычным и удобным для себя образом.

Анализ результатов может показать, что в стиле работы есть какие-то недостатки. Но он очень редко позволяет выявить причину этих ошибок.

Однако ее, как правило, не так уж сложно обнаружить другими способами. Для этого обычно требуется несколько лет практических тренировок. После этого человек получает представление о своем стиле работы и может довольно точно его описать. Однако сначала нужно узнать свои личные качества, определяющие стиль работы.

Для начала желательно узнать, является ли человек по своей природе читателем или слушателем (т.е. воспринимает ли он информацию визуально или на слух). Лишь немногие знают, что есть люди-читатели и есть люди- слушатели, и что оба этих качества очень редко уживаются в одном человеке. Еще меньшему количеству людей известно, к какому типу - читателям или слушателям - они сами относятся. А не зная своего типа восприятия информации, практически нельзя достичь эффективности в работе.

Вторая важная подробность, которую необходимо знать, касается стиля обучения. Здесь дело обстоит еще хуже, чем со зрительной или слуховой памятью, ибо повсеместно в организацию школьного процесса заложено совершенно ошибочное предположение о том, что существует лишь один правильный способ обучения, и что этот способ подходит для всех учеников.

Вот пример разных стилей обучения.

Бетховен оставил после себя невероятное количество черновиков, утверждая при этом, что при сочинении музыки никогда в них не заглядывает. На вопрос: “Для чего же вы их ведете?” - композитор обычно отвечал: “Если я не перенесу свою музыку сразу же в нотную тетрадь, я почти наверняка забуду ее. Если же я запишу ее на бумагу, то не забуду никогда - и при этом мне не придется заглядывать в ноты”.

Существует не меньше десятка способов научиться чему-нибудь. Кто- то, подобно Бетховену, ведет подробнейшие конспекты. А Альфред Слоун, например, никогда не делал записей во время совещаний. Одни учатся, слушая самих себя. Другие люди записывают все, что кажется им важным, третьи усваивают новое только в процессе работы. А в ходе одного неформального исследования, проведенного мною среди преподавателей американских университетов - авторов очень популярных учебников, я многократно слышал одно и то же: “Я читаю лекции, чтобы услышать свою речь, потому что только так я могу писать”.

Фактически, изо всех важных элементов самоподготовки умение учиться проще всего приобрести. На вопрос: “Как вы учитесь?” - большинство моих собеседников отвечают почти сразу.

Правда, вопрос: “Как вы применяете полученные знания?" - многих ставит в тупик. Тем не менее именно применение полученных знаний послужит залогом будущего успеха, залогом высокой эффективности. Соответственно, неприменение полученных знаний обрекает их носителя на неэффективную работу.

Вопросы о стиле работы и методы обучения относятся к самым важным. Но эти вопросы - далеко не единственные. Чтобы правильно распорядиться своими способностями, нужно ответить еще на один вопрос: “Как мне лучше работается - в коллективе или в одиночку?” И если человек уверен, что он действительно умеет работать в коллективе и что такая работа ему нравится, он должен задать себе следующий вопрос: “Какие взаимоотношения с людьми помогают мне работать лучше?”

Одни люди показывают лучшие результаты, будучи членами коллектива. Другим гораздо больше подходит роль учителей и наставников, тогда как третьим эта роль совершенно не по плечу.

Когда мы пытаемся понять, как работает тот или иной человек, как он добивается нужных результатов, очень важно также знать, способен ли он работать хорошо в условиях стресса или для успешной работы такому человеку требуется высокоструктурированное и предсказуемое окружение. Еще один важный вопрос: какая роль больше всего подходит данному работнику - роль рядового сотрудника организации-лидера или роль лидера в рядовой компании? Лишь немногие люди одинаково удачно справляются с обеими этими ролями. Можно привести множество примеров, когда преуспевающий работник крупной компании - например, General Electric или Citibank - полностью терялся при переходе в маленькую фирму. Не меньше случаев, когда специалисты, прекрасно зарекомендовавшие себя в маленьких организациях, не могли работать на том же уровне при переходе в крупную компанию.

Вот еще один важный вопрос: “В каком качестве я показываю лучшие результаты - как ответственный за принятие решений или как консультант?” Очень многие прекрасно справляются с ролью консультанта и, в то же время, не выдерживают бремени ответственности, которую приходится брать на себя человеку, принимающему решения. С другой стороны, встречается немало людей, которым необходим консультант, советчик, дающий новые идеи и подсказывающий им правильные решения. Что же касается собственно принятия решений и претворения этих решений в жизнь, то здесь они действуют быстро, решительно и уверенно.

Именно этими различиями объясняется, в частности, ситуация, при которой “человек номер два” зачастую терпит неудачу, когда его выводят “из тени” и назначают на ответственную должность. Работа “на первых ролях” предполагает ответственность за принятие решений. В то же время человеку, который готов принимать ответственность за решения, зачастую необходим консультант, помощник, которому он доверяет. Этот консультант, если такое положение соответствует его стремлениям и склонностям, показывает на своей должности блестящие результаты. Но когда, в силу тех или иных причин, его назначают руководителем организации, он терпит крах. Он знает, каким должно быть то или иное решение, но боится взять на себя ответственность за его принятие.

Поэтому не пытайтесь изменить себя - это вряд ли приведет к успеху. Но работайте - и работайте упорно - над совершенствованием своих сильных качеств. И старайтесь, по возможности, избегать работы, которую вы не умеете делать - или умеете, но плохо.


Глава 15

В этой главе вы познакомитесь с еще одним способом взаимодействия процессов, существенно отличающимся от тех, которые мы обсуждали в главах 13 и 14. До настоящего момента все рассматриваемые нами средства основывались на совместно используемых ресурсах одного компьютера. Ресурсы могли быть разными: областью файловой системы, сегментами совместно используемой памяти или очередями сообщений, но использовать их могли только процессы, выполняющиеся на одной машине.

В версию ОС Berkeley UNIX было включено новое средство коммуникации - интерфейс сокетов, - являющееся расширением концепции канала, обсуждавшейся в главе 13. В системах Linux также есть интерфейсы сокетов.

Вы можете применять сокеты во многом так же, как каналы, но они поддерживают взаимодействие в пределах компьютерной сети. Процесс на одной машине может использовать сокеты для взаимосвязи с процессом на другом компьютере, что делает возможным существование клиент-серверных систем, распределенных в сети. Процессы, выполняющиеся на одной машине, также могут применять сокеты.

Кроме того, интерфейс сокетов стал доступен в ОС Windows благодаря общедоступной спецификации Windows Sockets или WinSock. Сервисы сокетов в ОС Windows предоставляются системным файлом Winsock.dll. Стало быть, программы под управлением Windows могут взаимодействовать по сети с компьютерами под управлением Linux и UNIX и наоборот, реализуя, таким образом, клиент-серверные системы. Несмотря на то, что программный интерфейс для WinSock не совпадает полностью с интерфейсом сокетов в UNIX, в основе его лежат те же сокеты.

В одной-единственной главе мы не сможем дать исчерпывающее описание всех многообразных сетевых возможностей Linux, поэтому вы найдете здесь лишь основные программные сетевые интерфейсы, которые позволят вам писать собственные программы, работающие в сети.

Более подробно мы рассмотрим следующие темы:

□ как действует соединение с помощью сокетов;

□ атрибуты сокетов, адреса и обмен информацией;

□ сетевая информация и интернет-демон (inetd/xinetd);

□ клиенты и серверы.

Что такое сокет?

Сокет - это средство связи, позволяющее разрабатывать клиент-серверные системы для локального, на одной машине, или сетевого использования. Функции ОС Linux, такие как вывод, подключение к базам данных и обслуживание Web-страниц, равно как и сетевые утилиты, например

, предназначенная для удаленной регистрации, и , применяемая для передачи файлов, обычно используют сокеты для обмена данными.

Сокеты создаются и используются не так, как каналы, потому что они подчеркивают явное отличие между клиентом и сервером. Механизм сокетов позволяет создавать множество клиентов, присоединенных к единственному серверу.

Соединения на базе сокетов

Соединения на базе сокетов можно рассматривать как телефонные звонки в учреждение. Телефонный звонок поступает в организацию, и на него отвечает секретарь приемной, направляющий вызов в соответствующий отдел (серверный процесс) и оттуда к нужному сотруднику (сокет сервера). Каждый входящий телефонный звонок (клиент) направляется к соответствующей конечной точке, и промежуточные операторы могут заниматься последующими телефонными звонками. Прежде чем рассматривать установку соединений с помощью сокетов в системах Linux, нужно понять, как они ведут себя в приложениях сокетов, поддерживающих соединения.

Сначала серверное приложение создает сокет, который как файловый дескриптор представляет собой ресурс, присваиваемый единственному серверному процессу. Сервер создает его с помощью системного вызова

, и этот сокет не может использоваться совместно с другими процессами.

Далее сервер присваивает сокету имя. Локальные сокеты с заданными именами файлов в файловой системе Linux часто размещаются в каталоге /tmp или /usr/tmp. У сетевых сокетов имя файла будет идентификатором сервиса (номер порта/точка доступа), относящегося к конкретной сети, к которой могут подключаться клиенты. Этот идентификатор, задавая определенный номер порта, соответствующий корректному серверному процессу, позволяет Linux направлять входящие подключения по определенному маршруту. Например, Web-сервер обычно создает сокет для порта 80, идентификатор, зарезервированный для этой цели. Web-обозреватели знают о необходимости применять порт 80 для своих HTTP-подключений к Web- сайтам, которые пользователь хочет читать. Именуется сокет с помощью системного вызова

. Далее серверный процесс ждет подключения клиента к именованному сокету. Системный вызов формирует очередь входящих подключений. Сервер может принять их с помощью системного вызова .

Когда сервер вызывает

, создается новый сокет, отличающийся от именованного сокета. Этот новый сокет применяется только для взаимодействия с данным конкретным клиентом. Именованный сокет сохраняется для дальнейших подключений других клиентов. Если сервер написан корректно, он может извлечь выгоду из многочисленных подключений. Web-сервер добивается этого за счет одновременного предоставления страниц многих клиентам. В случае простого сервера все последующие клиенты ждут в очереди до тех пор, пока сервер не будет готов снова.

Клиентская сторона системы с применением сокетов гораздо проще. Клиент создает неименованный сокет с помощью вызова

. Затем он вызывает для подключения к серверу, используя в качестве адреса именованный сокет сервера.

Будучи установлены, сокеты могут применяться как низкоуровневые файловые дескрипторы, обеспечивая двунаправленный обмен данными.

Выполните упражнения 15.1 и 15.2.

Упражнение 15.1. Простой локальный клиент . Системный вызов мы подробно рассмотрим чуть позже, когда будем обсуждать некоторые проблемы адресации.

1. Включите нужные заголовочные файлы и задайте переменные:


2. Создайте сокет для клиента:

sockfd = socket(AF_UNIX, SOCK_STREAM, 0);

3. Назовите сокет по согласованию с сервером:

address.sun_family = AF_UNIX;
strcpy(address.sun_path, "server_socket");

4. Соедините ваш сокет с сокетом сервера:

:
printf("char from server = %c\n", ch);

Эта программа завершится аварийно, если вы попытаетесь выполнить ее, потому что еще не создан именованный сокет сервера, (Точное сообщение об ошибке может отличаться в разных системах.)

oops: client1: No such file or directory
Упражнение 15.2. Простой локальный сервер

Далее приведена программа простого сервера server1.с, которая принимает запрос на соединение от клиента. Она создает сокет сервера, присваивает ему имя, создает очередь ожидания и принимает запросы на соединения.

1. Включите необходимые заголовочные файлы и задайте переменные:


struct sockaddr_un server_address;
struct sockaddr_un client_address;

2. Удалите все старые сокеты и создайте неименованный сокет для сервера:

server_sockfd = socket(AF_UNIX, SOCK_STREAM, 0);

3. Присвойте имя сокету:

server_address.sun_family = AF_UNIX;
strcpy(server_address.sun_path, "server_socket");

4. Создайте очередь запросов на соединение и ждите запроса клиента:

5. Примите запрос на соединение:

(struct sockaddr *)&client_address, &client_len);

6. Читайте и записывайте данные клиента с помощью

:

Как это работает

В этом примере серверная программа в каждый момент времени может обслуживать только одного клиента. Она просто читает символ, поступивший от клиента, увеличивает его и записывает обратно. В более сложных системах, где сервер должен выполнять больше работы по поручению клиента, такой подход будет неприемлемым, потому что другие клиенты не смогут подключиться до тех пор, пока сервер не завершит работу. Позже вы увидите пару методов, позволяющих подключаться многочисленным клиентам.

Когда вы выполняете серверную программу, она создает сокет и ждет запросов на соединение. Если вы запустите ее в фоновом режиме, т.е. она будет выполняться независимо, вы сможете затем запускать клиентов как высокоприоритетные задачи.

Ожидая запросы на соединения, сервер выводит сообщение. В приведенном примере сервер ждет запрос с сокета файловой системы, и вы сможете увидеть его с помощью обычной команды

.

Хорошо взять за правило удалять сокет после окончания работы с ним, даже в случае аварийного завершения программы из-за получения сигнала. Это убережет файловую систему от загромождения неиспользуемыми файлами.

$ ls -lF server socket
srwxr-xr-x 1 neil users 0 2007-06-23 11:41 server_socket=

Здесь тип устройства - сокет, на что указывает символ

перед правами доступа и символ в конце имени. Сокет был создан как обычный файл с правами доступа, модифицированными текущей . Если применить команду , то можно увидеть сервер, выполняющийся в фоновом режиме. Он показан спящим (параметр равен ) и, следовательно, не потребляющим ресурсы ЦП.
F UID PID PPID PRI NI VSZ RSS WCHAN STAT TTY TIME COMMAND
0 1000 23385 10689 17 0 1424 312 361800 S pts/1 0:00 ./server1

Теперь, когда вы запустите программу, то успешно подключитесь к серверу. Поскольку сокет сервера существует, вы можете соединиться с ним и обмениваться данными.

server waiting char from server = В

На терминале вывод сервера и клиента перемешаны, но можно увидеть, что сервер получил символ от клиента, увеличил его и вернул. Далее сервер продолжает выполняться и ждет следующего клиента. Если вы запустите несколько клиентов вместе, они будут обслуживаться по очереди, хотя полученный вывод может оказаться еще более перемешанным.

$ ./client1 & ./client1 & ./client1 &

Атрибуты сокета

Для того чтобы до конца понять системные вызовы, применявшиеся в рассмотренном примере, необходимо узнать кое-что об организации сети в системах UNIX.

Сокеты характеризуются тремя атрибутами: доменом, типом и протоколом. У них также есть адрес, используемый как имя сокета. Форматы адресов меняются в зависимости от домена, также называемого семейством протоколов (protocol family). Каждое семейство протоколов может применять одно или несколько семейств адресов, определяющих формат адреса.

Домены сокетов

Домены задают сетевую рабочую среду, которую будет использовать соединение сокетов. Самый популярный домен сокетов -

, ссылающийся на сеть Интернет и применяемый во многих локальных сетях Linux и, конечно, в самом Интернете. Низкоуровневый протокол Internet Protocol (IP), у которого только одно адресное семейство, накладывает определенный способ задания компьютеров, входящих в сеть. Он называется IP-адресом.
Примечание

Для преодоления некоторых проблем стандартного протокола IP существенно ограниченного количества доступных адресов был разработан интернет-протокол нового поколения IPv6. Он использует другой домен сокетов

и иной формат адресов. Ожидается, что со временем IPv6 заменит IP, но для этого потребуется много лет. Несмотря на то, что уже есть реализации IPv6 для Linux, их обсуждение выходит за рамки этой книги.

Несмотря на то, что у машин в Интернете почти всегда есть имена, их преобразуют в IP-адреса. Пример IP-адреса - 192.168.1.99. Все IP-адреса представлены четырьмя числами, каждое из которых меньше 256, и образуют так называемые четверки с точками. Когда клиент подключается по сети с помощью сокетов, ему нужен IP- адрес компьютера сервера.

На компьютере сервера может быть доступно несколько сервисов. Клиент может обратиться к конкретному сервису на компьютере, включенном в сеть, с помощью IP-порта. Внутри системы порт идентифицируется уникальным 16-разрядным целым числом, а за пределами системы - комбинацией IP-адреса и номера порта. Сокеты - это коммуникационные конечные точки, которые должны быть связаны с портами, прежде чем передача данных станет возможна.

Серверы ожидают запросов на соединения от определенных клиентов. У хорошо известных сервисов есть выделенные номера портов, которые используются всеми машинами под управлением ОС Linux и UNIX. Обычно, но не всегда, эти номера меньше 1024. Примерами могут служить буфер печати принтера (515),

(513), (21) и (80). Последний из названных - стандартный порт для Web-серверов. Обычно номера портов, меньшие 1024, зарезервированы для системных сервисов и могут обслуживаться процессами с правами суперпользователя. Стандарт X/Open определяет в заголовочном файле netdb.h константу для указания наибольшего номера зарезервированных портов.

Поскольку для стандартных сервисов есть стандартный набор номеров портов, компьютеры могут легко соединяться друг с другом, не угадывая правильный номер порта. Локальный сервисы могут применять адреса нестандартных портов.

Домен в первом упражнении,

, - это домен файловой системы UNIX, который может использоваться сокетами, находящимися на единственном компьютере, возможно, даже не входящем в сеть. Если это так, то низкоуровневый протокол - это файловый ввод/вывод, а адреса - имена файлов. Для сокета сервера применялся адрес , который, как вы видели, появлялся в текущем каталоге, когда вы выполняли серверное приложение.

Кроме того, могут применяться и другие домены:

для сетей на основе стандартных протоколов ISO и для Xerox Network System (сетевая система Xerox). В этой книге мы их не будем обсуждать. Типы сокетов

У домена сокетов может быть несколько способов обмена данными, у каждого из которых могут быть разные характеристики. В случае сокетов домена

проблемы не возникают, т.к, они обеспечивают надежный двунаправленный обмен данными. В сетевых доменах необходимо знать характеристики базовой сети и их влияние на различные механизмы передачи данных.

Интернет-протоколы предоставляют два механизма передачи данных с разными уровнями обслуживания: потоки и дейтаграммы.

Потоковые сокеты

Потоковые сокеты (в чем-то подобные стандартным потокам ввода/вывода) обеспечивают соединение, представляющее собой последовательный и надежный двунаправленный поток байтов. Следовательно, гарантируется, что без указания возникшей ошибки данные не будут потеряны, продублированы или переупорядочены. Сообщения большого объема фрагментируются, передаются и снова собираются воедино. Это напоминает файловый поток, который принимает большие объемы данных и делит их на меньшие блоки для записи на физический диск. У потоковых сокетов предсказуемое поведение.

Потоковые сокеты, описываемые типом

, реализованы в домене соединениями на базе протоколов TCP/IP. Кроме того, это обычный тип сокетов и в домене . В этой главе мы сосредоточимся на сокетах типа , поскольку они чаще всего применяются при программировании сетевых приложений.
Примечание

TCP/IP - сокращение для протоколов Transmission Control Protocol/Internet Protocol. Протокол IP - низкоуровневый протокол передачи пакетов, обеспечивающий выбор маршрута при пересылке данных в сети от одного компьютера к другому. Протокол TCP обеспечивает упорядочивание, управление потоком и ретрансляцию, гарантирующие полную и корректную передачу больших объемов данных или же сообщение о соответствующей ошибочной ситуации.

Дейтаграммные сокеты

В отличие от потоковых дейтаграммные сокеты, описываемые типом

, не устанавливают и не поддерживают соединение. Кроме того, существует ограничение для размера дейтаграммы, которая может отправляться. Она передается как единое сетевое сообщение, которое может быть потеряно, продублировано или прибыть несвоевременно, т.е. перед дейтаграммами, посланными после нее.

Дейтаграммные сокеты реализованы в домене

с помощью соединений UDP/IP и предоставляют неупорядоченный ненадежный сервис. (UDP сокращенное название протокола User Datagram Protocol.) Однако они относительно экономичны с точки зрения расходования ресурсов, поскольку не нуждаются в поддержке сетевых соединений. Они быстры, т.к. не тратится время на установку сетевого соединения.

Дейтаграммы полезны для однократных запросов к информационным сервисам, для предоставления обычных сведений о состоянии или для выполнения низкоприоритетной регистрации данных. Их преимущество в том, что остановка сервера не причинит чрезмерных неудобств клиенту и не потребует перезапуска клиента. Поскольку серверы на базе дейтаграмм обычно сохраняют данные без соединения, их можно останавливать и запускать снова, не мешая их клиентам.

На этом мы закончим обсуждение дейтаграмм, дополнительную информацию см. в разд. "Дейтаграммы" в конце данной главы.

Протоколы сокетов

Если низкоуровневый механизм передачи данных позволяет применять несколько протоколов, предоставляющих сокет требуемого типа, можно выбрать конкретный протокол или сокет. В этой главе мы сосредоточимся на сокетах сети UNIX и ее файловой системы, которые не требуют от вас выбора протокола, отличного от заданного по умолчанию.

Создание сокета

Системный вызов socket создает сокет и возвращает дескриптор, который может применяться для доступа к сокету:

#include
#include
int socket(int domain, int type, int protocol);

Созданный сокет - это одна конечная точка линии передачи. Параметр

задает семейство адресов, параметр определяет тип используемого с этим сокетом обмена данными, a - применяемый протокол.

В табл. 15.1 приведены имена доменов.


Таблица 15.1

К наиболее популярным доменам сокетов относятся

, применяемый для локальных сокетов, реализуемых средствами файловых систем UNIX и Linux, и , используемый для сетевых сокетов UNIX. Сокеты домена могут применяться программами, взаимодействующими в сетях на базе протоколов TCP/IP, включая Интернет. Интерфейс ОС Windows Winsock также предоставляет доступ к этому домену сокетов.

Параметр сокета type задает характеристики обмена данными, применяемые для нового сокета. Возможными значениями могут быть

и . - это упорядоченный, надежный, основанный на соединении, двунаправленный поток байтов. В случае домена сокетов этот тип обмена данными по умолчанию обеспечивается TCP-соединением, которое устанавливается между двумя конечными точками потоковых сокетов при подключении. Данные могут передаваться в двух направлениях по линии связи сокетов. Протоколы TCP включают в себя средства фрагментации и последующей повторной сборки сообщений больших объемов и повторной передачи любых их частей, которые могли быть потеряны в сети. - дейтаграммный сервис. Вы можете использовать такой сокет для отправки сообщений с фиксированным (обычно небольшим) максимальным объемом, но при этом нет гарантии, что сообщение будет доставлено или что сообщения не будут переупорядочены в сети. В случае сокетов домена этот тип передачи данных обеспечивается дейтаграммами UDP (User Datagram Protocol, пользовательский протокол дейтаграмм).

Протокол, применяемый для обмена данными, обычно определяется типом сокета и доменом. Как правило, выбора нет. Параметр

применяется в тех случаях, когда выбор все же предоставляется. Задание 0 позволяет выбрать стандартный протокол, используемый во всех примерах данной главы.

Системный вызов

возвращает дескриптор, во многом похожий на низкоуровневый файловый дескриптор. Когда сокет подключен к концевой точке другого сокета, для отправки и получения данных с помощью сокетов можно применять системные вызовы и с дескриптором сокета. Системный вызов используется для удаления сокетного соединения.

Адреса сокетов

Каждый домен сокетов требует своего формата адресов. В домене

адрес описывается структурой , объявленной в заголовочном файле sys/un.h:
sa_family_t sun_family; /* AF_UNIX */
char sun_path; /* Путь к файлу */

Для того чтобы адреса разных типов могли передаваться в системные вызовы для обработки сокетов, все адресные форматы описываются похожей структурой, которая начинается с поля (в данном случае

), задающего тип адреса (домен сокета). В домене адрес задается именем файла в поле структуры .

В современных системах Linux тип

, описанный в стандарте X/Open как объявляемый в заголовочном файле sys/un.h, интерпретируется как тип . Кроме того, размер , задаваемого в поле , ограничен (в Linux указывается 108 символов; в других системах может применяться именованная константа, например, ). Поскольку размер адресной структуры может меняться, многие системные вызовы сокетов требуют или предоставляют на выходе длину, которая будет использоваться для копирования конкретной адресной структуры. адрес задается с помощью структуры с именем , определенной в файле netinet/in.h, которая содержит как минимум следующие элементы:
short int sin_family; /* AF_INET */
unsigned short int sin_port; /* Номер порта */
struct in_addr sin_addr; /* Интернет-адрес */

Структура IP-адреса типа in_addr определена следующим образом:

unsigned long int s_addr;

Четыре байта IP-адреса образуют одно 32-разрядное значение. Сокет домена

полностью описывается IP-адресом и номером порта. С точки зрения приложения все сокеты действуют как файловые дескрипторы, и их адреса задаются уникальными целочисленными значениями.

Именование сокета

Для того чтобы сделать сокет (созданный с помощью вызова

) доступным для других процессов, серверная программа должна присвоить сокету имя. Сокеты домена связаны с полным именем файла в файловой системе, как вы видели в программе-примере server1. Сокеты домена связаны с номером IP-порта.
#include
int bind(int socket, const struct sockaddr *address, size_t address len);

Системный вызов

присваивает адрес, заданный в параметре , неименованному сокету, связанному с дескриптором сокета . Длина адресной структуры передается в параметре :

Длина и формат адреса зависят от адресного семейства. В системном вызове

указатель конкретной адресной структуры должен быть приведен к обобщенному адресному типу .

В случае успешного завершения

возвращает 0. Если он завершается аварийно, возвращается -1, и переменной присваивается одно из значений, перечисленных в табл. 15.2.

Таблица 15.2

Создание очереди сокетов

Для приема запросов на входящие соединения на базе сокетов серверная программа должна создать очередь для хранения ждущих обработки запросов. Формируется она с помощью системного вызова

.
#include
int listen(int socket, int backlog);

Система Linux может ограничить количество ждущих обработки соединений, которые могут храниться в очереди. В соответствии с этим максимумом вызов

задает длину очереди, равной . Входящие соединения, не превышающие максимальной длины очереди, сохраняются в ожидании сокета; последующим запросам на соединение будет отказано, и клиентская попытка соединения завершится аварийно. Этот механизм реализуется вызовом для того, чтобы можно было сохранить ждущие соединения запросы, пока серверная программа занята обработкой запроса предыдущего клиента. Очень часто параметр равен 5. вернет 0 в случае успешного завершения и -1 в случае ошибки. Как и для системного вызова , ошибки могут обозначаться константами , И .

Прием запросов на соединение

После создания и именования сокета серверная программа может ждать запросы на выполнение соединения с сокетом с помощью системного вызова

:
#include
int accept(int socket, struct sockaddr *address, size_t *address_len);

Системный вызов

возвращает управление, когда клиентская программа пытается подключиться к сокету, заданному в параметре . Этот клиент - первый из ждущих соединения в очереди данного сокета. Функция создает новый сокет для обмена данными с клиентом и возвращает его дескриптор. У нового сокета будет тот же тип, что и у сокета сервера, ждущего запросы на соединения.

Предварительно сокету должно быть присвоено имя с помощью системного вызова

и у него должна быть очередь запросов на соединение, место для которой выделил системный вызов . Адрес вызывающего клиента будет помещен в структуру , на которую указывает параметр . Если адрес клиента не представляет интереса, в этом параметре может задать пустой указатель.

Параметр

задает длину адресной структуры клиента. Если адрес клиента длиннее, чем это значение, он будет урезан. Перед вызовом в параметре должна быть задана ожидаемая длина адреса. По возвращении из вызова в будет установлена реальная длина адресной структуры запрашивающего соединение клиента.

Если нет запросов на соединение, ждущих в очереди сокета, вызов accept будет заблокирован (так что программа не сможет продолжить выполнение) до тех пор, пока клиент не сделает запрос на соединение. Вы можете изменить это поведение, применив флаг

в файловом дескрипторе сокета с помощью вызова в вашей программе следующим образом:
int flags = fcntl(socket, F_GETFL, 0);
fcntl(socket, F_SETFL, O_NONBLOCK | flags);
возвращает файловый дескриптор нового сокета, если есть запрос клиента, ожидающего соединения, и -1 в случае ошибки. Возможные значения ошибок такие же, как у вызовов и плюс дополнительная константа в случае, когда задан флаг и нет ждущих запросов на соединение. Ошибка возникнет, если процесс прерван во время блокировки в функции .

Запросы соединений

Клиентские программы подключаются к серверам, устанавливая соединение между неименованным сокетом и сокетом сервера, ждущим подключений. Делают они это с помощью вызова

:
#include
int connect(int socket, const struct sockaddr *address, size_t address_len);

Сокет, заданный в параметре

, соединяется с сокетом сервера, заданным в параметре , длина которого равна . Сокет должен задаваться корректным файловым дескриптором, полученным из системного вызова .

Если функция

завершается успешно, она возвращает 0, в случае ошибки вернется -1. Возможные ошибки на этот раз включают значения, перечисленные в табл. 15.3.

Таблица 15.3

Если соединение не может быть установлено немедленно, вызов

будет заблокирован на неопределенный период ожидания. Когда допустимое время ожидания будет превышено, соединение разорвется и вызов завершится аварийно. Однако, если вызов прерван сигналом, который обрабатывается, connect завершится аварийно (со значением errno, равным ), но попытка соединения не будет прервана - соединение будет установлено асинхронно и программа должна будет позже проверить, успешно ли оно установлено.

Как и в случае вызова

, возможность блокировки в вызове можно исключить установкой в файловом дескрипторе флага . В этом случае, если соединение не может быть установлено немедленно, вызов завершится аварийно с переменной , равной , и соединение будет выполнено асинхронно.

Хотя асинхронные соединения трудно обрабатывать, вы можете применить вызов

к файловому дескриптору сокета, чтобы убедиться в том, что сокет готов к записи. Мы обсудим вызов чуть позже в этой главе.

Закрытие сокета

Вы можете разорвать сокетное соединение в серверной или клиентской программах, вызвав функцию

, так же как в случае низкоуровневых файловых дескрипторов. Сокеты следует закрывать на обоих концах. На сервере это нужно делать, когда вернет ноль. Имейте в виду, что вызов может быть заблокирован, если сокет, у которого есть непереданные данные, обладает типом, ориентированным на соединение, и установленным параметром . Дополнительную информацию об установке параметров сокета вы узнаете позже в этой главе.

Обмен данными с помощью сокетов

Теперь, когда мы описали основные системные вызовы, связанные с сокетами, давайте повнимательнее рассмотрим программы-примеры. Вы попытаетесь переработать их, заменив сокет файловой системы сетевым сокетом. Недостаток сокета файловой системы состоит в том, что если автор не использует полное имя файла, он создается в текущем каталоге серверной программы. Для того чтобы сделать его полезным в большинстве случаев, следует создать сокет в общедоступном каталоге (например, /tmp), подходящем для сервера и его клиентов. В случае сетевых серверов достаточно выбрать неиспользуемый номер порта.

Для примера выберите номер порта 9734. Это произвольный выбор, позволяющий избежать использования портов стандартных сервисов (вы не должны применять номера портов, меньшие 1024, поскольку они зарезервированы для системного использования). Другие номера портов с обеспечиваемыми ими сервисами часто приводятся в системном файле /etc/services. При написании программ, использующих сокеты, всегда выбирайте номер порта, которого нет в этом файле конфигурации.

Примечание

Вам следует знать, что в программах client2.c и server2.c умышленно допущена ошибка, которую вы устраните в программах client3.c и server3.c. Пожалуйста, не используйте текст примеров client2.c и server2.c в собственных программах.

Вы будете выполнять ваши серверную и клиентскую программы в локальной сети, но сетевые сокеты полезны не только в локальной сети, любая машина с подключением к Интернету (даже по модемной линии связи) может применять сетевые сокеты для обмена данными с другими компьютерами. Программу, основанную на сетевых подключениях, можно применять даже на изолированном компьютере с ОС UNIX, т. к. такой компьютер обычно настроен на использование виртуальной сети или внутренней петли (loopback network), включающей только его самого. Для демонстрационных целей данный пример использует виртуальную сеть, которая может быть также полезна для отладки сетевых приложений, поскольку она устраняет любые внешние сетевые проблемы.

Виртуальная сеть состоит из единственного компьютера, традиционно именуемого

, со стандартным IP-адресом 127.0.0.1. Это локальная машина. Ее адрес вы сможете найти в файле сетевых узлов etc/hosts наряду с именами и адресами других узлов, входящих в совместно используемые сети.

У каждой сети, с которой компьютер обменивается данными, есть связанный с ней аппаратный интерфейс. У компьютера в каждой сети может быть свое имя и конечно будут разные IP-адреса. Например, у машины Нейла с именем tilde три сетевых интерфейса и, следовательно, три адреса. Они записаны в файле /etc/hosts следующим образом.

127.0.0.1 localhost # Петля
192.168.1.1 tilde.localnet # Локальная частная сеть Ethernet
158.152.X.X tilde.demon.co.uk # Модемная линия связи

Первая строка - пример виртуальной сети, ко второй сети доступ осуществляется с помощью адаптера Ethernet, а третья - модемная линия связи с провайдером интернет-сервисов. Вы можете написать программу, применяющую сетевые сокеты, для связи с серверами с помощью любого из приведенных интерфейсов без каких-либо корректировок.

Выполните упражнения 15.3 и 15.4.

Упражнение 15.3. Сетевой клиент

Далее приведена измененная программа-клиент client2.c, предназначенная для использования сетевого соединения на базе сокета в виртуальной сети. Она содержит незначительную ошибку, связанную с аппаратной зависимостью, но мы обсудим ее чуть позже в этой главе.

1. Включите необходимые директивы

и задайте переменные:
#include
#include
struct sockaddr_in address;

2. Создайте сокет клиента:

3. Присвойте имя сокету по согласованию с сервером:

address.sin_addr.s_addr = inet_addr("127.0.0.1");
address.sin_port = 9734;

Оставшаяся часть программы такая же, как в приведенном ранее в этой главе примере. Когда вы выполните эту версию, она завершится аварийно, потому что на данном компьютере нет сервера, выполняющегося на порте 9734.

oops: client2: Connection refused

Как это работает

Клиентская программа использует структуру

из заголовочного файла netinet/in.h для задания адреса . Она пытается подключиться к серверу, размещенному на узле с IP-адресом 127.0.0.1. Программа применяет функцию для преобразования текстового представления IP-адреса в форму, подходящую для адресации сокетов. На страницах интерактивного справочного руководства для inet вы найдете дополнительную информацию о других функциях, преобразующих адреса. Упражнение 15.4. Сетевой сервер

Вам также нужно модифицировать серверную программу, ждущую подключений на выбранном вами номере порта. Далее приведена откорректированная программа сервера server2.c.

1. Вставьте необходимые заголовочные файлы и задайте переменные:

#include
#include

int server_sockfd, client_sockfd;

2. Создайте неименованный сокет для сервера:

3. Дайте имя сокету:

server_address.sin_port.s_addr = inet_addr("127.0.0.1");
server_address.sin_port = 9734;
server_len = sizeof(server_address);
bind(server_sockfd, (struct sockaddr *)&server_address, server_len);

Как это работает

Серверная программа создает сокет домена

и выполняет необходимые действия для приема запросов на подключение к нему. Сокет связывается с выбранным вами портом. Заданный адрес определяет, каким машинам разрешено подсоединяться. Задавая такой же адрес виртуальной сети, как в клиентской программе, вы ограничиваете соединения только локальной машиной.

Если вы хотите разрешить серверу устанавливать соединения с удаленными клиентами, необходимо задать набор IP-адресов, которые разрешены. Можно применить специальное значение

для того, чтобы показать, что будете принимать запросы на подключение от всех интерфейсов, имеющихся на вашем компьютере. Если необходимо, вы можете разграничить интерфейсы разных сетей, чтобы отделить соединения локальной сети от соединений глобальной сети. Константа - 32-разрядное целое число, которое можно использовать в поле адресной структуры. Но прежде вам нужно решить проблему.

Порядок байтов на компьютере и в сети

Если запустить приведенные версии серверной и клиентской программ на машине на базе процессора Intel под управлением Linux, то с помощью команды

можно увидеть сетевые соединения. Эта команда есть в большинство систем UNIX, настроенных на работу в сети. Она отображает клиент-серверное соединение, ожидающее закрытия. Соединение закрывается после небольшой задержки. (Повторяем, что вывод в разных версиях Linux может отличаться.)
$ ./server2 & ./client2
Active Internet connections (w/o servers)
tcp 1 0 localhost:1574 localhost:1174 TIME_WAIT root
Примечание

Прежде чем испытывать последующие примеры этой главы, убедитесь в том, что завершено выполнение серверных программ-примеров, поскольку они будут конкурировать при приеме соединений клиентов, и вы увидите вводящие в заблуждение результаты. Удалить их все (включая те, что будут приведены позже в этой главе) можно с помощью следующей команды:

killall server1 server2 server3 server4 server5

Вы сможете увидеть номера портов, присвоенные соединению сервера с клиентом. Локальный адрес отображает сервер, а внешний адрес - удаленного клиента. (Даже если клиент размещен на той же машине, он все равно подключается через сеть.) Для четкого разделения всех сокетов порты клиентов обычно отличаются от сокета сервера, ожидающего запросы на соединения, и уникальны в пределах компьютера.

Отображается локальный адрес (сокет сервера) 1574 (или может выводиться имя сервиса

) и выбранный в примере порт 9734. Почему они отличаются? Дело в том, что номера портов и адреса передаются через интерфейсы сокета как двоичные числа. В разных компьютерах применяется различный порядок байтов для представления целых чисел. Например, процессор Intel хранит 32-разрядное целое в виде четырех последовательных байтов памяти в следующем порядке 1-2-3-4, где 1-й байт - самый старший. Процессоры IBM PowerPC будут хранить целое со следующим порядком следования байтов: 4-3-2-1. Если используемую для хранения целых память просто побайтно копировать, два компьютера не придут к согласию относительно целочисленных значений.

Для того чтобы компьютеры разных типов могли согласовать значения многобайтовых целых чисел, передаваемых по сети, необходимо определить сетевой порядок передачи байтов. Перед передачей данных клиентские и серверные программы должны преобразовать собственное внутреннее представление целых чисел в соответствии с принятым в сети порядком следования байтов. Делается это с помощью функций, определенных в заголовочном файле netinet/in.h. К ним относятся следующие:

#include
unsigned long int htonl(unsigned long int hostlong);
unsigned short int htons(unsigned short int hostshort);
unsigned long int ntohl(unsigned long int netlong);
unsigned short int ntohs(unsigned short int netshort);

Эти функции преобразуют 16- и 32-разрядные целые из внутреннего формата в сетевой порядок следования байтов и обратно. Их имена соответствуют сокращенному названию выполняемых преобразований, например "host to network, long" (htonl, компьютерный в сетевой, длинные целые) и "host to network, short" (htons, компьютерный в сетевой, короткие целые). Компьютерам, у которых порядок следования байтов соответствует сетевому, эти функции предоставляют пустые операции.

Для обеспечения корректного порядка следования при передаче 16-разрядного целого числа ваши сервер и клиент должны применить эти функции к адресу порта. В программу server3.c следует внести следующие изменения:

server_address.sin_addr_s_addr = htonl(INADDR_ANY);

Результат, возвращаемый функцией

, преобразовывать не нужно, потому что в соответствии со своим определением она возвращает результат с сетевым порядком следования байтов. В программу client3.c необходимо внести следующее изменение:
address.sin_port = htons(9734);

В сервер, благодаря применению константы

, внесено изменение, позволяющее принимать запросы на соединение от любых IP-адресов.

Теперь, выполнив программы server3 и client3, вы увидите корректный номер порта, используемый для локального соединения:

Proto Recv-Q Send-Q Local Address Foreign Address (State) User
tcp 1 0 localhost:9734 localhost:1175 TIME_WAIT root
Примечание

Если вы пользуетесь компьютером, у которого собственный формат представления целых совпадает с сетевым порядком следования байтов, вы не увидите никакой разницы. Но для обеспечения корректного взаимодействия клиентов и серверов с разной архитектурой важно всегда применять функции преобразования.

Сетевая информация

До сих пор у клиентских и серверных программ были адреса и номера портов, компилируемые в них. В более универсальных серверных и клиентских программах для определения применяемых адресов и портов вы можете использовать данные сети.

Если у вас есть на это право, можно добавить свой сервер к списку известных сервисов в файл /etc/services, который назначает имена номерам портов, так что клиенты могут использовать вместо номеров символические имена сервисов.

Точно так же зная имя компьютера, можно определить IP-адрес, вызвав функции базы данных сетевых узлов (host database), которые найдут эти адреса. Делают они это, обращаясь за справкой к конфигурационным файлам, например, etc/hosts или к сетевым информационным сервисам, таким как NIS (Network Information Services (сервисы сетевой информации), ранее известным как Yellow Pages (желтые страницы)) и DNS (Domain Name Service, служба доменных имен).

Функции базы данных сетевых узлов или хостов (Host database) объявлены в заголовочном файле интерфейса netdb.h:

struct hostent *gethostbyaddr(const void* addr, size_t len, int type);
struct hostent* gethostbyname(const char* name);

Структура, возвращаемая этими функциями, должна как минимум содержать следующие элементы.

char *h_name; /* Имя узла */
char **h_aliases; /* Перечень псевдонимов (nicknames) */
int h_addrtype; /* Тип адреса */
int h_length; /* Длина адреса в байтах */
char **h_addr_list /* Перечень адреса (сетевой порядок байтов) */

Если в базе данных нет элемента, соответствующего заданному узлу или адресу, информационные функции вернут пустой указатель.

Аналогично информацию, касающуюся сервисов и связанных номеров портов, можно получить с помощью информационных функций сервисов:

struct servent *getservbyname(const char *name, const char *proto);
struct servent *getservbyport(int port, const char *proto);

Параметр

задает протокол, который будет применяться для подключения к сервису, либо "tcp" для TCP-соединений типа , либо "udp" для UDP-дейтаграмм типа .

Структура

содержит как минимум следующие элементы:
char *s_name; /* Имя сервиса */
char **s_aliases; /* Список псевдонимов (дополнительных имен) */
int s_port; /* Номер IP-порта */
char *s_proto; /* Тип сервиса, обычно "tcp" или "udp" */

Вы можете собрать воедино информацию о компьютере из базы данных сетевых узлов, вызвав функцию

и выведя ее результаты. Учтите, что адрес необходимо преобразовать в соответствующий тип и перейти от сетевого упорядочивания к пригодной для вывода строке с помощью преобразования , определенного следующим образом:
#include
char *inet_ntoa(struct in_addr in);

Функция преобразует адрес интернет-узла в строку формата четверки чисел с точками. В случае ошибки она возвращает -1, но в стандарте POSIX не определены конкретные ошибки. Еще одна новая функция, которую вы примените, -

:
int gethostname(char *name, int name length);

Эта функция записывает имя текущего узла в строку, заданную параметром

. Имя узла будет нуль-терминированной строкой. Аргумент содержит длину строкового имени и, если возвращаемое имя узла превысит эту длину, оно будет обрезано. Функция возвращает 0 в случае успешного завершения и -1 в случае ошибки. И снова ошибки в стандарте POSIX не определены.

Выполните упражнение 15.5.

Упражнение 15.5. Сетевая информация

Данная программа getname.c получает сведения о компьютере.

1. Как обычно, вставьте соответствующие заголовочные файлы и объявите переменные:


char *host, **names, **addrs;

2. Присвойте переменной

значение аргумента, предоставляемого при вызове программы , или по умолчанию имя машины пользователя:

3. Вызовите функцию gethostbyname и сообщите об ошибке, если никакая информация не найдена:

fprintf(stderr, "cannot get info for host: %s\n", host);

4. Отобразите имя узла и любые псевдонимы, которые у него могут быть:

printf("results for host %s:\n", host);
printf("Name: %s\n", hostinfo->h_name);
printf(" %s", *names); names++;

5. Если запрашиваемый узел не является IP-узлом, сообщите об этом и завершите выполнение:

if (hostinfo->h_addrtype != AF_INET) {
fprintf(stderr, "not an IP host!\n");

6. В противном случае выведите IP-адрес (адреса):

addrs = hostinfo->h_addr_list;
printf(" %s", inet_ntoa(*(struct in_addr*)*addrs));

Для определения узла по заданному IP-адресу можно применить функцию

. Вы можете использовать ее на сервере для того, чтобы выяснить, откуда клиент запрашивает соединение.

Как это работает

Программа getname вызывает функцию gethostbyname для извлечения сведений об узле из базы данных сетевых узлов. Она выводит имя компьютера, его псевдонимы (другие имена, под которыми известен компьютер) и IP-адреса, которые он использует в своих сетевых интерфейсах. На одной из машин авторов выполнение примера и указание в качестве аргумента имени tilde привело к выводу двух интерфейсов: сети Ethernet и модемной линии связи.

Когда используется имя узла

, задается виртуальная сеть:

Теперь вы можете изменить свою программу-клиента для соединения с любым именованным узлом сети. Вместо подключения к серверу из вашего примера, вы соединитесь со стандартным сервисом и сможете извлечь номер порта.

Большинство систем UNIX и некоторые ОС Linux делают доступными свои системные время и дату в виде стандартного сервиса с именем

. Клиенты могут подключаться к этому сервису для выяснения мнения сервера о текущих времени и дате. В упражнении 15:6 приведена программа-клиент getdate.c, именно это и делающая. Упражнение 15.6. Подключение к стандартному сервису

1. Начните с обычных директив

и объявлений:
int main(int argc, char *argv) {

2. Найдите адрес узла и сообщите об ошибке, если адрес не найден:

hostinfo = gethostbyname(host);

3. Убедитесь, что на компьютере есть сервис

:
servinfo = getservbyname("daytime", "tcp");
printf("daytime port is %d\n", ntohs(servinfo->s_port));

4. Создайте сокет:

sockfd = socket(AF_INET, SOCK_STREAM, 0);

5. Сформируйте адрес для соединения:

address.sin_family = AF_INET;
address.sin_port = servinfo->s_port;
address.sin_addr = *(struct in_addr *)*hostinfo->h_addr_list;

6. Затем подключитесь и получите информацию:

result = connect(sockfd, (struct sockaddr *)&address, len);
result = read(sockfd, buffer, sizeof(buffer));

Вы можете применять программу

для получения времени суток с любого известного узла сети.
read 26 bytes: 24 JUN 2007 06:03:03 BST

Если вы получаете сообщение об ошибке, такое как

oops: getdate: Connection refused
oops: getdate: No such file or directory

причина может быть в том, что на компьютере, к которому вы подключаетесь, не включен сервис

. Такое поведение стало стандартным для большинства современных систем Linux. В следующем разделе вы увидите, как включать этот и другие сервисы.

Как это работает

При выполнении данной программы можно задать узел, к которому следует подключиться. Номер порта сервиса

определяется функцией сетевой базы данных , которая возвращает сведения о сетевых сервисах таким же способом, как и при получении информации об узле сети. Программа пытается соединиться с адресом, который указан первым в списке дополнительных адресов заданного узла. Если соединение успешно, программа считывает сведения, возвращаемые сервисом daytime, символьную строку, содержащую системные дату и время.

Интернет-демон (xinetd/inetd)

Системы UNIX, предоставляющие ряд сетевых сервисов, зачастую делают это с помощью суперсервера. Эта программа (интернет-демон xinetd или inetd) ожидает одновременно запросы на соединения с множеством адресов портов. Когда клиент подключается к сервису, программа-демон запускает соответствующий сервер. При таком подходе серверам не нужно работать постоянно, они могут запускаться по требованию.

Примечание

В современных системах Linux роль интернет-демона исполняет программа xinetd. Она заменила оригинальную UNIX-программу inetd, которую вы все еще можете встретить в более ранних системах Linux и других UNIX-подобных системах.

Программа xinetd обычно настраивается с помощью пользовательского графического интерфейса для управления сетевыми сервисами, но вы можете изменять и непосредственно файлы конфигурации программы. К ним относятся файл /etc/xinetd.conf и файлы в каталоге /etc/xinetd.d.

У каждого сервиса, предоставляемого программой xinetd, есть файл конфигурации в каталоге /etc/xinetd.d. Программа xinetd считает все эти файлы конфигурации во время запуска и повторно при получении соответствующей команды.

.
# По умолчанию: отключен
# Описание: сервер daytime. Это версия tcp.

Следующий файл конфигурации предназначен для сервиса передачи файлов.

# По умолчанию: отключен
# FTP-сервер vsftpd обслуживает FTP-соединения. Он использует
# для аутентификации обычные, незашифрованные имена пользователей и
# пароли, vsftpd спроектирован для безопасной работы.
# Примечание: этот файл содержит конфигурацию запуска vsftpd для xinetd.
# Файл конфигурации самой программы vsftpd находится в
# log_on_success += DURATION USERID
, к которому подключается программа , обычно обрабатывается самой программой xinetd (он помечен как внутренний) и может включаться с помощью как сокетов типа (tcp), так и сокетов типа (udp).

Сервис передачи файлов

подключается только сокетами типа и предоставляется внешней программой, в данном случае vsftpd. Демон будет запускать эту внешнюю программу, когда клиент подключится к порту .

Для активизации конфигурационных изменений сервиса можно отредактировать конфигурацию xinetd и отправить сигнал отбоя (hang-up) процессу-демону, но мы рекомендуем использовать более дружелюбный способ настройки сервисов. Для того чтобы разрешить вашему клиенту подключаться к сервису

, включите этот сервис с помощью средств, предоставляемых системой Linux. В системах SUSE и openSUSE сервисы можно настраивать из SUSE Control Center (Центр управления SUSE), как показано на рис. 15.1. У версий Red Hat (и Enterprise Linux, и Fedora) есть похожий интерфейс настройки. В нем сервис включается для TCP- и UDP-запросов.

Рис. 15.1


Для систем, применяющих программу inetd вместо xinetd, далее приведено эквивалентное извлечение из файла конфигурации inetd, /etc/inetd.conf, которое программа inetd использует для принятия решения о запуске серверов:

#
# Echo, discard, daytime и chargen используются в основном для
daytime stream tcp nowait root internal
daytime dgram udp wait root internal
# Это стандартные сервисы.
ftp stream tcp-nowait root /usr/sbin/tcpd /usr/sbin/wu.ftpd
telnet stream tcp nowait root /usr/sbin/tcpd /usr/sbin/in.telnetd
# Конец файла inetd.conf.

Обратите внимание на то, что в нашем примере сервис ftp предоставляется внешней программой wu.ftpd. Если в вашей системе выполняется демон inetd, вы можете изменить набор предоставляемых сервисов, отредактировав файл /etc/inetd.conf (знак # в начале строки указывает на то, что это строка комментария) и перезапустив процесс inetd. Сделать это можно, отправив сигнал отбоя (hang-up) с помощью команды

. Для облегчения этого процесса некоторые системы настроены так, что программа inetd записывает свой ID в файл. В противном случае можно применить команду :

Параметры сокета

Существует много параметров, которые можно применять для управления поведением соединений на базе сокетов - слишком много для подробного описания в этой главе. Для манипулирования параметрами используют функцию

:
#include
int setsockopt(int socket, int level, int option_name,
const void *option value, size_t option len);

Задавать параметры можно на разных уровнях иерархии протоколов. Для установки параметров на уровне сокета вы должны задать

равным . Для задания параметров на более низком уровне протоколов (TCP, UDP и т.д.) приравняйте параметр level номеру протокола (полученному либо из заголовочного файла netinet/in.h, либо из функции ).

В аргументе

указывается имя задаваемого параметра, аргумент содержит произвольное значение длиной байтов, передаваемое без изменений обработчику низкоуровневого протокола.

Параметры уровня сокета определены в заголовочном файле sys/socket.h и включают приведенные в табл. 15.4 значения.


Таблица 15.5

Параметры

и принимают целое значение для установки или включения (1) и сброса или выключения (0). Для параметра нужна структура типа , определенная в файле sys/socket.h и задающая состояние параметра и величину интервала задержки. возвращает 0 в случае успеха и -1 в противном случае. На страницах интерактивного справочного руководства описаны дополнительные параметры и ошибки.

Множественные клиенты

До сих пор в этой главе вы видели, как применяются сокеты для реализации клиент-серверных систем, как локальных, так действующих, в сети. После установки соединения на базе сокетов они ведут себя как низкоуровневые открытые файловые дескрипторы и во многом как двунаправленные каналы.

Теперь необходимо рассмотреть случай множественных клиентов, одновременно подключающихся к серверу. Вы видели, что, когда серверная программа принимает от клиента запрос на соединение, создается новый сокет, а исходный сокет, ожидающий запросы на соединение, остается доступен для последующих запросов. Если сервер не сможет немедленно принять поступившие позже запросы на соединения, они сохранятся в очереди ожидания.

Тот факт, что исходный сокет все еще доступен, и что сокеты ведут себя как файловые дескрипторы, дает нам метод одновременного обслуживания многих клиентов. Если сервер вызовет функцию

для создания своей второй копии, открытый сокет будет унаследован новым дочерним процессом. Далее он сможет обмениваться данными с подключившимся клиентом, в то время как основной сервер продолжит прием последующих запросов на соединение. В действительности в вашу программу сервера нужно внести очень простое изменение, показанное в упражнении 15.7.

Поскольку вы создаете дочерние процессы, но не ждете их завершения, следует сделать так, чтобы сервер игнорировал сигналы

, препятствуя возникновению процессов-зомби. Упражнение 15.7. Сервер для многочисленных клиентов

1. Программа server4.c начинается так же, как последний рассмотренный сервер с важным добавлением директивы

для заголовочного файла signal.h. Переменные и процедуры создания и именования сокета остались прежними:
int server_sockfd, client_sockfd;
struct sockaddr_in server_address;
struct sockaddr_in client_address;
server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
server_address.sin_family = AF_INET;
server_address.sin_port = htons(9734);
server_len = sizeof(server_address);
bind(server_sockfd, (struct sockaddr *)&server_address, server_len);

2. Создайте очередь соединений, игнорируйте подробности завершения дочернего процесса и ждите запросов клиентов:

listen(server_sockfd, 5);
signal(SIGCHLD, SIG_IGN);
printf("server waiting\n");

3. Примите запрос на соединение:

client_len = sizeof(client_address);
client_sockfd = accept(server_sockfd,
(struct_sockaddr*)&client_address, &client_len);

4. Вызовите

с целью создания процесса для данного клиента и выполните проверку, чтобы определить, родитель вы или потомок: . Пятисекундная задержка нужна для того, чтобы это продемонстрировать:
read(client_sockfd, &ch, 1);
write(client_sockfd, &ch, 1);
close(client_sockfd);

6. В противном случае вы должны быть родителем и ваша работа с данным клиентом закончена:

close(client_socket);

Код включает пятисекундную задержку при обработке запроса клиента для имитации вычислений сервера или обращения к базе данных. Если бы вы проделали это в предыдущем сервере, каждое выполнение программы client3 заняло бы пять секунд. С новым сервером вы сможете обрабатывать множественные клиентские программы client3 параллельно с общим затраченным временем, чуть превышающим пять секунд.

$ ./client3 & ./client3 & ./client3 & ps x

Как это работает

Теперь серверная программа создает новый дочерний процесс для обработки каждого клиента, поэтому вы можете видеть несколько сообщений об ожидании сервера, поскольку основная программа продолжает ждать новые запросы на подключения. В выводе команды

(отредактированном) показан главный процесс server4 с PID, равным 26 566, который ожидает новых клиентов, в то время, как три клиентских процесса client3 обслуживаются тремя потомками сервера. После пятисекундной паузы все клиенты получают свои результаты и завершаются. Дочерние серверные процессы тоже завершаются, оставляя только один главный серверный процесс.

Серверная программа применяет вызов

для обработки множественных клиентов. В приложении для работы с базой данных это может быть не самым удачным решением, т.к. серверная программа может быть довольно большой, и, кроме того, существует проблема координации обращений к базе данных множественных копий сервера. На самом деле, все, что вам нужно, - это способ обработки множественных клиентов единственным сервером без блокировки и ожидания доставки клиентских запросов. Решение этой задачи включает одновременную обработку множественных открытых файловых дескрипторов и не ограничено только приложениями с применением сокетов. Рассмотрим функцию .

select

Очень часто при разработке приложений Linux вам может понадобиться проверка состояния ряда вводов для того, чтобы определить следующее предпринимаемое действие. Например, программа обмена данными, такая как эмулятор терминала, нуждается в эффективном способе одновременного чтения с клавиатуры и с последовательного порта. В однопользовательской системе подойдет цикл "активного ожидания", многократно просматривающий ввод в поиске данных и читающий их, как только они появятся. Такое поведение очень расточительно в отношении времени ЦП.

Системный вызов

позволяет программе ждать прибытия данных (или завершения вывода) одновременно на нескольких низкоуровневых файловых дескрипторах. Это означает, что программа эмулятора терминала может блокироваться до тех пор, пока у нее не появится работа. Аналогичным образом сервер может иметь дело с многочисленными клиентами, ожидая запросы одновременно на многих открытых сокетах. оперирует структурами данных , представляющими собой множества открытых файловых дескрипторов. Для обработки этих множеств определен набор макросов:
#include #include
void FD_ZERO(fd_set *fdset);
void FD_CLR(int fd, fd_set *fdset);
void FD_SET(int fd, fd_set *fdset);
int FD_ISSET(int fd, fd_set *fdset);

Как и предполагается в соответствии с их именами, макрос

инициализирует структуру пустым множеством, и задают и очищают элементы множества, соответствующего файловому дескриптору, переданному как параметр , а макрос возвращает ненулевое значение, если файловый дескриптор, на который ссылается , является элементом структуры , на которую указывает параметр . Максимальное количество файловых дескрипторов в структуре типа задается константой . может также использовать значение для времени ожидания, чтобы помешать бесконечной блокировке. Это значение задается с помощью структуры . Она определена в файле sys/time.h и содержит следующие элементы:
time_t tv_sec; /* Секунды */
long tv_usec; /* Микросекунды */
, определенный в файле sys/types.h, - целочисленный. Системный вызов объявляется следующим образом:
#include
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *errorfds, struct timeval *timeout);

Вызов . Если параметр - пустой указатель и нет активности на сокетах, вызов может быть заблокирован на неопределенное время. возвращает управление программе, множества дескрипторов будут модифицированы для того, чтобы указать на готовые к чтению или записи или имеющие ошибки дескрипторы. Для их проверки следует использовать макрос , позволяющий определить, какие дескрипторы требуют внимания. Можно изменить значение timeout для того, чтобы показать время, остающееся до следующего превышения времени ожидания, но такое поведение не задано стандартом X/Open. При превышении времени ожидания все множества дескрипторов будут очищены.

Вызов select возвращает общее количество дескрипторов в модифицированных множествах. В случае сбоя он вернет -1 и установит значение переменной

, описывающее ошибку. Возможные ошибки - Выполните упражнение 15.8. Упражнение 15.8. Функция

Далее для демонстрации применения функции select приведена программа select.c. Более сложный пример вы увидите чуть позже. Программа читает данные с клавиатуры (стандартный ввод - дескриптор 0) со временем ожидания 2,5 секунды. Данные читаются только тогда, когда ввод готов. Естественно расширить программу, включив в зависимости от характера приложения другие дескрипторы, такие как последовательные каналы (serial lines) и сокеты.

1. Начните как обычно с директив

и объявлений, а затем инициализируйте для обработки ввода с клавиатуры:

2. Подождите ввод из файла stdin в течение максимум 2,5 секунд:

result = select(FD_SETSIZE, &testfds, (fd_set *)NULL,

3. Спустя это время проверьте

. Если ввода не было, программа выполнит цикл еще раз. Если в нем возникла ошибка, программа завершается:

4. Если во время ожидания у вас наблюдаются некоторые действия, связанные с файловым дескриптором, читайте ввод из stdin и выводите его при каждом получении символа EOL (конец строки), до нажатой комбинации клавиш +:

$ Сервер может применять функцию

одновременно к сокету, ожидающему запросы на подключение, и к сокетам клиентских соединений. Как только активность зафиксирована, можно использовать макрос для проверки в цикле всех возможных файловых дескрипторов и выявления активных среди них.

Если сокет, ожидающий запросов на подключение, готов к вводу, это означает, что клиент пытается подсоединиться, и вы можете вызывать функцию

без риска блокировки. Если клиентский дескриптор указывает на готовность, это означает, что есть запрос клиента, ждущий, что вы сможете прочесть и обработать его. Чтение 0 байтов означает, что клиентский процесс завершился, и вы можете закрыть сокет и удалить его из множества своих дескрипторов.

Выполните упражнение 15.9.

Упражнение 15.9. Улучшенное клиент-серверное приложение

1. В финальный пример программы server5.с вы включите заголовочные файлы sys/time.h и sys/ioctl.h вместо signal.h, использованного в предыдущей программе, и объявите несколько дополнительных переменных для работы с вызовом

:
int server_sockfd, client_sockfd;
struct sockaddr_in server_address;
struct sockaddr_in client_address;

2. Создайте сокет для сервера и присвойте ему имя:

server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = htonl(INADDR_ANY);
server_address.sin_port = htons(9734);
server_len = sizeof(server_address);
bind(serversockfd, (struct sockaddr *)&server_address, server_len);

3. Создайте очередь запросов на соединение и инициализируйте множество

для обработки ввода с сокета
server_sockfd

6. Если зафиксирована активность на

, это может быть запрос на новое соединение, и вы добавляете в множество дескрипторов соответствующий :
client_len = sizeof(client_address);
client_sockfd = accept(server_sockfd,
(struct sockaddr*)&client_address, &client_len);
FD_SET(client_sockfd, &readfds);
printf("adding client on fd %d\n", client_sockfd);

Если активен не сервер, значит, активность проявляет клиент. Если получен

, клиент исчезает, и можно удалить его из множества дескрипторов. В противном случае вы "обслуживаете" клиента, как и в предыдущих примерах.
printf("removing client on fd %d\n", fd);

Для полноты аналогии, упомянутой в начале главы, в табл. 15.5 приведены параллели между соединениями на базе сокетов и телефонными переговорами.


Таблица 15.5

Дейтаграммы

В этой главе мы сосредоточились на программировании приложений, поддерживающих связь со своими клиентами с помощью TCP-соединений на базе сокетов. Существуют ситуации, в которых затраты на установку и поддержку соединения с помощью сокетов излишни.

Хорошим примером может служить сервис

, использованный ранее в программе getdate.c. Вы создаете сокет, выполняете соединение, читаете единственный ответ и разрываете соединение. Столько операций для простого получения даты! так же доступен с помощью UDP-соединений, применяющих дейтаграммы. Для того чтобы воспользоваться им, просто пошлите сервису одну дейтаграмму и получите в ответ единственную дейтаграмму, содержащую дату и время. Все просто.

Сервисы, предоставляемые по UDP-протоколу, применяются в тех случаях, когда клиенту нужно создать короткий запрос к серверу, и он ожидает единственный короткий ответ. Если стоимость времени процессора достаточно низкая, сервер способен обеспечить такой сервис, обрабатывая запросы клиентов по одному и разрешая операционной системе хранить очередь входящих запросов. Такой подход упрощает программирование сервера.

Поскольку UDP - не дающий гарантий сервис, вы можете столкнуться с потерей вашей дейтаграммы или ответа сервера. Если данные важны для вас, возможно, придется тщательно программировать ваших UDP-клиентов, проверяя ошибки и при необходимости повторяя попытки. На практике в локальных сетях UDP-дейтаграммы очень надежны.

Для доступа к сервису, обеспечиваемому UDP-протоколом, вам следует применять системные вызовы

и , но вместо использования вызовов и для сокета вы применяете два системных вызова, характерных для дейтаграмм: и .
/* Начните с обычных include и объявлений. */

int main(int argc, char *argv) {
if (argc == 1) host = "localhost";
/* Ищет адрес хоста и сообщает об ошибке, если не находит. */
hostinfo = gethostbyname(host);
fprintf(stderr, "no host: %s\n", host);
/* Проверяет наличие на компьютере сервиса daytime. */
servinfo = getservbyname("daytime", "udp");
fprintf(stderr, "no daytime service\n");
printf("daytime port is %d\n", ntohs(servinfo->s_port));
/* Создает UDP-сокет. */
sockfd = socket(AF_INEТ, SOCK_DGRAM, 0);
/* Формирует адрес для использования в вызовах sendto/recvfrom... */
address.sin_family = AF_INET;
address.sin_port = servinfo->s_port;
address.sin_addr = *(struct in_addr*)*hostinfo->h_addr_list;
result = sendto(sockfd, buffer, 1, 0, (struct sockaddr *)&address, len);
result = recvfrom(sockfd, buffer, sizeof(buffer), 0,
(struct sockaddr *)&address, &len);
printf("read %d bytes: %s", result, buffer);

Как видите, необходимы лишь незначительные изменения. Как и раньше, вы ищете сервис

с помощью вызова , но задаете дейтаграммный сервис, запрашивая UDP-протокол. Дейтаграммный сокет создается с помощью вызова с параметром . Адрес назначения задается, как и раньше, но теперь вместо чтения из сокета вы должны послать дейтаграмму.

Поскольку вы не устанавливаете явное соединение с сервисами на базе UDP, у вас должен быть способ оповещения сервера о том, что вы хотите получить ответ. В данном случае вы посылаете дейтаграмму (в нашем примере вы отправляете один байт из буфера, в который вы хотите получить ответ) сервису и он посылает в ответ дату и время.

Системный вызов

отправляет дейтаграмму из буфера на сокет, используя адрес сокета и длину адреса. У этого вызова фактически следующий прототип:

int sendto(int sockfd, void *buffer, size_t len, int flags,

struct sockaddr *to, socklen_t tolen);

В случае обычного применения параметр

можно оставлять нулевым.

Системный вызов recvfrom ожидает дейтаграмму в соединении сокета с заданным адресом и помещает ее в буфер. У этого вызова следующий прототип:

int recvfrom(int sockfd, void *buffer, size_t len, int flags, и времени ожидания, позволяющих определить, поступили ли данные, так же, как в случае серверов с устанавливаемыми соединениями. В противном случае можно применить сигнал тревоги для прерывания операции получения данных (см. главу 11).

Резюме

В этой главе мы предложили еще один способ взаимодействия процессов - сокеты. Они позволяют разрабатывать по-настоящему распределенные клиент-серверные приложения, которые выполняются в сетевой среде. Было дано краткое описание некоторых информационных функций базы данных сетевых узлов и способы обработки в системе Linux стандартных системных сервисов с помощью интернет-демонов. Вы проработали ряд примеров клиент-серверных программ, демонстрирующих обработку и сетевую организацию множественных клиентов.

В заключение вы познакомились с системным вызовом

, позволяющим уведомлять программу об активности ввода и вывода сразу на нескольких открытых файловых дескрипторах и сокетах.



Загрузка...