глава 27 ftp: протокол передачи файлов ftp это еще одно широко используемое приложение. оно является стандартом internet для передачи файлов. необходимо различать передачу файлов, именно то, что предоставляет ftp, и доступ к файлам, что предоставляется такими приложениями как nfs (network file system, глава 29). передача файлов заключается в копировании целого файла из одной системы в другую. чтобы использовать ftp, необходимо иметь открытый бюджет на сервере, или можно воспользоваться так называемым анонимным ftp (anonymous ftp). как и telnet, ftp был создан для того, чтобы работать между хостами работающими под управлением различных операционных систем, использующих различные структуры файлов и, возможно, различные наборы символов. telnet, однако, обеспечивает связь между разнородными системами, заставляя каждого участника соединения работать с одним и тем же стандартом: nvt, использующий 7-битный ascii. ftp сглаживает различия между системами с использованием другого подхода. ftp поддерживает ограниченное количество типов файлов (ascii, двоичное и так далее) и структуру файлов (поток байтов или ориентированный на запись). rfc 959 [postel and reynolds 1985] является официальной спецификацией ftp. этот rfc описывает историю и развитиие передачи файлов в течение времени. ftp отличается от других приложений тем, что он использует два tcp соединения для передачи файла.
на рисунке 27.1 показано общение клиента и сервера по двум соединениям. рисунок 27.1 процессы, участвующие в передаче файлов.
из рисунка видно, что интерактивный пользователь обычно не видит команды и отклики, которые передаются по управляющему соединению. эти детали оставлены двум интерпретаторам протокола. квадратик, помеченный как "пользовательский интерфейс", это именно то, что видит интерактивный пользователь (полноэкранный интерфейс, основанный на меню, командные строки и так далее). интерфейс конвертирует ввод пользователя в ftp команды, которые отправляются по управляющему соединению. отклики, возвращаемые сервером по управляющему соединению, конвертируются в формат, удобный для пользователя. обратите внимание на то, что существуют два интерпретатора протокола, которые по необходимости используют две функции передачи данных. протокол ftp предоставляет различные способы управления передачей и хранения файлов. необходимо сделать выбор по четырем пунктам.
(а) (b) (c) (d)
(a) (b) (c)
если посчитать количество комбинаций из приведенных вариантов, то получится 72 способа передачи и хранения файла. к счастью, можно игнорировать многие из этих опций, потому что они не поддерживаются в большинстве реализаций. самые распространенные unix реализации ftp клиента и сервера предоставляют следующий выбор:
это ограничивает нас одним из двух режимов: ascii или двоичный. подобная реализация отвечает минимальным требованиям к хостам host requirements rfc. (rfc также требует обеспечить поддержку для структуры записи, однако только если операционная система поддерживает это, а unix, как правило, не поддерживает.) большинство не-unix реализаций предоставляет ftp возможности, которые позволяют обрабатывать их собственные форматы файлов. требование к хостам host requirements rfc говорит: "протокол ftp включает множество характеристик, некоторые из которых распространены не очень широко. однако, для каждой характеристики в ftp существует по меньшей мере одна реализация."
команды и отклики передаются по управляющему соединению между клиентом и сервером в формате nvt ascii. в конце каждой строки команды или отклика присутствует пара cr, lf. единственные команды telnet (начинающиеся с iac), которые могут быть отправлены клиентом серверу - это команда прерывания процесса (<iac, ip>) и telnet сигнал синхронизации (<iac, dm> в режиме срочности). мы увидим, что эти две команды telnet используются для прекращения передачи файла или для того, чтобы отправить серверу запрос в процессе передачи. если сервер получает от клиента команду с telnet опцией (will, wont, do или dont), он отвечает либо dont, либо wont. команды состоят из 3 или 4 байт, а именно из заглавных ascii символов, некоторые с необязательными аргументами. клиент может отправить серверу более чем 30 различных ftp команд. на рисунке 27.2 показаны некоторые наиболее широко используемые команды, большинство из которых мы рассмотрим в этой главе.
рисунок 27.2 распространенные ftp команды.
в примерах мы увидим, что некоторые команды полностью совпадают с тем, что вводит интерактивный пользователь в качестве ftp команд. в этом случае они передаются по управляющему соединению, однако некоторые вводимые пользователем команды генерируют несколько ftp команд, которые, которые в свою очередь, передаются по управляющему соединению. отклики состоят из 3-циферных значений в формате ascii, и необязательных сообщений, которые следуют за числами. подобное представление откликов объясняется тем, что программному обеспечению необходимо посмотреть только цифровые значения, чтобы понять, что ответил процесс, а дополнительную строку может прочитать человек. поэтому пользователю достаточно просто прочитать сообщение (причем нет необходимости запоминать все цифровые коды откликов). каждая из трех цифр в коде отклика имеет собственный смысл. (в главе 28 мы увидим, что протокол передачи почтовых сообщений - smtp, использует те же соглашения для своих команд и откликов.) на рисунке 27.3 показаны значения первых и вторых цифр в коде отклика.
рисунок 27.3 значения первой и второй цифр в 3-циферном коде отклика.
третья цифра дает дополнительное объяснение сообщению об ошибке. ниже приведены некоторые типичные отклики с возможными объясняющими строками.
обычно каждая ftp команда генерируют отклик в одну строку. например, команда quit сгенерирует следующий отклик:
221 goodbye.
если необходим отклик в несколько строк, первая строка содержит дефис вместо пробела после 3-циферного кода отклика, а последняя строка содержит тот же самый 3-циферный код отклика, за которым следует пробел. например, команда help сгенерирует следующий отклик:
использовать соединение данных можно тремя способами.
ftp сервер посылает список файлов по соединению данных, вместо того чтобы посылать многострочные отклики по управляющему соединению. при этом появляется возможность избежать любых ограничений в строках, накладывающихся на размер списка директории, и позволяет просто сохранить список директории в файле, вместо того чтобы выдавать список на терминал. мы сказали, что управляющее соединение остается в активизированном состоянии все время, пока установлено соединение клиент-сервер, однако соединение данных может выключаться и включаться по необходимости. как выбираются номера портов для соединения данных, и кто осуществляет активное открытие, а кто пассивное открытие? во-первых, как было сказано ранее, распространенный режим передачи (в случае unix это единственный режим передачи) - это потоковый режим. в этом режиме конец файла обозначает закрытие соединения данных. из этого следует, что для передачи каждого файла или списка директории требуется новое соединение данных. обычная процедура выглядит следующим образом:
на рисунке 27.4 показано состояние соединений, пока осуществляется шаг номер 3. мы предполагаем, что динамически назначаемый порт клиента для управляющего соединения имеет номер 1173, а динамически назначаемый порт клиента для соединения данных имеет номер 1174. команда, посылаемая клиентом - port, а ее аргументы это шесть десятичных цифр в формате ascii, разделенные запятыми. четыре первых числа - это ip адрес клиента, на который сервер должен осуществить активное открытие (140.252.13.34 в данном примере), а следующие два - это 16-битный номер порта. так как 16-битный номер порта формируется из двух цифр, его значение в этом примере будет 4 x 256 + 150 = 1174. на рисунке 27.5 показано состояние соединений, когда сервер осуществляет активное открытие на конец клиента соединения данных. конечная точка сервера это порт 20. рисунок 27.4 команда port, передаваемая по управляющему соединению ftp. рисунок 27.5 ftp сервер осуществляет активное открытие соединения данных.
сервер всегда осуществляет активное открытие соединения данных. обычно сервер также осуществляет активное закрытие соединения данных, за исключением тех случаев, когда клиент отправляет файл на сервер в потоковом режиме, который требует, чтобы клиент закрыл соединение (что делается с помощью уведомления сервера о конце файла). если клиент не выдает команду port, сервер осуществляет активное открытие на тот же самый номер порта, который использовался клиентом для управляющего соединения (1173 в данном примере). в этом случае все работает корректно, так как номера порта сервера для двух соединений различны: один 20, другой 21. тем не менее, в следующем разделе мы посмотрим, почему современные реализации не поступают таким образом. сейчас мы рассмотрим некоторые примеры использования ftp: как осуществляется управление соединением данных, как передаются текстовые файлы с использованием nvt ascii, как в ftp используется сигнал синхронизации telnet для прекращения процесса передачи. в завершение мы рассмотрим "анонимный ftp" (anonymous ftp). управление соединением: динамически назначаемый порт давайте, рассмотрим управление ftp соединением на примере простой ftp сессии, в течение которой просматривается список файлов на сервере. клиент запущен на хосте svr4 с флагом -d (отладка). при этом печатаются команды и отклики, которыми происходит обмен по управляющему соединению. все строки, начинающиеся с --->, отправляются клиентом серверу, а строки, которые начинаются с 3-циферных чисел, это отклики от сервера. клиенту выдается приглашение в виде ftp>.
когда ftp клиент просит нас ввести имя пользователя, он выводит имя по умолчанию (наше имя на хосте клиента). когда мы нажимаем клавишу return, отправляется это имя по умолчанию. когда мы спрашиваем, присутствует ли указанный файл в директории, устанавливается соединение данных. этот пример является продолжением процедуры, показанной на рисунках 27.4 и 27.5. клиент спрашивает свой tcp модуль о динамически назначаемом номере порта для своего конца соединения данных и отправляет этот номер порта (1174) серверу в виде команды port. мы также видим, что одна команда, введенная пользователем (dir) генерирует две ftp команды (port и list). на рисунке 27.6 приведена временная диаграмма, иллюстрирующая обмен пакетами по управляющему соединению. (мы удалили все, что связано с установлением и прерыванием управляющего соединения, вместе со всеми объявлениями размера окна.) на этом рисунке показано как открывается соединение данных, используется и затем закрывается. рисунок 27.7 это временная диаграмма для соединения данных. времена на этом рисунке начинаются с того же момента, как и на рисунке 27.6. мы удалили все объявления окна, однако оставили поле типа сервиса, чтобы показать, что соединение данных использует отличный тип сервиса (максимальная пропускная способность), нежели управляющее соединение (минимальная задержка). (значения tos приведены на рисунке 3.2.) на этой временной диаграмме ftp сервер осуществляет активное открытие соединения данных с порта 20 (который называется ftp-data) на номер порта из команды port (1174). также в этом примере, где сервер выдает информацию в соединение данных, сервер осуществляет активное закрытие соединения данных, тем самым сообщая клиенту, когда список завершен. рисунок 27.6 пример управляющего соединения ftp. рисунок 27.7 пример соединения данных ftp.
управление соединением: порт данных по умолчанию если клиент не посылает команду port на сервер, чтобы указать номер порта для клиентской стороны соединения данных, сервер использует тот же номер порта для соединения данных, который был использован для управляющего соединения. это может вызвать проблемы для клиента, который использует потоковый режим (который unix ftp клиенты и сервера используют всегда), как мы покажем ниже. требование к хостам host requirements rfc рекомендует, чтобы ftp клиент использовал потоковый режим, посылая команду port, чтобы не использовать номер порта по умолчанию перед каждым использованием соединения данных.
давайте снова обратимся к предыдущему примеру (рисунок 27.6). что произойдет, если мы попробуем узнать содержимое другой директории через несколько секунд после того, как узнали содержимое первой? клиент попросит свое ядро выбрать еще один динамически назначаемый порт (может быть, с номером 1175), после чего будет открыто следующее соединение данных между svr4 порт 1175 и bsdi порт 20. однако на рисунке 27.7 сервер осуществляет активное закрытие соединения данных, и мы видели на рисунке 18.6, что сервер не в состоянии назначить порт 20 для нового соединения данных, так как считается, что локальный порта используется предыдущим соединением, пока находится в состоянии ожидания 2msl. сервер обходит эту проблему, указывая опцию so_reuseaddr, которую мы упоминали в разделе "диаграмма состояний передачи tcp" главы 18. это позволяет ему назначить порт 20 для нового соединения, которое будет иметь другой номер удаленного порта (1175). в состоянии ожидания 2msl находится порт 1174. однако процедура открытия соединения изменяется, если клиент не пошлет команду port, указывая динамически назначаемый номер порта клиента. мы можем смоделировать подобную ситуацию, исполнив пользовательскую команду sendport на ftp клиенте. unix ftp клиенты используют эту команду, чтобы выключить отправку команд port на сервер перед каждым использованием соединения данных. на рисунке 27.8 показана временная диаграмма для соединений данных при использовании двух команд list. управляющее соединение установлено от порта 1176 на хосте svr4, так что в случае отсутствия команды port клиент и сервер используют тот же самый номер порта для соединения данных. (мы удалили объявления окна и значения типа сервиса.) рисунок 27.8 соединение данных для двух последовательных команд list.
последовательность событий в данном случае следующая.
<svr4, 1176, bsdi, 20>
различны (номер порта на bsdi отличается). tcp демультиплексирует входящие сегменты, просматривая ip адрес источника, номер порта источника, ip адрес назначения и номер порта назначения, поэтому пока один из четырех элементов отличается, все в порядке.
помещается в состояние ожидания 2msl. в этом месте bsd серверы повторяют попытки установить соединение каждые 5 секунд, до 18 раз, что в целом составляет 90 секунд. мы видим, что сегмент 9 успешно прошел примерно через одну минуту. (мы говорили в главе 18, что svr4 использует msl равное 30 секундам, при этом получается, что ожидание 2msl составляет 1 минуту.) мы не увидим ни одного syn в это время на временной диаграмме, потому что активное открытие не удалось, и tcp модуль сервера даже не пытается посылать syn.
причина, по которой требования к хостам host requirements rfc рекомендуют использовать команду port, заключается в том, что эта команда позволяет обойти состояние ожидания 2msl между последовательными использованиями соединения данных. так как порты последовательно меняются на одном конце, проблема, которую мы только что показали, исчезает сама собой. передача текстовых файлов: представление nvt ascii или двоичное? давайте убедимся в том, что при передаче текстовых файлов по умолчанию используется формат nvt ascii. в этот раз мы не будем использовать флаг -d, поэтому мы не увидим команды клиента, однако клиент все еще печатает отклики от сервера:
сорок два байта было передано по соединению данных, потому что файл содержит четыре строки. каждый unix символ новой строки (\n) конвертируется в 2-байтную последовательность nvt ascii конец строки (\r\n) сервером для передачи, а затем конвертируется обратно клиентом при записи на диск. более новые клиенты стараются определить, используется ли подобная система на сервере, и если да, передают файлы в двоичном виде (image тип файла) вместо ascii. это помогает в двух случаях.
мы можем увидеть подобную оптимизацию с использованием bsd/386 клиента и сервера. включен отладочный режим, что позволяет увидеть команды ftp клиента:
после того как мы правильно ввели имя и пароль серверу, ftp клиент автоматически посылает команду syst, в ответ на которую сервер сообщает свой тип системы. если отклик начинается со строки "215 unix type: l8", и если клиент работает под управлением unix системы с 8-битными байтами, для передачи всех файлов используется двоичный (image) режим, если его не сменит пользователь. когда мы забираем файл hello.c, клиент автоматически посылает команду type i, чтобы установить тип файла в двоичный. на этот раз по соединению данных было передано 38 байт. требования к хостам host requirements rfc говорят, что ftp сервер должен поддерживать команду syst (это было необязательным условием в rfc 959). из систем описанных в тексте (см. внутреннюю сторону обложки) эту команду поддерживают bsd/386 и aix 3.2.2. sunos 4.1.3 и solaris 2.x выдают на эту команду отклик 500 (команда неизвестна). svr4 ведет себя совсем по-дикому: отвечает 500 и закрывает управляющее соединение!
прекращение передачи файла: сигнал синхронизации telnet сейчас мы посмотрим, как ftp клиент прекращает передачу файла от сервера. прекратить передачу файла от клиента к серверу достаточно просто - клиент прекращает посылку данных по соединению данных и посылает abor на сервер по управляющему соединению. прекращение приема, однако, более сложная задача, потому что клиент хочет, чтобы сервер немедленно прекратил передачу данных. ранее мы упоминали, что для этого используется сигнал синхронизации telnet. мы начали прием, после чего ввели символ прерывания. ниже приведена интерактивная сессия, процесс идентификации пользователя удален:
после того как введен символ прерывания, клиент немедленно сообщает, что он инициализировал прерывание передачи файла и ожидает, когда сервер его завершит. сервер посылает два отклика: 426 и 226. оба отклика посылаются unix сервером, когда он принимает срочные данные от клиента с командой abor. на рисунках 27.9 и 27.10 показаны временные диаграммы для этой сессии. мы объединили вместе управляющее соединение (сплошные линии) и соединение данных (прерывистые линии), чтобы показать взаимосвязь между ними. рисунок 27.9 прерывание передачи файла (первая половина).
первые 12 сегментов на рисунке 27.9 как раз такие, как мы ожидали. команды и отклики по управляющему соединению настраивают системы на передачу файла, открывается соединение данных и от сервера к клиенту посылается первый сегмент данных. рисунок 27.10 прерывание передачи файла (вторая половина).
сегмент 13 на рисунке 27.10 - это квитанция на шестой сегмент данных от сервера по соединению данных. затем следует сегмент 14, который сгенерирован нашим вводом символа прерывания. для того чтобы прервать передачу, клиент посылает десять байт:
<iac, ip, iac, dm, a, b, o, r, \r, \n>
мы видим два сегмента (14 и 15), это связано с проблемой определения положения указателя срочности tcp (подробно описанной в разделе "режим срочности (urgent mode)" главы 20). (на рисунке 26.17 мы видели, как telnet решает эту проблему.) требование к хостам host requirements rfc говорит, что указатель срочности должен указывать на последний байт срочных данных, тогда как большинство berkeley реализаций указывают на один байт позади последнего байта срочных данных. ftp клиент специально пишет первые 3 байта как срочные данные, зная, что указатель срочности будет (некорректно) указывать на следующий байт, который будет записан (метка данных, dm, с номером последовательности 54). эта первая запись из 3 байт срочных данных посылается немедленно, вместе с указателем срочности, за ними следуют следующие 7 байт. (bsd ftp сервер не имеет проблемы с интерпретацией указателя срочности, который используется клиентом. когда сервер принимает срочные данные по управляющему соединению, он читает следующую ftp команду, в поиске abor или stat, игнорируя любые вложенные команды telnet.) несмотря на то, что сервер сообщил о прекращении передачи (сегмент 18, по управляющему соединению), клиент получил еще 14 сегментов данных (номера последовательности 1537 - 5120) по соединению данных. эти сегменты, скорее всего, были поставлены в очередь в драйвере сетевого устройства на сервере, когда был принят сигнал о прекращении передачи. однако клиент печатает "1536 байт принято", а это означает, что он проигнорировал эти сегменты данных (сегменты 17 и позже), которые были приняты после отправки прерывания передачи (сегменты 14 и 15). в случае telnet, когда пользователь вводит символ прерывания (рисунок 26.17), unix клиент по умолчанию не посылает команду прерывания процесса в виде срочных данных. в этом нет ничего страшного, потому что существует очень маленькая вероятность того, что поток данных от клиента к серверу остановлен управлением потока данных. в случае ftp клиент также посылает команду прерывания процесса по управляющему соединению, и так как используется два соединения, существует очень небольшая вероятность того, что управляющее соединение будет остановлено управлением потока данных. почему ftp посылает команду прерывания процесса в виде срочных данных, тогда как telnet не делает этого? дело в том, что ftp использует два соединения, тогда как telnet использует одно, а для некоторых операционных систем довольно сложно обработать информацию приходящую по двум соединениям одновременно. ftp подразумевает, что эти операционные системы по крайней мере смогут понять, что по управляющему соединению срочные прибыли данные, и что в свою очередь позволит серверу переключиться от обработки соединения данных на обработку управляющего соединения. существует невероятно популярная форма использования ftp. она называется анонимный ftp (anonymous ftp). если эта форма поддерживается сервером, она позволяет любому получить доступ к серверу и использовать ftp для передачи файлов. с помощью анонимного ftp можно получить доступ к огромному объему свободно распространяемой информации. тут возникает еще одна проблема, которая заключается в том, что очень сложно найти то, что нужно в этом море информации. кратко это описано в разделе "archie, wais, gopher, veronica и www" главы 30. воспользуемся анонимным ftp, чтобы получить файл опечаток для этой книги с хоста ftp.uu.. чтобы использовать анонимный ftp, мы входим в систему с именем пользователя "anonymous" (чтобы выучить, как пишется это слово, попробуйте повторить его несколько раз). когда появляется приглашение ввести пароль, мы вводим наш адрес электронной почты.
программа uncompress используется потому, что большинство файлов, доступных через анонимный ftp, сжаты с использованием unix программы compress(1), такие файлы имеют расширение .z. эти файлы должны быть переданы в виде двоичных файлов, а не ascii файлов. анонимный ftp с неизвестного ip адреса мы можем объединить вместе некоторые характеристики маршрутизации и системы имен (domain name system) с использованием анонимного ftp. в разделе "запросы указателя" главы 14 мы говорили о запросах указателя в dns - которые воспринимают ip адреса и возвращают имя хоста. к сожалению, не все администраторы систем корректно конфигурируют свои dns серверы таким образом, чтобы они отвечали на запросы указателей. они часто добавляют новые хосты к файлам, необходимым для установления соответствия имя-адрес, однако забывают добавить их в файлы, устанавливающие соответствие адрес-имя. с этим можно столкнуться, работая с программой traceroute, когда она выдает ip адреса вместо имен хостов. некоторые анонимные ftp серверы требуют, чтобы клиент имел корректное имя домена. это позволяет серверу зарегистрировать имя домена и хоста, с который осуществлен заход. единственная информация, которую может узнать сервер о клиенте из ip датаграммы - это ip адрес клиента. сервер осуществляет запрос указателя, чтобы узнать доменное имя клиента. если dns сервер, отвечающий за хост клиента, не сконфигурирован корректно, запрос указателя не сработает. давайте смоделируем подобную ошибку.
хост slip доступен по internet, потому что, как мы видели в разделе "rip: протокол обмена информацией о маршрутизации" главы 10, маршрутизаторы gateway и netb все еще посылают датаграммы, которые предназначены в подсеть 140.252.13, на маршрутизатор sun. наш маршрутизатор sun знает, что делать с этими датаграммами, в соответствии с пунктом маршрутизации, который мы сделали в шаге номер 3. таким образом, мы создали хост, который полностью подключен к internet, однако не имеющий корректного имени домена. таким образом, запрос указателя для ip адреса 140.252.13.67 не будет работать. а сейчас воспользуемся анонимным ftp на сервер, который, как мы знаем, требует корректного имени домена. slip % ftp ftp.uu.net connected to ftp.uu.net. 220 ftp.uu.net ftp server (version 2.owu(13) fri apr 9 20:44:32 edt 1993) ready. name (ftp.uu.net:rstevens): anonymous 530- sorry, we're unable to map your ip address 140.252.13.67 to a hostname 530- in the dns. this is probably because your nameserver does not have a 530- ptr record for your address in its tables, or because your reverse 530- nameservers are not registered. we refuse service to hosts whose 530- names we cannot resolve. if this is simply because your nameserver is 530- hard to reach or slow to respond then try again in a minute or so, and 530- perhaps our nameserver will have your hostname in its cache by then. 530- if not, try reaching us from a host that is in the dns or have your 530- system administrator fix your servers. 530 user anonymous access denied.. login failed. remote system type is unix. using binary mode to transfer files. ftp> quit 221 goodbye.
(к сожалению, мы не можем найти имя хоста, соответствующее вашему ip адресу. возможно, это потому, что у вашего сервера имен нет в таблице ptr записи, соответствующей вашему адресу, или ваш сервер не зарегистрирован. мы не работаем с хостами, для которых мы не можем определить имя домена. если проблема в том, что ваш dns сервер медленно отвечает или до него сложно достучаться, попробуйте еще раз через минуту. может быть, в этот раз dns сервер уже будет иметь ваше имя в своем кэше. если дело не в этом, попробуйте зайти к нам с хоста, который корректно зарегистрирован в dns, или попросите администратора починить ваш сервер.) сообщение об ошибке говорит само за себя. ftp это стандарт, признанный в internet, для передачи файлов. в отличие от большинства других tcp приложений, он использует два tcp соединения между клиентом и сервером - управляющее соединение, которое существует в течение всего продолжения сессии клиент-сервер, и соединение данных, которое создается и удаляется по необходимости. управление соединением, которое осуществляется ftp, позволило нам увидеть, какие требования выдвигает tcp к управлению соединением. мы видели состояние ожидания 2msl на клиенте, который не выдает команды port. ftp использует формат nvt ascii для всех команд и откликов, которые передаются по управляющему соединению. режим передачи данных по умолчанию это, как правило, также nvt ascii. мы видели, что более новые unix клиенты автоматически посылают команду, чтобы убедиться в том, что сервер это unix хост с 8-битными байтами, и если так, используют двоичный режим для передачи всех файлов, которые являются более эффективными. также мы показали пример анонимного ftp, популярного способа распространения программного обеспечения по internet. упражнения local: hello.c remote: hello.c выдаются клиентом. не заглядывая в исходные тексты, как можно определить, что они не от сервера?
|