Start of trail Stream C API Function Reference navigation bar

Table of Contents > QUARC > User's Guide > QUARC External Interfaces > C Functions > Communications > Stream API

Getting Started with the Stream C API

The Quanser Stream API is used to create communication channels between C/C++ based applications and the real-time code generated for a Simulink model. The Quanser Stream API is the core to the QUARC communications blocks used in Simulink. These blocks use this interface to operate within the MATLAB environment. For more information about the communications blockset, please refer to QUARC Targets/Communications library in the Simulink Library Browser.

Quanser software (e.g., QUARC, RCP) supports a variety of common communications protocols which are all accessed via the Quanser Stream API functions. The Stream API at its lowest level resembles the BSD socket interface, with functions to connect to a remote host, to listen for client connections and to accept client connections. For a complete list of functions included in the Stream API and their description, you can refer to the Stream API header file, .

The Stream API is designed to support any kind of communications channel. This flexibility is provided by loading protocol-specific drivers to handle a particular communications protocol. Hence, new communications protocols may readily be added without any changes to the functions of the Stream API. However, this flexibility requires that the Stream API have a means of specifying the communications protocol and any protocol-specific options. The protocol and options are provided through the use of Universal Resource Identifiers or URIs. For more information about URIs and the supported communications protocols by QUARC, please refer to Quanser Communications Protocols section of the QUARC documentation.

With the Quanser Stream API flexibility in supporting any kind of communications protocols, the user only needs to change the URI to change the communications protocol in use. For instance, consider an application using shared memory. In order to change the protocol to TCP/IP, all you need to do is to change the URI from shared memory to TCP/IP.

Warning The user needs to be careful when using the UDP protocol. Be sure to read the documentation on the UDP protocol thoroughly, particularly on the different peer options. In particular, note that UDP is a datagram protocol and not a stream protocol, unlike TCP/IP. Hence, it is a connectionless protocol. Thus, it does not "listen" for client connections like the TCP/IP protocol. Therefore, listening for client connections does not establish a "listening" socket but simply results in a placeholder stream.

Warning Likewise, "accepting" a client connection using UDP creates a socket that is bound to the port specified by the URI used for listening and returns immediately. In general, the peer to whom datagrams should be sent is established by the first client from whom a UDP datagram is received. Thus, for server connections, a receive operation should always be done before the first send operation.

Warning Similarly, connecting to the server on the client side using UDP does not actually "connect" to a remote host. Instead, the client sets the address that will be used when sending packets. The first time a packet is sent the socket becomes implicitly bound to that address. Hence, in general, when using UDP protocol for communications on the client side, a send operation should always be before the first receive operation.

The Stream API functions and the communications blocks in Simulink operate in two modes; blocking and non-blocking. In blocking mode, functions and blocks will not return until the requested operation is completed. In non-blocking mode, they will return immediately with no dependence on the outcome of the operation. The mode of operation only dictates the way functions or blocks work in a particular application or model. Therefore, it is possible to have a server working in blocking mode and a client operating in non-blocking mode and vice versa. Nevertheless, we will demonstrate the case in which the application and the model are operating in the same mode.

This section is a guide on how to use the Quanser Stream API to establish connections between C/C++ applications and the real-time code. The C/C++ based application can be acting as either a client trying to connect to the real-time code or as a server accepting connections from the real-time code. We will demonstrate both cases in this section in addition to showcasing the two modes of communications used in each case. Before going into details, a set of steps are required to setup your C/C++ application to use the Stream API. Please use the following list to refer to each topic:

Setting Up the C/C++ Application to Use the Stream API

The user needs to follow a set of steps to setup the C/C++ application to use the Stream API. We assume that the Microsoft Visual Studio 2005 or later version is being used to develop the application. Please perform the following procedure to setup your application, once in the Microsoft Visual Studio environment:

  1. Open the Property Pages window by right-clicking on the project containing your application and choosing the Properties menu item in the context menu. The following figure illustrates this window.

    Property Pages Window

  2. In the Property Pages window, click on the C/C++ pane. The first parameter in this pane is called Additional Include Directories. Type in $(QSDK_DIR)include in its field as shown in the figure below. Note that QSDK_DIR is an environment variable referring to the directory in which QUARC has been installed.

    Property Pages Window

  3. Click on the Linker pane and expand its treeview. In the General section of this pane, there is a parameter called Additional Library Directories. For the Win32 platform, type in $(QSDK_DIR)lib\windows in its field as shown in the figure below. For the x64 platform, use $(QSDK_DIR)lib\win64 instead, since the code must be linked with 64-bit versions of the QUARC libraries in that case.

    Property Pages Window

  4. While in the Linker pane, click on the Input section. The first parameter in this section is called Additional Dependencies. Type in quanser_communications.lib quanser_runtime.lib quanser_common.lib in its field as illustrated in the following figure. Note that the entries in the field should be separated by one space.

    Property Pages Window

  5. Once all these changes are made, click on Apply and then OK.

