Skip to content

Commit

Permalink
C dev (#273)
Browse files Browse the repository at this point in the history
* Initial review for structure

* nearly done

* add topic

* Finish subscribe

* complete subscribe

* Add Windows build scripts

* Cleanup README

* Add License info

* cleanup some comments

* Improve some of the output info

* And another piece of additional output

* make waitInterval configurable. don't play with sub wildcards
  • Loading branch information
ibmmqmet authored Nov 27, 2024
1 parent 32975f5 commit 190cff7
Show file tree
Hide file tree
Showing 16 changed files with 2,158 additions and 61 deletions.
19 changes: 19 additions & 0 deletions C/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@

# Don't try to track the binary programs
sampleput
sampleget
samplerequest
sampleresponse
samplepublish
samplesubscribe

# Windows builds
*.exe
*.obj

# My test scripts
doit
.gdbrc

# On MacOS these directories may be created with debug info
*.dSYM
40 changes: 40 additions & 0 deletions C/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
COMMONSRC = common.c config.c
COMMONINC = common.h config.h

MQINC=/opt/mqm/inc
MQLIB=/opt/mqm/lib64

APPS = sampleput \
sampleget \
samplerequest \
sampleresponse \
samplepublish \
samplesubscribe

# CDEBUG=-g

all: $(APPS)
@rm -rf *.dSYM

clean:
rm -f $(APPS)
@rm -rf *.dSYM
@rm -f *.exe *.obj

sampleput: sampleput.c $(COMMONSRC) $(COMMONINC) Makefile
$(CC) $(CDEBUG) -o $@ $@.c $(COMMONSRC) -I$(MQINC) -L$(MQLIB) -lmqm_r

sampleget: sampleget.c $(COMMONSRC) $(COMMONINC) Makefile
$(CC) $(CDEBUG) -o $@ $@.c $(COMMONSRC) -I$(MQINC) -L$(MQLIB) -lmqm_r

samplerequest: samplerequest.c $(COMMONSRC) $(COMMONINC) Makefile
$(CC) $(CDEBUG) -o $@ $@.c $(COMMONSRC) -I$(MQINC) -L$(MQLIB) -lmqm_r

sampleresponse: sampleresponse.c $(COMMONSRC) $(COMMONINC) Makefile
$(CC) $(CDEBUG) -o $@ $@.c $(COMMONSRC) -I$(MQINC) -L$(MQLIB) -lmqm_r

samplepublish: samplepublish.c $(COMMONSRC) $(COMMONINC) Makefile
$(CC) $(CDEBUG) -o $@ $@.c $(COMMONSRC) -I$(MQINC) -L$(MQLIB) -lmqm_r

samplesubscribe: samplesubscribe.c $(COMMONSRC) $(COMMONINC) Makefile
$(CC) $(CDEBUG) -o $@ $@.c $(COMMONSRC) -I$(MQINC) -L$(MQLIB) -lmqm_r
84 changes: 84 additions & 0 deletions C/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# IBM MQ C samples
These samples use the C MQI to demonstrate basic messaging operations.

## Dependencies
* Install the IBM MQ client for your system, or unpack the Redistributable Client package if available.
* The SDK component is needed in order to compile these programs.
* You also need a C compiler

## Introduction to the C samples

**sampleput.c** - Puts message to a queue

**sampleget.c** - Gets message from a queue

**samplesubscribe.c** - Subscribes to a topic string and gets publications/messages

**samplepublish.c** - Publishes messages to a topic string

**samplerequest.c** - Puts a message on a request queue and waits for a response

**sampleresponse.c**- Gets message from a request queue, does something with the message and puts it to the reply queue.

***common.c*** - Common functions, including managing the connection to the queue manager

***config.c*** - A simple parser for the configuration file

The connection to the queue manager demonstrates a variety of approaches. Choices are made based on the configuration.
These include
* Authentication
* Client or local connections
* TLS when using clients
* Application name used by Uniform Clusters

## Compiling the programs
On Linux and MacOS, you can use the Makefile by simply running _make_. The generated programs are created in the current
directory. If you have installed MQ into a non-default location, then you may need to change the directories named in
the Makefile.

On Windows, the `win_dev.bat` script sets the environment for the Visual Studio 2022 C compiler. And then the
`win_bld.bat` script compiles the programs. Default installation paths are assumed, so you might need to edit these
scripts for your system.

## Configuration
All of the programs read a JSON-formatted configuration file. The name of the file can be given as a command-line
parameter or by setting the JSON_CONFIG environment variable. If neither is set, the _env.json_ file from the parent
directory is used.

These samples do not use a real JSON parsing library, and are sensitive to the exact layout of the configuration file.
In particular, each item is expected to be on a separate line of the file.
The default format, from the file in the root of this repository, should be referred to.

All configuration parameters can also be set through environment variables. See _config.h_ for the names of these
environment variables.

Setting the `DEBUG` environment variable to any value causes the active configuration to be printed.

## Running the programs
Apart from the optional command line parameter naming the configuration file, there are no
other parameters to any of the programs.

You might need to run `setmqenv` to create environment variables pointing at your MQ installation
libraries. And on MacOS, the `DYLD_LIBRARY_PATH` will usually need to be set to include the
`/opt/mqm/lib64` directory.

See [here](https://www.ibm.com/docs/en/ibm-mq/latest?topic=reference-setmqenv-set-mq-environment) for
more information about `setmqenv`.

### Put/Get
The `sampleput` application places a short string message onto the queue.

The `sampleget` application reads all messages from the queue and displays the contents.

### Publish/Subscribe
Run these samples as a pair.

Start the `samplesubcribe` program in one window (or in the background) and immediately afterwards start the
`samplepublish` program in another window.

### Request/Response
Run these samples as a pair.

Start the `sampleresponse` program in one window (or in the background) and immediately afterwards start the
`samplerequest` program in another window.

221 changes: 221 additions & 0 deletions C/common.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
/**
* Copyright 2024 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/

#include <stdio.h>
#include <string.h>
#include <ctype.h>

#include <cmqc.h>
#include <cmqstrc.h>
#include <cmqxc.h>

#include "common.h"
#include "config.h"

static const char *hexChars = "0123456789ABCDEF";

/*
* Connect to a queue manager
* Use the loaded configuration information to build the various structures
* Both client and local connections can be made
*/
int connectQMgr(PMQHCONN pHConn) {
MQLONG compCode;
MQLONG reason;
int rc = 0;
int i;

MQCNO mqcno = {MQCNO_DEFAULT};
MQSCO mqsco = {MQSCO_DEFAULT};
MQCSP mqcsp = {MQCSP_DEFAULT};
MQCD mqcd = {MQCD_CLIENT_CONN_DEFAULT};
MQCHAR ConnectionName[MQ_CONN_NAME_LENGTH + 1] = {0};
int s = sizeof(ConnectionName);

mqEndpoint_t ep = mqEndpoints[0];

// Set structure version high enough to include all the fields we might want to use
mqcno.Version = MQCNO_VERSION_8;

// Build the connection information. If there is a CCDT, then point at that.
// Otherwise build the MQCD client channel structure if we have configuration for that
// Otherwise attempt the default type of connection which might be local bindings or client
// - MQSERVER and MQ_CONNECT_TYPE environment variables can control that process
if (ep.ccdtUrl) {
mqcno.Options |= MQCNO_CLIENT_BINDING;
mqcno.CCDTUrlPtr = ep.ccdtUrl;
mqcno.CCDTUrlLength = strlen(ep.ccdtUrl);
} else {
if (ep.channel && ep.host) {
mqcno.Options |= MQCNO_CLIENT_BINDING;

// Use strncpy with the maximum length of the field in the structure
// The MQI does not use NULL-terminated strings in its char fields.
strncpy(mqcd.ChannelName,ep.channel,MQ_CHANNEL_NAME_LENGTH);

// Build the connname. If there are multiple endpoints defined,
// then we use the host/port info from all of them to build the conname.
// Should end up with a string like "host1(port1),host2,host3(port3)"
for (i=0;i<=epIdx;i++) {
if (i > 0) {
strncat(ConnectionName,",", s);
}
strncat(ConnectionName,mqEndpoints[i].host,s);
if (mqEndpoints[i].port) {
strncat(ConnectionName,"(",s);
strncat(ConnectionName,mqEndpoints[i].port,s);
strncat(ConnectionName,")",s);
}
}
strncpy(mqcd.ConnectionName,ConnectionName,MQ_CONN_NAME_LENGTH);

// Set the TLS Cipher to be used
if (ep.cipher) {
strncpy(mqcd.SSLCipherSpec,ep.cipher,MQ_SSL_CIPHER_SPEC_LENGTH);
} else if (ep.cipherSuite) {
strncpy(mqcd.SSLCipherSpec,ep.cipherSuite,MQ_SSL_CIPHER_SPEC_LENGTH);
}

// Point at a certificate repository. This needs to contain. at minimum,
// the signing information for the queue manager's certificate.
// There are more options that COULD be used here, but this is the simplest.
if (ep.keyRepository) {
strncpy(mqsco.KeyRepository,ep.keyRepository,MQ_SSL_KEY_REPOSITORY_LENGTH);
mqcno.SSLConfigPtr = &mqsco;
}

// The client configuration is now referenced from the connect options structure
mqcno.ClientConnPtr = &mqcd;

} else {
// Just take the default connection type. Don't try to explicitly set
// client or local bindings.
}

}

// Authentication can apply for both local and client connections
// Using JWT tokens would require code to actually get the token from
// a server first, so that's not going in here for now.
if (ep.appUser) {
mqcsp.CSPUserIdPtr = ep.appUser;
mqcsp.CSPUserIdLength = strlen(ep.appUser);
mqcsp.CSPPasswordPtr = ep.appPassword;
mqcsp.CSPPasswordLength = strlen(ep.appPassword);
mqcsp.AuthenticationType = MQCSP_AUTH_USER_ID_AND_PWD;
mqcno.SecurityParmsPtr = &mqcsp;
}

if (ep.applName) {
strncpy(mqcno.ApplName,ep.applName,MQ_APPL_NAME_LENGTH);
}

// Finally we can try to connect to the queue manager
MQCONNX(ep.qmgr, &mqcno, pHConn, &compCode, &reason);
if (reason != MQRC_NONE) {
printError("MQCONNX", compCode, reason);
rc = -1;
}

return rc;
}

// Disconnect from the queue manager. No need for
// any error return code as there's no sensible recovery possible
// if it went wrong.
void disconnectQMgr(PMQHCONN pHConn) {
MQLONG compCode;
MQLONG reason;

MQDISC(pHConn, &compCode, &reason);
if (reason != MQRC_NONE) {
printError("MQDISC", compCode, reason);
}
}

// Close the object. No need for
// any error return code as there's no sensible recovery possible
// if it went wrong. Closing a queue is more common than closing other
// object types so we've got an explicitly-named function that does no more
// than calling the generic one.
void closeQueue(MQHCONN hConn, PMQHOBJ pHObj) {
closeObject(hConn,pHObj);
}

void closeObject(MQHCONN hConn, PMQHOBJ pHObj) {
MQLONG compCode;
MQLONG reason;
MQLONG options = 0;

MQCLOSE(hConn, pHObj, options, &compCode, &reason);

if (reason != MQRC_NONE) {
printError("MQCLOSE", compCode, reason);
}

return;
}

// Print the CC/RC values in a formatted string. Show both the numeric and string values
void printError(char *verb, MQLONG compCode, MQLONG reason) {
printf("Call: %s CompCode: %d [%s] Reason: %d [%s]\n", verb, compCode, MQCC_STR(compCode), reason, MQRC_STR(reason));
return;
}


/* A simple formatter for hex data showing chars and bytes */
void dumpHex(const char *title, void *buf, int length) {
int i, j;
unsigned char *p = (unsigned char *)buf;
int rows;
int o;
char line[80];
FILE *fp = stdout;

fprintf(fp, "-- %s -- (%d bytes) --------------------\n", title, length);

rows = (length + 15) / 16;
for (i = 0; i < rows; i++) {

memset(line, ' ', sizeof(line));
o = snprintf(line, sizeof(line)-1, "%8.8X : ", i * 16);

for (j = 0; j < 16 && (j + (i * 16) < length); j++) {
line[o++] = hexChars[p[j] >> 4];
line[o++] = hexChars[p[j] & 0x0F];
if (j % 4 == 3)
line[o++] = ' ';
}

o = 48;
line[o++] = '|';
for (j = 0; j < 16 && (j + (i * 16) < length); j++) {
char c = p[j];
if (!isalnum((int)c) && !ispunct((int)c) && (c != ' '))
c = '.';
line[o++] = c;
}

o = 65;
line[o++] = '|';
line[o++] = 0;

fprintf(fp, "%s\n", line);
p += 16;
}

return;
}
Loading

0 comments on commit 190cff7

Please sign in to comment.