Freigeben über


Распространение сообщений с применением наследования

Благодаря свободной от занятий неделе я нашел время для проектирования системы, идея которой родилась на школьных занятиях. Идея проста: веб-приложение, позволяющее пользователю связаться с максимально широкой аудиторией посредством распространения сообщений различными способами. Сообщения могут доставляться получателям по электронной почте, в виде смс и мгновенных сообщений, по телефону и множеством других способов. С помощью такой системы перегруженные работой преподаватели смогут за 30 секунд подготовить единственное сообщение, которое будет мгновенно доставлено всем учащимся и их родителям по различным каналам. Тренер наконец-то сможет запросто связываться с участниками своей команды и для этого ему не потребуется публиковать объявления на сайте, который плохо поддерживается и мало кем посещается.

Роберт Уитофф (Robert Witoff)

https://www.chalk2me.com/

Сложность: средняя.

Необходимое время: 3-6 часов.

Затраты: нулевые.

ПО : Visual Basic или Visual C# Express Editions

Оборудование:

Загрузки: Загрузить

Замечание: В данной статье примеры приведены только на C#. Если кто-то предпочитает Visual Basic.Net, сообщите нам, мы перепишем их на VB.

Задумка была крутая, но при этом я понимал, что реализовать ее будет не слишком трудно. Моей целью было создать метод для отправки сообщений любого типа, которые затем будут подхватываться соответствующими потоками для доставки. Поскольку идея в целом заключалась в создании и доставке сообщений, логично было начинать с создания структуры для хранения полей нашего сообщения. При создании собственной службы полезно помнить о других службах. Я начал с четырех полей, встречающихся в любом сообщении электронной почты. Их достаточно для представления содержимого сообщения и остается лишь добавить немного информации, необходимой для доставки. Она содержится в полях способа доставки и дополнительной информации, специфичной для различных способов доставки, которая идентифицирует получателя. На всякий случай я добавил и второе идентифицирующее поле. Вот что получилось:

    1: public struct ctMessage
    2: {
    3:     public ctMessage(string sendToUser, string fromUser, string subj, string msg, string
    4:  
    5:     mediumType, string mediumArg1, string mediumArg)
    6:     {
    7:         this.toUser = sendToUser;
    8:         this.fromUser = fromUser;
    9:         this.subject = subj;
   10:         this.message = msg;
   11:         this.mediumType = mediumType;
   12:         this.mediumArg1 = mediumArg1;
   13:         this.mediumArg2 = mediumArg2;
   14:     }
   15:  
   16:     public string toUser; // имя получателя
   17:     public string fromUser; // имя отправителя
   18:     public string subject;
   19:     public string subject;
   20:     public string mediumType; // узел, который должен это доставить
   21:     public string mediumArg1; // разнообразные данные, идентифицирующие получателя
   22:     public string mediumArg2; //разнообразные данные, идентифицирующие получателя
   23: }