C/C++ Application as Client

The C/C++ application could be acting as a client connecting to the real-time code. In this case, the real-time code serves as the server. There are, as mentioned earlier, two modes of operation; blocking and non-blocking. In blocking mode, the functions and blocks will not return until the requested operation is completed. On the contrary, in non-blocking mode, the blocks and functions will return immediately with no concern about the completeness of the requested operation. In this section, one Simulink model is used to demonstrate the server side and two C++ applications are used to demonstrate the client side (blocking and non-blocking).

Simulink Server Model

The Stream Server block acts as the server in the communication process. It listens for and accepts a connection from a local or remote host and sends and/or receives data from that host. This block is a high-level block and does not require a sophisticated knowledge of communications to be used. For more information about this block, please refer to the Stream Server block help page in the MATLAB help for QUARC. You can also refer to the Basic Communications section of the QUARC documentation to gain more insight on how to use this block. Even though the intermediate or advanced blocks are more flexible, we will not demonstrate how to use them in this example for the sake of simplicity. Information on how to use the intermediate or advanced communications blocks is provided in the Communications section.

The data to be sent by the server model is a sine wave generated by a Sine Wave block. The received data is what is sent by the client application. In our case, the client/server system acts as a loop-back system. The data sent to the client gets sent back to the server by that client. In order to check the latency of our system, the data to be sent is plotted on a scope along with the actual received data from the client. The data sent is passed through a Memory block in order to compensate for the fact that the block operates at a fixed sample rate so the data received will not be available until the sampling instant immediately after it is received. In the ideal case, the two curves will be on top of each other, indicating that the latency is less than one sampling instant. Due to limited communications bandwidth, thread context switching latency and other factors, there will always be some latency in communications, although it will not be noticeable if the latency is less than one sampling instant.

There are a few block properties for the Stream Server block to be set. Since we demonstrate the case where both the application and the model are operating in the same mode, there will be a figure illustrating the Stream Server block properties window with the correct values set for each mode of operation.

Client Application - Blocking Mode

Before going into the details of the source code, remember to include the header files containing the functions used in your code. For the Stream API, you need to include the "quanser_stream.h" header file. In addition, another interface is used in our examples to facilitate the interpretation of error codes. The header file for this API is called . For more information about this API, please refer to the Error Handling section.

The following figure shows the Stream Server block properties window with parameters set for blocking operation.

Stream Server Block Properties

Notice the blue highlighted field. This is where you specify the mode of operation. By choosing the blocking mode, four other fields get enabled, namely Send FIFO size in samples, Receive FIFO size in samples, Send thread priority and Receive thread priority . Use the default values for these parameters. For more information on these parameters, you can refer to the Stream Server block help page in the MATLAB help for QUARC. Another important parameter to consider is the URI upon which to listen. It is set to shmem://foobar:1 which indicates that the server listens on the shared memory buffer called "foobar" at port number 1.

The required header files are grouped into a common header file called stdafx.h in this example. The contents of this header file are:

#include <stdio.h>
#include <signal.h>
#include "quanser_messages.h"
#include "quanser_stream.h"

The first section of the source code is dedicated to variable definitions and initialization. The following code segment shows this section.

#include "stdafx.h"
static int stop = 0;
static void control_c_handler(int signum) {
	stop = 1;
}

