Содержание


Работаем с Mono

Часть 9. Взаимодействие по протоколам POP3 и FTP с помощью сокетов

Comments

Серия контента:

Этот контент является частью # из серии # статей: Работаем с Mono

Следите за выходом новых статей этой серии.

Этот контент является частью серии:Работаем с Mono

Следите за выходом новых статей этой серии.

Как упоминалось в предыдущих статьях, в пространстве имен System.Net.Mail есть класс, использующий протокол SMTP для отправки почтовых сообщений. Однако часто требуется взаимодействовать с удаленными серверами, используя другие протоколы. В данной статье рассматривается применение сокетов для работы с протоколами POP3 (для приема почты) и FTP (загрузка файлов).

Работа по протоколу POP3

Протокол POP3 (Post Office Protocol — version 3) — один из наиболее популярных протоколов для получения почтовых сообщений. Этот протокол описывается в стандарте RFC1939 и предполагает использование текстовых команд для обмена информацией между клиентом и сервером. По умолчанию подразумевается, что клиент подключается к 110-ому порту сервера по TCP-протоколу. Общая схема работы по протоколу POP3 показана на рисунке 1.

Рисунок 1. Схема работы по протоколу POP3
Рисунок 1. Схема работы по протоколу POP3
Рисунок 1. Схема работы по протоколу POP3

Сервер может находиться в одном из трех состояний:

  • авторизация — выполняется идентификация пользователя;
  • транзакция — идет непосредственная работа с клиентом (получение количества сообщений, самих сообщений, пометка сообщений к удалению).
  • обновление — производится удаление отмеченных сообщений и закрытие соединения.

На любую команду, отправленную клиентом, сервер возвращает один из двух возможных ответов:

  • +OK {дополнительная информация, зависящая от команды} — при удачном исполнении команды;
  • -ERR {описание ошибки} — если при выполнении команды возникла ошибка.

Перед отправкой следующей команды необходимо получить от сервера ответ на предыдущую команду.

В состоянии авторизации доступны следующие команды:

  • USER {имя} — передача серверу имени пользователя;
  • PASS {пароль} — передача серверу пароля;
  • QUIT — закрытие соединения;

После успешной авторизации сервер переходит в состояние транзакции, в котором становятся доступными другие команды.

Получив команду STAT, сервер возвращает количество сообщений в почтовом ящике и суммарный объем места, занимаемый этими сообщениями.

Команда LIST {номер сообщения} возвращает номер сообщения и его размер. Когда команда LIST передается без параметров, то возвращается общее количество писем в ящике, а также отдельными строками номер и размер для каждого сообщения.

Примечание: Согласно протоколу POP3 завершающим символом для ответа сервера является единственная точка (.), размещенная на отдельной строке. Может возникнуть проблема, когда в какой-либо строке сообщения, передаваемого из почтового ящика, уже содержится единственная точка. В этом случае точка удваивается и признаком конца передачи уже являться не будет, но на метод, который будет заниматься разбором пришедшего сообщения, накладывается дополнительное требование по замене двух точек подряд в строке на одну.

Запрос сообщения с заданным номером выполняется командой RETR {номер сообщения}. В первой строке приходит ответ, затем само сообщение, а заканчивается передача одиночной точкой на строке.

Команда DELE {номер сообщения} помечает сообщение с заданным номером для последующего удаления. Физически сообщение будет удалено при переходе в состояние обновления, однако помеченное сообщение, как и информация о нем, уже будут недоступны для клиента.

Команда RSET сбрасывает пометки с «удаляемых» сообщений, и они снова становятся доступны.

Команда QUIT переводит соединение в состояние обновления для последующего закрытия. В состоянии обновления клиент не может передавать команды серверу. Сервер удаляет помеченные к удалению сообщения и закрывает соединение. Переход в состояние обновления также выполняется при отсутствии активности со стороны клиента в течение определенного времени.

В протоколе POP3 имеются и другие команды, а также огромное количество расширений (поддержка которых зависит от конкретного почтового сервера). Но для демонстрации примера работы с протоколом POP3 вполне достаточно представленных команд.

В листинге 1 представлен класс POP3Message, использующийся для хранения информации о сообщении.