Если есть сообщение, надо его как-то отправить! Поскольку нужно отправлять сообщения самых разнообразных типов, правильно спроектированный базовый класс может сэкономить массу времени. Хотя все службы сообщений, которые мы назвали «узлы», будут отправлять сообщения совершенно по-разному, им всем потребуется выполняться в аналогичных потоках, отправляющих сообщения, поступающие из единого источника. Если грамотно написать абстрактный базовый класс, в дальнейшем новые службы можно добавлять простой подменой метода отправки.

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

    1: public abstract class ctNode
    2: {
    3:     public ctNode()
    4:     {
    5:         //Размер устанавливается в соответствии с временем, требуемым для доступа к N-му сообщению
    6:         this.maxQueueSize = 100; 
    7:         this.msgQueue = new Queue(maxQueueSize);
    8:         //При использовании БД опрашивайте ее без паузы, т. к. запрос дает задержку
    9:         this.msPauseAfterRun = 10000; 
   10:         this.runnerThread = new Thread(new ThreadStart(this.run));
   11:     }
   12:  
   13:     private Thread runnerThread;
   14:     protected int msPauseAfterRun;
   15:     protected Queue msgQueue;
   16:     public int maxQueueSize;
   17:  
   18:     void run()
   19:     {
   20:         while (true)
   21:         {
   22:             while (this.msgQueue.Count > 0)
   23:             {
   24:                 //извлечь сообщение и отправить его
   25:                 ctMessage ctm = (ctMessage)msgQueue.Dequeue();
   26:  
   27:                 try
   28:                 {
   29:                     sendSingleMessage(ctm);
   30:                 }
   31:                 catch (Exception e)
   32:                 {
   33:                     //Сделать здесь более подробную запись в журнал
   34:                     Console.writeLine("the message could not be sent!" + e.toString());
   35:                 }
   36:             }
   37:  
   38:             //Получить новые сообщения
   39:             ctNode ctn = this;
   40:             ctMessage[] newMessages = messageSender.getMessages(ref ctn, getRoomInQueue());
   41:  
   42:             //Подождать перед проверкой других сообщений!
   43:             if (newMessages.Length == 0)
   44:                 Thread.Sleep(msPauseAfterRun);
   45:             else
   46:                 this.EnqueueMessages(newMessages);
   47:         }
   48:     }
   49:  
   50:     public virtual void EnqueueMessages(ctMessage[] messagesToSend)
   51:     {
   52:         int roomInQueue = maxQueueSize - msgQueue.Count;
   53:  
   54:         if (messagesToSend.Length > roomInQueue)
   55:             throw new Exception("Too many Messages have been inserted");
   56:  
   57:         for (int i = 0; i < messagesToSend.Length; i++)
   58:         {
   59:             msgQueue.Enqueue(messagesToSend[i]);
   60:         }
   61:     }
   62:  
   63:     public virtual void EnqueueMessages(ctMessage messageToSend)
   64:     {
   65:         ctMessage[] ctm = new ctMessage[1];
   66:         ctm[0] = messageToSend;
   67:         this.EnqueueMessages(ctm);
   68:     }
   69:  
   70:     protected virtual void sendSingleMessage(ctMessage ctm)
   71:     {
   72:         //Код отправки отдельных сообщений
   73:     }
   74:  
   75:     public void startThread()
   76:     {
   77:         runnerThread.Name = this.nodeType;
   78:         runnerThread.Start();
   79:     }
   80:  
   81:     protected string nodeType;
   82:  
   83:     public string getNodeType()
   84:     {
   85:         return this.nodeType;
   86:     }
   87: }

Заметьте: в этой программе не хватает важной вещи — места для хранения сообщений! Сначала я поместил хранилище сообщений во внешний статический класс. Получилось централизованное хранилище, легко доступное из других нуждающихся в нем объектов с возможностью реализации безопасных потоков и дальнейших усовершенствований механизма хранения (или применения БД). Посмотрите, как все просто:

    1: public static class messageSender
    2: {
    3:     private static List<ctMessage> messagesToSend = new List<ctMessage>();
    4:  
    5:     public static ctMessage[] getMessages(ref ctNode nodeRef, int numberOfMessages)
    6:     {
    7:         //Работу с БД мы реализуем позже
    8:         List<ctMessage> returnMessages = new List<ctMessage>();
    9:  
   10:         lock (messagesToSend)
   11:             for (int i = 0; i < messagesToSend.Count; i++)
   12:                 if (messagesToSend[i].mediumArgs == nodeRef.getNodeType())
   13:                 {
   14:                     returnMessages.Add(messagesToSend[i]);
   15:                     messagesToSend.RemoveAt(i);
   16:                     i--;
   17:                 }
   18:  
   19:         return returnMessages.ToArray();
   20:     }
   21:  
   22:     public static void sendMessage(ctMessage message)
   23:     {
   24:         lock (messagesToSend)
   25:             messagesToSend.Add(message);
   26:     }
   27: }

Подготовив фундамент, я приступил к самому интересному — реализации передачи сообщений. Я начал с узла электронной почты. Если вы использовали электронную почту в .NET-программах, то наверняка знаете и любите класс SmtpClient из пространства имен System.Net.Mail. Если у вас не установлен локальный SMTP-клиент, вы можете спокойно использовать учетную запись Gmail в качестве SMTP-сервера. В приведенной ниже реализации нам потребовалось лишь установить тип узла, инициализировать SmtpClient и подменить метод отправки. Отправляем сообщение нашим отправителем сообщений и наш узел подхватывает его и доставляет. Наследование — это круто!

    1: ctMessage ctm = new ctMessge("Friend", "Developer", "testing", "My first message", "email", "yourEmail@yourDomain.com", "");
    2: messageSender.sendMessage(ctm);
    3:  
    4: public class ctNodeEmail : ctNode
    5: {
    6:     public ctNodeEmail() : base()
    7:     {
    8:         this.nodeType = "email";
    9:  
   10:         //
   11:         //Настройка SMTP-клиента
   12:         //
   13:  
   14:         //Сервер GMAIL
   15:         this.mSmtpClient = new SmtpClient("gmail.com/mail", 25);
   16:         this.mSmtpClient.Host = "smtp.gmail.com";
   17:         this.mSmtpClient.Port = 25;
   18:         this.mSmtpClient.EnableSsl = true;
   19:         this.mSmtpClient.Credentials = new System.Net.NetworkCredential("YOURADDRESS@gmail.com", "YOUR_PASSWORD");
   20:  
   21:         //Локальный сервер
   22:         //this.mSmtpClient = new SmtpClient("localhost", 25);
   23:     }
   24:  
   25:     SmtpClient mSmtpClient;
   26:  
   27:     protected override void sendSingleMessage(ctMessage ctm)
   28:     {
   29:         MailMessage msgMail = new MailMessage(new MailAddress(ctm.fromUser + "@ChalkTalkNow.com"), new MailAddress(ctm.mediumArgs));
   30:         msgMail.Subject = ctm.subject;
   31:         string msg = ctm.message;
   32:         msgMail.Body = msg;
   33:  
   34:         // Отправить почтовое сообщение
   35:         mSmtpClient.Send(msgMail);
   36:     }
   37: }

С электронной почтой все получилось быстро, теперь пойдем дальше — сделаем отправку смс-сообщений. Не секрет, что большинство сотовых операторов предоставляют адрес электронной почты, соответствующий номеру абонента, к которому надо лишь добавить правильный домен. При создании нашего узла для отправки текстовых сообщений мы использовали первый mediumArg для номера телефона, а второй mediumArg — для названия оператора.

    1: public class ctNodeTextMessage : ctNode
    2: {
    3:     public ctNodeTextMessage() : base()
    4:     {
    5:         this.nodeType = "textmessage";
    6:  
    7:         //
    8:         //Настройка SMTP-клиента
    9:         //
   10:         this.mSmtpClient = new SmtpClient("gmail.com/mail", 25);
   11:         this.mSmtpClient.Host = "smtp.gmail.com";
   12:         this.mSmtpClient.Port = 25;
   13:         this.mSmtpClient.EnableSsl = true;
   14:         this.mSmtpClient.Credentials = new System.Net.NetworkCredential("GMAIL@gmail.com", "PASSWORD");
   15:  
   16:     }
   17:     SmtpClient mSmtpClient;
   18:  
   19:     private string getEmailAddress(string phoneNumber, string provider)
   20:     {
   21:         switch (provider)
   22:         {
   23:             case "t-mobile":
   24:                 return phoneNumber + "@tmomail.net";
   25:             case "virginmobile":
   26:                 return phoneNumber + "@vmobl.com";
   27:             case "cingular":
   28:                 return phoneNumber + "@cingularme.com";
   29:             case "att":
   30:                 return phoneNumber + "@cingularme.com";
   31:             case "sprint":
   32:                 return phoneNumber + "@messaging.sprintpcs.com";
   33:             case "verizon":
   34:                 return phoneNumber + "@vtext.com";
   35:             case "nextel":
   36:                 return phoneNumber + "@messaging.nextel.com";
   37:             default:
   38:                 // Предполагаем, что cingular/att — самые распространенные
   39:                 return phoneNumber + "@cingularme.com";
   40:         }
   41:     }
   42:  
   43:     protected override void sendSingleMessage(ctMessage ctm)
   44:     {
   45:         //Отправляем единственное сообщение
   46:         string emailAddress = getEmailAddress(ctm.mediumArg1, ctm.mediumArg2);
   47:  
   48:         MailMessage msgMail = new MailMessage(new MailAddress(ctm.fromUser + "@chalktalk.net", ctm.fromUser), new MailAddress(emailAddress));
   49:         msgMail.Subject = ctm.subject;
   50:         msgMail.Body = ctm.message;
   51:         sendEmail(msgMail);
   52:     }
   53:  
   54:     private void sendEmail(MailMessage msgMail)
   55:     {
   56:         // Отправить почтовое сообщение
   57:         this.mSmtpClient.Send(msgMail);
   58:     }
   59: }

Для проверки отправляем смс, так же, как отправляли e-mail. Круто!

    1: ctMessage ctm = new ctMessge("Friend", "Developer", "testing", "hello world via text", "textmessage", "#########", "verizon");
    2: messageSender.sendMessage(ctm);
Завершение

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

Следующие шаги
  • Создание интерфейса пользователя для работы с информационным содержимым и с распространением сообщений.
  • Изменение класса messageSender для хранения сообщений в базе данных. Это сделает приложение гораздо более гибким и позволит сохранять отправленные сообщения.
  • Строго типизировать все члены перечислений, чтобы избежать досадных ошибок.
  • Реализуйте узел для своего любимого способа передачи сообщений, используя соответствующий SDK.
  • Присоединяйтесь к нашей команде и включайтесь в работу!