int _tmain(int argc, _TCHAR* argv[])
{
	const char	  uri[]			   = "shmem://foobar:1";
	const t_boolean nonblocking		 = false;
	const t_int	 send_buffer_size	= 8000;
	const t_int	 receive_buffer_size = 8000;
	const char *	locale			  = NULL;

	t_stream client;
	t_error result;
	char message[512];

	/* Register a Ctrl+C handler to stop the code upon Ctrl+C being pressed. */ 
	signal(SIGINT, control_c_handler);

The variables initialized here are used by different Stream API functions. For instance, the uri variable is used by the stream_connect C function to connect to the server using the specified URI. We have set this value to the URI configured in the Stream Server block. The nonblocking boolean variable is set to false to indicate that the mode of operation is blocking.

The next section is where the client tries to connect to the server and upon establishing the connection, sends/receives data to/from the server model. It is illustrated by the code segment below.

	/* This function attempts to connect to the server using the specified URI. */	
	result = stream_connect(uri, nonblocking, send_buffer_size, receive_buffer_size, &client);
	if (result == 0) /* now connected */
	{
	
		unsigned long count = 0;
		t_double value = 0.0;
		
		/* The loop for sending/receiving data to/from the server */
		while (!stop)
		{
			result = stream_receive_double(client, &value);
			
			if (result == -QERR_CONNECTION_NOT_BOUND)
			{
				/* 
					This case is not required for most protocols. However, it is required for UDP when the stream_receive
					is done first, as it is in this example. The reason is that the stream_connect does not explicitly bind
					the UDP socket to the port, as the server does, because otherwise the client and server could not be run
					on the same machine. Hence, the stream_connect implicitly binds the UDP socket to the port when
					the first stream_send operation is performed. Thus, a stream_send should always be executed prior to the
					first stream_receive for the UDP protocol. Otherwise, the -QERR_CONNECTION_NOT_BOUND error is returned.
					Therefore, because this example does the stream_receive first, we check for the -QERR_CONNECTION_NOT_BOUND
					error and issue a stream_send in this instance. For other protocols, this case is never invoked, but
					the code is included here so that the protocol can be changed to UDP and the example left unchanged.
				*/
				result = stream_send_double(client, value);
				if (result < 0)
					break;
				
				result = stream_flush(client);
				if (result < 0)
					break;
			}
			else if (result <= 0)
				break;
			
			result = stream_send_double(client, value);
			if (result < 0)
				break;
			count++;
			
			result = stream_flush(client);
			if (result < 0)
				break;
		}

		/* Connection has been closed either by the server or an error occurrence. Close the client stream. */		
		stream_close(client);
		printf("Connection closed. Value: %lg. Number of items: %lu\n", value, count);

		/*
			Check to see if an error occurred while sending/receiving.
			In case of an error, print the appropriate message.
		*/
		if (result < 0)
		{
			msg_get_error_messageA(locale, result, message, sizeof(message));
			printf("Error communicating on URI '%s'. %s\n", uri, message);
		}
	}
	/*
		If the stream_connect function encountered an error, the client cannot connect
		to the server. Print the message corresponding to the error occurred.
	*/
	else
	{
		msg_get_error_messageA(locale, result, message, sizeof(message));
		printf("Unable to connect to URI '%s'. %s\n", uri, message);
	}
	printf("Press enter to exit\n");
	getchar();
	return 0;
}

The stream_connect function tries to connect to the server using the given URI. Since the blocking mode is chosen, this function will not return until the operation is completed or an error occurs. If there are any errors in the attempt, it will return a negative error code. Otherwise, a value of zero is returned. The while loop is where sending/receiving takes place. The data sent by the server is first received from the receive buffer and then sent back to the send buffer. The stream_flush function flushes the send buffer onto the underlying communications channel. There is an additional section included to support the UDP protocol. As mentioned at the beginning of this document, for UDP protocol, the client needs to send a packet of data to bind the socket to the specified port. In the code segment designated for UDP, we check to see if the error code -QERR_CONNECTION_NOT_BOUND is returned by stream_receive. If this error code is returned, a value of 0 is sent to the server side to bind the socket to the specified port. Note that for other communication protocols, this section of the code is not invoked. In case of errors in any of the mentioned operations, the program exits the while loop. Once the connection is closed either by the server, peer or an error occurring, the program exits the while loop and the appropriate message is printed. Upon successful communication, the printed message consists of the last value received from the server and the number of items received.

You can now build your Simulink model and the client application. To start the communication process, always remember to run the server side before the client side. Running the client before the server would cause the program to produce an error and exit. Therefore, you should run the Simulink model first and then the client application. Once the communication process starts, you can stop either the application or the model to end the session. Now if you check the last value sent by the server, it should be the same as the value printed on the console window. You can also look at the "Data Received" Scope block in the server model to see the plots of data received from the client and the data sent by the server.

Warning As mentioned at the beginning of this document, you can have a non-blocking server and a blocking client and vice versa. To verify this claim, you can change the server model's mode to non-blocking and run it with the blocking client application. The results should be the same as for the case where both use the same mode.

Client Application - Non-Blocking Mode

In non-blocking mode, functions return immediately after execution. An error code called -QERR_WOULD_BLOCK is returned by each function to indicate that the function would have blocked if it was operating in blocking mode. With this fact in mind, the user needs to check whether the returned value by each function equals the mentioned error code. In case they are equal, the program should not consider it an error case and has to handle it properly. Before going into the details of the source code, you need to set the block properties of the Stream Server block to enable non-blocking mode for the server model. The following figure illustrates this block's properties window.

Stream Server Block Properties

