Файл: Понятие прикладных протоколов и серверы приложений (Сетевой сокет и многопоточный вариант сервера).pdf

ВУЗ: Не указан

Категория: Курсовая работа

Дисциплина: Не указана

Добавлен: 19.06.2023

Просмотров: 517

Скачиваний: 3

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

Канальный уровень. На физическом уровне просто пересылаются биты. При этом не учитывается, что в некоторых сетях, в которых линии связи используются (разделяются) попеременно несколькими парами взаимодействующих компьютеров, физическая среда передачи может быть занята. Поэтому одной из задач канального уровня является проверка доступности среды передачи. Другой задачей канального уровня является реализация механизмов обнаружения и коррекции ошибок.[19]

В протоколах канального уровня, используемых в локальных сетях, заложена определенная структура связей между компьютерами и способы их адресации. Хотя канальный уровень и обеспечивает доставку кадра между любыми двумя узлами локальной сети, он это делает только в сети с совершенно определенной топологией связей, именно той топологией, для которой он был разработан. К таким типовым топологиям, поддерживаемым протоколами канального уровня локальных сетей, относятся общая шина, кольцо и звезда. Примерами протоколов канального уровня являются протоколы Ethernet, Token Ring, FDDI, 100VG-AnyLAN.

В локальных сетях протоколы канального уровня используются компьютерами, мостами, коммутаторами и маршрутизаторами. В компьютерах функции канального уровня реализуются совместными усилиями сетевых адаптеров и их драйверов.[20]

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

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

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

Сообщения сетевого уровня принято называть пакетами (packets). При организации доставки пакетов на сетевом уровне используется понятие "номер сети". В этом случае адрес получателя состоит из номера сети и номера компьютера в этой сети.[22]

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


На сетевом уровне определяется два вида протоколов. Первый вид относится к определению правил передачи пакетов с данными конечных узлов от узла к маршрутизатору и между маршрутизаторами.[23] Именно эти протоколы обычно имеют в виду, когда говорят о протоколах сетевого уровня. К сетевому уровню относят и другой вид протоколов, называемых протоколами обмена маршрутной информацией. С помощью этих протоколов маршрутизаторы собирают информацию о топологии межсетевых соединений. Протоколы сетевого уровня реализуются программными модулями операционной системы, а также программными и аппаратными средствами маршрутизаторов.[24]

Примерами протоколов сетевого уровня являются протокол межсетевого взаимодействия IP стека TCP/IP и протокол межсетевого обмена пакетами IPX стека Novell.

Транспортный уровень. На пути от отправителя к получателю пакеты могут быть искажены или утеряны. Хотя некоторые приложения имеют собственные средства обработки ошибок, существуют и такие, которые предпочитают сразу иметь дело с надежным соединением. Работа транспортного уровня заключается в том, чтобы обеспечить приложениям или верхним уровням стека - прикладному и сеансовому - передачу данных с той степенью надежности, которая им требуется.[25] Модель OSI определяет пять классов сервиса, предоставляемых транспортным уровнем. Эти виды сервиса отличаются качеством предоставляемых услуг: срочностью, возможностью восстановления прерванной связи, наличием средств мультиплексирования нескольких соединений между различными прикладными протоколами через общий транспортный протокол, а главное - способностью к обнаружению и исправлению ошибок передачи, таких как искажение, потеря и дублирование пакетов.

Выбор класса сервиса транспортного уровня определяется, с одной стороны, тем, в какой степени задача обеспечения надежности решается самими приложениями и протоколами более высоких, чем транспортный, уровней, а с другой стороны, этот выбор зависит от того, насколько надежной является вся система транспортировки данных в сети.[26] Как правило, все протоколы, начиная с транспортного уровня и выше, реализуются программными средствами конечных узлов сети - компонентами их сетевых операционных систем. В качестве примера транспортных протоколов можно привести протоколы TCP и UDP стека TCP/IP и протокол SPX стека Novell.


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

Уровень представления. Этот уровень обеспечивает гарантию того, что информация, передаваемая прикладным уровнем, будет понятна прикладному уровню в другой системе. При необходимости уровень представления выполняет преобразование форматов данных в некоторый общий формат представления, а на приеме, соответственно, выполняет обратное преобразование. Таким образом, прикладные уровни могут преодолеть, например, синтаксические различия в представлении данных. На этом уровне может выполняться шифрование и дешифрование данных, благодаря которому секретность обмена данными обеспечивается сразу для всех прикладных сервисов. Примером протокола, работающего на уровне представления, является протокол Secure Socket Layer (SSL), который обеспечивает секретный обмен сообщениями для протоколов прикладного уровня стека TCP/IP.[29]

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

1.4 Сетевой сокет и многопоточный вариант сервера

Для работы сервера используется специальный вид сокета — ServerSocket.

При его создании указывается порт, который он должен занять на локальном компьютере, что он и делает, если порт свободен и доступен. По поводу доступности — операционные системы могут ограничивать пользовательские приложения к некоторым номерам портов. Например уже упомянутый порт “80”. Он считается портом по умолчанию для HTTP-запросов. Или порт FTP — ”21”.[31]


Также надо учитывать, что сервер, в отличии от клиента, ЖДЕТ запросы. Как только приходит запрос, серверный сокет создает соединение и по нему точно так же можно отправлять/принимать данные. Вы увидите, что при создании очередного соединения создается экземпляр класса Socket, с которым вы познакомились ранее.[32]

