-
Notifications
You must be signed in to change notification settings - Fork 166
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* 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
Showing
16 changed files
with
2,158 additions
and
61 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
Oops, something went wrong.