Note that the blue highlighted field is where you choose the mode of operation. Notice that the first parameter, URI upon which to listen, is set to udp://localhost:18000. The URI is modified to change the communications protocol in the previous section (from shared memory to UDP). The reason for changing the protocol is to demonstrate the use of a different protocol. Normally, all you need to do to change the communications protocol is to change the URI. However, using the UDP protocol requires a slight modification to the source code which will be discussed later in this section.

The first section of the client application is the same as the one for the blocking mode except for two changes. The nonblocking variable should be set to true and the uri variable is set to "udp://localhost:18000". Also note that the header files to be included are the same as the ones mentioned for the blocking client application. The following code segment illustrates the first section.

#include "stdafx.h"
static int stop = 0;

static void control_c_handler(int signum) {
	stop = 1;
}

int _tmain(int argc, _TCHAR* argv[])
{
	const char	  uri[]			   = "udp://localhost:18000";
	const t_boolean nonblocking		 = true;
	const t_int	 send_buffer_size	= 8000;
	const t_int	 receive_buffer_size = 8000;
	const char *	locale			  = NULL;

	t_stream client;
	t_error result;
	char message[512];

	/* Register a Ctrl+C handler to stop the code upon Ctrl+C being pressed. */ 
	signal(SIGINT, control_c_handler);

The next section is where the client attempts to connect to the server. It is illustrated by the following code segment.

	/* This function attempts to connect to the server. */
	result = stream_connect(uri, nonblocking, send_buffer_size, receive_buffer_size, &client);
	
	/* Since we are in non-blocking mode, the stream is polled to see if the connection is established or not. */
	if (result == -QERR_WOULD_BLOCK)
	{
		t_timeout timeout;
		
		timeout.seconds	 = 30;
		timeout.nanoseconds = 0;
		timeout.is_absolute = false;
		
		/* 
			The flag input to the stream_poll function (STREAM_POLL_CONNECT)
			forces the function to check if a client connection has been made.
		*/
		result = stream_poll(client, &timeout, STREAM_POLL_CONNECT);
		
		/* If polling was unsuccessful, close the client connection, print the error and exit the application. */
		if (result < 0)
		{
			stream_close(client);

			msg_get_error_messageA(locale, result, message, sizeof(message));
			printf("Unable to connect to URI '%s'. %s\n", uri, message);
			printf("Press Enter to exit\n");
			getchar();
			return 1;
		}
	}

The stream_connect function returns immediately because it is in non-blocking mode. Therefore, you need to poll the stream to check if the connection is established. This is done using the stream_poll function, passing the STREAM_POLL_CONNECT flag as its input. The function will return only when the connection can be made, the polling time expires (expiry time is set by the timeout variable) or an error occurs during polling. In case of an error occurrence, the program prints the appropriate message and exits.

The next section is where sending/receiving of data takes place and is illustrated by the following code segment.

	if (result >= 0)
	{ /* The client is now connected. */	
		unsigned long count = 0;
		t_double value = 0.0;

		/* This while loop is where sending/receiving takes place. */
		while (!stop)
		{
			result = stream_receive_double(client, &value);
			if (result > 0)
			{
				result = stream_send_double(client, value);
				if (result > 0)
				{
					result = stream_flush(client);
					if (result < 0 && result != -QERR_WOULD_BLOCK)
						break;
				}
				else if (result != -QERR_WOULD_BLOCK)
					break;
			}
			else if (result == -QERR_CONNECTION_NOT_BOUND)
			{
				/* 
					This case is not required for most protocols. However, it is required for UDP when the stream_receive
					is done first, as it is in this example. The reason is that the stream_connect does not explicitly bind
					the UDP socket to the port, as the server does, because otherwise the client and server could not be run
					on the same machine. Hence, the stream_connect implicitly binds the UDP socket to the port when
					the first stream_send operation is performed. Thus, a stream_send should always be executed prior to the
					first stream_receive for the UDP protocol. Otherwise, the -QERR_CONNECTION_NOT_BOUND error is returned.
					Therefore, because this example does the stream_receive first, we check for the -QERR_CONNECTION_NOT_BOUND
					error and issue a stream_send in this instance. For other protocols, this case is never invoked, but
					the code is included here so that the protocol can be changed to UDP and the example left unchanged.
				*/
				result = stream_send_double(client, value);
				if (result > 0)
				{	
					result = stream_flush(client);
					if ( result < 0 && result != -QERR_WOULD_BLOCK)
						break;
				}
				else if (result != -QERR_WOULD_BLOCK)
					break;
			}
			
			else if (result != -QERR_WOULD_BLOCK)
				break;
			count++;
		}

		/*
			The connection is closed either by server or an error occurrence.
			Close the client stream.
		*/
		stream_close(client);
		printf("Connection closed. Value: %lg. Number of loop iterations: %lu\n", value, count);
	
		/* Print the message corresponding to the error occurred which led to connection being closed. */
		if (result < 0)
		{
			msg_get_error_messageA(locale, result, message, sizeof(message));
			printf("Error communicating on URI '%s'. %s\n", uri, message);
		}
	}
	/* If the polling function returned with an error, the connection cannot be made. */	
	else 
	{ 
		msg_get_error_messageA(locale, result, message, sizeof(message)); 
		printf("Unable to connect to URI '%s'. %s\n", uri, message); 
	}	
	printf("Press Enter to exit\n");
	getchar();
	return 0;
}

