Original article: Ivan lattner: inter-process communication in. Net Using Named Pipes, Part 1 http://www.codeproject.com/KB/threads/dotnetnamedpipespart1.aspx
Use a named pipe in. Net to complete inter-process communication
Have you ever had to exchange data between two. NET applications on the same machine? For example, a web site and a Windows service ?. Net Framework provides several good options to complete inter-process communication (IPC): Web Service, remoting. The fastest is remoting, because it uses the TCP channel and binary format. However, if you need to call another application from one application frequently, and you are mainly concerned about performance, remoting is still a little slower. What slows down remoting is not a protocol, but serialization. In general, remoting is good, but if only two processes on the local machine communicate with each other, its processing mechanism increases unnecessary overhead. Therefore, we need to consider some other options. The better is the named pipeline (Named Pipes), which does not perform binary serialization, So it provides a faster IPC. Remember, the most effective use of this solution is when one application needs to communicate with another application frequently and in short text, it is on the same machine or within the same LAN. For structured data exchange, these text messages can also be XML documents or serialized. Net objects. There is no security layer during communication, because the named pipe can only run in the LAN at most, so it is assumed that security issues are handled by other layers.1. Implement named PipelinesThe following are the main classes in the. NET naming pipeline solution. . Namedpipenative: this class is associated with kernal32.dll to implement communication between named pipelines, including some common methods and constants. . Namedpipewrapper: this class is a package of namedpipenative. . Apipeconnection: This is an abstract class that defines the methods for naming pipe connections, reading, and writing data. This class is inherited from clientpipeconnection and serverpipeconnection, which are used in client and server applications respectively. . Clientpipeconnection: used by client applications to communicate with servers using named pipes. . Serverpipeconnection: allows the named pipe server to create a connection and communicate with the client. . Pipehandle: stores the local handle of the operating system and the current status of the Pipeline Connection. After learning about the above classes, you need to understand the operation of the named pipeline.2. Create a server named pipeThe syntax of the server-side MPs queue name is \. \ PIPE \ pipename. The "pipename"... section is the specific name of the pipeline. To connect to the MPs queue, a client named MPs queue with the same name must be created for the client application. If the client is on a different machine, the name of the server-side pipeline should be \ Server \ PIPE \ pipename. The following code is a static method of namedpipewrapper. It is used to instantiate a server-side named pipe.
Public static pipehandle create (string name, uintoutbuffer, uintinbuffer) {name = @"\. \ PIPE \ "+ name; pipehandle handle = new pipehandle (); For (INTI = 1; I <= attempts; I ++) {handle. state = interprocessconnectionstate. creating; handle. handle = namedpipenative. createnamedpipe (name, namedpipenative. pipe_access_duplex, namedpipenative. pipe_type_message | namedpipenative. pipe_readmode_message | namedpipenative . Pipe_wait, namedpipenative. pipe_unlimited_instances, outbuffer, inbuffer, namedpipenative. nmpwait_wait_forever, intptr. Zero); If (handle. Handle. toint32 ()! = Namedpipenative. invalid_handle_value) {handle. state = interprocessconnectionstate. created; break;} if (I> = attempts) {handle. state = interprocessconnectionstate. error; throw new namedpipeioexception ("error creating named pipe" + name + ". internalerror: "+ namedpipenative. getlasterror (). tostring (), namedpipenative. getlasterror () ;}} returnhandle ;} |
By calling the namedpipenative. createnamedpipe method, the above method creates a named pipeline for mutual communication, and the specified pipeline can have unlimited instances. The constant names are all in English. It is not difficult to understand them. Assuming that the named pipe on the server side is successfully created, it can start listening to the client connection.3. Connect to the client PipelineThe named MPs Queue Server must be set to the listening status so that the client MPs queue can connect to it. This can be done by calling the namedpipenative. connectnamedpipe method. Call the namedpipenative. createfile method to create a named pipeline client and connect it to the server pipeline of a listener. The following code is part of namedpipewrapper. connecttopipe.
Public static pipehandle connecttopipe (string pipename, string servername) {pipehandle handle = new pipehandle (); // buildthename ofthe pipe. string name = @ "\" + servername + @ "\ PIPE \" + pipename; For (INTI = 1; I <= attempts; I ++) {handle. state = interprocessconnectionstate. connectingtoserver; // try to connect to the server handle. handle = namedpipenative. createfile (name, namedpipenative. generic_read | namedpipenative. generic_write, 0, null, namedpipenative. open_existing, 0, 0 ); |
After creating a pipehandle object and creating a pipeline name, we call namedpipenative. createfile to create a client named pipeline and connect it to the specified server-side pipeline. In our example, the client pipeline is configured as readable and writable. If the client pipeline is successfully created, the namedpipenative. createfile method returns the corresponding local handle, which will be used in future operations. If creation fails for some reason, the method returns 1 and sets namedpipenative as the invalid_handle_value constant. Before the client naming pipeline can be used for reading and writing, you need to do one more thing. We need to set handle to pipe_readmode_message. You can call namedpipenative. setnamed-pipehandlestate.
If (handle. Handle. toint32 ()! = Namedpipenative. invalid_handle_value) {// The client managed to connect to the server pipe handle. state = interprocessconnectionstate. connectedtoserver; // set the Read mode of the Pipe Channel uint mode = namedpipenative. pipe_readmode_message; If (namedpipenative. setnamedpipehandlestate (handle. handle, refmode, intptr. zero, intptr. zero) {break ;} |
Each client pipeline communicates with an instance of a server pipeline. If the maximum number of server-side instances is reached, the creation of the client pipeline will fail.4. Read and Write DataWhen reading data from a named pipeline, we cannot know the message length in advance. Our solution does not need to process long messages, so the system. int32 variable is used to specify the message length. The namedpipewrapper. writebytes method can write messages to a named pipe. messages are UTF-8 encoded and then transmitted in byte arrays.
Public static void writebytes (pipehandle handle, byte [] bytes) {byte [] numreadwritten = new byte [4]; uint Len; If (Bytes = NULL) {bytes = newbyte [0];} If (bytes. length = 0) {bytes = new byte [1]; bytes = system. text. encoding. utf8.getbytes ("");} // get the message length: Len = (uint) bytes. length; handle. state = interprocessconnectionstate. writing; // get the byte representation of the message length. Write the four byte if (namedpipenative. writefile (handle. handle, Bi Tconverter. getbytes (LEN), 4, numreadwritten, 0) {// write the remaining message if (! Namedpipenative. writefile (handle. handle, bytes, Len, numreadwritten, 0) {handle. state = interprocessconnectionstate. error; thrownewnamedpipeioexception ("errorwritingtopipe. internalerror: "+ namedpipenative. getlasterror (). tostring (), namedpipenative. getlasterror () ;}} else {handle. state = interprocessconnectionstate. error; thrownewnamedpipeioexception ("errorwritingtopipe. internalerror: "+ namedpipenative. getlasterror (). tostring (), namedpipenative. getlasterror ();} handle. state = interprocessconnectionstate. flushing; // activate the MPs queue to ensure that any cached data is written to the MPs queue and will not be lost: flush (handle); handle. state = interprocessconnectionstate. flusheddata ;} |
To read data from a named pipeline, first convert the first four bytes into Integers to determine the message length. Next, you can read the remaining data. Please refer to the namedpipewrapper. readbytes method below.
Public static byte [] readbytes (pipehandle handle, int maxbytes) {byte [] numreadwritten = newbyte [4]; byte [] intbytes = newbyte [4]; byte [] msgbytes = NULL; intlen; handle. state = interprocessconnectionstate. reading; handle. state = interprocessconnectionstate. flushing; // read the first four bytes and convert them to an integer: If (namedpipenative. readfile (handle. handle, intbytes, 4, numreadwritten, 0) {Len = bitconverter. toint32 (intbytes, 0); msgbytes = newb Yte [Len]; handle. State = interprocessconnectionstate. Flushing; // read the remaining data or throw an exception: If (! Namedpipenative. readfile (handle. handle, msgbytes, (uint) Len, numreadwritten, 0) {handle. state = interprocessconnectionstate. error; thrownewnamedpipeioexception ("error readingfrompipe. internalerror: "+ namedpipenative. getlasterror (). tostring (), namedpipenative. getlasterror () ;}} else {handle. state = interprocessconnectionstate. error; thrownewnamedpipeioexception ("errorreadingfrompipe. internalerror: "+ namedpipenative. getlasterror (). tostring (), namedpipenative. getlasterror ();} handle. state = interprocessconnectionstate. readdata; If (LEN> maxbytes) {returnnull;} returnmsgbytes ;} |
The above is the implementation of the named pipeline and some main methods. The following describes how to create a named pipeline server and client application for text message communication.5. Create a named pipeline ServerThe named MPs Queue Server is a multithreaded engine used to create new threads and MPs queue connections for concurrent request services. Appmodule. namedpipes Assembly contains a base class apipeconnection, which encapsulates operations on common named pipelines, such as creating pipelines and reading and writing data. This is an abstract class. In addition, there are two Pipeline Connection classes inherited from apipeconnection: clientpipeconnection and serverpipeconnection. They reload some methods (such as connection and close) and provide implementation for the server and client named pipe respectively. Both clientpipeconnection and serverpipeconnection have the Destructor that call the dispose method to clear uncontrolled resources. The named MPs Queue Server is responsible for creating named MPs queues and processing client connections. Two main classes provide service functions: servernamedpipe and pipemanager. (1) servernamedpipe class. Its constructor is as follows :..
Internal timeout (stringname, uint outbuffer, uintinbuffer, timeout) {pipeconnection = newserverpipeconnection (name, outbuffer, inbuffer, maxreadbytes); pipethread = newthread (newthreadstart (pipelistener); pipethread. isbackground = true; pipethread. name = "pipethread" + this. pipeconnection. nativehandle. tostring (); lastaction = datetime. now ;} |
The constructor creates a new serverpipeconnection instance and calls the pipelistener method. The subsequent main parts are loop listening to client connections and reading and writing data.
Private void pipelistener () {checkifdisposed (); try {Listen = form1.pipemanager. listen; form1.activityref. appendtext ("pipe" + this. pipeconnection. nativehandle. tostring () + ": new pipe started" + environment. newline); While (Listen) {lastaction = datetime. now; // read data from the client pipeline: stringrequest = pipeconnection. read (); lastaction = datetime. now; If (request. trim ()! = "") {// The pipemanager. handlerequest method accepts the client request for processing, // then responds, and the response is then written to the pipeline. Pipeconnection. write (form1.pipemanager. handlerequest (request); form1.activityref. appendtext ("pipe" + this. pipeconnection. nativehandle. tostring () + ": requesthandled" + environment. newline);} else {pipeconnection. write ("error: badrequest");} lastaction = datetime. now; // disconnect pipeconnection from the client pipe. disconnect (); If (Listen) {form1.activityref. appendtext ("pipe" + this. pipeconnection. nativehandle. tostring () + ": listening" + environment. newline); // start listening for a new connection: connect ();} form1.pipemanager. wakeup () ;}} catch (system. threading. threadabortexceptionex) {} catch (system. threading. threadstateexceptionex) {} catch (exceptionex) {// logexception} finally {This. close ();}} |
Do not close the server pipeline because creating a server pipeline is a relatively expensive operation, which may cause expensive overhead. (2) The pipemanager class is responsible for creating server pipelines, managing threads, and generating responses to client requests when necessary. In the following code, the initialize method calls the start method to create a new thread:
Public void initialize () {pipes = hashtable. synchronized (_ pipes); Mre = newmanualresetevent (false); mainthread = newthread (newthreadstart (start); mainthread. isbackground = true; mainthread. name = "mainpipethread"; mainthread. start (); thread. sleep (1000 );} |
The pipemanager class only creates new pipeline connections and threads when receiving requests. This means that the serverpipeconnection object is created only when no connection exists or all connections are busy responding to requests. Generally, two or three named pipeline instances can process high-load concurrent client requests, but this mainly depends on the time when the client requests are processed and the response is generated. The reference of the created serverpipeconnection object is saved in the pipeline hash table.
Private void start () {try {While (_ listen) {int [] keys = newint [Pipes. keys. count]; pipes. keys. copyto (keys, 0); // cyclically checks whether the serverpipeconnection object is still available: foreach (intkeyinkeys) {servernamedpipeserverpipe = (servernamedpipe) pipes [Key]; If (serverpipe! = NULL & datetime. Now. Subtract (serverpipe. lastaction). milliseconds> pipe_max_stuffed_time & serverpipe. pipeconnection. getstate ()! = Interprocessconnectionstate. waitingforclient) {serverpipe. listen = false; serverpipe. pipethread. abort (); removeserverchannel (serverpipe. pipeconnection. nativehandle) ;}}// the numberpipes field contains the maximum number of named pipelines that can be owned on the server if (numchannels <= numberpipes) {servernamedpipe pipe = new servernamedpipe (pipename, outbuffer, inbuffer, max_read_bytes); try {// connect method to set the new pipeline to the listening mode. Pipe. connect (); pipe. lastaction = datetime. now; system. threading. interlocked. increment (refnumchannels); // start the serverpipeconnection Thread Pipe. start (); pipes. add (pipe. pipeconnection. nativehandle, pipe);} catch (interprocessioexception ex) {removeserverchannel (pipe. pipeconnection. nativehandle); pipe. dispose () ;}} else {MRE. reset (); MRE. waitone (1000, false) ;}} catch {// logexception }} |
6. Create a client pipe connectionTo connect a client application to the server using a named pipeline, we must create an instance of the clientpipeconnection class and use it to read and write data.
Iinterprocessconnectionclientconnection = NULL; try {clientconnection = newclientpipeconnection ("mypipe ",". "); clientconnection. connect (); clientconnection. write (textbox1.text); clientconnection. close ();} catch {clientconnection. dispose ();} |
The pipe name "mypipe" must be the same as the name of the server pipe. if the name of the pipe server is also on the same machine, the second parameter of the clientpipeconnection constructor should be ".". If not on the same machine, the second parameter is the Network Name of the server. As mentioned above, I have introduced the solution of the named pipeline. I reiterate that the most effective use of the named pipeline is that one application needs to be frequently used with another application, in the case of short text message communication, and on the same machine or within the LAN. If you encounter such a situation, I hope these codes can give you inspiration and reference.