Getting Started with MLLP (EDI Integrator)

Requirements: EDI Integrator

Contents

Introduction

The EDI Integrator toolkit provides two components that enable easy transmission of HL7 data using the MLLP protocol: MLLPClient and MLLPServer.

This article provides a brief overview of MLLP, followed by information and code examples showing how to use the MLLPClient and MLLPServer components together.

MLLP Overview

MLLP, short for "Minimal Lower Layer message transport Protocol", is a simple protocol used primarily for transmitting HL7 data. MLLP is synchronous in nature and runs on top of reliable transport protocols such as TCP/IP. It has two versions, 1 and 2, both of which are supported by the MLLP components in EDI Integrator.

MLLP works by transmitting blocks, which are composed of the following (in order):

  1. A start block character: ASCII vertical tab (1 byte - 0x0B).
  2. The actual data being transmitted (variable number of bytes).
  3. An end block character: ASCII file separator (1 byte - 0x1C).
  4. A carriage return character: ASCII carriage return (1 byte - 0x0D).
Note: For brevity, this article will use the term "end block" to refer to both the end block and carriage return characters, unless otherwise noted.

Any data outside of a well-formed block (known as "unframed data") is ignored. Additionally, MLLP peers will often use a receive timeout, which is the period of time they will wait to receive an end block (after receiving a start block) before considering a transmission attempt invalid and timing it out. Any data received as part of a transmission attempt that times out is considered invalid and discarded (as is any data received afterwards, until another start block is received).

Commit Acknowledgements (MLLP v2 Only)

For MLLP version 1, blocks are the entirety of the protocol. MLLP version 2, however, also adds the concept of commit acknowledgements in order to make the protocol more reliable.

Commit acknowledgements are easy to understand: after an MLLP peer sends a block, it will wait for the other side to send back an acknowledgement that the block was received successfully. Until this acknowledgement is received (or a timeout occurs), the sender cannot send another block.

The only other detail about acknowledgements to keep in mind is that they can be positive (an ACK) or negative (a NAK). A positive acknowledgement indicates that the receiver was able to successfully store the block's data; while a negative acknowledgement indicates that while the block was received successfully, the receiver wasn't able to store the data it contained.

Connecting

The first step in using MLLPClient and MLLPServer is to get them configured and connected. Since our example will utilize both components in tandem, we'll start by configuring MLLPServer to listen for (and accept) incoming connections, followed by how to use MLLPClient to connect to it.

Configuring MLLPServer

To configure the MLLPServer component:

  1. Set the LocalPort property to the desired listening port.
  2. If using MLLP version 1, change the Version property to mvVersion1 (by default, it's set to mvVersion2).
  3. Set the Listening property to True.

At this point the ConnectionRequest event will fire anytime a client wishes to connect, and the Connected event will fire each time a client successfully connects. Connection requests are accepted by default, so you don't need to implement the ConnectionRequest event unless you wish to have finer control over which connection requests to accept.

When a client connects, it is assigned a connection Id that uniquely identifies it. This connection Id is exposed through various events, as well as by the ConnectionId field of the objects in the Connections collection property; and must be specified when calling MLLPServer methods that affect connections (e.g., the Send method).

mllpServer.OnConnected += (s, e) => {
  // A client connected.
  Console.WriteLine("A new MLLP client connected; ConnectionId=" + e.ConnectionId);
};

mllpServer.LocalPort = 4444;
mllpServer.Listening = true;

Connecting with MLLPClient

Now that our MLLPServer is listening for connections, we can connect to it with our MLLPClient in one line using the Connect method, passing it the appropriate hostname and port values.

// Connect to our MLLPServer.
mllpClient.Connect("localhost", 4444);

(If you're using MLLP version 1, don't forget to change the Version property to mvVersion1 first, before connecting. By default, it's set to mvVersion2.)

Sending Data

Now that our MLLPClient and MLLPServer components are connected to each other, they can exchange data. Let's look at how to send data using MLLPClient first, followed by how to send data to MLLP clients using MLLPServer.

Sending from MLLPClient

Sending data from MLLPClient is easy:

  1. Assign the input data by doing one of the following (MLLPClient will check each place, in this order, and use the first available input data):
    • Use the SetInputStream method to pass a stream containing input data to the component (only available in .NET and Java editions).
    • Set the InputFilename property to the path of a file that contains input data.
    • Set the input data directly to the InputData property.
  2. Call the MLLPClient's Send method to send the data to the server.

If using MLLP version 2, the Send method will block until an acknowledgement is received from the server. When the acknowledgement is received, the AckIn event will fire, and if it was a negative acknowledgement then the Send method will throw an error with code 670.

mllpClient.OnAckIn += (s, e) => {
  Console.WriteLine((e.Accepted ? "Positive" : "Negative") + 
    " acknowledgement received from the server.");
};

mllpClient.InputFilename = "C:\test\HL7_Example_Data.dat";

try {
  mllpClient.Send();
} catch (InEDIException ex) {
  // If error code is 670, it was because we received a NAK. Otherwise it was a real problem.
  if (ex.Code == 670) Console.WriteLine("Received a negative acknowledgement (NAK) from the server.");
  else throw ex;
}

Sending from MLLPServer

Sending data from the MLLPServer component is almost exactly the same as it is when using MLLPClient; the only difference is where things are set, since the component has to be able to handle multiple client connections.

To send from MLLPServer, first determine the connection Id of the client you wish to send to, and then:

  1. Get the MLLPConnection object associated with the connection Id from the Connections collection property.
  2. Assign the input data by doing one of the following (MLLPServer will check each place, in this order, and use the first available input data):
    • Set the InputStream field of the MLLPConnection object to a stream containing input data (only available in .NET and Java editions).
    • Set the InputFilename field of the MLLPConnection object to the path of a file that contains input data.
    • Set the input data directly to the InputData field of the MLLPConnection object.
  3. Call the MLLPServer's Send method, passing it the connection Id, to send the data to the client associated with that connection.

If using MLLP version 2, the Send method will block until an acknowledgement is received from the client. When the acknowledgement is received, the AckIn event will fire, and if it was a negative acknowledgement then the Send method will throw an error with code 670.

mllpServer.OnAckIn += (s, e) => {
  Console.WriteLine((e.Accepted ? "Positive" : "Negative") + 
    " acknowledgement received from the client with ConnectionId=" + e.ConnectionId + ".");
};

// Assume a variable "connId" exists that contains the connection Id of the desired client.
mllpServer.Connections[connId].InputFilename = "C:\test\HL7_Example_Data.dat";

try {
  mllpServer.Send(connId);
} catch (InEDIException ex) {
  // If error code is 670, it was because we received a NAK. Otherwise it was a real problem.
  if (ex.Code == 670) Console.WriteLine("Received a negative acknowledgement (NAK) from the client.");
  else throw ex;
}

Receiving Data

Receiving data (and sending back acknowledgements, if using MLLP version 2) happens mostly automatically in both MLLPClient and MLLPServer; but there is a bit of configuration that needs to be done to make sure the data goes where you want it to, and in more complex cases you might want to manually specify which type of acknowledgement is sent back.

Receiving with MLLPServer

Let's look at how receiving data works with MLLPServer first. For each connection, MLLPServer will automatically store data it receives in one of the following places:

  • If an output stream has been specified for the connection using the OutputStream field of the associated MLLPConnection object, the data is written to that stream.
  • Otherwise, if an output file path has been set to the OutputFilename field of the associated MLLPConnection object, the data is written to that file.
  • Otherwise, the data is stored in the OutputData field of the associated MLLPConnection object.

As mentioned above, acknowledgements are sent back automatically when using MLLP version 2, but you can control whether each acknowledgement is positive or negative using the AckOut event if necessary.

mllpServer.OnAckOut += (s, e) => {
  // The 'Accept' event parameter can be changed to control whether a positive (true),
  // or negative (false) acknowledgement is sent back. By default, it is set to true,
  // unless there was an error in receiving the data, so we don't have any real logic
  // here since this is a simple example.
};

mllpServer.OnStartTransfer += (s, e) => {
  // The StartTransfer event fires each time we start receiving a block; we'll use it
  // to configure the connection in question so that each block's data will be written
  // to a unique output file.
  mllpServer.Connections[e.ConnectionId].OutputFilename = "C:\test\Received_Server_" +
    e.ConnectionId + "\HL7_Output_Data_" + DateTime.Now.Ticks + ".dat";
};

Receiving with MLLPClient

Receiving data with the MLLPClient component is exactly the same as it is with MLLPServer (simpler, actually, since there aren't multiple connections to deal with). MLLPClient will automatically store data it receives in one of the following places:

  • If an output stream has been specified using the SetOutputStream method, the data is written to that stream.
  • Otherwise, if an output file path has been set to the OutputFilename property, the data is written to that file.
  • Otherwise, the data is stored in the OutputData property.

Again, as mentioned above, acknowledgements are sent back automatically when using MLLP version 2, but you can control whether each acknowledgement is positive or negative using the AckOut event if necessary.

mllpClient.OnAckOut += (s, e) => {
  // The 'Accept' event parameter can be changed to control whether a positive (true),
  // or negative (false) acknowledgement is sent back. By default, it is set to true,
  // unless there was an error in receiving the data, so we don't have any real logic
  // here since this is a simple example.
};

mllpClient.OnStartTransfer += (s, e) => {
  // The StartTransfer event fires each time we start receiving a block; we'll use it
  // to configure the component so that each block's data will be written to a unique
  // output file.
  mllpClient.OutputFilename = "C:\test\Received_Client\HL7_Output_Data_" + 
    DateTime.Now.Ticks + ".dat";
};

We appreciate your feedback.  If you have any questions, comments, or suggestions about this article please contact our support team at kb@nsoftware.com.