The data sent by the server is first received from the receive buffer and then sent back to the send buffer. The stream_flush function flushes the send buffer onto the underlying communications channel. There is an additional section included to support the UDP protocol. As mentioned at the beginning of this document, for UDP protocol, the client needs to send a packet of data to bind the socket to the specified port. In the code segment designated for UDP, we check to see if the error code -QERR_CONNECTION_NOT_BOUND is returned by stream_receive. If this error code is returned, a value of 0 is sent to the server side to bind the socket to the specified port. Note that for other communication protocols, this section of the code is not invoked. In case of errors in any of the mentioned operations, the program exits the while loop. Once the connection is closed either by the server, peer or an error occurring, the program exits the while loop and the appropriate message is printed. Upon successful connection, the printed message consists of the last value received from the server and the number of items received.

You can now build your Simulink model and the client application. To start the communication process, always remember to run the server side before the client side. Running the client before the server would cause the program to produce an error and exit. Therefore, you should run the Simulink model first and then the client application. Once the communication process starts, you can stop either the application or the model to end the session. Now if you check the last value sent by the server, it should be the same as the value printed on the console window. You can also look at the "Data Received" Scope block in the server model to see the plots of data received from the client and the data sent by the server.

To observe the difference between blocking and non-blocking modes of operation, compare the number of items in blocking mode vs. the number of loop iterations in non-blocking mode (try to run the server/client system for both applications for about the same time period). Both of these numbers refer to the number of sending/receiving loop iterations. Each time the sending/receiving while loop is executed, the counter is incremented by one. In blocking mode, the number of loop iterations is the actual number of items received from the server model, since the functions in the loop block until their operations are completed. On the contrary, in non-blocking mode, the number of loop iterations does not correspond to the actual number of items recevied. In fact, it is much larger than the number of items received. This is because in non-blocking mode, the functions return immediately causing the while loop to constantly iterate even if there is no new data to be sent/received.

Warning As mentioned at the beginning of this document, you can have a blocking server and a non-blocking client and vice versa. To verify this claim, you can change the server model's mode to blocking and run it with the non-blocking client application. The results should be the same as for the case where both use the same mode.

C/C++ Application as Server

The C/C++ application could be acting as a server accepting connections from the real-time code. In this case, the real-time code serves as the client. There are, as mentioned earlier, two modes of operation; blocking and non-blocking. In blocking mode, the functions and blocks will not return until the requested operation is completed. On the contrary, in non-blocking mode, the blocks and functions will return immediately with no concern about the completeness of the requested operation. In this section, one Simulink model is used to demonstrate the client side and two C++ applications are used to demonstrate the server side (blocking and non-blocking).

Simulink Client Model

The example model used in this section is illustrated by the following figure.

Example Simulink Model

The Stream Client block acts as the client in the communication process. This block establishes a "persistent" connection with a host. The host may be local or remote. If the connection to the host is lost, this block automatically attempts to reconnect to the host. The Stream Client block is a high-level block and does not require a sophisticated knowledge of communications to be used. For more information about this block, please refer to the Stream Client block help page in the MATLAB help for QUARC. You can also refer to the Basic Communications section of the QUARC documentation to gain more insight on how to use this block. Even though the intermediate or advanced blocks are more flexible, we will not demonstrate how to use them in this example for the sake of simplicity. Information on how to use the intermediate or advanced communications blocks is provided in the Communications section.

The data to be sent by the client model is a sine wave generated by a Sine Wave block. The received data is what is sent by the server application. In our case, the client/server system acts as a loop-back system. The data sent to the server gets sent back to the client by that server. In order to check the latency of our system, the data to be sent is plotted on a scope along with the actual received data from the server. The data sent is passed through a Memory block in order to compensate for the fact that the block operates at a fixed sample rate so the data received will not be available until the sampling instant immediately after it is received. In the ideal case, the two curves will be on top of each other, indicating that the latency is less than one sampling instan. Due to limited communications bandwidth, thread context switching latency and other factors, there will always be some latency in communications, although it will not be noticeable if the latency is less than one sampling instant.