Листинг 1. Класс POP3Message
class POP3Message
{
  public int number;  //номер сообщения
  public int size;    //размер сообщения
  public string body; //текст сообщения
  public POP3Message()
  {
    number = 0;
    size = 0;
    body = null;
}

В поле body может храниться текст сообщения или null, если была получена только информация о сообщении, а само сообщение еще не было получено (команда LIST).

Основная функциональность находится в классе POP3Client. Этот класс использует класс TcpClient, являющийся надстройкой над классом Socket и позволяющий передавать и принимать данные при помощи потока NetworkStream. В листинге 2 приведен фрагмент класса POP3Client, содержащий поля и конструктор этого класса.

Листинг 2. Конструктор и поля класса POP3Client
class POP3Client
{
  private TcpClient client;
  private List<POP3Message> messages; //список сообщений, полученных клиентом
  public POP3Client()
  {
    messages = new List<POP3Message>();
  }
}

Далее потребуется реализовать методы для отправки сформированной команды и получения ответа. Проще всего выполняется отправка команды, для этого достаточно получить поток NetworkStream из класса TcpClient и записать в него последовательность байт, полученную из строки, как показано в листинге 4.

Листинг 4. Метод для отправки команды на сервер
public void SendCommand(string command)
{
  ASCIIEncoding ae = new ASCIIEncoding();
  NetworkStream stream = client.GetStream();
  stream.Write(ae.GetBytes(command), 0, ae.GetBytes(command).Length);
}

Ответ может состоять из одной (команда LIST) или нескольких строк (команда RETR), поэтому имеет смысл реализовать общий метод для обработки однострочного ответа, а работу с многострочными ответами выполнять непосредственно в методах, отвечающих за выполнение команд STAT и RETR. В листинге 5 приведена реализация метода GetStringResponse для обработки однострочного ответа.

Листинг 5. Получение однострочного ответа
public string GetStringResponse()
{
  ASCIIEncoding ae = new ASCIIEncoding();
  int size = 0;
  byte []buf = new byte[512];
  NetworkStream stream = client.GetStream();
  while(true)
  {
    byte[] b_arr = new byte[1];
    //чтение из потока выполняется в побайтовом режиме
    int read_size = stream.Read(b_arr, 0, 1);
    if(read_size != 1)
      break;
    else
    {
      buf[size] = b_arr[0];
      size++;
      //если встречается символ перевода строки, то чтение завершается
      if(buf[size-1] == '\n')
        break;
    }
  }
  return ae.GetString(buf, 0, size);
}

При выполнении каждой команды необходимо проанализировать ответ сервера, поэтому потребуется метод, определяющий статус завершения операции.

Листинг 6. Метод для проверки статуса завершения операции
public bool IsAnswerGood(string answer)
{
  return String.Equals(answer.Substring(0, 3), "+OK");
}

В листинге 7 приведена реализация метода для установки соединения с указанием имени сервера, имени и пароля пользователя. Соединение происходит по 110-ому порту, который используется POP3-серверами по умолчанию. В случае удачного соединения метод возвращает true, иначе — false.

Листинг 7. Подключение к POP3-серверу
public bool Connect(string server, string name, string pass)
{
  client = new TcpClient();
  client.Connect(server, 110);
  string answer = GetStringResponse();
  if(!IsAnswerGood(answer))
  {
    client.Close();
    return false;
  }
  SendCommand("USER " + name + "\r\n");
  answer = GetStringResponse();
  if(!IsAnswerGood(answer))
  {
    client.Close();
    return false;
  }
  SendCommand("PASS " + pass + "\r\n");
  answer = GetStringResponse();
  if(!IsAnswerGood(answer))
  {
    client.Close();
    return false;
  }
  return true;
}

Из остальных команд можно рассмотреть реализацию LIST (см. листинг 8), RETR (см. листинг 9) и QUIT (см. листинг 10).

Листинг 8. Реализация команды LIST
public void GetMessagesList()
{
  string answer;
  SendCommand("LIST\r\n");
  answer = GetStringResponse();
  if(!IsAnswerGood(answer))
    return;
  messages.Clear();
  while(true)
  {
    answer = GetStringResponse();
    if(String.Equals(answer, ".\r\n"))
      return;
    POP3Message msg = new POP3Message();
    string[] parameters = answer.Split(new char[] { ' ' });
    msg.number = Int32.Parse(parameters[0]);
    msg.size = Int32.Parse(parameters[1]);
    messages.Add(msg);
  }
}
Листинг 9. Получение сообщения с помощью команды RETR
public void RetrieveMessage(POP3Message msg)
{
  string text = "";
  SendCommand("RETR " + msg.number.ToString() + "\r\n");
  string answer = GetStringResponse();
  if(!IsAnswerGood(answer))
    return;
  while(true)
  {
    answer = GetStringResponse();
    if(String.Equals(answer, ".\r\n"))
    {
      msg.body = text;
      return;
    }
    text = text + answer;
  }
}
Листинг 10. Закрытие соединения
public void Disconnect()
{
  SendCommand("QUIT");
}

Полный код и пример использования класса POP3Example можно найти в архиве pop3_example.zip, прикрепленном к этой статье.

Работа по протоколу FTP

Протокол FTP (File Transfer Protocol) применяется для передачи файлов по сети. В некоторой степени работа по протоколу FTP схожа с работой по протоколу POP3. Соединение устанавливается с 21-ым портом удаленного компьютера, затем производится отправка текстовых команд и получение текстовых ответов. Спецификация протокола описана в стандарте RFC959.

Однако при использовании протокола FTP также открывается дополнительное соединение для передачи данных, которое может работать в активном или пассивном режиме. В активном режиме, при соединении клиент сообщает серверу номер порта (из динамического диапазона 1024-65535), чтобы сервер мог соединиться с клиентом для передачи данных. Сервер подключается к указанному клиентом порту, используя 20-ый TCP-порт для передачи данных со своей стороны.

В пассивном режиме после установки соединения сервер сообщает клиенту номер порта (из динамического диапазона 1024-65535), к которому клиент должен подключиться для передачи данных. Схема работы по протоколу FTP приведена на рисунке 2.

Рисунок 2. Схема работы по протоколу FTP
Рисунок 2. Схема работы по протоколу FTP
Рисунок 2. Схема работы по протоколу FTP

На каждую команду, отправленную клиентом, сервер должен вернуть трехзначный код и текст ответа, который может состоять из одной или нескольких строк. Работать с протоколом FTP сложнее, чем с POP3, так как в FTP присутствует больше стандартных команд и требуется открытие второго соединения.

В качестве примера будет разработано приложение для получения файла (последняя версия ядра Linux) с удаленного сервера по протоколу FTP. Для выполнения этой задачи будут использоваться перечисленные ниже команды.

В команде USER {имя пользователя} передается имя пользователя для аутентификации на FTP-сервере. Если используется общедоступный сервер, то, как правило, передается имя anonymous.

Команда PASS {пароль} используется для передачи пароля пользователя, для анонимного FTP может использоваться любое значение, но чаще всего указывается адрес электронной почты.

Команда QUIT разрывает соединение с FTP сервером.

Для непосредственной работы с файлами используются следующие команды:

