| Россия |
Основы программирования для WebSphere MQ
Программа rewriter (модель "один к одному")
Первая программа будет достаточно простая и реализует так называемую модель "один к одному" или "точка-точка". Эта программа предназначена для чтения сообщений из очереди 1, записи их в очередь 2 и лог-файл на диске. Эта программа имеет практическое значение. Достаточно часто необходимо иметь файл переданных сообщений за определенный период времени, чтобы быстро ответить на вопрос "Было ли передано сообщение с такими идентификационными параметрами в теле сообщения:…"? WebSphere MQ сохраняет persistent сообщения на диске, но эти лог-файлы малопонятны, предназначены для восстановления сообщений при сбоях и достаточно быстро перезаписываются менеджерами очередей при значение параметра logging = circular (по умолчанию) и больших потоках сообщений (logging = linear рекомендуется только для систем промышленной эксплуатации и в этом случае администратор WebSphere MQ должен заботиться о том, чтобы лог-файлы не "замусорили" весь жесткий диск). Поэтому наша программа может быть достаточно полезной.
Автору приходилось сталкиваться с "плохим" стилем программирования, когда параметры программы "зашиваются" в текст. Даже в учебных курсах этого следует избегать, несмотря на некоторое усложнение программ. В наших программах мы будем использовать простые файлы инициализации, чтобы избежать этой ошибки. Назовем нашу программу rewriter.exe и файл инициализации rewriter.ini, в котором 1-я строка – имя очереди для чтения, 2-я строка – имя очереди для записи, 3-я строка – имя лог-файла, как показано ниже.
QUEUE_INPUT QUEUE_OUTPUT C:\TEMP\rewriter.log
Разрабатываемая программа может быть представлена в следующей последовательности псевдокода:
MQCONN MQOPEN --> цикл чтения сообщений | (на основе gmo.WaitInterval): | MQGET | MQPUT |-- конец цикла MQCLOSE MQDISC
Ниже приводится листинг программы rewriter.cpp для Microsoft Visual C++ ver.6.0. Не забудьте добавить mqm.Lib в Project => Settings => Link и обратиться к документации [ 15 ] , [ 16 ] , [ 17 ] в случае проблем с программированием.
/* Листинг программы rewriter */
/**********************************************************************/
/* Program name: Rewriter */
/* Description: Rewriter C program pass messages to output queue */
/* Function: */
/* Rewriter is a sample C program to demonstrate the main MQI calls; */
/* each message is copied from the input queue to the output */
/* queue, and sends a report to the log file */
/**********************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <signal.h>
#include <io.h>
/* includes for MQI */
#include <cmqc.h>
char queue1[48] = "";
char queue2[48] = "";
char logfilename[48] = "";
char logfilename2[48] = "";
char buf[48];
int queuenamelen;
time_t tmr;
FILE * fp;
FILE *fptr;
void cntrl_c_handler(int sig);
/* Declare MQI structures needed */
MQOD odG = {MQOD_DEFAULT}; /* Object Descriptor for GET */
MQOD odP = {MQOD_DEFAULT}; /* Object Descriptor for PUT */
MQOD odI = {MQOD_DEFAULT}; /* Object Descriptor for InitQ */
MQOD odR = {MQOD_DEFAULT}; /* Object Descriptor for report */
MQMD md = {MQMD_DEFAULT}; /* Message Descriptor */
MQGMO gmo = {MQGMO_DEFAULT}; /* get message options */
MQPMO pmo = {MQPMO_DEFAULT}; /* put message options */
MQTMC2 *trig; /* trigger message structure */
MQCHAR48 QManager; /* queue manager name */
MQHCONN Hcon; /* connection handle */
MQHOBJ Hobj; /* object handle, server queue */
MQHOBJ Hinq; /* handle for MQINQ */
MQHOBJ Hout; /* handle for MQPUT */
MQLONG O_options; /* MQOPEN options */
MQLONG C_options; /* MQCLOSE options */
MQLONG CompCode; /* completion code */
MQLONG Reason; /* reason code */
MQLONG CReason; /* reason code (MQCONN) */
MQBYTE buffer[8001]; /* message buffer */
MQLONG buflen; /* buffer length */
MQLONG messlen; /* message length received */
MQLONG Select[1]; /* attribute selectors */
MQLONG SelectValue[1]; /* value attribute selectors */
MQLONG char_count;
int main(int argc, char **argv)
{
strcpy(QManager, ""); /* Работаем с менеджером очередей по умолчанию */
if ( (fptr=fopen ("rewriter.ini","r" )) == NULL )
{printf("Cannot open rewriter.ini file" ); exit(1); }
else{ /* Открываем ini-файл и присваиваем значения переменным */
fgets(queue1, 48, fptr);
queuenamelen = strlen(queue1) - 1;
queue1[queuenamelen] = ' ';
fgets(queue2, 48, fptr);
queuenamelen = strlen(queue2) - 1;
queue2[queuenamelen] = ' ';
fgets(logfilename, 48, fptr);
queuenamelen = strlen(logfilename) - 1;
logfilename[queuenamelen] = ' ';
tmr = time(NULL);
strcpy ( buf, ctime(&tmr));
buf[strlen(buf)-1]=0; // переход на новую строку
strncat (logfilename, buf,10);
strcpy(odG.ObjectName, queue1);
strcpy(odP.ObjectName, queue2);
fclose (fptr);
}
MQCONN(QManager, &Hcon, &CompCode, &CReason);
if (CompCode == MQCC_FAILED)
{
printf("MQCONN ended with reason code %ld\n", CReason);
exit(CReason);
}
O_options = MQOO_INPUT_SHARED + MQOO_FAIL_IF_QUIESCING;
MQOPEN(Hcon, &odG, O_options, &Hobj, &CompCode, &Reason); /* открываем очередь для чтения - &odG */
if (Reason != MQRC_NONE) { printf("MQOPEN (input) ended with reason code %ld\n", Reason); }
if (CompCode == MQCC_FAILED) { exit(Reason); }
O_options = MQOO_OUTPUT + MQOO_FAIL_IF_QUIESCING;
MQOPEN(Hcon, &odP, O_options, &Hout, &CompCode, &Reason); /* открываем очередь для записи - &odP */
if (Reason != MQRC_NONE) { printf("MQOPEN (output) ended with reason code %ld\n", Reason); }
if (CompCode == MQCC_FAILED) { exit(Reason); }
fp = fopen (logfilename,"a");
if ( fp==NULL ){ printf("Cannot open log file %s\n", logfilename); }
printf("Rewriter(C) sending messages from %s to %s and to log-file %s \n
",odG.ObjectName, odP.ObjectName, logfilename);
/*****************************************************************************/
/* Читаем сообщения из QUEUE_INPUT и пишем в QUEUE_OUTPUT */
/* до тех пор пока не встретим сообщение об ошибке */
/*****************************************************************************/
buflen = sizeof(buffer) - 1;
while (CompCode == MQCC_OK)
{
gmo.Options = MQGMO_ACCEPT_TRUNCATED_MSG + MQGMO_WAIT;
gmo.WaitInterval = 3000; /* Ожидаем новые сообщения 3 секунды */
//gmo.WaitInterval = MQWI_UNLIMITED;
memcpy(md.MsgId, MQMI_NONE, sizeof(md.MsgId));
memcpy(md.CorrelId, MQMI_NONE, sizeof(md.CorrelId));
MQGET(Hcon, Hobj, &md, &gmo, buflen, buffer, &messlen, &CompCode, &Reason);
if ((CompCode == MQCC_OK) || (CompCode == MQCC_WARNING))
{
buffer[messlen] = '\0'; /* заносим символ конец строки в буфер с прочитанным сообщением */
buflen = messlen;
MQPUT(Hcon, Hout, &md, &pmo, buflen, buffer, &CompCode, &Reason);
if ((CompCode == MQCC_OK) || (CompCode == MQCC_WARNING))
{
tmr = time(NULL);
strcpy ( buf, ctime(&tmr));
buf[strlen(buf)-1]=0; // переход к новой строке
Reason = fprintf( fp, "%s: %s\n", buf, buffer );
}
} /* конец обработки входного сообщения */
} /* конец цикла чтения/записи сообщений функциями MQGET, MQPUT */
fclose (fp);
C_options = 0; /* нет никаких опций при закрытии */
MQCLOSE(Hcon, &Hobj, C_options, &CompCode, &Reason); /* закрываем очередь для чтения */
if (Reason != MQRC_NONE)
{printf("MQCLOSE (input) ended with reason code %ld\n", Reason); }
MQCLOSE(Hcon, &Hout, C_options, &CompCode, &Reason); /* закрываем очередь для записи */
if (Reason != MQRC_NONE)
{printf("MQCLOSE (output) ended with reason code %ld\n", Reason); }
if (CReason != MQRC_ALREADY_CONNECTED)
{
MQDISC(&Hcon, &CompCode, &Reason);
if (Reason != MQRC_NONE){ printf("MQDISC ended with reason code %ld\n", Reason); }
}
return(0);
}
Листинг
9.1.
Rewriter C program pass messages to output queue
В данной версии мы выходим из цикла программы по опции gmo.WaitInterval = 3000, когда ожидаем сообщение в очереди в течении 3 сек, а его там нет (опция gmo.WaitInterval работает быстрее, чем если бы мы опрашивали очередь по собственному временному циклу). Другой вариант программы может быть таким. Задаем gmo.WaitInterval = MQWI_UNLIMITED ; что соответствует gmo.WaitInterval= -1. Программа будет крутиться "бесконечно" до тех пор, пока мы не остановим её принудительно, например, нажатием клавиш CNTRL_C (стандартный останов). В этом случае нужно добавить обработчик прерываний по нажатию CNTRL_C потому, что при таком выходе объекты очереди останутся не закрытыми и идентификаторы объектов окажутся "зависшими" в виртуальной памяти компьютера. А это может привести к тому, что при повторном запуске программы эти "зависшие" идентификаторы будут мешать нормальному функционированию программы. Во втором варианте открытие и закрытие лог-файла необходимо также делать в обработчике прерываний или после каждой команды MQPUT , в противном случае лог-файл не будет формироваться. Следует отметить, что размер массива buffer ограничивает длину сообщения 8Кб и при появлении сообщений большей длины следует увеличить размер буфера.
Программа rewriter.exe работает достаточно быстро и сравнительные скорости работы данного алгоритма при длине сообщения 1Кб на компьютере INTEL Pentium 1.8Ггц приведены в таблице ниже.
| Язык программы\тип очереди | Not Persistent | Persistent |
|---|---|---|
| С++ | 1000 сооб/сек | 400 сооб/сек |
| Visual Basic 6.0 | 200 сооб/сек | 140 сооб/сек |
Увеличение длины сообщения не ведет к пропорциональному уменьшению скорости. Эти исследования читатель может проделать самостоятельно. Реальные приложения, работающие с базами данных, имеют скорость обработки сообщений в 3-4 раза меньше.
Возвращаясь к вопросу о стилях программирования, следует отметить, что обработка кода ошибки является обязательным атрибутом качественного программирования и об этом не следует забывать. В нашей программе дается предупредительное сообщение и делается выход из программы. Если этого не сделать, то простая описка в rewriter.ini файле приведет к зависанию программы и мучительному поиску причин такого зависания, не говоря о других более сложных ситуациях, например, когда очередь открыта эксклюзивно другим приложением.
Для версии программы gmo.WaitInterval = MQWI_UNLIMITED полезно сделать вывод на экран передаваемых сообщений, чтобы наблюдать динамику работы созданного интерфейса. Таких улучшений может быть достаточно много и мы рассмотрим две достаточно полезные модификации.
-
Программа rewriter может вызываться как MQSeries-триггер. Для этого параметры можно задать следующим образом. Входная очередь – это очередь, на которой определен триггеринг. Выходная очередь – это User Data в триггерном процессе и имя лог файла – это Environment Date в триггерном процессе. В этом случае код в начале программы будет такой.
/* Код для вызова rewriter.exe как MQSeries-триггер */ int main(int argc, char **argv) { if (argc > 1) {trig = (MQTMC2*)argv[1]; strncpy(odG.ObjectName, trig->QName, MQ_Q_NAME_LENGTH); strncpy(queue1, trig->QName, MQ_Q_NAME_LENGTH); strncpy(QManager, trig->QMgrName, MQ_Q_MGR_NAME_LENGTH); strncpy(odP.ObjectName, trig->UserData, MQ_PROCESS_USER_DATA_LENGTH); strncpy(queue2, trig->UserData, MQ_PROCESS_USER_DATA_LENGTH); strncpy(logfilename2, trig->EnvData, 48); }Возможная модификация этого варианта - программа rewriter может вызываться с передачей параметров через командную строку и эту модификацию читатель может проделать самостоятельно.
- Программа rewriter может быть модифицирована в программу разветвитель mqsplitter.exe: чтение сообщений из очереди 1 и запись их в очередь 2, в очередь 3 и лог-файл на диске.
Можно сделать программу mqsplitter.exe на разных языках, например, на Visual Basic 6.0 с интерфейсом, показанным на рис.9.1, и сравнить производительность программ на разных языках, реализующих один и тот же алгоритм. Такая задача будет хорошим лабораторным практикумом.
Модификацию программы mqsplitter.exe читателю предлагается сделать самостоятельно и одновременно проверить идею создания "вечного двигателя". Для создания "вечного двигателя" понадобиться изменить исходный mqsplitter.ini файл следующим образом:
Если в очереди QUEUE_INPUT будет хотя бы одно сообщение, то ваша программа будет посылать сообщения в очередь QUEUE_OUTPUT1 до тех пор, пока не будет остановлена. Можно заложить в ini-файл программы пятый параметр: время опроса очереди, измеряемое в миллисекундах. Такая программа окажется весьма полезной при тестировании интерфейсов.