As discussed at the beginning of this documentation, using UDP protocol requires the client side to perform a send operation first. In the C/C++ Application as Client section, this ordering is implemented in the code. However, in the case where the Simulink model is the client, the Stream Client block implements this ordering internally. Therefore, there is no need to have special logic for the UDP protocol. With this in mind, all you need to do to change the protocol in use is to change the URI.

There are a few block properties for the Stream Client block to be set. Since we demonstrate the case where both the application and the model are operating in the same mode, there will be a figure illustrating the Stream Client block properties window with the correct values set for each mode of operation.

Server Application - Blocking Mode

Before going into the details of the source code, remember to include the header files containing the functions used in your code. For the Stream API, you need to include the "quanser_stream.h" header file. In addition, another interface is used in our examples to facilitate the interpretation of error codes. The header file for this API is called . For more information about this API, please refer to the Error Handling section.

The following figure shows the Stream Client block properties window with parameters set for blocking operation.

Stream Client Block Properties

Notice the blue highlighted field. This is where you specify the mode of operation. By choosing the blocking mode, four other fields get enabled, namely Send FIFO size in samples, Receive FIFO size in samples, Send thread priority and Receive thread priority. Use the default values for these parameters. For more information on these parameters, you can refer to the Stream Client block help page in the MATLAB documentation for QUARC. Another important parameter to consider is the URI of host to which to connect. It is set to tcpip://localhost:18000 which indicates that the client attempts to connect to the server listening on local host at port 18000 using the TCP/IP protocol.

The required header files are grouped into a common header file called stdafx.h in this example. The contents of this header file are:

#include <stdio.h>
#include <signal.h>
#include "quanser_messages.h"
#include "quanser_stream.h"

The first section of the source code is dedicated to variable definitions and initialization. The following code segment shows this section.

#include "stdafx.h"
static int stop = 0;
static void control_c_handler(int signum) {
	stop = 1;
}

int _tmain(int argc, _TCHAR* argv[])
{
	const char	  uri[]			   = "tcpip://localhost:18000";
	const t_boolean nonblocking		 = false;
	const t_int	 send_buffer_size	= 8000;
	const t_int	 receive_buffer_size = 8000;
	const char *	locale			  = NULL;

	t_stream server;	
	t_stream client;
	t_error result;
	char message[512];

	/* Register a Ctrl+C handler to stop the code upon Ctrl+C being pressed. */ 
	signal(SIGINT, control_c_handler);

The variables defined here are used by different Stream API functions. For instance, the uri variable is used by the stream_listen C function to listen for client connections using the specified URI. We have set this value to the URI configured in the Stream Client block. The nonblocking boolean variable is set to false to indicate that the mode of operation is blocking.

The next section is where the server listens for client connections and accepts them. It is given by the following code segment.

	/* This function establishes a server stream which listens on the given URI. */
	result = stream_listen(uri, nonblocking, &server);
	
	/* The server stream was successfully created. */
	if (result == 0)
	{
		/* persistent connection */
		
		while (!stop)
		{
			printf("Waiting for a new connection from a client...\n");
			
			/*
				This function accepts new connections from client using the server 
				stream created by stream_listen. Upon successful operation, it creates 
				a client stream to be used for communications.
			*/
			result = stream_accept(server,send_buffer_size,receive_buffer_size,&client);
			
			
			if (result == 0)
			{/* The client is now connected. */	

The stream_listen function establishes a server stream which listens on the given URI. If the server stream is created successfully, it will return zero, otherwise an error code will be returned. The while loop is intended for persistent connection. Once a client connection is closed, the server starts listening and accepting new connections without exiting the program. The stream_accept function accepts new connections. Upon successful operation, it creates a client stream and returns a value of zero.

The next section is where sending/receiving of data takes place. It is illustrated by the following code segment.

				unsigned long count = 0;
				t_double value;
				
				/* This is the loop where sending/receiving takes place. */ 
				while (!stop)
				{
					
					result = stream_receive_double(client, &value);
					if (result <= 0)
						break;

					count++;

					result = stream_send_double(client, value);
					if (result < 0)
						break;

					result = stream_flush(client);
					if (result < 0)
						break;
				}
				
				/*
					Connection has been closed either by the client or an error occurrence.
					Close the client stream. The program goes back to the start of the while loop
					for persistent connection.
				*/
				stream_close(client);
				printf("Connection closed. Value: %lg. Number of items: %lu\n", value, count);
				
				/* Print the appropriate message in case of errors in communication process. */
				if (result < 0)
				{
					msg_get_error_messageA(locale, result, message, sizeof(message));
					printf("Error communicating on URI '%s'. %s\n", uri, message);
				}	
			}
			
			/* The connection cannot be accepted. Print the error message and exit the persistent connection while loop. */
			else if (result < 0)
			{
				msg_get_error_messageA(locale, result, message, sizeof(message));
				printf("Unable to accept connections on URI '%s'. %s\n", uri, message);
				break;
			}
		}
	}
	
	/* If there was an error trying to create the server stream, close the server stream and print the error message. */
	else
	{
		msg_get_error_messageA(locale, result, message, sizeof(message));
		printf("Unable to listen on URI '%s'. %s\n", uri, message);
	}

		/* The server session is ended. Close the server stream. */
	stream_close(server);
	printf("Press Enter to exit\n");
	getchar();

	return 0;
}

