Introduction
Alright, so you guys might know about the famous web-browser, Google Chrome. One remarkable thing in this (pretty) simple looking application is the use of a programming technique called IPC or Interprocess Communication; with IPC you can communicate with another process without invoking and other third party events. One of the most irritating about .Net is that the app freezes if it is trying to communicate with a web-service. With IPC we can eradicate this problem.What can you do with it?
So, lets first take an example of what exactly we can do with this: suppose that you have a licensing application named LicMan.exe and a processing app called LicPro.exe. LicMan has the UI and every other part of standard human interference. And LicPro.exe just takes the License Key provided and then processes it and sends it back to LicMan.exe for display. This way the app would not hang and you have a happy customer :-)Lots of bad ways of doing IPC:
- Shared Memory: Difficult to manage and set-up.
- Shared Files / Registry: Very slow doe to the writing & reading to/from disk. Difficult to manage as well.
- SendMessage / PostMessage: Locks up the UI thread while the message is processed. Messages are limited to integer values. Can't communicate from a non-admin process to a admin process. Assumes that your process has a window.
Named Pipes:
Inter Process Communication using named pipes is what Google Chrome uses. So, let me teach you about Inter Process Communication using Named Pipes.
What do you put in the pipes?
Virtually you can push any data through pipes and transfer it to and fro your apps. Now the question arises that what you should transfer. The rule of thumb says that, "Keep it short and simple." You can use command codes and/or response codes.
Coding time: The code which I have included is self-explanatory and is very well commented, so have a look at it.
So, that's it from Chris Harrison for Ultimate Programming Tutorials; catch me next week when I write about Make your .NET app shine with professionalism.
Code:
PipeClient :
PipeServer :
Download Source Code:
PipeServer.zip
Coding time: The code which I have included is self-explanatory and is very well commented, so have a look at it.
So, that's it from Chris Harrison for Ultimate Programming Tutorials; catch me next week when I write about Make your .NET app shine with professionalism.
Code:
PipeClient :
using System; using System.IO; using System.Runtime.InteropServices; using System.Threading; using Microsoft.Win32.SafeHandles; namespace VisualTech.DevTools { ////// Allow pipe communication between a server and a client /// public class PipeClient { [DllImport("kernel32.dll", SetLastError = true)] static extern SafeFileHandle CreateFile( String pipeName, uint dwDesiredAccess, uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplate); ////// Handles messages received from a server pipe /// /// The byte message received public delegate void MessageReceivedHandler(byte[] message); ////// Event is called whenever a message is received from the server pipe /// public event MessageReceivedHandler MessageReceived; ////// Handles server disconnected messages /// public delegate void ServerDisconnectedHandler(); ////// Event is called when the server pipe is severed. /// public event ServerDisconnectedHandler ServerDisconnected; const int BUFFER_SIZE = 4096; FileStream stream; SafeFileHandle handle; Thread readThread; ////// Is this client connected to a server pipe /// public bool Connected { get; private set; } ////// The pipe this client is connected to /// public string PipeName { get; private set; } ////// Connects to the server with a pipename. /// /// The name of the pipe to connect to. public void Connect(string pipename) { if (Connected) throw new Exception("Already connected to pipe server."); PipeName = pipename; handle = CreateFile( PipeName, 0xC0000000, // GENERIC_READ | GENERIC_WRITE = 0x80000000 | 0x40000000 0, IntPtr.Zero, 3, // OPEN_EXISTING 0x40000000, // FILE_FLAG_OVERLAPPED IntPtr.Zero); //could not create handle - server probably not running if (handle.IsInvalid) return; Connected = true; //start listening for messages readThread = new Thread(Read) { IsBackground = true }; readThread.Start(); } ////// Disconnects from the server. /// public void Disconnect() { if (!Connected) return; // we're no longer connected to the server Connected = false; PipeName = null; //clean up resource if (stream != null) stream.Close(); handle.Close(); stream = null; handle = null; } void Read() { stream = new FileStream(handle, FileAccess.ReadWrite, BUFFER_SIZE, true); byte[] readBuffer = new byte[BUFFER_SIZE]; while (true) { int bytesRead = 0; using (MemoryStream ms = new MemoryStream()) { try { // read the total stream length int totalSize = stream.Read(readBuffer, 0, 4); // client has disconnected if (totalSize == 0) break; totalSize = BitConverter.ToInt32(readBuffer, 0); do { int numBytes = stream.Read(readBuffer, 0, Math.Min(totalSize - bytesRead, BUFFER_SIZE)); ms.Write(readBuffer, 0, numBytes); bytesRead += numBytes; } while (bytesRead < totalSize); } catch { //read error has occurred break; } //client has disconnected if (bytesRead == 0) break; //fire message received event if (MessageReceived != null) MessageReceived(ms.ToArray()); } } // if connected, then the disconnection was // caused by a server terminating, otherwise it was from // a call to Disconnect() if (Connected) { //clean up resource stream.Close(); handle.Close(); stream = null; handle = null; // we're no longer connected to the server Connected = false; PipeName = null; if (ServerDisconnected != null) ServerDisconnected(); } } ////// Sends a message to the server. /// /// The message to send. ///True if the message is sent successfully - false otherwise. public bool SendMessage(byte[] message) { try { // write the entire stream length stream.Write(BitConverter.GetBytes(message.Length), 0, 4); stream.Write(message, 0, message.Length); stream.Flush(); return true; } catch { return false; } } } }
PipeServer :
using System; using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; using System.Threading; using Microsoft.Win32.SafeHandles; namespace VisualTech.DevTools { ////// Allow pipe communication between a server and a client /// public class PipeServer { [DllImport("kernel32.dll", SetLastError = true)] static extern SafeFileHandle CreateNamedPipe( String pipeName, uint dwOpenMode, uint dwPipeMode, uint nMaxInstances, uint nOutBufferSize, uint nInBufferSize, uint nDefaultTimeOut, IntPtr lpSecurityAttributes); [DllImport("kernel32.dll", SetLastError = true)] static extern int ConnectNamedPipe( SafeFileHandle hNamedPipe, IntPtr lpOverlapped); [DllImport("kernel32.dll", SetLastError = true)] static extern bool DisconnectNamedPipe(SafeFileHandle hHandle); [StructLayoutAttribute(LayoutKind.Sequential)] struct SECURITY_DESCRIPTOR { public byte revision; public byte size; public short control; public IntPtr owner; public IntPtr group; public IntPtr sacl; public IntPtr dacl; } [StructLayout(LayoutKind.Sequential)] public struct SECURITY_ATTRIBUTES { public int nLength; public IntPtr lpSecurityDescriptor; public int bInheritHandle; } private const uint SECURITY_DESCRIPTOR_REVISION = 1; [DllImport("advapi32.dll", SetLastError = true)] static extern bool InitializeSecurityDescriptor(ref SECURITY_DESCRIPTOR sd, uint dwRevision); [DllImport("advapi32.dll", SetLastError = true)] static extern bool SetSecurityDescriptorDacl(ref SECURITY_DESCRIPTOR sd, bool daclPresent, IntPtr dacl, bool daclDefaulted); public class Client { public SafeFileHandle handle; public FileStream stream; } ////// Handles messages received from a client pipe /// /// The byte message received public delegate void MessageReceivedHandler(byte[] message); ////// Event is called whenever a message is received from a client pipe /// public event MessageReceivedHandler MessageReceived; ////// Handles client disconnected messages /// public delegate void ClientDisconnectedHandler(); ////// Event is called when a client pipe is severed. /// public event ClientDisconnectedHandler ClientDisconnected; const int BUFFER_SIZE = 4096; Thread listenThread; readonly Listclients = new List (); /// /// The total number of PipeClients connected to this server /// public int TotalConnectedClients { get { lock (clients) { return clients.Count; } } } ////// The name of the pipe this server is connected to /// public string PipeName { get; private set; } ////// Is the server currently running /// public bool Running { get; private set; } ////// Starts the pipe server on a particular name. /// /// The name of the pipe. public void Start(string pipename) { PipeName = pipename; //start the listening thread listenThread = new Thread(ListenForClients) { IsBackground = true }; listenThread.Start(); Running = true; } void ListenForClients() { SECURITY_DESCRIPTOR sd = new SECURITY_DESCRIPTOR(); // set the Security Descriptor to be completely permissive InitializeSecurityDescriptor(ref sd, SECURITY_DESCRIPTOR_REVISION); SetSecurityDescriptorDacl(ref sd, true, IntPtr.Zero, false); IntPtr ptrSD = Marshal.AllocCoTaskMem(Marshal.SizeOf(sd)); Marshal.StructureToPtr(sd, ptrSD, false); SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES { nLength = Marshal.SizeOf(sd), lpSecurityDescriptor = ptrSD, bInheritHandle = 1 }; IntPtr ptrSA = Marshal.AllocCoTaskMem(Marshal.SizeOf(sa)); Marshal.StructureToPtr(sa, ptrSA, false); while (true) { // Creates an instance of a named pipe for one client SafeFileHandle clientHandle = CreateNamedPipe( PipeName, // DUPLEX | FILE_FLAG_OVERLAPPED = 0x00000003 | 0x40000000; 0x40000003, 0, 255, BUFFER_SIZE, BUFFER_SIZE, 0, ptrSA); //could not create named pipe instance if (clientHandle.IsInvalid) continue; int success = ConnectNamedPipe(clientHandle, IntPtr.Zero); //could not connect client if (success == 0) { // close the handle, and wait for the next client clientHandle.Close(); continue; } Client client = new Client { handle = clientHandle }; lock (clients) clients.Add(client); Thread readThread = new Thread(Read) { IsBackground = true }; readThread.Start(client); } // free up the ptrs (never reached due to infinite loop) Marshal.FreeCoTaskMem(ptrSD); Marshal.FreeCoTaskMem(ptrSA); } void Read(object clientObj) { Client client = (Client)clientObj; client.stream = new FileStream(client.handle, FileAccess.ReadWrite, BUFFER_SIZE, true); byte[] buffer = new byte[BUFFER_SIZE]; while (true) { int bytesRead = 0; using (MemoryStream ms = new MemoryStream()) { try { // read the total stream length int totalSize = client.stream.Read(buffer, 0, 4); // client has disconnected if (totalSize == 0) break; totalSize = BitConverter.ToInt32(buffer, 0); do { int numBytes = client.stream.Read(buffer, 0, Math.Min(totalSize - bytesRead, BUFFER_SIZE)); ms.Write(buffer, 0, numBytes); bytesRead += numBytes; } while (bytesRead < totalSize); } catch { //read error has occurred break; } //client has disconnected if (bytesRead == 0) break; //fire message received event if (MessageReceived != null) MessageReceived(ms.ToArray()); } } // the clients must be locked - otherwise "stream.Close()" // could be called while SendMessage(byte[]) is being called on another thread. // This leads to an IO error & several wasted days. lock (clients) { //clean up resources DisconnectNamedPipe(client.handle); client.stream.Close(); client.handle.Close(); clients.Remove(client); } // invoke the event, a client disconnected if (ClientDisconnected != null) ClientDisconnected(); } ////// Sends a message to all connected clients. /// /// The message to send. public void SendMessage(byte[] message) { lock (clients) { //get the entire stream length byte[] messageLength = BitConverter.GetBytes(message.Length); foreach (Client client in clients) { // length client.stream.Write(messageLength, 0, 4); // data client.stream.Write(message, 0, message.Length); client.stream.Flush(); } } } } }
Download Source Code:
PipeServer.zip
1 comments:
C# Codings for Beginners
ReplyPost a Comment
Note: Only a member of this blog may post a comment.