|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionFor a recent project I needed to create a local LAN chat application that was secure and connectionless. From prior projects I knew a simple way to accomplish this was by using Windows Mailslots. Unfortunately, .NET does not have built-in support for using them and one is forced to P/Invoke API calls directly. After searching many newsgroups and sites such as CP, I found some suggestions and random P/Invoke signatures but nothing usable so I had to create it from scratch. And now I offer my findings to you. One important thing to note is that this project will throw an exception if run from the debugger. This is because the BackgroundIf you're new to programming, you probably aren't familiar with what a Mailslot is. In simplest terms, a "slot" is a virtual file. In order to pass data between two points, two things must happen at each point. Both ends must open the slot in read mode locally, and in write mode remotely. Remember we are treating the slot as a file. Client A writes a message to the slot \\ClientB\Slotname and Client B reads it from \\.\Slotname. Alternatively, if the remote slot is opened as \\*\Slotname, then the message will be sent to Slotname on all computers on the local domain or workgroup. If a computer receives a message to a slot that it does not have opened, Windows will ignore it by design. Mailslot communication is done over UDP port 137. The useful thing about this method is that it requires no actual connection to be established, and no central server is required. Using the controlBecause I have written this as a control, it requires very little code to be used. One thing to note is that I have not figured out how to make the control invisible on the Form so be sure to set its Quick start guide
Behind the scenesFor those who want to know how it works, here we go. The most frequent newsgroup / forum posting I see is how to correctly declare the API calls that are needed to implement Mailslots. It took a lot of trial and error to get the right combination of types between all calls. So let's get those out of the way as some of you will be here purely for this information. We have five API functions to declare: CreateMailSlot[DllImport("kernel32.dll")]
static extern IntPtr CreateMailslot(string lpName,
uint nMaxMessageSize,
uint lReadTimeout,
IntPtr lpSecurityAttributes);
GetMailslotInfo[DllImport("kernel32.dll")]
static extern bool GetMailslotInfo(IntPtr hMailslot,
int lpMaxMessageSize,
ref int lpNextSize,
IntPtr lpMessageCount,
IntPtr lpReadTimeout);
CreateFile[DllImport("Kernel32.dll", SetLastError = true,
CharSet = CharSet.Auto)]
static extern IntPtr CreateFile(
string fileName,
[MarshalAs(UnmanagedType.U4)] FileAccess fileAccess,
[MarshalAs(UnmanagedType.U4)] FileShare fileShare,
int securityAttributes,
[MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
int flags,
IntPtr template);
ReadFile[DllImport("kernel32.dll", SetLastError = true)]
private unsafe static extern bool ReadFile(
IntPtr hFile,
void* lpBuffer,
int nNumberOfBytesToRead,
int* lpNumberOfBytesRead,
int overlapped);
WriteFile[DllImport("kernel32.dll", SetLastError = true)]
static extern bool WriteFile(
IntPtr hFile,
byte[] lpBuffer,
uint nNumberOfBytesToWrite,
out uint lpNumberOfBytesWritten,
[In] ref System.Threading.NativeOverlapped lpOverlapped);
As the title implies, this control monitors the slot asynchronously, or in another thread. With that said, you'll see in the constructor we create this thread. We set the thread priority to public vMailslot()
{
InitializeComponent();
readThread = new Thread(new ThreadStart(ThreadReadSlot));
readThread.Priority = ThreadPriority.BelowNormal;
}
In the public bool Connect(string Scope)
{
if (_SlotName.Length > 0)
{
_ReadHandle = CreateMailslot("\\\\.\\mailslot\\" + _SlotName, 0, 0,
IntPtr.Zero);
_WriteHandle = CreateFile("\\\\" + Scope + "\\mailslot\\" + _SlotName,
FileAccess.Write, FileShare.Read, 0, FileMode.Open, 0, IntPtr.Zero);
}
if ((_ReadHandle.ToInt32() * _WriteHandle.ToInt32()) > 0)
{
readThread.Start();
return true;
}
return false;
}
Once public bool SendText(string Username, string Command, string Data)
{
int iCounter;
string[] SplitData;
string PreEncode;
Byte[] EncVar;
System.Threading.NativeOverlapped stnOverlap =
new System.Threading.NativeOverlapped();
Data = Data.Trim();
if (Data.Length > 0)
{
SplitData = sbSplit(Username, Command, Data,
400 - Username.Length - Command.Length - seqNum.ToString().Length - 7);
for (iCounter = 0; iCounter < SplitData.Length; iCounter++)
if (SplitData[iCounter] != null)
{
EncVar = System.Text.Encoding.Default.GetBytes(SplitData[iCounter]);
PreEncode = System.Text.Encoding.Default.GetString(EncVar);
RC4(ref EncVar, EncKey);
WriteFile(_WriteHandle, EncVar, (uint)EncVar.Length,
out bytesWritten, ref stnOverlap);
if (SentText != null)
SentText(PreEncode);
}
}
return false;
}
If you look at this function you may have already figured out that the control implements RC4 encryption. The encryption key is an array of bytes which can be altered in the control source. The function is based on the same one that you will find in almost all RC4 examples and will not be examined in this article. With that said, you will see a call to the function public string[] sbSplit(string Username, string Command,
string Text, int MaxLen)
{
int Index, txtLeft, txtLen, offS, Iteration;
string tmpStr;
string[] ReturnText = new string[1];
txtLen = Text.Length;
txtLeft = txtLen;
offS = 0;
Index = 0;
Iteration = 0;
while (txtLeft > 0)
{
Iteration++;
if (ReturnText.Length <= Iteration)
ReturnText = ResizeArray(ReturnText, Iteration + 1);
if (txtLeft <= MaxLen)
{
tmpStr = Text.Substring(Text.Length - txtLeft, txtLeft);
seqNum++;
ReturnText[Iteration] = seqNum.ToString() + "§" + Username + "§" +
"END" + "§" + Command + "§" + tmpStr;
return ReturnText;
}
tmpStr = Text.Substring(offS, MaxLen);
Index = tmpStr.Length - 1;
while ((tmpStr[Index] != ' ') & (Index > 0))
{
Index--;
}
if (Index <= 0)
Index = MaxLen;
tmpStr = tmpStr.Substring(0, Index);
txtLeft = txtLeft - tmpStr.Length;
seqNum++;
ReturnText[Iteration] = seqNum.ToString() + "§" + Username + "§" +
"FRAG" + "§" + Command + "§" + tmpStr;
offS += Index;
}
return null;
}
When
I have no good segway here, so let's just go right into the thread that monitors the slot for received data. private void ThreadReadSlot()
{
string readData = "";
string[] parsedData = new string[5];
while (true)
{
try
{
readData = this.ReadSlot();
if (readData.Length > 0)
if (OnReceivedData != null)
{
parsedData = readData.Split('§');
if (parsedData[2] == "FRAG")
{
AddFrag(parsedData[1], parsedData[3], parsedData[4]);
}
else
OnReceivedData(parsedData[1], parsedData[3], Defrag(parsedData[1],
parsedData[4]));
}
}
catch (Exception ex) { MessageBox.Show(ex.Message + "\r\n" + ex.Source +
"\r\n" + ex.StackTrace); }
Thread.Sleep(200);
}
}
After creating this function, the CPU was constantly at 100%. Adding the Now I'm starting to get a bit ahead of myself so let's check out the unsafe public string ReadSlot()
{
int iMsgSize, iRead;
byte[] Data = new byte[424];
bool IsData;
bool IsDupe = false;
iMsgSize = 0;
iRead = 0;
byte[] RetValue;
string sRetVal;
GetMailslotInfo(_ReadHandle, 0, ref iMsgSize, IntPtr.Zero, IntPtr.Zero);
//Read the current status of the mailslot,
//notably the size of any waiting messages
IsData = (iMsgSize > 0);
if (IsData)
{
fixed (byte* p = Data)
{
ReadFile(_ReadHandle, p, iMsgSize, &iRead, 0);
RetValue = new byte[iMsgSize];
System.Array.Copy(Data, RetValue, iMsgSize);
RC4(ref RetValue, EncKey);
}
sRetVal = System.Text.Encoding.Default.GetString(RetValue);
foreach (string prevLine in _MessageQue)
{
if (sRetVal == prevLine)
IsDupe = true;
}
if (IsDupe == false)
{
_MessageQue[0] = _MessageQue[1];
_MessageQue[1] = _MessageQue[2];
_MessageQue[2] = _MessageQue[3];
_MessageQue[3] = sRetVal;
return sRetVal;
}
}
return "";
}
I believe I briefly mentioned duplicate messages above. Allow me to explain. Mailslots, by design, will send the data using every available transport. This means if you have TCP/IP, IPX, net NETBUI/NETBIOS protocols installed/enabled, you will send the data out 3 times, once over each protocol. The same is true when receiving data. The receiver would get 3 copies of the same message. For this reason, we have the Sequence Number as previously mentioned. That, combined with the The UserFrag Class and Supporting FunctionsGetting back to the fragmenting / defragmenting of long messages, we have the private class UserFrag
{
private string _UserName;
private string _Command;
private string _Text;
public UserFrag(string Username, string Command, string Text)
{
_UserName = Username;
_Command = Command;
_Text = Text;
}
public string User
{
get { return _UserName; }
}
public string Defrag(string Text)
{
return _Text + Text;
}
public void AddFrag(string Text)
{
_Text += Text;
}
}
The This looks easy enough, but there is something missing. We need 2 more functions to properly handle the fragments. The naming is somewhat confusing, but these functions will be private void AddFrag(string User, string Command, string Text)
{
bool isNewFrag = true;
foreach (UserFrag frag in fragQ)
{
if (frag.User == User)
{
frag.AddFrag(Text);
isNewFrag = false;
}
}
if (isNewFrag == true)
{
UserFrag newFrag = new UserFrag(User, Command, Text);
fragQ.Add(newFrag);
}
}
When The final function, private string Defrag(string User, string Text)
{
string result = "";
UserFrag rFrag = null;
foreach (UserFrag frag in fragQ)
{
if (frag.User == User)
{
result = frag.Defrag(Text);
rFrag = frag;
}
}
if (result == "")
result = Text;
if (rFrag != null)
fragQ.Remove(rFrag);
return result;
}
Above you can see where we call Points of InterestThere are many possible applications for this control. It would be an easy drop in for a LAN chat program, or for remote control purposes. The You are free to use this control and source for any personal or educational / non-profit projects. For there is any interest in a commercial application, get in touch with me and I'm sure we can work out something. History
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||