Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Благодаря свободной от занятий неделе я нашел время для проектирования системы, идея которой родилась на школьных занятиях. Идея проста: веб-приложение, позволяющее пользователю связаться с максимально широкой аудиторией посредством распространения сообщений различными способами. Сообщения могут доставляться получателям по электронной почте, в виде смс и мгновенных сообщений, по телефону и множеством других способов. С помощью такой системы перегруженные работой преподаватели смогут за 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.
- Присоединяйтесь к нашей команде и включайтесь в работу!