Chapter 6. The Transport Layer The transport layer is not just another layer. It is the heart of the whole protocol hierarchy. Its task is to provide reliable, cost-effective data transport from the source machine to the destination machine, independently of the physical network or networks currently in use. Without the transport layer, the whole concept of layered protocols would make little sense. In this chapter we will study the transport layer in detail, including its services, design, protocols, and performance. Транспортный уровень — это не просто очередной уровень. Это сердцевина всей иерархии протоколов. Его задача состоит в предоставлении надежной и экономичной передачи данных от машины-источника машине-адресату вне зависимости от физических характеристик использующейся сети или сетей. Без транспортного уровня вся концепция многоуровневых протоколов потеряет смысл. В данной главе мы подробно рассмотрим транспортный уровень, включая его сервисы, устройство, протоколы и производительность. 6.1 The Transport Service In the following sections we will provide an introduction to the transport service. We look at what kind of service is provided to the application layer. To make the issue of transport service more concrete, we will examine two sets of transport layer primitives. First comes a simple (but hypothetical) one to show the basic ideas. Then comes the interface commonly used in the Internet. В следующих разделах мы познакомимся с транспортной службой. Мы рассмотрим виды сервисов, предоставляемых прикладному уровню. Чтобы наш разговор не был слишком абстрактным, мы разберем два набора примитивов транспортного уровня. Сначала рассмотрим простой (но не применяемый на практике) набор, просто чтобы показать основные идеи, а затем — реально применяемый в Интернете интерфейс. 6.1.1 Services Provided to the Upper Layers The ultimate goal of the transport layer is to provide efficient, reliable, and cost-effective service to its users, normally processes in the application layer. To achieve this goal, the transport layer makes use of the services provided by the network layer. The hardware and/or software within the transport layer that does the work is called the transport entity. The transport entity can be located in the operating system kernel, in a separate user process, in a library package bound into network applications, or conceivably on the network interface card. The (logical) relationship of the network, transport, and application layers is illustrated in Fig. 61. Конечная цель транспортного уровня заключается в предоставлении эффективных, надежных и экономичных услуг (сервисов) своим пользователям, которыми обычно являются процессы прикладного уровня. Для достижения этой цели транспортный уровень пользуется услугами, предоставляемыми сетевым уровнем. Аппаратура и/или программа, выполняющая работу транспортного уровня, называется транспортной сущностью или транспортным объектом. Транспортный объект может располагаться в ядре операционной системы, в отдельном пользовательском процессе, в библиотечном модуле, загруженном сетевым приложением, или в сетевой интерфейсной плате. Логическая взаимосвязь сетевого, транспортного и прикладного уровней проиллюстрирована на рис. 6.1. Figure 6-1. The network, transport, and application layers. Just as there are two types of network service, connection-oriented and connectionless, there are also two types of transport service. The connection-oriented transport service is similar to the connection-oriented network service in many ways. In both cases, connections have three phases: establishment, data transfer, and release. Addressing and flow control are also similar in both layers. Furthermore, the connectionless transport service is also very similar to the connectionless network service. Сервисы транспортного уровня, как и сервисы сетевого уровня, могут быть ориентированными на соединение или не требующими соединений. Ориентированный на соединение транспортный сервис во многом похож на ориентированный на соединение сетевой сервис. В обоих случаях соединение проходит три этапа: установка, передача данных и разъединение. Адресация и управление потоком на разных уровнях также схожи. Более того, похожи друг на друга и не требующие соединений сервисы разных уровней. The obvious question is then this: If the transport layer service is so similar to the network layer service, why are there two distinct layers? Why is one layer not adequate? The answer is subtle, but crucial, and goes back to Fig. 1-9. The transport code runs entirely on the users' machines, but the network layer mostly runs on the routers, which are operated by the carrier (at least for a wide area network). What happens if the network layer offers inadequate service? Suppose that it frequently loses packets? What happens if routers crash from time to time? Возникает закономерный вопрос: если сервис транспортного уровня так схож с сервисом сетевого уровня, то зачем нужны два различных уровня? Почему недостаточно одного уровня? Для ответа на этот важный вопрос следует вернуться к рис. 1.7. На рисунке мы видим, что транспортный код запускается целиком на пользовательских машинах, а сетевой уровень запускается в основном на машрутизаторах, которые управляются оператором связи (по крайней мере, в глобальных сетях). Что произойдет, если сетевой уровень будет предоставлять ориентированный на соединение сервис, но этот сервис будет ненадежным? Например, если он часто будет терять пакеты? Можно себе представить, что случится, если маршрутизаторы будут время от времени выходить из строя. Problems occur, that's what. The users have no real control over the network layer, so they cannot solve the problem of poor service by using better routers or putting more error handling in the data link layer. The only possibility is to put on top of the network layer another layer that improves the quality of the service. If, in a connection-oriented subnet, a transport entity is informed halfway through a long transmission that its network connection has been abruptly terminated, with no indication of what has happened to the data currently in transit, it can set up a new network connection to the remote transport entity. Using this new network connection, it can send a query to its peer asking which data arrived and which did not, and then pick up from where it left off. В этом случае пользователи столкнутся с большими проблемами. У них нет контроля над сетевым уровнем, поэтому они не смогут улучшить качество обслуживания, используя хорошие маршрутизаторы или совершенствуя обработку ошибок уровня передачи данных. Единственная возможность заключается в использовании еще одного уровня, расположенного над сетевым, для улучшения качества обслуживания. Если транспортный объект узнает, что его сетевое соединение было внезапно прервано, но не получит какихлибо сведений о том, что случилось с передаваемыми в этот момент данными, он может установить новое соединение с удаленной транспортной сущностью. С помощью нового сетевого соединения он может послать запрос к равноранговому объекту и узнать, какие данные дошли до адресата, а какие нет, после чего продолжить передачу данных. In essence, the existence of the transport layer makes it possible for the transport service to be more reliable than the underlying network service. Lost packets and mangled data can be detected and compensated for by the transport layer. Furthermore, the transport service primitives can be implemented as calls to library procedures in order to make them independent of the network service primitives. The network service calls may vary considerably from network to network (e.g., connectionless LAN service may be quite different from connection-oriented WAN service). By hiding the network service behind a set of transport service primitives, changing the network service merely requires replacing one set of library procedures by another one that does the same thing with a different underlying service. По сути, благодаря наличию транспортного уровня транспортный сервис может быть более надежным, чем лежащий ниже сетевой сервис. Транспортным уровнем могут быть обнаружены потерянные пакеты и искаженные данные, после чего потери могут быть компенсированы. Более того, примитивы транспортной службы могут быть разработаны таким образом, что они будут независимы от примитивов сетевой службы, которые могут значительно варьироваться от сети к сети (например, сервис локальной сети без соединений может значительно отличаться от сервиса ориентированной на соединение глобальной сети). Если спрятать службы сетевого уровня за набором примитивов транспортной службы, то для изменения сетевой службы потребуется просто заменить один набор библиотечных процедур другими, делающими то же самое, но с помощью других сервисов более низкого уровня. Thanks to the transport layer, application programmers can write code according to a standard set of primitives and have these programs work on a wide variety of networks, without having to worry about dealing with different subnet interfaces and unreliable transmission. If all real networks were flawless and all had the same service primitives and were guaranteed never, ever to change, the transport layer might not be needed. However, in the real world it fulfills the key function of isolating the upper layers from the technology, design, and imperfections of the subnet. Благодаря наличию транспортного уровня прикладные программы могут использовать стандартный набор примитивов и сохранять работоспособность в самых различных сетях. Им не придется учитывать имеющееся разнообразие интерфейсов подсетей и беспокоиться о ненадежной передаче данных. Если бы все реальные сети работали идеально и у всех сетей был один набор служебных примитивов, то транспортный уровень, вероятно, был бы не нужен. Однако в реальном мире он выполняет ключевую роль изолирования верхних уровней от деталей технологии, устройства и несовершенства подсети. For this reason, many people have traditionally made a distinction between layers 1 through 4 on the one hand and layer(s) above 4 on the other. The bottom four layers can be seen as the transport service provider, whereas the upper layer(s) are the transport service user. This distinction of provider versus user has a considerable impact on the design of the layers and puts the transport layer in a key position, since it forms the major boundary between the provider and user of the reliable data transmission service. Именно по этой причине часто проводится разграничение между уровнями с первого по четвертый и уровнями выше четвертого. Нижние четыре уровня можно рассматривать как поставщика транспортных услуг, а верхние уровни — как пользователя транспортных услуг. Разделение на поставщика и пользователя оказывает серьезное влияние на устройство уровней и помещает транспортный уровень в ключевую позицию, поскольку он формирует основную границу между поставщиком и пользователем надежной службы передачи данных. 6.1.2 Transport Service Primitives To allow users to access the transport service, the transport layer must provide some operations to application programs, that is, a transport service interface. Each transport service has its own interface. In this section, we will first examine a simple (hypothetical) transport service and its interface to see the bare essentials. In the following section we will look at a real example. Чтобы пользователи могли получить доступ к транспортной службе, транспортный уровень должен совершать некоторые действия по отношению к прикладным программам, то есть предоставлять интерфейс транспортной службы. У всех транспортных служб есть свои интерфейсы. В этом разделе мы вначале рассмотрим простой (но гипотетический) пример транспортной службы и ее интерфейсов, просто чтобы узнать основные принципы и понятия. Следующий раздел будет посвящен реальному примеру. The transport service is similar to the network service, but there are also some important differences. The main difference is that the network service is intended to model the service offered by real networks, warts and all. Real networks can lose packets, so the network service is generally unreliable. Транспортная служба подобна сетевой, но имеет и некоторые существенные отличия. Главное отличие состоит в том, что сетевая служба предназначена для моделирования сервисов, предоставляемых реальными сетями, со всеми их особенностями. Реальные сети теряют пакеты, поэтому в общем случае сетевая служба ненадежна. The (connection-oriented) transport service, in contrast, is reliable. Of course, real networks are not error-free, but that is precisely the purpose of the transport layer—to provide a reliable service on top of an unreliable network. Ориентированная на соединение транспортная служба, напротив, является надежной. Конечно, реальные сети содержат ошибки, но именно транспортный уровень как раз и должен обеспечивать надежность сервисов ненадежных сетей. As an example, consider two processes connected by pipes in UNIX. They assume the connection between them is perfect. They do not want to know about acknowledgements, lost packets, congestion, or anything like that. What they want is a 100 percent reliable connection. Process A puts data into one end of the pipe, and process B takes it out of the other. This is what the connection-oriented transport service is all about—hiding the imperfections of the network service so that user processes can just assume the existence of an error-free bit stream. В качестве примера рассмотрим два процесса, соединенных каналами в системе UNIX. Эти процессы предполагают, что соединение между ними идеально. Они не желают знать о подтверждениях, потерянных пакетах, заторах и т. п. Им требуется стопроцентно надежное соединение. Процесс А помещает данные в один конец канала, а процесс В извлекает их на другом. Именно для этого и предназначена ориентированная на соединение транспортная служба — скрывать несовершенство сетевого уровня, чтобы пользовательские процессы могли считать, что существует безошибочный поток битов. As an aside, the transport layer can also provide unreliable (datagram) service. However, there is relatively little to say about that, so we will mainly concentrate on the connectionoriented transport service in this chapter. Nevertheless, there are some applications, such as clientserver computing and streaming multimedia, which benefit from connectionless transport, so we will say a little bit about it later on. Кстати, транспортный уровень может также предоставлять ненадежный (дейтаграммный) сервис, но о нем сказать почти нечего, поэтому мы в данной главе сконцентрируемся на транспортной службе, ориентированной на соединение. Тем не менее, некоторые приложения, например, клиент-серверные вычислительные системы и потоковое мультимедиа, даже выигрывают от дейтаграммных сервисов, поэтому далее мы еще упомянем их. A second difference between the network service and transport service is whom the services are intended for. The network service is used only by the transport entities. Few users write their own transport entities, and thus few users or programs ever see the bare network service. In contrast, many programs (and thus programmers) see the transport primitives. Consequently, the transport service must be convenient and easy to use. Второе различие между сетевой и транспортной службами состоит в том, для кого они предназначены. Сетевая служба используется только транспортными объектами. Мало кто пишет свои собственные транспортные объекты, и поэтому пользователи и программы почти не встречаются с голой сетевой службой. Транспортные примитивы, напротив, используются многими программами, а следовательно, и программистами. Поэтому транспортная служба должна быть удобной и простой в употреблении. To get an idea of what a transport service might be like, consider the five primitives listed in Fig. 6-2. This transport interface is truly bare bones, but it gives the essential flavor of what a connection-oriented transport interface has to do. It allows application programs to establish, use, and then release connections, which is sufficient for many applications. Чтобы получить представление о транспортной службе, рассмотрим пять примитивов, перечисленных в табл. 6.1. Этот транспортный интерфейс сильно упрощен, но он дает представление о назначении ориентированного на соединение транспортного интерфейса. Он позволяет прикладным программам устанавливать, использовать и освобождать соединения, чего вполне достаточно для многих приложений. Figure 6-2. The primitives for a simple transport service. To see how these primitives might be used, consider an application with a server and a number of remote clients. To start with, the server executes a LISTEN primitive, typically by calling a library procedure that makes a system call to block the server until a client turns up. When a client wants to talk to the server, it executes a CONNECT primitive. The transport entity carries out this primitive by blocking the caller and sending a packet to the server. Encapsulated in the payload of this packet is a transport layer message for the server's transport entity. Чтобы понять, как могут быть использованы эти примитивы, рассмотрим приложение, состоящее из сервера и нескольких удаленных клиентов. Вначале сервер выполняет примитив LISTEN — обычно для этого вызывается библиотечная процедура, которая обращается к системе. В результате сервер блокируется, пока клиент не обратится к нему. Когда клиент хочет поговорить с сервером, он выполняет примитив CONNECT. Транспортный объект выполняет этот примитив, блокируя обратившегося к нему клиента и посылая пакет серверу. Поле данных пакета содержит сообщение транспортного уровня, адресованное транспортному объекту сервера. A quick note on terminology is now in order. For lack of a better term, we will reluctantly use the somewhat ungainly acronym TPDU (Transport Protocol Data Unit) for messages sent from transport entity to transport entity. Thus, TPDUs (exchanged by the transport layer) are contained in packets (exchanged by the network layer). In turn, packets are contained in frames (exchanged by the data link layer). When a frame arrives, the data link layer processes the frame header and passes the contents of the frame payload field up to the network entity. The network entity processes the packet header and passes the contents of the packet payload up to the transport entity. This nesting is illustrated in Fig. 6-3. Следует сказать пару слов о терминологии. За неимением лучшего термина, для сообщений, посылаемых одной транспортной сущностью другой транспортной сущности, нам придется использовать несколько неуклюжее сокращение TPDU (Transport Protocol Data Unit— модуль данных транспортного протокола). Модули данных, которыми обмениваются транспортные уровни, помещаются в пакеты (которыми обмениваются сетевые уровни). Эти пакеты, в свою очередь, содержатся в кадрах, которыми обмениваются уровни передачи данных. Получив кадр, уровень передачи данных обрабатывает заголовок кадра и передает содержимое поля полезной нагрузки кадра наверх, сетевой сущности. Сетевая сущность обрабатывает заголовок пакета и передает содержимое поля полезной нагрузки пакета наверх, транспортной сущности. Эта вложенность проиллюстрирована на рис. 6.2. Figure 6-3. Nesting of TPDUs, packets, and frames. Getting back to our client-server example, the client's CONNECT call causes a CONNECTION REQUEST TPDU to be sent to the server. When it arrives, the transport entity checks to see that the server is blocked on a LISTEN (i.e., is interested in handling requests). It then unblocks the server and sends a CONNECTION ACCEPTED TPDU back to the client. When this TPDU arrives, the client is unblocked and the connection is established. Итак, вернемся к нашему примеру общения клиента и сервера. В результате запроса клиента CONNECT серверу посылается модуль данных транспортного протокола, содержащий CONNECTION REQUEST (запрос соединения). Когда он прибывает, транспортная сущность проверяет, заблокирован ли сервер примитивом LISTEN (то есть заинтересован ли сервер в обработке запросов). Затем она разблокирует сервер и посылает обратно клиенту модуль данных CONNECTION ACCEPTED (соединение принято). Получив этот модуль, клиент разблокируется, после чего соединение считается установленным. Data can now be exchanged using the SEND and RECEIVE primitives. In the simplest form, either party can do a (blocking) RECEIVE to wait for the other party to do a SEND. When the TPDU arrives, the receiver is unblocked. It can then process the TPDU and send a reply. As long as both sides can keep track of whose turn it is to send, this scheme works fine. Теперь клиент и сервер могут обмениваться данными с помощью примитивов SEND и RECEIVE. В простейшем случае каждая из сторон может использовать блокирующий примитив RECEIVE для перехода в режим ожидания модуля данных, посылаемого противоположной стороной при помощи примитива SEND. Когда модуль данных прибывает, получатель разблокируется. Затем он может обработать полученный модуль и послать ответ. Такая схема прекрасно работает, пока обе стороны помнят, чей черед посылать, а чей — принимать. That at the transport layer, even a simple unidirectional data exchange is more complicated than at the network layer. Every data packet sent will also be acknowledged (eventually). The packets bearing control TPDUs are also acknowledged, implicitly or explicitly. These acknowledgements are managed by the transport entities, using the network layer protocol, and are not visible to the transport users. Similarly, the transport entities will need to worry about timers and retransmissions. None of this machinery is visible to the transport users. To the transport users, a connection is a reliable bit pipe: one user stuffs bits in and they magically appear at the other end. This ability to hide complexity is the reason that layered protocols are such a powerful tool. Обратите внимание на то, что на сетевом уровне даже простая однонаправленная пересылка данных оказывается сложнее, чем на транспортном уровне. Каждый посланный пакет данных будет, в конце концов, подтвержден. Пакеты, содержащие управляющие модули данных, также подтверждаются, явно или неявно. Эти подтверждения управляются транспортными сущностями при помощи протокола сетевого уровня и не видны пользователям транспортного уровня. Аналогично транспортным сущностям нет необходимости беспокоиться о таймерах и повторных передачах. Все эти механизмы не видны пользователям транспортного уровня, для которых соединение представляется надежным битовым каналом. Один пользователь помещает в канал биты, которые волшебным образом появляются на другом конце канала. Эта способность скрывать сложность от пользователей свидетельствует о том, что многоуровневые протоколы являются довольно мощным инструментом. When a connection is no longer needed, it must be released to free up table space within the two transport entities. Disconnection has two variants: asymmetric and symmetric. In the asymmetric variant, either transport user can issue a DISCONNECT primitive, which results in a DISCONNECT TPDU being sent to the remote transport entity. Upon arrival, the connection is released. Когда соединение больше не требуется, оно должно быть разорвано, чтобы можно было освободить место в таблицах двух транспортных сущностей. Разъединение существует в двух вариантах: симметричном и асимметричном. В асимметричном варианте любой пользователь транспортной службы может вызвать примитив DISCONNECT, в результате чего удаленной транспортной сущности будет послан управляющий модуль TPDU DISCONNECTION REQUEST (запрос разъединения). После получения модуля TPDU удаленной транспортной сущностью соединение разрывается. In the symmetric variant, each direction is closed separately, independently of the other one. When one side does a DISCONNECT, that means it has no more data to send but it is still willing to accept data from its partner. In this model, a connection is released when both sides have done a DISCONNECT. В симметричном варианте каждое направление закрывается отдельно, независимо от другого. Когда одна сторона выполняет примитив DISCONNECT, это означает, что у нее больше нет данных для передачи, но что она все еще готова принимать данные от своего партнера. В этой схеме соединение разрывается, когда обе стороны выполняют примитив DISCONNECT. A state diagram for connection establishment and release for these simple primitives is given in Fig. 6-4. Each transition is triggered by some event, either a primitive executed by the local transport user or an incoming packet. For simplicity, we assume here that each TPDU is separately acknowledged. We also assume that a symmetric disconnection model is used, with the client going first. Please note that this model is quite unsophisticated. We will look at more realistic models later on. Диаграмма состояний для установки и разрыва соединения показана на рис. 6.3. Каждый переход вызывается каким-то событием или примитивом, выполненным локальным пользователем транспортной службы или входящим пакетом. Для простоты мы будем считать, что каждый модуль TPDU подтверждается отдельно. Мы также предполагаем, что используется модель симметричного разъединения, в которой клиент делает первый ход. Обратите внимание на простоту этой модели. Позднее мы рассмотрим более реалистичные модели. Figure 6-4. A state diagram for a simple connection management scheme. Transitions labeled in italics are caused by packet arrivals. The solid lines show the client's state sequence. The dashed lines show the server's state sequence. 6.1.3 Berkeley Sockets Let us now briefly inspect another set of transport primitives, the socket primitives used in Berkeley UNIX for TCP. These primitives are widely used for Internet programming. They are listed in Fig. 6-5. Roughly speaking, they follow the model of our first example but offer more features and flexibility. We will not look at the corresponding TPDUs here. That discussion will have to wait until we study TCP later in this chapter. Теперь рассмотрим другой набор транспортных примитивов — примитивы сокетов (иногда называемых гнездами), используемые в операционной системе Berkeley UNIX для протокола TCP (Transmission Control Protocol — протокол управления передачей). Они приведены в табл. 6.2. Модель сокетов во многом подобна рассмотренной ранее модели транспортных примитивов, но обладает большей гибкостью и предоставляет больше возможностей. Модули TPDU, соответствующие этой модели, будут рассматриваться далее в этой главе, когда мы будем изучать TCP. Figure 6-5. The socket primitives for TCP. The first four primitives in the list are executed in that order by servers. The SOCKET primitive creates a new end point and allocates table space for it within the transport entity. The parameters of the call specify the addressing format to be used, the type of service desired (e.g., reliable byte stream), and the protocol. A successful SOCKET call returns an ordinary file descriptor for use in succeeding calls, the same way an OPEN call does. Первые четыре примитива списка выполняются серверами в таком же порядке. Примитив SOCKET создает новый сокет и выделяет для него место в таблице транспортной сущности. Параметры вызова указывают используемый формат адресов, тип требуемой услуги (например, надежный байтовый поток) и протокол. В случае успеха примитив SOCKET возвращает обычный описатель файла, используемого при вызове следующих примитивов, подобно тому, как возвращает описатель файла процедура OPEN. Newly-created sockets do not have network addresses. These are assigned using the BIND primitive. Once a server has bound an address to a socket, remote clients can connect to it. The reason for not having the SOCKET call create an address directly is that some processes care about their address (e.g., they have been using the same address for years and everyone knows this address), whereas others do not care. У только что созданного сокета нет сетевых адресов. Они назначаются с помощью примитива BIND. После того как сервер привязывает адрес к сокету, с ним могут связаться удаленные клиенты. Вызов SOCKET не создает адрес напрямую, так как некоторые процессы придают адресам большое значение (например, они использовали один и тот же адрес годами, и этот адрес всем известен), тогда как другим процессам это не важно. Next comes the LISTEN call, which allocates space to queue incoming calls for the case that several clients try to connect at the same time. In contrast to LISTEN in our first example, in the socket model LISTEN is not a blocking call. Следом идет вызов LISTEN, который выделяет место для очереди входящих соединений на случай, если несколько клиентов попытаются соединиться одновременно. В отличие от примитива LISTEN в нашем первом примере, примитив LISTEN гнездовой модели не является блокирующим вызовом. To block waiting for an incoming connection, the server executes an ACCEPT primitive. When a TPDU asking for a connection arrives, the transport entity creates a new socket with the same properties as the original one and returns a file descriptor for it. The server can then fork off a process or thread to handle the connection on the new socket and go back to waiting for the next connection on the original socket. ACCEPT returns a normal file descriptor, which can be used for reading and writing in the standard way, the same as for files. Чтобы заблокировать ожидание входящих соединений, сервер выполняет примитив ACCEPT. Получив TPDU-модуль с запросом соединения, транспортная сущность создает новый сокет с теми же свойствами, что и у исходного сокета, и возвращает описатель файла для него. При этом сервер может разветвить процесс или поток, чтобы обработать соединение для нового сокета и вернуться к ожиданию следующего соединения для оригинального сокета. Now let us look at the client side. Here, too, a socket must first be created using the SOCKET primitive, but BIND is not required since the address used does not matter to the server. The CONNECT primitive blocks the caller and actively starts the connection process. When it completes (i.e., when the appropriate TPDU is received from the server), the client process is unblocked and the connection is established. Both sides can now use SEND and RECV to transmit and receive data over the full-duplex connection. The standard UNIX READ and WRITE system calls can also be used if none of the special options of SEND and RECV are required. Теперь посмотрим на этот процесс со стороны клиента. В этом случае также сначала с помощью примитива SOCKET должен быть создан сокет, но примитив BIND здесь не требуется, так как используемый адрес не имеет значения для сервера. Примитив CONNECT блокирует вызывающего и инициирует активный процесс соединения. Когда этот процесс завершается (то есть когда соответствующий TPDU-модуль, посланный сервером, получен), процесс клиента разблокируется и соединение считается установленным. После этого обе стороны могут использовать примитивы SEND и RECV для передачи и получения данных по полнодуплексному соединению. Могут также применяться стандартные UNIX-вызовы READ и WRITE, если нет нужды в использовании специальных свойств SEND и RECV. Connection release with sockets is symmetric. When both sides have executed a CLOSE primitive, the connection is released. В модели сокетов используется симметричный разрыв соединения. Соединение разрывается, когда обе стороны выполняют примитив CLOSE. 6.1.4 An Example of Socket Programming: An Internet File Server As an example of how the socket calls are used, consider the client and server code of Fig. 6-6. Here we have a very primitive Internet file server along with an example client that uses it. The code has many limitations (discussed below), but in principle the server code can be compiled and run on any UNIX system connected to the Internet. The client code can then be compiled and run on any other UNIX machine on the Internet, anywhere in the world. The client code can be executed with appropriate parameters to fetch any file to which the server has access on its machine. The file is written to standard output, which, of course, can be redirected to a file or pipe. В качестве примера использования вызовов сокета рассмотрим программу, демонстрирующую работу клиента и сервера, представленную в листинге 6.1. Имеется примитивный файл-сервер, работающий в Интернете и использующий его клиент. У программы много ограничений (о которых еще будет сказано), но, в принципе, данный код, описывающий сервер, может быть скомпилирован и запущен на любой UNIX-системе, подключенной к Интернету. Код, описывающий клиента, может быть запущен с определенными параметрами. Это позволит ему получить любой файл, к которому у сервера есть доступ. Файл отображается на стандартном устройстве вывода, но, разумеется, может быть перенаправлен на диск или какому-либо процессу. Let us look at the server code first. It starts out by including some standard headers, the last three of which contain the main Internet-related definitions and data structures. Next comes a definition of SERVER_PORT as 12345. This number was chosen arbitrarily. Any number between 1024 and 65535 will work just as well as long as it is not in use by some other process. Of course, the client and server have to use the same port. If this server ever becomes a worldwide hit (unlikely, given how primitive it is), it will be assigned a permanent port below 1024 and appear on www.iana.org. Рассмотрим сперва ту часть программы, которая описывает сервер. Она начинается с включения некоторых стандартных заголовков, последние три из которых содержат основные структуры и определения, связанные с Интернетом. Затем SERVER_PORT определяется как 12 345. Значение выбрано случайным образом. Любое число от 1024 до 65 535 подойдет с не меньшим успехом, если только оно не используется каким-либо другим процессом. Понятно, что клиент и сервер должны обращаться к одному и тому же порту. Если сервер в один прекрасный день станет популярным во всем мире (что маловероятно, учитывая то, насколько он примитивен), ему будет присвоен постоянный порт с номером менее 1024, который появится на www.iana.org. The next two lines in the server define two constants needed. The first one determines the chunk size used for the file transfer. The second one determines how many pending connections can be held before additional ones are discarded upon arrival. В последующих двух строках определяются две необходимые серверу константы. Первая из них задает размер участка данных для файловой передачи. Вторая определяет максимальное количество незавершенных соединений, после установки которых новые соединения будут отвергаться. After the declarations of local variables, the server code begins. It starts out by initializing a data structure that will hold the server's IP address. This data structure will soon be bound to the server's socket. The call to memset sets the data structure to all 0s. The three assignments following it fill in three of its fields. The last of these contains the server's port. The functions htonl and htons have to do with converting values to a standard format so the code runs correctly on both big-endian machines (e.g., the SPARC) and little-endian machines (e.g., the Pentium). Their exact semantics are not relevant here. После объявления локальных переменных начинается сама программа сервера. Вначале она инициализирует структуру данных, которая будет содержать IP-адрес сервера. Эта структура будет связана с серверным сокетом. Вызов memset полностью обнуляет структуру данных. Последующие три присваивания заполняют три поля этой структуры. Последнее из них содержит порт сервера. Функции htonl и htons занимаются преобразованием значений в стандартный формат, что позволяет программе нормально выполняться на машинах с представлением числовых разрядов как в возрастающем порядке (например, SPARC), так и в убывающем (например, Pentium). Детали их семантики здесь роли не играют. Next the server creates a socket and checks for errors (indicated by s < 0). In a production version of the code, the error message could be a trifle more explanatory. The call to setsockopt is needed to allow the port to be reused so the server can run indefinitely, fielding request after request. Now the IP address is bound to the socket and a check is made to see if the call to bind succeeded. The final step in the initialization is the call to listen to announce the server's willingness to accept incoming calls and tell the system to hold up to QUEUE_SIZE of them in case new requests arrive while the server is still processing the current one. If the queue is full and additional requests arrive, they are quietly discarded. После этого сервером создается и проверяется на ошибки (определяется по s < 0) сокет. В конечной версии программы сообщение об ошибке может быть чуть более понятным. Вызов setsockopt нужен для того, чтобы порт мог использоваться несколько раз, а сервер — бесконечно, обрабатывая запрос за запросом. Теперь IP-адрес привязывается к сокету и выполняется проверка успешного завершения вызова bind. Конечным этапом инициализации является вызов listen, свидетельствующий о готовности сервера к приему входящих вызовов и сообщающий системе о том, что нужно ставить в очередь до QUEUE_SI2E вызовов, пока сервер обрабатывает текущий вызов. При заполнении очереди прибытие новых запросов спокойно игнорируется. At this point the server enters its main loop, which it never leaves. The only way to stop it is to kill it from outside. The call to accept blocks the server until some client tries to establish a connection with it. If the accept call succeeds, it returns a file descriptor that can be used for reading and writing, analogous to how file descriptors can be used to read and write from pipes. However, unlike pipes, which are unidirectional, sockets are bidirectional, so sa (socket address) can be used for reading from the connection and also for writing to it. В этом месте начинается основной блок программы, который никогда не пропускается. Его можно остановить только извне. Вызов accept блокирует сервер на то время, пока клиент пытается установить соединение. Если вызов завершается успешно, accept возвращает дескриптор файла, который можно использовать для чтения и записи, аналогично тому, как файловые дескрипторы могут записываться и читаться в каналах. Однако, в отличие от однонаправленных каналов, сокеты двунаправлены, поэтому для чтения (и записи) данных из соединения надо использовать sa (адрес сокета). After the connection is established, the server reads the file name from it. If the name is not yet available, the server blocks waiting for it. After getting the file name, the server opens the file and then enters a loop that alternately reads blocks from the file and writes them to the socket until the entire file has been copied. Then the server closes the file and the connection and waits for the next connection to show up. It repeats this loop forever. После установки соединения сервер считывает имя файла. Если оно пока недоступно, сервер блокируется, ожидая его. Получив имя файла, сервер открывает файл и входит в цикл, который читает блоки данных из файла и записывает их в сокет. Это продолжается до тех пор, пока не будут скопированы все запрошенные данные. Затем файл закрывается, соединение разрывается, и начинается ожидание нового вызова. Данный цикл повторяется бесконечно. Now let us look at the client code. To understand how it works, it is necessary to understand how it is invoked. Assuming it is called client, a typical call is Теперь рассмотрим часть кода, описывающую клиента. Чтобы понять, как работает программа, необходимо вначале разобраться, как она запускается. Если она называется client, ее типичный вызов будет выглядеть так: client flits.cs.vu.nl /usr/tom/filename >f This call only works if the server is already running on flits.cs.vu.nl and the file /usr/tom/filename exists and the server has read access to it. If the call is successful, the file is transferred over the Internet and written to f, after which the client program exits. Since the server continues after a transfer, the client can be started again and again to get other files. Этот вызов сработает только в том случае, если сервер расположен по адресу flits.cs.vu.nl, файл usr/tom/fi lename существует и у сервера есть доступ для чтения этого файла. Если вызов произведен успешно, файл передается по Интернету и записывается на место f, после чего клиентская программа заканчивает свою работу. Поскольку серверная программа продолжает работать, клиент может запускать новые запросы на получение файлов. The client code starts with some includes and declarations. Execution begins by checking to see if it has been called with the right number of arguments (argc = 3 means the program name plus two arguments). Note that argv [1] contains the server's name (e.g., flits.cs.vu.nl) and is converted to an IP address by gethostbyname. This function uses DNS to look up the name. We will study DNS in Chap. 7. Клиентская программа начинается с подключения файлов и объявлений. Работа начинается с проверки корректности числа аргументов (агдс = 3 означает, что в строке запуска содержались имя программы и два аргумента). Обратите внимание на то, что argv[l] содержит имя сервера (например, flits.cs.vu.nl) и переводится в IP-адрес с помощью gethostbyname. Для поиска имени функция использует DNS. Мы будем изучать технологию DNS в главе 7. Next a socket is created and initialized. After that, the client attempts to establish a TCP connection to the server, using connect. If the server is up and running on the named machine and attached to SERVER_PORT and is either idle or has room in its listen queue, the connection will (eventually) be established. Using the connection, the client sends the name of the file by writing on the socket. The number of bytes sent is one larger than the name proper since the 0byte terminating the name must also be sent to tell the server where the name ends. Затем создается и инициализируется сокет, после чего клиент пытается установить ТСР-соединение с сервером посредством connect. Если сервер включен, работает на указанной машине, соединен с SERVER_PORT и либо простаивает, либо имеет достаточно места в очереди 1 i sten (очереди ожидания), то соединение с клиентом рано или поздно будет установлено. По данному соединению клиент передает имя файла, записывая его в сокет. Количество отправленных байтов на единицу превышает требуемое для передачи имени, поскольку нужен еще нулевой байт-ограничитель, с помощью которого сервер может понять, где кончается имя файла. Figure 6.1 6-6. Client code using sockets. #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #define SERVER_PORT 12345 /* arbitrary, but client & server must agree */ #define BUF_SIZE 4096 /* block transfer size */ int main(int argc, char **argv) { int c, s, bytes; char buf[BUF_SIZE]; /* buffer for incoming file */ struct hostent *h; /* info about server */ struct sockaddr_in channel; /* holds IP address */ if (argc != 3) fatal("Usage: client server-name file-name"); h = gethostbyname(argv[1]); /* look up host's IP address */ if (!h) fatal("gethostbyname failed"); s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); i f (s <0) fatal("socket"); memset(&channel, 0, sizeof(channel)); channel.sin_family= AF_INET; memcpy(&channel.sin_addr.s_addr, h->h_addr, h->h_length); channel.sin_port= htons(SERVER_PORT); c = connect(s, (struct sockaddr *) &channel, sizeof(channel)); if (c < 0) fatal("connect failed"); /* Connection is now established. Send file name including 0 byte at end. */ write(s, argv[2], strlen(argv[2])+1); / * Go get the file and write it to standard output. */ while (1) { bytes = read(s, buf, BUF_SIZE); /* read from socket */ if (bytes <= 0) exit(0); /* check for end of file */ write(1, buf, bytes); /* write to standard output */ } } fatal(char *string) { printf("%s\n", string); exit(1); } [View full width] #include <sys/types.h> /* This is the server code */ #include <sys/fcntl.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #define SERVER_PORT 12345 /* arbitrary, but client & server must agree */ #define BUF_SIZE 4096 /* block transfer size */ #define QUEUE_SIZE 10 int main(int argc, char *argv[]) { int s, b, l, fd, sa, bytes, on = 1; char buf[BUF_SIZE]; /* buffer for outgoing file */ struct sockaddr_in channel; /* holds IP address */ /* Build address structure to bind to socket. */ memset(&channel, 0, sizeof(channel)); /* zerochannel */ 374 channel.sin_family = AF_INET; channel.sin_addr.s_addr = htonl(INADDR_ANY); channel.sin_port = htons(SERVER_PORT); /* Passive open. Wait for connection. */ s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); /* createsocket */ if (s < 0) fatal("socket failed"); setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on)); b = bind(s, (struct sockaddr *) &channel, sizeof(channel)); if (b < 0) fatal("bind failed"); l = listen(s, QUEUE_SIZE); /* specify queue size */ if (l < 0) fatal("listen failed"); /* Socket is now set up and bound. Wait for connection and process it. */ while (1) { sa = accept(s, 0, 0); /* block for connection request */ if (sa < 0) fatal("accept failed"); read(sa, buf, BUF_SIZE); /* read file name from socket */ /* Get and return the file. */ fd = open(buf, O_RDONLY); /* open the file to be sent back */ if (fd < 0) fatal("open failed"); while (1) { bytes = read(fd, buf, BUF_SIZE); /* read from file */ if (bytes <= 0) break; /* check for end of file */ write(sa, buf, bytes); /* write bytes to socket */ } close(fd); /* closefile */ close(sa); /* close connection */ } } Now the client enters a loop, reading the file block by block from the socket and copying it to standard output. When it is done, it just exits. Теперь клиентская программа входит в цикл, читает файл блок за блоком из сокета и копирует на стандартное устройство вывода. По окончании этого процесса она просто завершается. The procedure fatal prints an error message and exits. The server needs the same procedure, but it was omitted due to lack of space on the page. Since the client and server are compiled separately and normally run on different computers, they cannot share the code of fatal. Процедура fatal выводит сообщение об ошибке и завершается. Серверу также требуется эта процедура, и она пропущена в листинге только из соображений экономии места. Поскольку программы клиента и сервера компилируются отдельно и в обычной ситуации запускаются на разных машинах, код процедуры fatal не может быть разделяемым. Just for the record, this server is not the last word in serverdom. Its error checking is meager and its error reporting is mediocre. It has clearly never heard about security, and using bare UNIX system calls is not the last word in platform independence. It also makes some assumptions that are technically illegal, such as assuming the file name fits in the buffer and is transmitted atomically. Since it handles all requests strictly sequentially (because it has only a single thread), its performance is poor. These shortcomings notwithstanding, it is a complete, working Internet file server. In the exercises, the reader is invited to improve it. For more information about programming with sockets, see (Stevens, 1997). Кстати говоря, такой сервер построен далеко не по последнему слову техники. Осуществляемая проверка ошибок минимальна, а сообщения об ошибках реализованы весьма посредственно. Понятно, что ни о какой защите информации здесь говорить не приходится, а применение аскетичных системных вызовов UNIX — это не лучшее решение с точки зрения независимости от платформы. Делаются некоторые некорректные с технической точки зрения предположения, например, о том, что имя файла всегда поместится в буфер и будет передано без ошибок. Система будет обладать низкой производительностью, поскольку все запросы обрабатываются только последовательно (используется один поток запросов). Несмотря на эти недостатки, с помощью данной программы можно организовать полноценный работающий файл-сервер для Интернета. Более подробную информацию можно найти в (Stivens, 1997). 6.2 Elements of Transport Protocols The transport service is implemented by a transport protocol used between the two transport entities. In some ways, transport protocols resemble the data link protocols we studied in detail in Chap. 3. Both have to deal with error control, sequencing, and flow control, among other issues. Транспортная служба реализуется транспортным протоколом, используемым между двумя транспортными сущностями. В некоторых отношениях транспортные протоколы напоминают протоколы передачи данных, подробно изучавшиеся в главе 3. Все эти протоколы, наряду с другими вопросами, занимаются обработкой ошибок, управлением очередями и потоками. However, significant differences between the two also exist. These differences are due to major dissimilarities between the environments in which the two protocols operate, as shown in Fig. 6-7. At the data link layer, two routers communicate directly via a physical channel, whereas at the transport layer, this physical channel is replaced by the entire subnet. This difference has many important implications for the protocols, as we shall see in this chapter. Однако у протоколов разных уровней имеется и много различий, обусловленных различиями условий, в которых работают эти протоколы, как показано на рис. 6.4. На уровне передачи данных два маршрутизатора общаются напрямую по физическому каналу, тогда как на транспортном уровне физический канал заменен целой подсетью. Это отличие оказывает важное влияние на протоколы. Figure 6-7. (a) Environment of the data link layer. (b) Environment of the transport layer. For one thing, in the data link layer, it is not necessary for a router to specify which router it wants to talk to—each outgoing line uniquely specifies a particular router. In the transport layer, explicit addressing of destinations is required. Во-первых, на уровне передачи данных маршрутизатору не требуется указывать, с каким маршрутизатором он хочет поговорить, — каждая выходная линия однозначно определяет маршрутизатор. На транспортном уровне требуется явно указывать адрес получателя. For another thing, the process of establishing a connection over the wire of Fig. 6-7(a) is simple: the other end is always there (unless it has crashed, in which case it is not there). Either way, there is not much to do. In the transport layer, initial connection establishment is more complicated, as we will see. Во-вторых, процесс установки соединения по проводу (рис. 6.4, а) прост: противоположная сторона всегда присутствует (если только она не вышла из строя). В любом случае, работы не очень много. На транспортном уровне начальная установка соединения, как будет показано далее, происходит более сложно. Another, exceedingly annoying, difference between the data link layer and the transport layer is the potential existence of storage capacity in the subnet. When a router sends a frame, it may arrive or be lost, but it cannot bounce around for a while, go into hiding in a far corner of the world, and then suddenly emerge at an inopportune moment 30 sec later. If the subnet uses datagrams and adaptive routing inside, there is a nonnegligible probability that a packet may be stored for a number of seconds and then delivered later. The consequences of the subnet's ability to store packets can sometimes be disastrous and can require the use of special protocols. Еще одно весьма досадное различие между уровнем передачи данных и транспортным уровнем состоит в том, что подсеть потенциально обладает возможностями хранения информации. Когда маршрутизатор посылает кадр, он может прибыть или потеряться, но кадр не может побродить где-то какое-то время, спрятаться в отдаленном уголке земного шара, а затем внезапно появиться в самый неподходящий момент 30 секунд спустя. Если подсеть использует дейтаграммы и адаптивную маршрутизацию, то всегда есть ненулевая вероятность того, что пакет будет храниться где-нибудь несколько секунд, а уже потом будет доставлен по назначению. Последствия способности подсети хранить пакеты иногда могут быть катастрофичными и требуют применения специальных протоколов. A final difference between the data link and transport layers is one of amount rather than of kind. Buffering and flow control are needed in both layers, but the presence of a large and dynamically varying number of connections in the transport layer may require a different approach than we used in the data link layer. In Chap. 3, some of the protocols allocate a fixed number of buffers to each line, so that when a frame arrives a buffer is always available. In the transport layer, the larger number of connections that must be managed make the idea of dedicating many buffers to each one less attractive. In the following sections, we will examine all of these important issues and others. Последнее различие между уровнем передачи данных и транспортным уровнем является скорее количественным, чем качественным. Буферизация и управление потоком необходимы на обоих уровнях, но наличие большого динамически изменяющегося количества соединений на транспортном уровне может потребовать принципиально другого подхода, нежели использовавшийся на уровне передачи данных. Некоторые протоколы, упоминавшиеся в главе 3, выделяют фиксированное количество буферов для каждой линии, так что, когда прибывает кадр, всегда имеется свободный буфер. На транспортном уровне из-за большого количества управляемых соединений идея выделения нескольких буферов каждому соединению выглядит не столь привлекательно. В следующих разделах мы изучим эти и другие важные вопросы. 6.2.1 Addressing When an application (e.g., a user) process wishes to set up a connection to a remote application process, it must specify which one to connect to. (Connectionless transport has the same problem: To whom should each message be sent?) The method normally used is to define transport addresses to which processes can listen for connection requests. In the Internet, these end points are called ports. In ATM networks, they are called AAL-SAPs. We will use the generic term TSAP, (Transport Service Access Point). The analogous end points in the network layer (i.e., network layer addresses) are then called NSAPs. IP addresses are examples of NSAPs. Когда один прикладной процесс желает установить соединение с другим прикладным процессом, он должен указать, с кем именно он хочет связаться. (У нетребующей соединений транспортной службы проблемы такие же: кому следует посылать каждое сообщение?) Применяемый обычно метод состоит в определении транспортных адресов, к которым процессы могут посылать запросы на установку соединения. В Интернете такие конечные точки называются портами. В сетях ATM это точки доступа к службе AAL-SAP (Service Access Point). Мы будем пользоваться нейтральным термином TSAP (Transport Service Access Point —точка доступа к службам транспортного уровня). Аналогичные конечные точки сетевого уровня называются NSAP (Network Service Access Point — точка доступа к сетевому сервису). Примерами NSAP являются IP-адреса. Figure 6-8 illustrates the relationship between the NSAP, TSAP and transport connection. Application processes, both clients and servers, can attach themselves to a TSAP to establish a connection to a remote TSAP. These connections run through NSAPs on each host, as shown. The purpose of having TSAPs is that in some networks, each computer has a single NSAP, so some way is needed to distinguish multiple transport end points that share that NSAP. Рисунок 6.5 иллюстрирует взаимоотношения между NSAP, TSAP и транспортным соединением. Прикладные процессы как клиента, так и сервера могутсвязываться с TSAP для установки соединения с удаленным TSAP. Такие соединения проходят через NSAP на каждом хосте, как показано на рисунке. TSAP нужны для того, чтобы различать конечные точки, совместно использующие NSAP, в сетях, где у каждого компьютера есть свой NSAP. 1. 2. 3. 4. 5. 1. 2. 3. 4. Figure 6-8. TSAPs, NSAPs, and transport connections. A possible scenario for a transport connection is as follows. A time of day server process on host 2 attaches itself to TSAP 1522 to wait for an incoming call. How a process attaches itself to a TSAP is outside the networking model and depends entirely on the local operating system. A call such as our LISTEN might be used, for example. An application process on host 1 wants to find out the time-of-day, so it issues a CONNECT request specifying TSAP 1208 as the source and TSAP 1522 as the destination. This action ultimately results in a transport connection being established between the application process on host 1 and server 1 on host 2. The application process then sends over a request for the time. The time server process responds with the current time. The transport connection is then released. Возможный сценарий для транспортного соединения выглядит следующим образом: Серверный процесс хоста 2, сообщающий время суток, подсоединяется к точке доступа TSAP 1522 и ожидает входящего звонка. Вопрос о том, как процесс соединяется с TSAP, лежит за пределами сетевой модели и целиком зависит от локальной операционной системы. Например, может вызываться примитив, подобный LISTEN. Прикладной процесс хоста 1 желает узнать, который час, поэтому он обращается к сети с запросом CONNECT, указывая TSAP 1208 в качестве адреса отправителя и TSAP 1522 в качестве адреса получателя. Это действие в результате приводит к установке транспортного соединения между прикладным процессом хоста 1 и сервером 1, расположенным на хосте 2. Прикладной процесс отправляет запрос, надеясь выяснить, который час. Сервер обрабатывает запрос и в качестве ответа посылает информацию о точном времени. 5. Транспортное соединение разрывается. Note that there may well be other servers on host 2 that are attached to other TSAPs and waiting for incoming connections that arrive over the same NSAP. Обратите внимание на то, что на хосте 2 могут располагаться и другие серверы, соединенные со своими TSAP и ожидающие входящих запросов на соединение, приходящих с того же NSAP. The picture painted above is fine, except we have swept one little problem under the rug: How does the user process on host 1 know that the time-of-day server is attached to TSAP 1522? One possibility is that the time-of-day server has been attaching itself to TSAP 1522 for years and gradually all the network users have learned this. In this model, services have stable TSAP addresses that are listed in files in well-known places, such as the /etc/services file on UNIX systems, which lists which servers are permanently attached to which ports. Нарисованная картинка всем хороша, но мы обошли стороной один маленький вопрос: как пользовательский процесс хоста 1 узнает, что сервер, сообщающий время, соединен с TSAP 1522? Возможно, сервер, сообщающий время, подключается к TSAP 1522 в течение долгих лет, и постепенно об этом узнают все пользователи сети. В этом случае службы имеют постоянные TSAP-адреса, хранящиеся в файлах, расположенных в известных местах, таких как etc/services в UNIX-системах. В файлах перечисляются серверы, за которыми жестко закреплены определенные порты. While stable TSAP addresses work for a small number of key services that never change (e.g. the Web server), user processes, in general, often want to talk to other user processes that only exist for a short time and do not have a TSAP address that is known in advance. Furthermore, if there are potentially many server processes, most of which are rarely used, it is wasteful to have each of them active and listening to a stable TSAP address all day long. In short, a better scheme is needed. Хотя постоянные TSAP-адреса могут хорошо подходить для небольшого количества никогда не меняющихся ключевых служб (например, таких как веб-сервер), в общем случае пользовательские процессы часто хотят пообщаться с другими пользовательскими процессами, существующими только в течение короткого времени и не обладающими постоянными TSAP-адресами, известным всем заранее. Кроме того, при наличии большого количества серверных процессов, большая часть которых редко используется, слишком расточительным делом оказывается поддержка всех их в активном состоянии с постоянными TSAP-адресами. То есть требуется другая модель. One such scheme is shown in Fig. 6-9 in a simplified form. It is known as the initial connection protocol. Instead of every conceivable server listening at a well-known TSAP, each machine that wishes to offer services to remote users has a special process server that acts as a proxy for less heavily used servers. It listens to a set of ports at the same time, waiting for a connection request. Potential users of a service begin by doing a CONNECT request, specifying the TSAP address of the service they want. If no server is waiting for them, they get a connection to the process server, as shown in Fig. 6-9(a). Одна такая модель показана в упрощенном виде на рис. 6.6. Она называется протоколом начального соединения. Вместо того чтобы назначать всем возможным серверам хорошо известные TSAP-адреса, каждая машина, желающая предоставлять услуги удаленным пользователям, обзаводится специальным обрабатывающим сервером, действующим как прокси (посредник) для менее активно используемых серверов. Он прослушивает одновременно несколько портов, ожидая запроса на соединение. Потенциальные пользователи этой услуги начинают с того, что посылают запрос CONNECT, указывая TSAP-адрес нужной им службы. Если никакой сервер их не ждет, они получают соединение с обрабатывающим сервером, как показано на рис. 6.6, а. Figure 6-9. How a user process in host 1 establishes a connection with a time-of-day server in host 2. After it gets the incoming request, the process server spawns the requested server, allowing it to inherit the existing connection with the user. The new server then does the requested work, while the process server goes back to listening for new requests, as shown in Fig. 6-9(b). Получив запрос, обрабатывающий сервер порождает подпроцесс на запрошенном сервере, позволяя ему унаследовать существующее соединение с пользователем. Новый сервер выполняет требуемую работу, в то время как обрабатывающий сервер возвращается к ожиданию новых запросов, как показано на рис. 6.6, б. While the initial connection protocol works fine for those servers that can be created as they are needed, there are many situations in which services do exist independently of the process server. A file server, for example, needs to run on special hardware (a machine with a disk) and cannot just be created on-the-fly when someone wants to talk to it. Хотя протокол начального соединения прекрасно работает с серверами, которые можно создавать по мере надобности, есть много ситуаций, в которых службы существуют независимо от обрабатывающего сервера. Например, файловый сервер должен работать на специальном оборудовании (машине с диском) и не может быть создан на ходу, когда кто-нибудь захочет к нему обратиться. To handle this situation, an alternative scheme is often used. In this model, there exists a special process called a name server or sometimes a directory server. To find the TSAP address corresponding to a given service name, such as ''time of day,'' a user sets up a connection to the name server (which listens to a well-known TSAP). The user then sends a message specifying the service name, and the name server sends back the TSAP address. Then the user releases the connection with the name server and establishes a new one with the desired service. Чтобы справиться с этой ситуацией, часто используется другая схема. В этой модели используется специальный процесс, называющийся сервером имен или иногда каталоговым сервером. Чтобы найти TSAP-адрес, соответствующий данному имени службы, например ≪время суток≫, пользователь устанавливает соединение с сервером имен (TSAP-адрес которого всем известен). Затем пользователь посылает сообщение с указанием названия нужной ему услуги, и серверимен сообщает ему TSAP-адрес этой службы. После этого пользователь разрывает соединение с сервером имен и устанавливает новое соединение с нужной ему службой. In this model, when a new service is created, it must register itself with the name server, giving both its service name (typically, an ASCII string) and its TSAP. The name server records this information in its internal database so that when queries come in later, it will know the answers. В этой модели, когда создается новая служба, она должна зарегистрироваться на сервере имен, сообщив ему название услуги (обычно строка ASCII) и TSAP-адрес. Сервер имен сохраняет полученную информацию в своей базе данных, чтобы иметь возможность отвечать на будущие запросы. The function of the name server is analogous to the directory assistance operator in the telephone system—it provides a mapping of names onto numbers. Just as in the telephone system, it is essential that the address of the well-known TSAP used by the name server (or the process server in the initial connection protocol) is indeed well known. If you do not know the number of the information operator, you cannot call the information operator to find it out. If you think the number you dial for information is obvious, try it in a foreign country sometime. Функция сервера имен аналогична работе оператора телефонной справочной службы — он преобразует имена в номера. Как и в телефонной системе, важно, чтобы TSAP-адрес сервера имен (или обрабатывающего сервера в протоколе начального соединения) был действительно хорошо известен. Если вы не знаете номера телефонной справочной, вы не сможете позвонить оператору. Если вы полагаете, что номер справочной является очевидным, попытайтесь угадать его, находясь в другой стране.