The while loop is where sending/receiving takes place. In case the client closes the connection or an error occurs in the process, the client stream is closed and the program goes back to the beginning of the loop (for persistent connection) to start listening and accepting new connections. If the returned value of the stream_accept function is less than zero, the server cannot accept connections on the given URI. The persistent connection loop is exited and the appropriate message is printed. In case of errors occurring while the server stream is being created, the stream is closed and the program exits.

You can now build your Simulink model and the server application. To start the communication process, it is recommended to run the server side before the client side. However, the Stream Client block supports persistent connectivity. In other words, if the server has not started before the client model, this block would attempt to connect continually until the connection is made. Therefore, you do not necessarily need to run the server application first. Once the communication process starts, you can stop either the model or the application to end the session. Now if you check the last value sent by the client, it should be the same as the value printed on the console window. You can also look at the "Data Received" Scope block in the client model to see the plots of the data sent by the client and received from the server.

Warning As mentioned at the beginning of this document, you can have a non-blocking client and a blocking server and vice versa. To verify this claim, you can change the client model's mode to non-blocking and run it with the blocking server application. The results should be the same as for the case where both use the same mode.

Server Application - Non-Blocking Mode

In non-blocking mode, functions return immediately after execution. An error code called -QERR_WOULD_BLOCK is returned by each function to indicate that the function would have blocked if it was operating in blocking mode. With this fact in mind, the user needs to check whether the returned value by each function equals the mentioned error code. In case they are equal, the program should not consider it an error case and has to handle it properly. Before going into the details of the source code, you need to set the block properties of the Stream Client block to enable non-blocking mode for the client model. The following figure illustrates this block's properties window.

Stream Client Block Properties

Note that the blue highlighted field is where you choose the mode of operation. Notice that the first parameter, URI of host to which to connect, is set to udp://localhost:18000. The URI is modified to change the communications protocol from the one used in the previous section (TCP/IP) to UDP protocol. The reason for changing the protocol is to demonstrate the use of a different protocol. As mentioned earlier, when the Simulink model is acting as the client, all you need to do to change the protocol is to change the URI.

The first section of the server application is the same as the one for the blocking mode except for two changes. The nonblocking variable should be set to true and the uri variable is set to "udp://localhost:18000". Also note that the header files to be included are the same as the ones mentioned for the blocking server application. The following code segment illustrates the first section.

#include "stdafx.h"
static int stop = 0;
static void control_c_handler(int signum) {
	stop = 1;
}