Может создастся впечатление, что каждое соединение захватывает очередной порт (т.к. Создается объект типа Socket) на сервере, но на самом деле это не так. Если несколько упростить, то по сути серверный сокет работает как многоканальный телефон. Т.е. номер один, а клиентов можно обслужить сразу несколько. Все пакеты как-бы деляться по клиенту и все это выглядит так, что каждый объект типа Socket работает сам по себе.

Теперь мы напишем два приложения. Одно — клиент, который посылает строку с текстом. Второе — серверное, которое будет слушать запросы и отвечать на них. Клиентское приложение мы уже в принципе разбирали, так что сложностей с чтением кода быть не должно (если конечно, вы поняли первый пример).[33]

Есть некоторые отличия — во-первых, для записи мы открываем не OutputStream, а PrintWriter. Объект этого класса может принимать на вход строку и сам преобразовывает ее в байты. Есть смысл заглянуть в документацию и почитать про этот класс подробнее.[34]

Во-вторых — алгоритм работы клиента достаточно простой, но требует пояснения.

Первым шагом мы пишем текстовую строку — она определена в начале программы. Потом мы читаем ответ сервера (ответ сервера предусматривает дублирование нашей строки с префиксом «Server returns: «) и посылаем вторую строку — “bye”. Это сигнал, по которому сервер должен понять, что мы хотим прекратить работу. Он нам тоже отвечает “bye” и закрывает сокет, мы эту строку читаем и заканчиваем работу. Второе приложение — сервер. Для его работы используется специальный класс серверного сокета — ServerSocket. Серверный сокет “открывается” на локальном компьютере и занимает определенный порт.[35] В нашем случае порт 1777. Дальше сервер входит в бесконечный цикл, в рамках которого и происходит обработка запросов от клиентских приложений.

Первым шагом внутри цикла сервер переходит в режим ожидания соединения — вызов accept(). При приходе запроса от клиентского приложения метод возвращает объект Socket, который используется так же как и клиентский сокет.[36]

Дальше по коду можно видеть, что мы точно так же открываем два потока — на ввод и вывод и взаимодействуем с клиентом — принимается строка, которая анализируется, не равна ли она “bye”. Если “нет”, то возвращаем дубль строки от клиента с префиксом “Server returns: ”, если “да”, то тоже возвращаем “bye”, выходим из цикла общения с клиентом, закрываем потоки и сокет, который был нами получен из метода accept() и начинаем все заново — вызываем accept() и ждем нового соединения. Т.е схема работы серверного сокета упрощенно выглядит так:[37]


Создаем серверный сокет на определенном порту

Входим в цикл, в котором:

вызываем метод accept()

при приходе соединения получаем объект типа Socket

работаем с этим сокетом через потоки ввода-вывода

по окончанию закрываем потоки и объект типа Socket

Если проводит бытовую аналогию серверного сокета — в офисе на телефоне сидит секретарь (вызов метода accept). Как только приходит звонок, он поднимает трубку (появляется объект типа Socket) и проводит разговор (использует потоки ввода-вывода).[38] После окончания трубка кладется и цикл повторяется. Обратите внимание, что я сделал вызов accept до блока try .. catch. Внутри этого блока я определил еще одну переменную типа Socket — localSocket. Она указывает на наш открытый сокет. Если вы помните, то такая конструкция позволяет автоматически закрывать ресурс.Таким образом наш сокет будет автоматически закрываться. И потоки ввода-вывода тоже.[39]

В принципе можно было сделать вызов accept прямо в блоке try .. catch, но мне кажется, что так наш вариант становиться более наглядным и читабельным. Хотя тут можно спорить. Теперь предлагаю посмотреть код и прочитать комментарии. И еще замечание для внимательных — я не закрыл серверный сокет.[40] Это в общем не есть хорошо, в нашем случае это не является критичным, но для самостоятельной работы можете подумать, как сделать “закрытие” сокета. Недостаток заключается в том, что обработка ВСЕХ запросов происходит последовательно. По сути, у нас только один секретарь на много канальном телефоне. Все запросы от всех клиентов выстраиваются в очередь. Крайне неэффективное решение.

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

На помощь приходит возможность многопоточной обработки. Здесь мы оперируем потоками исполнения (threads) — не путайте с потоками ввода-вывода (streams). Идея и реализация достаточно простые — при приходе соединения мы “отстегиваем” отдельный поток, передаем туда полученный Socket и сразу возвращаемся в методу accept().

Теперь в отдельном потоке мы можем спокойно обработать запрос от клиента. Наша система будет справляться, если количество запросов можно обработать каким-то количеством потоков за необходимый временной интервал.[41] Пусть у вас в секунду приходит 20 запросов и каждый обрабатывается за 5 секунд. Значит для обработки вам потребуется 100 потоков. Для современных компьютеров вполне разумные цифры. Учитывая, что обработка не означает 100% загрузку процессора. Мы говорили об этом при обсуждении многопоточности.[42] Проводя аналогию с секретарем — теперь, его задача принять звонок и сразу перенаправить его другому сотруднику. Второй SocketThread — это класс для обработки клиентского запроса в отдельном потоке. Думаю, что вы должны просто внимательно прочитать код и потом запустить этот пример. Клиентское приложение остается тем же, что и было раньше.[43]