• С сервером одновременно может взаимодействовать только один клиент (этот недостаток легко устранить).
• Сервер может только отправлять файлы, но не может получать.
• Отсутствуют условия для ввода ограничений на передачу файлов анонимному удаленному пользователю.
• Выполняется очень поверхностная проверка ошибок, что, скорее всего, приведет к проблемам во время эксплуатации.
Клиент tftp начинает tftp-сеанс передачей "пакета запроса на чтение", содержащего имя файла, который нужно получить, и режим. Существует два исходных режима:
netascii
(выполняет некоторые простые преобразования файла) и
octet
(передает файл точно в таком же состоянии, в каком он находится на диске). Рассматриваемый
сервер поддерживает только режим
octet
, поскольку он проще.
При получении запроса для чтения tftp-сервер посылает файл (512 байт за один раз). Каждая дейтаграмма содержит номер блока (нумерация начинается с единицы). Когда клиент получает блок данных, содержащий менее 512 байтов, он знает, что файл получен должным образом. После каждой дейтаграммы клиент посылает ответную дейтаграмму с номером блока, подтверждающую, что данный блок успешно получен. Как только сервер видит подтверждение, он отправляет следующий блок данных.
Основной формат дейтаграммы определен в строках 17-46. Некоторые константы указывают тип посылаемой дейтаграммы, а также код ошибки, отправляемой в том случае, если запрашиваемый файл не существует (все остальные ошибки обрабатываются непосредственно сервером). Структура
struct tftpPacket
описывает внешний вид дейтаграммы и код операции, следующей за данными, которая зависит от типа дейтаграммы. Затем логическое объединение, вложенное в структуру, определяет остальные форматы дейтаграмм для ошибок, данных и пакетов подтверждения.
Первая часть
main
(строки 156—169) создает UDP-сокет и устанавливает номер локального порта с помощью вызова
bind
. Последний является либо официальной tftp-службой, либо портом, указанным в качестве единственного аргумента командной строки программы. В отличие от нашего примера TCP-сервера здесь нет необходимости вызывать ни
listen
, ни
accept
, поскольку UDP работает без установки соединений.
После создания сокета сервер ожидает получение дейтаграммы путем вызова
recvfrom
. Функция
handleRequest
, которая активизируется в строке 181, преобразует запрашиваемый файл и возвращает его. После этого вызова сервер еще раз активизирует
recvfrom
и ожидает запрос от следующего клиента.
Комментарий, расположенный перед вызовом
handleRequest
, сообщает, что данный сервер можно легко переключить с итеративного сервера на параллельный, позволив каждому вызову
handleRequest
работать как самостоятельному процессу.
В то время как главная часть программы использует неподключенный UDP-сокет (позволяющий любому клиенту соединение с ним),
handleSocket
применяет для преобразования файла подключенный UDP-сокет [144] . После анализа имени файла, который нужно передать, и проверки правильности режима преобразования в строке 93 создается сокет с тем же самым семейством, типом и протоколом, с которыми контактировал сервер. Затем используется
connect
для установки удаленного конца сокета на том адресе, от которого поступил запрос на файл, и начинается передача файла. После отправки каждого блока сервер ожидает пакет подтверждения, прежде чем продолжить передачу. Когда приходит последний пакет подтверждения, сервер закрывает сокет и возвращается к главному циклу.
144
Спецификация tftp-протокола требует, чтобы серверы посылали данные клиенту на номер порта, отличающийся от порта, на котором сервер ожидает новые соединения. При этом нетрудно создать параллельный сервер, поскольку каждый сокет сервера предназначен только для одного клиента.
Данный сервер, как правило, запускается с единственным аргументом —
номером порта. Для проверки можно применить стандартную клиентскую программу
tftp
, где первый аргумент является именем хоста для соединения (неплохим выбором будет
localhost
), а второй — номером порта, на котором работает сервер. После запуска клиента нужно активизировать команду
bin
, при этом файлы будут запрашиваться в режиме
octet
, а не в стандартном режиме
netascii
. Как только это сделано, команда
get
позволит передать любой файл от сервера клиенту.
1: /* tftpserver.c */
2:
3: /* Это частичная реализация tftp. Она не поддерживает даже тайм-ауты
4: или повторную передачу пакетов, и она не очень хорошо
5: обрабатывает непредвиденные события.*/
6:
7: #include <netdb.h>
8: #include <stdio.h>
9: #include <stdlib.h>
10: #include <string.h>
11: #include <sys/socket.h>
12: #include <unistd.h>
13: #include <fcntl.h>
14:
15: #include "sockutil.h" /* некоторые служебные функции */
16:
17: #define RRQ 1 /* запрос на чтение */
18: #define DATA 3 /* блок данных */
19: #define ACK 4 /* подтверждение */
20: #define ERROR 5 /* возникла ошибка */
21:
22: /* коды ошибок tftp */
23: #define FILE_NOT_FOUND 1
24:
25: struct tftpPacket {
26: short opcode;
27:
28: union {
29: char bytes[514]; /* самый большой блок, который мы
30: можем обработать, содержит 2 байта
31: для номера блока и 512 для данных */
32: struct {
33: short code;
34: char message[200];
35: } error;
36:
37: struct {
38: short block;
39: char bytes[512];
40: } data;
41:
42: struct {
43: short block;
44: } ack;
45: } u;
46: };
47:
48: void sendError(int s, int errorCode) {
49: struct tftpPacket err;
50: int size;
51:
52: err.opcode = htons(ERROR);
53:
54: err.u.error.code = htons(errorCode); /* файл не найден */
55: switch (errorCode) {
56: case FILE_NOT_FOUND:
57: strcpy(err.u.error.message, "файл не найден");