  • CWD {имя каталога} — команда, заставляющая сервер сменить текущий каталог;
  • LIST — список файлов в текущем каталоге (передается через второе соединение);
  • RECV {имя файла} — получение файла (передается через второе соединение);
  • SIZE {имя файла} — получение размера файла.

Для команд, которым требуется отдельное соединение для передачи данных, необходимо передать серверу параметры для установки соединения (активный режим) или запросить эти параметры у сервера (пассивный режим).

Команда PASV переводит взаимодействие в пассивный режим. Получив эту команду, сервер вернет ответ со строкой в формате (a1,a2,a3,a4,p1,p2). Соединение необходимо установить с IP-адресом a1.a2.a3.a4 и портом p1*256+p2.

Команда PORT {a1,a2,a3,a4,p1,p2} используется для активного режима. Получив эту команду, сервер попытается соединиться с IP-адресом a1.a2.a3.a4 и портом p1*256+p2.

Как говорилось выше, в ответе от сервера содержится трехзначный код.

Первая цифра кода означает:

  • 1 — команда принята к исполнению, но еще не завершена;
  • 2 — выполнение команды успешно завершено;
  • 3 — команда принята, ожидается дополнительная команда;
  • 4 — невозможно в данный момент выполнить команду;
  • 5 — выполнение команды невозможно в принципе.

Еще одной особенностью является то, что после трехзначного кода (на четвертой позиции в ответе) может быть символ "-", означающий, что в ответе на команду присутствует еще одна строка. Такой ответ надо построчно считывать до тех пор, пока не будет получена строка с кодом ответа, отделенным от остальной строки пробелом, а не "-".

Для получения файла будет использоваться класс FtpClient, который также будет использовать объекты типа TcpClient для установки соединения для обмена командами и соединения для обмена данными, как показано в листинге 11.

Листинг 11. Поля класса FtpClient
class FtpClient
{
  private TcpClient dataconn;
  private TcpClient commandconn;
}

Методы SendCommand и GetStringResponse будут практически идентичны соответствующим методам для протокола POP3 (см. листинг 4 и листинг 5). Дополнительно потребуется реализовать метод GetLastStringResponse, который будет получать от сервера многострочный ответ и выдавать только окончание ответа.

Листинг 12. Метод для получения многострочного ответа по протоколу FTP
public string GetLastStringResponse()
{
  string ret;
  while(true)
  {
    ret = GetStringResponse();
    if(ret[3] == '-')
      continue;
    break;
  }
  return ret;
}

Так как в протоколе FTP возможно большое количество различных ответов (трехзначных кодов), то прием, использовавшийся для проверки статуса завершения операции в протоколе POP3, уже не подходит, и потребуется сделать специальный метод для получения кода ответа.

Листинг 13. Получение трехзначного кода из ответа
public string GetAnswerCode(string answer)
{
  return answer.Substring(0, 3);
}

Для открытия командного соединения будет использоваться метод, немного похожий на аналогичный метод для протокола POP3, но с отличиями в области обработки ответов.

Листинг 14. Открытие соединения обмена командами по протоколу FTP
public bool Connect(string server, string name, string pass)
{
  commandconn = new TcpClient();
  commandconn.Connect(server, 21);
  string answer = GetLastStringResponse();
  if(GetAnswerCode(answer) != "220")
  {
    commandconn.Close();
    return false;
  }
  SendCommand("USER " + name + "\r\n");
  answer = GetLastStringResponse();
  if(GetAnswerCode(answer) != "331")
  {
    commandconn.Close();
    return false;
  }
  SendCommand("PASS " + pass + "\r\n");
  answer = GetLastStringResponse();
  if(GetAnswerCode(answer) != "230")
  {
    commandconn.Close();
    return false;
  }
  return true;
}

Также потребуется отдельно реализовать методы для отправки команд для смены текущего каталога (CWD), получения размера файла (SIZE) и закрытия соединения (QUIT).

Листинг 15. Методы для отправки команд по протоколу FTP
//смена текущего каталога
public bool ChangeDirectory(string dirname)
{
  SendCommand("CWD " + dirname + "\r\n");
  string answer = GetLastStringResponse();
  if(GetAnswerCode(answer) != "250")
    return false;
  return true;
}

//получение размера файла
public int GetFileSize(string filename)
{
  SendCommand("SIZE " + filename + "\r\n");
  string answer = GetLastStringResponse();
  if(GetAnswerCode(answer) != "213")
    return -1;
  return Int32.Parse(answer.Substring(4));
}

//отключение от FTP-сервера
public void Disconnect()
{
  SendCommand("QUIT\r\n");
}

Остается организовать соединение с сервером для приема данных и непосредственную передачу файла. В листинге 16 представлены методы для работы с командами PASV и RECV.

Листинг 16. Установка соединения в пассивном режиме и прием файла
//получение файла будет выполняться в пассивном режиме
public bool SetPassiveMode()
{
  SendCommand("PASV\r\n");
  string answer = GetLastStringResponse();
  if(GetAnswerCode(answer) != "227")
    return false;
  answer = answer.Substring(answer.IndexOf('(') + 1, 
                            answer.IndexOf(')') - answer.IndexOf('(') - 1);
  string[] parts = answer.Split(',');
  string addr = parts[0] + "." + parts[1] + "." + parts[2] + "." + parts[3];
  int port = Int32.Parse(parts[4]) * 256 + Int32.Parse(parts[5]);
  if((dataconn != null) && (dataconn.Connected))
    dataconn.Close();
  dataconn = new TcpClient();
  dataconn.Connect(addr, port);
  return true;
}

//получение файла
public byte[] ReceiveData(string name, int size)
{
  byte[] data = new byte[size];
  SendCommand("REСV " + name + "\r\n");
  string answer = GetLastStringResponse();
  if(GetAnswerCode(answer) != "150")
    return null;
  NetworkStream stream = dataconn.GetStream();
  stream.Read(data, 0, size);
  dataconn.Close();
  return data;
}

Полностью исходный код и пример использования класса FTPExample можно найти в файле ftp_example.zip, прикрепленном к этой статье.

Заключение

В данной статье были рассмотрены только основные приемы для работы с протоколами POP3 и FTP. Для этих протоколов существует огромное количество расширений, например, использование защищенных каналов и т.д.

Однако, используя материал, представленный в данной статье, в качестве отправной точки, можно начать разрабатывать собственные приложения для работы с протоколами FTP и POP3 или расширить функциональность созданных классов для поддержки дополнительных команд и расширений.


Ресурсы для скачивания


Похожие темы


Комментарии

Войдите или зарегистрируйтесь для того чтобы оставлять комментарии или подписаться на них.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, Open source
ArticleID=751954
ArticleTitle=Работаем с Mono: Часть 9. Взаимодействие по протоколам POP3 и FTP с помощью сокетов
publish-date=08112011