int _tmain(int argc, _TCHAR* argv[])
{
	const char	  uri[]			   = "udp://localhost:18000";
	const t_boolean nonblocking		 = true;
	const t_int	 send_buffer_size	= 8000;
	const t_int	 receive_buffer_size = 8000;
	const char *	locale			  = NULL;

	t_stream server;	
	t_stream client;
	t_error result;
	char message[512];
	
	/* Register a Ctrl+C handler to stop the code upon Ctrl+C being pressed. */ 
	signal(SIGINT, control_c_handler);

The variables defined here are used by different Stream API functions. For instance, the uri variable is used by the stream_listen C function to listen for client connections using the specified URI. We have set this value to the URI configured in the Stream Client block. The nonblocking boolean variable is set to true to indicate that the mode of operation is non-blocking.

The next section is where the server establishes a server stream to listens for new client connections and accepts them. This section is given by the code segment below.

	/* This function establishes a server stream which listens on the given URI. */
	result = stream_listen(uri, nonblocking, &server);
	
	/* The server stream was successfully created. */
	if (result == 0)
	{
		
		/* persistent connection */
		
		while (!stop)
		{
			
			printf("Waiting for a new connection from a client...\n");
			
			/* Poll the server stream to see if there is a pending client connection. */
			result = stream_poll(server, NULL, STREAM_POLL_ACCEPT);
		
			if (result == STREAM_POLL_ACCEPT)
			{/* There is a pending client connection. */
				
				/* This function accepts the pending connection. */
				result = stream_accept(server, send_buffer_size, receive_buffer_size, &client);
				
				/* 
					If the stream_accept function was unable to accept the connection, 
					the persistent connection loop should be exited.
				*/
				if (result < 0 && result != -QERR_WOULD_BLOCK)
				{
					stream_close(client);
					msg_get_error_messageA(locale, result, message, sizeof(message));
					printf("Error occurred while accepting client connection on URI '%s'. %s\n", uri, message);
					break;
				}

The stream_listen creates a server stream which listens to new connections on the given URI. Note that this function does not block in either blocking or non-blocking mode. Therefore, the returned value is never equal to -QERR_WOULD_BLOCK. We need to poll the created server stream to check if there are any pending client connections since the stream_accept would return immediately. Once the polling returned successfully, the stream_accept function is called to accept the connection and create a client stream. In case of errors in accepting the client connection, the program should exit the loop.

The next section is where sending/receiving of data takes place. It is illustrated by the code segment below.

				unsigned long count = 0;
				t_double value;

				/* The connection is accepted. This is where sending/receiving takes place. */
				while (!stop)
				{
					result = stream_receive_double(client, &value);
					if (result > 0)
					{
						result = stream_send_double(client, value);
						if (result > 0)
						{
							result = stream_flush(client);
							if (result < 0 && result != -QERR_WOULD_BLOCK)
							break;
						}
						else if (result != -QERR_WOULD_BLOCK)
							break;
					}
					else if (result != -QERR_WOULD_BLOCK)
						break;
					count++;
				}
				
				/* The connection has been closed either by the client or an error occurrence. */ 
				stream_close(client);
				printf("Connection closed. Value: %lg. Number of loop iterations: %lu\n", value, count);
				
				/* In case of errors, print the error message. */
				if (result < 0)
				{
					msg_get_error_messageA(locale, result, message, sizeof(message));
					printf("Error communicating on URI '%s'. %s\n", uri, message);
				}
			}
			
			/*
				If the polling was not successfull due to errors, print the error message and
				exit the persistent connection loop.
			*/
			else if ( result < 0)
			{
				msg_get_error_messageA(locale, result, message, sizeof(message));
				printf("Error occurred when polling the stream on URI '%s'. %s\n", uri, message);
				break;
			}
		}
	}	
	/* 
		If the stream_listen was not successfull in creating a server stream,
		print the error message.
	*/
	else
	{
		msg_get_error_messageA(locale, result, message, sizeof(message));
		printf("Unable to listen on URI '%s'. %s\n", uri, message);
	}
	
	/* End of server session. Close the server stream. */
	stream_close(server);	
	printf("Press Enter to exit\n");
	getchar();
	return 0;
}

The while loop is where sending/receiving takes place. In case the client closes the connection or an error occurs in the process, the client stream is closed and the program goes back to the beginning of the loop (for persistent connection) to start listening and accepting new connections. If the returned value of the stream_accept function is less than zero, the server cannot accept connections on the given URI. The persistent connection loop is exited and the appropriate message is printed. In case of errors occurring while the server stream is being created, the stream is closed and the program exits.

You can now build your Simulink model and the server application. To start the communication process, it is recommended to run the server side before the client side. However, the Stream Client block supports persistent connectivity. In other words, if the server has not started before the client model, this block would attempt to connect continually until the connection is made. Therefore, you do not necessarily need to run the server application first. Once the communication process starts, you can stop either the model or the application to end the session. Now if you check the last value sent by the client, it should be the same as the value printed on the console window. You can also look at the "Data Received" Scope block in the client model to see the plots of the data sent by the client and received from the server.

To observe the difference between blocking and non-blocking modes of operation, compare the number of items in blocking mode vs. the number of loop iterations in non-blocking mode (try to run the server/client system for both applications for about the same time period). Both of these numbers refer to the number of sending/receiving loop iterations. Each time the sending/receiving while loop is executed, the counter is incremented by one. In blocking mode, the number of loop iterations is the actual number of items received from the client model, since the functions in the loop block until their operations are completed. On the contrary, in non-blocking mode, the number of loop iterations does not correspond to the number of items received. In fact, it is much larger than the number of items received. This is because in non-blocking mode, the functions return immediately causing the while loop to constantly iterate even if there is no new data to be sent/received.

Warning As mentioned at the beginning of this document, you can have a non-blocking server and a blocking client and vice versa. To verify this claim, you can change the client model's mode to blocking and run it with the non-blocking server application. The results should be the same as for the case where both use the same mode.

 

navigation bar