Inherited Message Distributing
| | Given a week off of school I found the time to start designing a system that had been thought up in a school class. The idea was simple, a webapp that lets a user reach the widest possible audience by distributing messages across many mediums. One message could be delivered to each recipient as an email, text message, instant message, or phone call to name a few possibilities. With this system, an already overworked teacher could take 30 seconds to send one message that was delivered to each student and parent instantly across multiple mediums. A sports coach could finally reach everyone on the team, without trying to post news on a site that was rarely checked and poorly maintained. |
| Robert Witoff http://www.chalk2me.com/ Difficulty: Intermediate Time Required: 3-6 hours Cost: Free Hardware: Disclaimer: Currently this article is C# only. If you want it in Visual Basic.Net, please comment and tell us so we can transcode it to VB. |
Starting Out:
We thought we had come up with a cool concept, and I knew it wouldn’t be that difficult to build. After calling our dev-team into the office I started on it, by myself in my apartment. My goal was to have a single method to send any type of message that would then be picked up by an appropriate thread to deliver the message. As the whole idea here was to create and deliver messages, a logical place to start was a struct to hold our message fields. It’s always good to keep other services in mind while you’re building your own. I started off with the first 4 fields in any email. This covered message content and we only needed a bit more info to deliver the message. The medium type, along with an identifier field for your address (or screen name etc.), would cover this. Just in case a service we end up using needs a second identifier, I added one more. Check it out:
public struct ctMessage
{
public ctMessage(string sendToUser, string fromUser, string subj, string msg, string
mediumType, string mediumArg1, string mediumArg)
{
this.toUser = sendToUser;
this.fromUser = fromUser;
this.subject = subj;
this.message = msg;
this.mediumType = mediumType;
this.mediumArg1 = mediumArg1;
this.mediumArg2 = mediumArg2;
}
public string toUser; //name of recipient
public string fromUser; //name of sender
public string subject;
public string message;
public string mediumType; //which node should deliver this
public string mediumArg1; //varies, to identify the recipient
public string mediumArg2; //varies, to identify the recipient
}
Once we had a message designed I, of course, wanted to be able to send one! Eventually I wanted a bunch of different types of messages being sent, so a solid base class would save a ton of time later. Although all of the messaging services, or ‘nodes’ would undoubtedly send the messages completely different, they would all need to run in a similar thread that sent messages coming from a single location. By doing a good job writing an abstract base class, we could add new services by only overriding the 'send' method later.
This abstract class would have a field identifying the type of messages it will handle, and call an external send method whenever messages of that type existed. A continuous loop to poll for messages, given a modest polling frequency would do the trick. Notice the protected methods and fields that we'll want to override.
public abstract class ctNode
{
public ctNode()
{
this.maxQueueSize = 100; //Set the size according to how long it will take to reach the Nth message
this.msgQueue = new Queue(maxQueueSize);
this.msPauseAfterRun = 10000; //If using a DB, polling it with no pause b/w queries is an unnecessary load
this.runnerThread = new Thread(new ThreadStart(this.run));
}
private Thread runnerThread;
protected int msPauseAfterRun;
protected Queue msgQueue;
public int maxQueueSize;
void run()
{
while (true)
{
while (this.msgQueue.Count > 0)
{
//pop a message and send it
ctMessage ctm = (ctMessage)msgQueue.Dequeue();
try
{
sendSingleMessage(ctm);
}
catch (Exception e)
{
//Write a more detailed error log here
Console.writeLine("the message could not be sent!" + e.toString());
}
}
//get new messages
ctNode ctn = this;
ctMessage[] newMessages = messageSender.getMessages(ref ctn, getRoomInQueue());
//Wait some time before checking for more messages!
if (newMessages.Length == 0)
Thread.Sleep(msPauseAfterRun);
else
this.EnqueueMessages(newMessages);
}
}
public virtual void EnqueueMessages(ctMessage[] messagesToSend)
{
int roomInQueue = maxQueueSize - msgQueue.Count;
if (messagesToSend.Length > roomInQueue)
throw new Exception("Too many Messages have been inserted");
for (int i = 0; i < messagesToSend.Length; i++)
{
msgQueue.Enqueue(messagesToSend[i]);
}
}
public virtual void EnqueueMessages(ctMessage messageToSend)
{
ctMessage[] ctm = new ctMessage[1];
ctm[0] = messageToSend;
this.EnqueueMessages(ctm);
}
protected virtual void sendSingleMessage(ctMessage ctm)
{
//code to send an individual message
}
public void startThread()
{
runnerThread.Name = this.nodeType;
runnerThread.Start();
}
protected string nodeType;
public string getNodeType()
{
return this.nodeType;
}
}
You might have noticed the big thing missing from these classes, a place to store, and retrieve messages! I initially put our message storage into an external static class. This made them centralized, easily accessible from any other objects that might need to access them, simple to ensure thread safety, and gave us room to move to a smarter mechanism later (or database. Notice the simplicity:
public static class messageSender
{
private static List<ctMessage> messagesToSend = new List<ctMessage>();
public static ctMessage[] getMessages(ref ctNode nodeRef, int numberOfMessages)
{
//We'll do this in a database later, but for now this works similarly
List<ctMessage> returnMessages = new List<ctMessage>();
lock (messagesToSend)
for (int i = 0; i < messagesToSend.Count; i++)
if (messagesToSend[i].mediumArgs == nodeRef.getNodeType())
{
returnMessages.Add(messagesToSend[i]);
messagesToSend.RemoveAt(i);
i--;
}
return returnMessages.ToArray();
}
public static void sendMessage(ctMessage message)
{
lock (messagesToSend)
messagesToSend.Add(message);
}
}
Now that the groundwork was done, I grabbed some eye drops and an energy drink to refresh for the best part, function! I started simple with an email node, and if you’ve played with email before in .net, you’ve definitely seen and loved the SmtpClient class in the System.Net.Mail Namespace. If you don’t already have a local SMTP client set up, you can easily use a Gmail account as an SMTP server. Check out the following implementation, notice we only had to set the node type, initialize the SmtpClient and override the send method. Send a message through our message sender, and watch the node pick up the message and deliver it. Inheritance rocks!
ctMessage ctm = new ctMessge("Friend", "Developer", "testing", "My first message", "email", "yourEmail@yourDomain.com", "");
messageSender.sendMessage(ctm);
public class ctNodeEmail : ctNode
{
public ctNodeEmail() : base()
{
this.nodeType = "email";
//
//SET UP SMTP CLIENT
//
//GMAIL SERVER
this.mSmtpClient = new SmtpClient("gmail.com/mail", 25);
this.mSmtpClient.Host = "smtp.gmail.com";
this.mSmtpClient.Port = 25;
this.mSmtpClient.EnableSsl = true;
this.mSmtpClient.Credentials = new System.Net.NetworkCredential("YOURADDRESS@gmail.com", "YOUR_PASSWORD");
//LOCAL SERVER
//this.mSmtpClient = new SmtpClient("localhost", 25);
}
SmtpClient mSmtpClient;
protected override void sendSingleMessage(ctMessage ctm)
{
MailMessage msgMail = new MailMessage(new MailAddress(ctm.fromUser + "@ChalkTalkNow.com"), new MailAddress(ctm.mediumArgs));
msgMail.Subject = ctm.subject;
string msg = ctm.message;
msgMail.Body = msg;
// Send the mail message
mSmtpClient.Send(msgMail);
}
}
After writing this so quickly I wanted something a bit more than email, and knew text messages were only a step away. It’s no secret that all major cell companies give your phone an email address, you just need to append the proper domain to your number. Building off of our email node, we used the first mediumArg to hold a phone number, and put the carrier into the second mediumArg.
public class ctNodeTextMessage : ctNode
{
public ctNodeTextMessage() : base()
{
this.nodeType = "textmessage";
//
//SET UP SMTP CLIENT
//
this.mSmtpClient = new SmtpClient("gmail.com/mail", 25);
this.mSmtpClient.Host = "smtp.gmail.com";
this.mSmtpClient.Port = 25;
this.mSmtpClient.EnableSsl = true;
this.mSmtpClient.Credentials = new System.Net.NetworkCredential("GMAIL@gmail.com", "PASSWORD");
}
SmtpClient mSmtpClient;
private string getEmailAddress(string phoneNumber, string provider)
{
switch (provider)
{
case "t-mobile":
return phoneNumber + "@tmomail.net";
case "virginmobile":
return phoneNumber + "@vmobl.com";
case "cingular":
return phoneNumber + "@cingularme.com";
case "att":
return phoneNumber + "@cingularme.com";
case "sprint":
return phoneNumber + "@messaging.sprintpcs.com";
case "verizon":
return phoneNumber + "@vtext.com";
case "nextel":
return phoneNumber + "@messaging.nextel.com";
default:
//assume cingular/att is the most popular
return phoneNumber + "@cingularme.com";
}
}
protected override void sendSingleMessage(ctMessage ctm)
{
//send a single message
string emailAddress = getEmailAddress(ctm.mediumArg1, ctm.mediumArg2);
MailMessage msgMail = new MailMessage(new MailAddress(ctm.fromUser + "@chalktalk.net", ctm.fromUser), new MailAddress(emailAddress));
msgMail.Subject = ctm.subject;
msgMail.Body = ctm.message;
sendEmail(msgMail);
}
private void sendEmail(MailMessage msgMail)
{
// Send the mail message
this.mSmtpClient.Send(msgMail);
}
}
Now to test this out, just like we sent the email we’ll send a text. Cool!
ctMessage ctm = new ctMessge("Friend", "Developer", "testing", "hello world via text", "textmessage", "#########", "verizon");
messageSender.sendMessage(ctm);
Conclusion:
When you’re working on a project with a small team, it’s especially important to build a good foundation like this so you can focus on easily added functionality without getting bogged down in complexity. This portion of the project was written over the course of a week and later on allowed us to focus almost exclusively on adding new services like Instant Messengers and interactive phone calling. Having functionality so quickly was crucial, along with a UI built by my brother, in getting the attention and support of others to build a business around this.
Next Steps:
- Build a UI to manage content and run your messaging!
- Rewrite your messageSender class to store messages in a database. This will make your app much more extensible, and you can save a record of the messages sent in an ‘outbox’.
- Strongly type all textual identifiers in enumerations to avoid any frustrating mistakes.
- Find an SDK for your favorite messenger, and build a node for it
- Join our team and continue to build with us!