время эксплуатации.
Клиент 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()
для установки удаленного конца сокета на том адресе, от которого поступил запрос на файл, и начинается передача файла. После отправки каждого блока сервер ожидает пакет подтверждения, прежде чем продолжить передачу. Когда приходит последний пакет подтверждения, сервер закрывает сокет и возвращается к главному циклу.
Данный сервер, как правило, запускается с единственным аргументом — номером порта. Для проверки можно применить стандартную клиентскую программу 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); /* файл не найден */