Распространение сообщений с применением наследования
Благодаря свободной от занятий неделе я нашел время для проектирования системы, идея которой родилась на школьных занятиях. Идея проста: веб-приложение, позволяющее пользователю связаться с максимально широкой аудиторией посредством распространения сообщений различными способами. Сообщения могут доставляться получателям по электронной почте, в виде смс и мгновенных сообщений, по телефону и множеством других способов. С помощью такой системы перегруженные работой преподаватели смогут за 30 секунд подготовить единственное сообщение, которое будет мгновенно доставлено всем учащимся и их родителям по различным каналам. Тренер наконец-то сможет запросто связываться с участниками своей команды и для этого ему не потребуется публиковать объявления на сайте, который плохо поддерживается и мало кем посещается.
Роберт Уитофф (Robert Witoff)
Сложность: средняя.
Необходимое время: 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.
- Присоединяйтесь к нашей команде и включайтесь в работу!