Книга: Разработка приложений в среде Linux. Второе издание

17.6.3. Простой tftp-сервер

17.6.3. Простой tftp-сервер

Данный простой tftp-сервер иллюстрирует отправку и получение UDP-дейтаграмм как для подключенных, так и для неподключенных сокетов. Протокол tftp представляет собой несложный протокол передачи файлов, построенный на основе UDP[143]. Он часто используется встроенными компьютерными программами для пересылки первоначального загрузочного образа при сетевой загрузке. Сервер, который мы предлагаем рассмотреть, обладает рядом ограничений, поэтому он непригоден для какой-либо практической работы.

• С сервером одновременно может взаимодействовать только один клиент (этот недостаток легко устранить).

• Сервер может только отправлять файлы, но не может получать.

• Отсутствуют условия для ввода ограничений на передачу файлов анонимному удаленному пользователю.

• Выполняется очень поверхностная проверка ошибок, что, скорее всего, приведет к проблемам во время эксплуатации.

Клиент 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); /* файл не найден */
 55:  switch (errorCode) {
 56:  case FILE_NOT_FOUND:
 57:   strcpy(err.u.error.message, "файл не найден");
 58:   break;
 59:  }
 60:
 61:  /* 2 байта кода операции, 2 байта кода ошибки, сообщение и '' */
 62:  size = 2 + 2 + strlen(err.u.error.message) + 1;
 63:  if (send(s, &err, size, 0) != size)
 64:   die("erarorsend");
 65: }
 66:
 67: void handleRequest(struct addrinfo tftpAddr,
 68:  struct sockaddr remote, int remoteLen,
 69:  struct tftpPacket request) {
 70:  char * fileName;
 71:  char * mode;
 72:  int fd;
 73:  int s;
 74:  int size;
 75:  int sizeRead;
 76:  struct tftpPacket data, response;
 77:  int blockNum = 0;
 78:
 79:  request.opcode = ntohs(request.opcode);
 80:  if (request.opcode != RRQ) die("неверный код операции");
 81:
 82:  fileName = request.u.bytes;
 83:  mode = fileName + strlen(fileName) + 1;
 84:
 85:  /* здесь поддерживается только режим bin */
 86:  if (strcmp(mode, "octet")) {
 87:   fprintf(stderr, "неверный режим %sn", mode);
 88:   exit(1);
 89:  }
 90:
 91:  /* требуется передача при помощи сокета того же семейства и типа,
 92:     с которым мы начинали */
 93:  if ((s = socket(tftpAddr.ai_family, tftpAddr.ai_socktype,
 94:   tftpAddr.ai_protocol)) < 0)
 95:   die("send socket");
 96:
 97:  /* установить удаленный конец сокета на адрес, который
 98:     инициирует данное соединение */
 99:  if (connect(s, &remote, remoteLen))
100:   die("connect");
101:
102:  if ((fd = open(fileName, O_RDONLY)) < 0) {
103:   sendError(s, FILE_NOT_FOUND);
104:   close(s);
105:   return;
106:  }
107:
108:  data.opcode = htons(DATA);
109:  while ((size = read(fd, data.u.data.bytes, 512)) > 0) {
110:   data.u.data.block = htons(++blockNum);
111:
112:   /* размер составляют 2 байта (код операции), 2 байта (номер блока) и данные*/
113:   size += 4;
114:   if (send(s, &data, size, 0) != size)
115:    die("data send");
116:
117:   sizeRead = recv(s, &response, sizeof(response), 0);
118:   if (sizeRead < 0) die("recv ack");
119:
120:   response.opcode = ntohs(response.opcode);
121:   if (response.opcode != ACK) {
122:    fprintf(stderr, "непредвиденный код операции в откликеn");
123:    exit(1);
124:   }
125:
126:   response.u.ack.block = ntohs(response.u.ack.block);
127:   if (response.u.ack.block != blockNum) {
128:    fprintf(stderr, "получено подтверждение неверного блокаn");
129:    exit(1);
130:   }
131:
132:   /* если блок, который мы только что отправили, содержит
133:      меньше 512 байт, то задача выполнена */
134:   if (size < 516) break;
135:  }
136:
137:  close(s);
138: }
139:
140: int main(int argc, char ** argv) {
141:  struct addrinfo hints, * addr;
142:  char * portAddress = "tftp";
143:  int s;
144:  int rc;
145:  int bytes, fromLen;
146:  struct sockaddr from;
147:  struct tftpPacket packet;
148:
149:  if (argc > 2) {
150:   fprintf(stderr, "использование: tftpserver [порт]n");
151:   exit(1);
152:  }
153:
154:  if (argv[1]) portAddress = argv[1];
155:
156:  memset(&hints, 0, sizeof (hints));
157:
158:  hints.ai_socktype = SOCK_DGRAM;
159:  hints.ai_flags = AI_ADDRCONFIG | AI_PASSIVE;
160:  if ((rc = getaddrinfo(NULL, portAddress, &hints, &addr)))
161:   fprintf(stderr, "сбой поиска порта %sn",
162:    portAddress);
163:
164:  if ((s = socket(addr->ai_family, addr->ai_socktype,
165:   addr->ai_protocol)) < 0)
166:   die("socket");
167:
168:  if (bind(s, addr->ai_addr, addr->ai_addrlen))
169:   die("bind");
170:
171:  /* Основной цикл состоит из ожидания tftp-запроса, его обработки
172:     и затем ожидания следующего запроса. */
173:  while (1) {
174:   bytes = recvfrom(s, &packet, sizeof(packet), 0, &from,
175:    &fromLen);
176:   if (bytes < 0) die("recvfrom");
177:
178:   /* Если выполнить разветвление перед вызовом handleRequest() и
179:      завершить дочерний процесс после возврата функции, то данный
180:      сервер будет работать точно как параллельный tftp-сервер */
181:   handleRequest(*addr, from, fromLen, packet);
182:  }
183: }

Оглавление книги


Генерация: 1.329. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз