Table of Contents > QUARC > User's Guide > QUARC External Interfaces > C Functions > Communications
Communications API
The Quanser Communications API is a generic extensible API used to create communication channels between C/C++ based applications and the real-time code generated for a Simulink model. The Communications API defines a generic unbuffered communication API. It is the lowest-level API and only supports the reception and transmission of bytes. However, it is the foundation of all the other communications APIs.. With this API, you are not guaranteed to send or receive the number of bytes requested. It is not explicitly buffered but the underlying communication protocol may have its own buffering. It also does not handle byte swapping and different data types. For a buffered communications API, you can use the Stream API which is built on top of the Communications API and documented in the Stream API section.
Quanser software (e.g., QUARC, RCP) support a variety of common communications protocols which are all accessed via the Quanser Communications API functions. The Communications API contains 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 Communications API and their description, you can refer to the Communications API header file, .
The Communications API requires 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.
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.
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.
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 Communications API functions operate in two modes; blocking and non-blocking. In blocking mode, functions 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 Communications 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 Communications API. Please use the following list to refer to each topic:
Setting Up the C/C++ Application to Use the Communications API
The user needs to follow a set of steps to setup the C/C++ application to use the Communications 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:
Open the Property Pages window by right-clicking on the project containing your application and choosing the menu item in the context menu. The following figure illustrates this window.
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.
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.
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 example model used in this section is illustrated by the following figure.
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, refer to its help page in the MATLAB help browser. You can also refer to the QUARC Targets/Communications/Basic Communications section of the QUARC documentation in the MATLAB help to gain more insight on how to use this block. Even though it is more efficient to use the intermediate or advanced blocks to create the server model, 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 are provided in the Communications section of the QUARC documentation in the MATLAB help.
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 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.
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 Communications API, you need to include the "quanser_communications.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.
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.
Use the default values for these parameters. For more information on these parameters, you can refer to the
Stream Server block help page. 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_communications.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 char * locale = NULL; /* The client instance of t_connection variable used to create the client connection. */ t_connection 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 Communications API functions.
For instance, the uri
variable is used by the qcomm_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 connecting, sends and receives data over the connection. It is illustrated by the code segment below.
/* This function attempts to connect to the server using the client connection and the specified URI. */ result = qcomm_connect(uri, nonblocking, &client); if (result == 0) { /* now connected */ /* The variable used to send/receive data. */ t_double buffer = 0.0; t_int buffer_size = sizeof(buffer); /* The while loop where sending/receiving takes place. */ while (!stop) { /* This function receives data from the client connection. */ result = qcomm_receive_double(client, &buffer, buffer_size); 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 qcomm_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 qcomm_connect implicitly binds the UDP socket to the port when the first stream_send operation is performed. Thus, a qcomm_send should always be executed prior to the first qcomm_receive for the UDP protocol. Otherwise, the -QERR_CONNECTION_NOT_BOUND error is returned. Therefore, because this example does the qcomm_receive first, we check for the -QERR_CONNECTION_NOT_BOUND error and issue a qcomm_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 = qcomm_send(client, &buffer, buffer_size); if (result < 0) break; } else if (result <= 0) break; /* This function sends data over the client connection. */ result = qcomm_send(client, &buffer, buffer_size); if (result < 0) break; } /* Connection has been closed either by the server, the peer or an error occurrence. Close the client stream. */ qcomm_close(client); printf("Connection closed. The last value received is: %lf.\n", buffer); /* 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 qcomm_connect returned with an error, close the client connection and print the error message. */ else { qcomm_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 0; }
The qcomm_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 and then sent back to the server.
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 qcomm_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.
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 model or the application 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.
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.
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 tcpip://localhost:18000
. The URI is modified to change the communications protocol
in the previous section (from shared memory to TCP/IP). 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
"tcpip://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[] = "tcpip://localhost:18000"; const t_boolean nonblocking = true; const char * locale = NULL; t_connection 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 = qcomm_connect(uri, nonblocking, &client); /* Since we are in non-blocking mode, the client connection 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 (QCOMM_POLL_CONNECT) forces the function to check if a client connection has been made. */ result = qcomm_poll(client, &timeout, QCOMM_POLL_CONNECT); /* If polling was unsuccessful, close the client connection, print the error and exit the application. */ if (result <= 0) { qcomm_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 qcomm_connect
function returns immediately because it is in non-blocking mode. Therefore, you need
to poll the client connection to check if it is established. This is done using the qcomm_poll
function,
passing the QCOMM_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. */ /* The double variable used to send/receive data. */ t_double buffer = 0.0; t_int buffer_size = sizeof (buffer); /* This while loop is where sending/receiving takes place. */ while (!stop) { /* This function receives data from the client connection. */ result = qcomm_receive(client, &buffer, buffer_size); if (result > 0) { /* This function sends data over the client connection. */ result = qcomm_send(client, &buffer, buffer_size); if (result < 0 && 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 qcomm_receive is done first, as it is in this example. The reason is that the qcomm_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 qcomm_connect implicitly binds the UDP socket to the port when the first qcomm_send operation is performed. Thus, a qcomm_send should always be executed prior to the first qcomm_receive for the UDP protocol. Otherwise, the -QERR_CONNECTION_NOT_BOUND error is returned. Therefore, because this example does the qcomm_receive first, we check for the -QERR_CONNECTION_NOT_BOUND error and issue a qcomm_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 = qcomm_send(client, &buffer, buffer_size); if (result < 0 && result != -QERR_WOULD_BLOCK) break; } else if (result != -QERR_WOULD_BLOCK) break; } /* The connection is closed either by server or an error occurrence. Close the client connection. */ qcomm_close(client); printf("Connection closed. The last value received is: %lf.\n", buffer); /* 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 and then sent back to the server.
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 qcomm_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.
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 model or the application 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.
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.
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. You can also refer to the Basic Communications section of the QUARC documentation in MATLAB to gain more insight on how to use this block. Even though it is more efficient to use the intermediate or advanced blocks to create the client model, we will not demonstrate how to use them. The client model is more complicated using the more advanced blocks and our focus is not on the Simulink model but rather on the C/C++ based application. Nevertheless, the information on how to use the intermediate or advanced communications blocks is provided in the Communications section of the QUARC documentation installed with MATLAB.
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 Server 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 Communications API, you need to include the "quanser_communications.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.
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, 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 help. 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_communications.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 char * locale = NULL; /* The server instance of t_connection variable used to create the server connection. */ t_connection server; /* The client instance of t_connection variable used to create the client connection. */ 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 Communications API functions.
For instance, the uri
variable is used by the qcomm_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 creates the server connection to listen for new client connections using the specified URI. */ result = qcomm_listen(uri, nonblocking, &server); /* If the result of qcomm_listen is zero, the server connection is creaetd. Start accepting connections. */ if (result == 0) { /* This is the while loop for persistent connection. Once a client has connected and disconnected, this loop enables the server to continue accepting new connections. */ while (!stop) { printf("Waiting for a new connection from a client...\n"); /* This function accepts the any client connections and returns zero on successfull execution. */ result = qcomm_accept(server, &client); if (result == 0) {/* The client is now connected. */
The qcomm_listen
function establishes a server connection which listens on the given URI.
If the server connection 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 qcomm_accept
function accepts new connections. Upon successful operation, it creates a client connection 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.
t_double buffer; t_int buffer_size = sizeof (buffer); /* This is the loop where sending/receiving takes place. */ while (!stop) { /* This function receives data from the client connection. */ result = qcomm_receive(client, &buffer, buffer_size); if (result <= 0) break; /* This function sends data over the client connection. result = qcomm_send(client, &buffer, buffer_size); 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. */ qcomm_close(client); printf("Connection closed. The last value received is: %lf.\n", buffer); /* 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 qcomm_listen returned with errors, close the server connection 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); } qcomm_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 connection 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 qcomm_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 connection is being created, it 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.
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.
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 char * locale = NULL; /* The server instance of t_connection variable used to create the server connection. */ t_connection server; /* The client instance of t_connection variable used to create the client connection. */ 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 Communications API functions.
For instance, the uri
variable is used by the qcomm_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 connection to listens for new client connections and accepts them. This section is given by the code segment below.
/* This function establishes a server connection which listens on the given URI. */ result = qcomm_listen(uri, nonblocking, &server); /* The server connection was successfully created. */ if (result == 0) { /* Persistent connection while (!stop) { printf("Wiating for a new connection from a client...\n"); /* Poll the server connection to see if there is a pending client connection. */ result = qcomm_poll(server, NULL, QCOMM_POLL_ACCEPT); if (result == QCOMM_POLL_ACCEPT) {/* There is a pending client connection. */ /* This function accepts the pending connection. */ result = qcomm_accept(server, &client); /* If the qcomm_accept function was unable to accept the connection, the persistent connection loop should be exited. if (result < 0 && result != -QERR_WOULD_BLOCK) { qcomm_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 qcomm_listen
creates a server connection which listens to new client 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 connection to check if there are any pending client connections since the qcomm_accept
would return immediately. Once the polling returned successfully, the qcomm_accept
function is called to accept
the connection and create a client connection. 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.
/* The double variable used to send/receive data. */ t_double buffer; t_int buffer_size = sizeof (buffer); /* The connection is accepted. This is where sending/receiving takes place. */ while (!stop) { /* This function receives data from the client connection. */ result = qcomm_receive(client, &buffer, buffer_size); if (result > 0) { /* This function sends data over the client connection. */ result = qcomm_send(client, &buffer, buffer_size); if (result < 0 && result != -QERR_WOULD_BLOCK) break; } else if (result != -QERR_WOULD_BLOCK) break; } /* The connection has been closed either by the client or an error occurrence. */ qcomm_close(client); printf("Connection closed. The last value received is: %lf.\n", buffer); /* 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 qcomm_listen was not successfull in creating a server connection, 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 connection. */ qcomm_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 connection 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 qcomm_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 connection is being created, it 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.
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.
Copyright ©2024 Quanser Inc. This page was generated 2024-10-17. Submit feedback to Quanser about this page.
Link to this page.