Характеристики объектов Allowed, а также параметров триггеринга, извлечении статистических
данных (Message Count и т.п.). Необходимость работы с функциями
и , предназначенными для этих целей, возникает не так часто, но
без этого иногда трудно обойтись.
Функция позволяет извлечь атрибуты любой очереди, процесса,
менеджера или списка кластеров namelist. дает возможность
изменить эти параметры, но только для очереди. Обе функции используют
массивы идентификаторов (selectors), в которых указывается, какие
характеристики объектов должны быть извлечены или изменены. Имена
идентификаторов имеют префиксы: MQCA_ для символьных данных
(например, имя очереди) и MQIA_ для числовых данных (например,
Допустим, для некоторой очереди необходимо определить характеристики: количество сообщений в очереди, максимальное количество сообщений, имя очереди и ее описание.
Формат команды:
MQINQ (Hconn, Hobj, SelectorCount, Selectors,
IntAttrCount, IntAttrs, CharAttrLength,
CharAttrs, CompCode, Reason)
Очередь должна быть открыта и Hconn, Hobj известны.
Пусть общее количество идентификаторов MQLONG SelectorCount = 4 ; они
должны быть перечислены в массиве MQLONG Selectors[4] ;
Selectors [0] = MQIA_CURRENT_Q_DEPTH; Selectors [1] = MQIA_MAX_Q_DEPTH; Selectors [2] = MQCA_Q_NAME; Selectors [3] = MQCA_Q_DESC;
Число цифровых атрибутов задается в MQLONG IntAttrCount = 2 ; они
должны вернуться в массив MQLONG IntAttrs [2] ; Длина затребованных
символьных данных MQLONG CharAttrLength = 112 ; (48 для MQCA_Q_NAME и
64 для MQCA_Q_DESC ) и они должны вернуться в массив CHAR
CharAttrs[112] = "" ; Теперь можно выполнять . Не стоит забывать
одно важное правило: цифровые и символьные характеристики следуют в
массивах IntAttrs и CharAttrs в том порядке, как они перечислены в
массиве Selectors.
В случае, когда программа rewriter (см. лекцию 8), запускается как
триггер, при поступлении сообщения в очередь INPUT.Q, использовать
ini-файл неудобно и гораздо проще задать необходимые параметры в
свойствах очереди. Для этого в свойствах очереди INPUT.Q в закладке Triggering указывается:
Trigger Control = On
Initiation Queue Name = INPUT_INIT.Q
Process Name = rewriter
а для процесса rewriter определяется
PROCESS_USER_DATA = OUTPUT.Q
/* queue output */
PROCESS_APPL_ID = "C:\rewriter\rewriter.exe"
PROCESS_ENV_DATA = rewriter.log
/* имя лог-файла не более 24 символов */
Теперь в программе легко можно извлечь все необходимые параметры с
помощью . После завершения работы программы очередь
инициализации следует очистить. Это делает следующий фрагмент кода:
MQLONG Select[1];
/* attribute selectors */
MQLONG SelectValue[1];
/* value attribute selectors */
MQLONG char_count;
char queueinitname[48] = "";
int queuenamelen;
Select[0] = MQCA_INITIATION_Q_NAME;
/* attribute selectors */
queuenamelen = 0;
char_count = 48;
MQINQ(Hcon, Hobj, 1, Select, 0, NULL,
char_count, queueinitname,
CompCode, Reason);
queuenamelen = strlen(queueinitname) - 1;
queueinitname[queuenamelen] = ' ';
strncpy(odI.ObjectName, queueinitname, 24);
O_options = MQOO_INPUT_SHARED +
MQOO_FAIL_IF_QUIESCING;
MQOPEN(Hcon, odI, O_options, Hinq,
CompCode, Reason);
if (Reason != MQRC_NONE) {
printf("MQOPEN (input) ended with reason
code %ld\n", Reason); }
if (CompCode == MQCC_FAILED) {
exit(Reason); }
memcpy(md.MsgId, MQMI_NONE,
sizeof(md.MsgId));
memcpy(md.CorrelId, MQMI_NONE,
sizeof(md.CorrelId));
while (CompCode == MQCC_OK)
{
gmo.Options = MQGMO_ACCEPT_TRUNCATED_MSG +
MQGMO_WAIT;
gmo.WaitInterval = 1;
memcpy(md.MsgId, MQMI_NONE,
sizeof(md.MsgId));
memcpy(md.CorrelId, MQMI_NONE,
sizeof(md.CorrelId));
buflen = sizeof(buffer) - 1;
MQGET(Hcon, Hinq, md, gmo, buflen,
buffer, messlen, CompCode,
Reason);
// printf("Inituation queue clear with
// reason code %ld and CompCode %ld\n",
// Reason, CompCode);
}
CompCode = MQCC_OK ;
В этом фрагменте используется для извлечения только одного
параметра: имени очереди инициализации. Очистка этой очереди делается
командой до тех пор, пока очередь не будет пуста.
Функция по формату полностью аналогична , разница между
ними заключается только в направлении потоков данных. В качестве
примера можно рассмотреть работу триггера с условиями Trigger Type =
First и Trigger Depth = 1. При срабатывании триггера флажок из
состояния Trigger Control = On переходит в состояние Trigger Control
= Off и его надо восстанавливать. Это делает следующий фрагмент кода:
Select[0] = MQIA_TRIGGER_CONTROL;
/* attribute selectors */
SelectValue[0] = 1 ;
MQSET(Hcon, Hobj, 1, Select, 1, SelectValue,
0, NULL, CompCode, Reason);
Заключительный вывод по разделу: функция позволяет извлечь
атрибуты любой очереди, процесса, менеджера или списка кластеров namelist, а функция - изменить эти параметры, но только для
очереди.
Теперь полезно изучить способы контроля доставки сообщений, тем более
что для этого существуют специальные поля в дескрипторе сообщений: и . Например, некоторое приложение отправляет запросы
на обработку в очередь Queue1 и ожидает уведомления о доставке в
очереди ReplyToQ = Queue2. Совершенно очевидно, что уведомления о
доставке не обязаны появиться в очереди Queue2 в той же
последовательности, в какой отправлялись запросы в очередь Queue1.
Как же в таком случае отслеживать доставку сообщений?
Другой пример, приложение отправляет запросы в разные системы, в
частности, по банковским счетам клиента и по проверке его кредитной
истории и ожидает получить ответ от запрашиваемых систем в одну и ту
же очередь для всех клиентов. Как же идентифицировать поступающие
ответы? Самое простое - использовать идентификаторы и ,
которые являются уникальными для каждого сообщения и создаются на
основе времени выполнения команды . Идентификаторы и создаются менеджером, если они установлены в NULL, либо
заданы опции MQPMO_NEW_MSG_ID и/или MQPMO_NEW_CORREL_ID ; в противном
случае, и создаются приложением.
Таким образом, задавая значения и при выполнении , появляется возможность автоматически получать значения и при чтении командой . При этом не надо забывать, что в
цикле считывания перед командой следует ставить обнуление этих
переменных ( и ), иначе можно
получить ошибку считывания сообщений из очереди MQRC_NO_MSG_AVAILABLE, несмотря на то, что сообщения в очереди
имеются.
Рассмотрим на примере, как работает с и на практике.
Пусть в некоторую очередь поступили сообщения в определенном порядке,
как указано в таблице ниже.
| Номер сообщения | ||
|---|---|---|
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 1 | 3 |
| 4 | 2 | 1 |
| 5 | 2 | 2 |
| 6 | 3 | 1 |
Теперь варьируя и , можно читать самые разные
сообщения.
читает первое доступное
сообщение независимо от и , то есть сообщение 1.
может прочитать только одно
сообщение, имеющее значение , это сообщение 3.
читает первое сообщение со
значением , это сообщение 4.
может прочитать только одно сообщение,
имеющее уникальное сочетание и , это сообщение
5.
Таким образом, если никакие другие сообщения не поступят в очередь,
то в очереди после четырех останутся 2 сообщения. Здесь уместен
вопрос о производительности работы менеджера очередей при таком
поиске сообщений. Если число сообщений в очереди не превышает 100, то
время поиска будет незначительным, оно практически не зависит от
числа сообщений. Но если число сообщений в очереди превысит 1000, то
менеджер будет сканировать очередь и время поиска будет заметным. На
OS/390 или (но
не одновременно) как индекс и это существенно ускорит поиск нужного
сообщения в очереди. Следует также отметить, что на других платформах
(AS/400, HP_UX, AIX, Sun Solaris, Windows) в версии 5.3 MQMO_MATCH_MSG_ID
и MQMO_MATCH_CORREL_ID. Если эти опции соответствия не будут
определены, то и будут игнорироваться, как если бы
использовались опции MQMI_NONE и MQCI_NONE и будет извлекаться
очередное сообщение.
Сформулируем одно общее правило при разработке программ с контролем доставки сообщений
с помощью и : если приложение перемещает сообщение из
входной очереди в выходную, то входного сообщения перемещается
в выходного сообщения и создается новый уникальный
идентификатор выходного сообщения. На уровне псевдокода это
правило можно записать следующим образом:
INPUT_MSG_DESC.MsgId = MQMI_NONE;
INPUT_MSG_DESC.CorrelId = MQCI_NONE;
MQGET (..........);
/* Обработка входного сообщения */
OUT_MSG_DESC.MsgId = MQMI_NONE;
OUT_MSG_DESC.CorrelId = INPUT_MSG_DESC.MsgId;
MQPUT (..........);
Разрабатывая логику в цепи программ-обработчиков сообщений на разных
платформах следует стремиться к тому, чтобы содержал
исходного запроса, а выходного сообщения содержал уникальный
идентификатор, полученный наиболее важной в функциональном смысле
выходной программой. На основе такой логики легко получить
подтверждение на выходе, что исходный запрос был отработан, и
промежуточные программы проставили свой в выходном сообщении.
Это позволяет осуществить контроль времени прохождения запроса и
получения сообщения-отчета.
В завершение раздела, уж если речь пошла о контроле доставки
сообщений, необходимо отметить возможность использования полей context (контекст) в дескрипторе сообщения для контроля авторизации
пользователя и обеспечения безопасности. Группа полей context состоит
из 8 полей, их значения по умолчанию приводятся в таблице ниже.
| Поле | Размер | Значение | Поле | Размер | Значение |
|---|---|---|---|---|---|
| UserIdentifier | CHAR12 | Имя пользователя | PutAppName | CHAR28 | Имя приложения |
| AccountingToken | BYTE32 | Учетный номер приложения-отправителя | PutDate | CHAR8 | YYYYMMDD |
| AppIdentityData | CHAR32 | Пробелы | PutTime | CHAR8 | HHMMSSTH |
| PutApplType | LONG | UNIX и т.п. | AppOriginData | CHAR4 | пробелы |
Используя UserIdentifier и AccountingToken, а также опции для работы
с полями для альтернативной авторизации,
позволяющей одному пользователю посылать сообщения от имени другого,
можно контролировать авторизацию пользователей, запускающих задачи.
Эти права устанавливает . Об этом не следует забывать при разработке приложений.
До настоящего момента говорилось о
Группа сообщений это одно или несколько
MsgId и CorrelId ; MsgId и CorrelId .Пусть необходимо объединить в группу три сообщения. Для этого в
дескрипторе сообщений полю MQMD_GroupId (24 байта) нужно присвоить
уникальный идентификатор и записать его в каждом сообщении.
Следующему полю MQMD_MsgSeqNumber присвоить значения 1, 2 и 3 для
первого, второго и третьего сообщений, соответственно, как показано
это на рис.11.1. Если сообщение не включено в группу, то для него будет NULL (MQGI_NONE), а в MsgSeqNumber будет значение 1. MsgSeqNumber во время
выполнения функции , если в PMO задана опция MQPMO_LOGICAL_ORDER. В этом случае также понадобится установить
значение в новое поле дескриптора сообщений: MQMD.MsgFlags, которое
может иметь значения MQMF_MSG_IN_GROUP и MQMF_LAST_MSG_IN_GROUP. Если
в было установлено значение MQGI_NONE, то для будет
сгенерирован новый уникальный идентификатор при использовании MQPMO_LOGICAL_ORDER. И еще одно правило для вывода группы сообщений:
PERSISTENCE и NOT_PERSISTENCE не должны
перемешиваться в одной группе.
Для пояснения изложенного следует привести фрагмент программы, использующей вывод сообщений в виде группы.
if ((CompCode == MQCC_OK) ||
(CompCode == MQCC_WARNING))
{
pmo.Options = MQPMO_LOGICAL_ORDER +
MQPMO_SYNCPOINT ;
memcpy(md.GroupId, "1",
sizeof(md.GroupId));
memcpy(md. MsgFlags, "MQMF_MSG_IN_GROUP",
sizeof(md. MsgFlags));
//memcpy(md.MsgSeqNumber, 1,
// sizeof(md.MsgSeqNumber));
//memcpy(md.MsgFlags, MQMF_SEGMENT,
// sizeof(md.MsgFlags));
/* блок формирования buffer */
MQPUT1(Hcon, odR, md, pmo, messlen,
buffer, CompCode, Reason);
if (Reason != MQRC_NONE)
printf("MQPUT1 ended with reason
code %ld\n", Reason);
}
/* конец цикла чтения поступающих сообщений */
memcpy(md. MsgFlags,
"MQMF_ LAST_MSG_IN_GROUP",
sizeof(md. MsgFlags));
/* блок формирования buffer */
MQPUT1(Hcon, odR, md, pmo, messlen,
buffer, CompCode, Reason);
if (Reason != MQRC_NONE)
printf("MQPUT1 ended with
reason code %ld\n", Reason);
(рис 11.1) Группа сообщений и сегментация сообщенияПроцесс извлечения группы во многом будет зависеть от значений, заданных в опции GMO.
Если задать значение MQGMO_ALL_MSGS_AVAILABLE, то сообщения будут
доступны для чтения только, когда все сообщения группы поступят в
очередь. Если задана опция MQGMO_LOGICAL_ORDER, то сообщения можно
извлекать в порядке, определяемом MsgSeqNumber и только по одному
сообщению из группы. Если опция MQGMO_LOGICAL_ORDER не задана, то
приложение должно выбирать сообщения двумя способами:
GroupId и MsgSeqNumber, либо переключаться на MQGMO_LOGICAL_ORDER, либо переслать сообщения дальше.MatchOption, если они заданы:MQMO_MATCH_MSG_ID,MQMO_MATCH_CORREL_ID,MQMO_MATCH_GROUP_ID,MQMO_MATCH_MSG_SEQ_NUMBER.Например, опция CORREL_ID означает, что должно выбираться сообщение,
корреляционный идентификатор которого, соответствует значению поля параметра MsgDesc функции . Это согласование является
дополнением к любому другому соответствию, заданному в MatchOption.
Если корреляционный идентификатор есть MQCI_NONE, то это равносильно
тому, что опция MQMO_MATCH_CORREL_ID не определена.
Теперь следует дать расширенное определение сегмента сообщения.
Сегментом сообщения называется , MsgSeqNumber и Offset. Из лекции 8 известно, что Offset это поле формата MQLONG в структурах MQOD и MQMD. Таким
образом, одно
Разбиение на сегменты ( MsgFlag значение MQMF_SEGMENTATION_ALLOWED, и менеджер очередей
определил, что сообщение является слишком большим для его MaxMsgLength или для значения параметра MaxMsgLength для очереди, то
тогда менеджер осуществляет
Реассемблирование с помощью менеджера осуществляется, если при чтении
сообщений командой была задана GMO-опция: MQGMO_COMPLETE_MESSAGE. В этом случае извлекаются все сегменты, и
только полное MQRC_UOW_AVAILABLE ) или
приложения (опция MQRC_UOW_NOT_AVAILABLE ).
Для опцию MQPMO_LOGICAL_ORDER и проследить за правильным включением
в поле MsgFlags флажков MQMF_SEGMENT и MQMF_LAST_SEGMENT. Менеджер
очередей позаботиться о назначении и поддержании , MsgSeqNumber и Offset.
Реассемблирование с помощью приложения осуществляется, если при
чтении сообщений командой была задана GMO-опция: MQGMO_COMPLETE_MESSAGE и флажок MsgFlags имеет значение MQMF_SEGMENTATION_ALLOWED. В этом случае приложение извлекает
сегменты самостоятельно в зависимости от ограничений на свой размер
буфера. Опции MQGMO_ALL_MSGS_AVAILABLE и MQGMO_ALL_SEGMENTS_AVAILABLE
позволят начать работу, если все сообщения или все сегменты поступят
в очередь. Как только первое сообщение поступит в очередь, можно
использовать MQGMO_LOGICAL_ORDER опцию, чтобы быть уверенным, что все
последующие сегменты MQGMO_LOGICAL_ORDER не задана, то приложение может выбирать
сообщения на основе опций соответствия MatchOption, как это делалось
для групп:
MQMO_MATCH_MSG_ID,MQMO_MATCH_CORREL_ID,MQMO_MATCH_GROUP_ID,MQMO_MATCH_MSG_SEQ_NUMBER,MQMO_MATCH_OFFSET.Приложение может определить окончание работы с сегментом с помощью
возвращаемого значения поля MQGMO.SegmentStatus, которое может
принимать следующие значения: MQSS_SEGMENT, MQSS_LAST_SEGMENT, MQSS_NOT_A_SEGMENT.
В завершение раздела можно привести примеры использования функций и для вывода и чтения
/* пример использования функции MQPUT */
PMO.Options = MQGMO_LOGICAL_ORDER +
MQGMO_SYNCPOINT
/* помещаем в очередь первое логическое
сообщение*/
MQPUT (MQMF_MSG_IN GROUP + MQMF_SEGMENT)
MQPUT (MQMF_MSG_IN GROUP + MQMF_SEGMENT)
MQPUT (MQMF_MSG_IN GROUP + MQMF_LAST_SEGMENT)
/* помещаем в очередь второе логическое
сообщение */
MQPUT (MQMF_MSG_IN GROUP + MQMFSEGMENT)
/* помещаем в очередь третье логическое
сообщение */
MQPUT (MQMF_LAST_MSG_IN GROUP + MQMF_SEGMENT)
MQPUT (MQMF_ LAST_MSG_IN GROUP +
MQMF_LAST_SEGMENT)
MQCMIT
/* пример использования функции MQGET */
GMO.Options = MQGMO_LOGICAL_ORDER +
MQGMO_SYNCPOINT +
MQGMO_ALL_MSGS_AVAILABLE +
MQGMO_WAIT
Do while (Not MQGS_LAST_MSG_IN GROUP and
Not MQSS_LAST_SEGMENT)
MQGET (........)
/* обработка сегмента или законченного
логического сообщения */
End
MQCMIT
Иногда возникает потребность применять mqexit.dll с точкой входа MsgExit. Для работы программы,
ее надо прописать в том канале, через который идут сообщения:
| Message Exit Name: | mqexit(MsgExit) |
| Message Exit Data: | \mydir\mqexit.ini |
С помощью программы это может быть сделано командой:
alter chl(mychannel) chltype(mychannel_type)
msgexit(' mqexit(MsgExit)')
msgdata('\mydir\mqexit.ini')
Программа mqexit.dll и настроечный файл mqexit.ini помещаются в
стандартной директории (mydir ) для C:\Program Files\IBM\WebSphere MQ\Exits\, для UNIX HP_UX
это /var/mqm/exits/, аналогичные пути существуют для других
платформ).
Настроечный файл mqexit.ini может содержать имя лог файла, имя
буферного файла для записи входящего сообщения; имя очереди, в
которую перекладывается содержимое буферного файла; имя менеджера,
ведь менеджеров может быть несколько на одном сервере.
В учебных целях стоит упростить задачу, не использовать
mqexit.ini-файл и записать имя лог-файла непосредственно в Message
Exit Data (например, C:\TEMP\mqexit.log ), а с сообщениями пусть
разбирается прежняя программа-обработчик, читающая входящие сообщения
из очереди. При этом предполагается, что используется менеджер
очередей по умолчанию. В этом случае программа mqexit.dll для
Windows выглядит следующим образом.
/* Листинг программы mqexit.dll */
/************************************************************************/
/* Program name: mqexit */
/* Description: This is a sample message exit function record all messages */
/* This function will work on any channels, e.g. send, receive, requester, etc. */
/* This function logs all channel activities into the Channel Log file. */
/* Please see Intercommunication documentation to specify the Channel Message Exit */
/* Authors: Vladimir Makushkin, Moscow, Russia */
/* email: vmakushkin@mail.ru */
/* Disclaimer: this program has been tested under HP_UX and Windows to the */
/* authors' satisfaction. You may use this program at your own risk. Author are not */
/* responsible for any unexpected results generated by this program. */
/************************************************************************/
/* standard headers */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <locale.h>
#include <sys/types.h>
#include <sys/timeb.h>
#include <time.h>
/* MQSeries headers */
#include <cmqc.h>
#include <cmqxc.h>
char timebuf??(130??);
char Channel_Logfile??(129??);
char MsgBufFile??(129??);
char ini_filename??(129??);
FILE * OST = 0;
#define DBGPRINTF(x) { \
OST=fopen(Channel_Logfile,"a+"); \
if(OST) fprintf x; \
fclose(OST); \
}
void writer (long *, char *); /* prototype for file writer function */
void getINI( void );
void MQENTRY MQStart(void) {;}
void MQENTRY MsgExit( PMQCXP pChannelExitParams,
PMQCD pChannelDefinition,
PMQLONG pDataLength,
PMQLONG pAgentBufferLength,
PMQBYTE AgentBuffer,
PMQLONG pExitBufferLength,
PMQPTR pExitBufferAddr)
{
short i;
struct timeb t;
char millisec??(25??);
time_t clock = time( (time_t*) NULL);
struct tm *tmptr = localtime(clock);
strcpy(timebuf, asctime(tmptr));
ftime( t );
memset( millisec, 0, sizeof( millisec ) );
sprintf( millisec, " Time:%ld.%d", t.time, t.millitm );
strcat( timebuf, millisec );
if ( strlen(pChannelExitParams->ExitData) )
{
memset( ini_filename, 0, sizeof( ini_filename ) );
strncpy( ini_filename, pChannelExitParams->ExitData, MQ_EXIT_DATA_LENGTH );
/*------------------------*/
/* remove space from data */
/*------------------------*/
for ( i = MQ_EXIT_DATA_LENGTH - 1; i > 0; i-- )
{
if ( ini_filename??(i??) != ' ' ini_filename??(i??) )
break;
ini_filename??(i??) = 0;
}
}
getINI();
void writer(long *lngth, char *bf)
{
long msgsize;
int n;
FILE *fp;
getINI();
fp = fopen(MsgBufFile, "ab");
msgsize = *lngth;
n = fwrite(msgsize, sizeof(msgsize), 1, fp); /* put record length */
n = fwrite(bf, msgsize, 1, fp); /* and message buffer */
fclose(fp);
}
/*---------------------------------------------------------
getINI() - read information from INI file
-----------------------------------------------------------*/
void getINI( void )
{
FILE *ini_fp;
char *ptr, *name_ptr;
char field_val??(129??), Ini_s??(129??), fn??(129??);
short len;
memset( Ini_s, 0, sizeof( Ini_s ) );
memset( field_val, 0, sizeof( field_val ) );
memset( MsgBufFile, 0, sizeof ( MsgBufFile ) );
memset( Channel_Logfile, 0, sizeof( Channel_Logfile ) );
if ( strlen( ini_filename ) ) /* if Channel Exit defined INI filename */
strcpy( fn, ini_filename ); /* use it */
else /* otherwise use default INI filename */
strcpy( fn, INI_FILENAME );
if ( (ini_fp = fopen( fn, "r" )) == NULL )
{
fprintf(stderr, "\nUnable to open %s. Error #: %d", fn, errno );
return;
}
/*-----------------------------*/
/* read the INI file until eof */
/*-----------------------------*/
while ( (ptr = fgets( Ini_s, sizeof(Ini_s) - 1, ini_fp )) != NULL )
{
len = strlen( Ini_s );
if ( Ini_s??(len -1??) == LINEFEED )
Ini_s??(len-1??) = 0; /* null out '\n' */
if ( (name_ptr = strchr( Ini_s, EQUAL )) == NULL ) /* no '=' found */
continue;
ptr = name_ptr + 1;
*name_ptr = 0; /* set null for s[] */
strcpy( field_val, ptr );
fclose( ini_fp );
return;
}
В качестве комментария к программе надо отметить, что обязательным
является соблюдение трех рекомендаций для Windows из документа
-
MQStart и ChannelExit программы mqexit.c:#include <cmqc.h>
#include <cmqxc.h>
void MQStart() {;}
/* dummy entry point - for
consistency only */
void MQENTRY ChannelExit (
PMQCXP pChannelExitParms,
PMQCD pChannelDefinition,
PMQLONG pDataLength,
PMQLONG pAgentBufferLength,
PMQVOID pAgentBuffer,
PMQLONG pExitBufferLength,
PMQPTR pExitBufferAddr)
{
... Insert code here
}
MQMVX.LIB. В
настройках проекта для генерации C/C++ кода изменить выпадающее меню
с "Use Run-.DEF файл ( mqexit.def ):LIBRARY mqexit
DESCRIPTION 'Provides Retry
and Channel exits'
HEAPSIZE 4096
STACKSIZE 8192
EXPORTS ChannelExit
И после этого проходящие по каналу сообщения начинают записываться в
файл, определенный в Message Exit Data, включая заголовок и данные
сообщения. Скорость прохождения сообщений с использованием
В заключение лекции следует отметить, что остались механизмы, которые не нашли отражение в этой книге и могли бы представлять интерес для читателя-программиста, а именно:
С этими вопросами читателю предлагается ознакомиться самостоятельно по документации.
Ключевым моментом интеграции приложений является использование
WebSphere Business Integration Message Broker $$\text{\textregistered}$$, сокращенно WBI
Message Broker или просто WBI Broker (ранее - IBM
Характеристики объектов Allowed, а также параметров триггеринга, извлечении статистических
данных (Message Count и т.п.). Необходимость работы с функциями
и , предназначенными для этих целей, возникает не так часто, но
без этого иногда трудно обойтись.
Функция позволяет извлечь атрибуты любой очереди, процесса,
менеджера или списка кластеров namelist. дает возможность
изменить эти параметры, но только для очереди. Обе функции используют
массивы идентификаторов (selectors), в которых указывается, какие
характеристики объектов должны быть извлечены или изменены. Имена
идентификаторов имеют префиксы: MQCA_ для символьных данных
(например, имя очереди) и MQIA_ для числовых данных (например,
Допустим, для некоторой очереди необходимо определить характеристики: количество сообщений в очереди, максимальное количество сообщений, имя очереди и ее описание.
Формат команды:
MQINQ (Hconn, Hobj, SelectorCount, Selectors,
IntAttrCount, IntAttrs, CharAttrLength,
CharAttrs, CompCode, Reason)
Очередь должна быть открыта и Hconn, Hobj известны.
Пусть общее количество идентификаторов MQLONG SelectorCount = 4 ; они
должны быть перечислены в массиве MQLONG Selectors[4] ;
Selectors [0] = MQIA_CURRENT_Q_DEPTH; Selectors [1] = MQIA_MAX_Q_DEPTH; Selectors [2] = MQCA_Q_NAME; Selectors [3] = MQCA_Q_DESC;
Число цифровых атрибутов задается в MQLONG IntAttrCount = 2 ; они
должны вернуться в массив MQLONG IntAttrs [2] ; Длина затребованных
символьных данных MQLONG CharAttrLength = 112 ; (48 для MQCA_Q_NAME и
64 для MQCA_Q_DESC ) и они должны вернуться в массив CHAR
CharAttrs[112] = "" ; Теперь можно выполнять . Не стоит забывать
одно важное правило: цифровые и символьные характеристики следуют в
массивах IntAttrs и CharAttrs в том порядке, как они перечислены в
массиве Selectors.
В случае, когда программа rewriter (см. лекцию 8), запускается как
триггер, при поступлении сообщения в очередь INPUT.Q, использовать
ini-файл неудобно и гораздо проще задать необходимые параметры в
свойствах очереди. Для этого в свойствах очереди INPUT.Q в закладке Triggering указывается:
Trigger Control = On
Initiation Queue Name = INPUT_INIT.Q
Process Name = rewriter
а для процесса rewriter определяется
PROCESS_USER_DATA = OUTPUT.Q
/* queue output */
PROCESS_APPL_ID = "C:\rewriter\rewriter.exe"
PROCESS_ENV_DATA = rewriter.log
/* имя лог-файла не более 24 символов */
Теперь в программе легко можно извлечь все необходимые параметры с
помощью . После завершения работы программы очередь
инициализации следует очистить. Это делает следующий фрагмент кода:
MQLONG Select[1];
/* attribute selectors */
MQLONG SelectValue[1];
/* value attribute selectors */
MQLONG char_count;
char queueinitname[48] = "";
int queuenamelen;
Select[0] = MQCA_INITIATION_Q_NAME;
/* attribute selectors */
queuenamelen = 0;
char_count = 48;
MQINQ(Hcon, Hobj, 1, Select, 0, NULL,
char_count, queueinitname,
CompCode, Reason);
queuenamelen = strlen(queueinitname) - 1;
queueinitname[queuenamelen] = ' ';
strncpy(odI.ObjectName, queueinitname, 24);
O_options = MQOO_INPUT_SHARED +
MQOO_FAIL_IF_QUIESCING;
MQOPEN(Hcon, odI, O_options, Hinq,
CompCode, Reason);
if (Reason != MQRC_NONE) {
printf("MQOPEN (input) ended with reason
code %ld\n", Reason); }
if (CompCode == MQCC_FAILED) {
exit(Reason); }
memcpy(md.MsgId, MQMI_NONE,
sizeof(md.MsgId));
memcpy(md.CorrelId, MQMI_NONE,
sizeof(md.CorrelId));
while (CompCode == MQCC_OK)
{
gmo.Options = MQGMO_ACCEPT_TRUNCATED_MSG +
MQGMO_WAIT;
gmo.WaitInterval = 1;
memcpy(md.MsgId, MQMI_NONE,
sizeof(md.MsgId));
memcpy(md.CorrelId, MQMI_NONE,
sizeof(md.CorrelId));
buflen = sizeof(buffer) - 1;
MQGET(Hcon, Hinq, md, gmo, buflen,
buffer, messlen, CompCode,
Reason);
// printf("Inituation queue clear with
// reason code %ld and CompCode %ld\n",
// Reason, CompCode);
}
CompCode = MQCC_OK ;
В этом фрагменте используется для извлечения только одного
параметра: имени очереди инициализации. Очистка этой очереди делается
командой до тех пор, пока очередь не будет пуста.
Функция по формату полностью аналогична , разница между
ними заключается только в направлении потоков данных. В качестве
примера можно рассмотреть работу триггера с условиями Trigger Type =
First и Trigger Depth = 1. При срабатывании триггера флажок из
состояния Trigger Control = On переходит в состояние Trigger Control
= Off и его надо восстанавливать. Это делает следующий фрагмент кода:
Select[0] = MQIA_TRIGGER_CONTROL;
/* attribute selectors */
SelectValue[0] = 1 ;
MQSET(Hcon, Hobj, 1, Select, 1, SelectValue,
0, NULL, CompCode, Reason);
Заключительный вывод по разделу: функция позволяет извлечь
атрибуты любой очереди, процесса, менеджера или списка кластеров namelist, а функция - изменить эти параметры, но только для
очереди.
Теперь полезно изучить способы контроля доставки сообщений, тем более
что для этого существуют специальные поля в дескрипторе сообщений: и . Например, некоторое приложение отправляет запросы
на обработку в очередь Queue1 и ожидает уведомления о доставке в
очереди ReplyToQ = Queue2. Совершенно очевидно, что уведомления о
доставке не обязаны появиться в очереди Queue2 в той же
последовательности, в какой отправлялись запросы в очередь Queue1.
Как же в таком случае отслеживать доставку сообщений?
Другой пример, приложение отправляет запросы в разные системы, в
частности, по банковским счетам клиента и по проверке его кредитной
истории и ожидает получить ответ от запрашиваемых систем в одну и ту
же очередь для всех клиентов. Как же идентифицировать поступающие
ответы? Самое простое - использовать идентификаторы и ,
которые являются уникальными для каждого сообщения и создаются на
основе времени выполнения команды . Идентификаторы и создаются менеджером, если они установлены в NULL, либо
заданы опции MQPMO_NEW_MSG_ID и/или MQPMO_NEW_CORREL_ID ; в противном
случае, и создаются приложением.
Таким образом, задавая значения и при выполнении , появляется возможность автоматически получать значения и при чтении командой . При этом не надо забывать, что в
цикле считывания перед командой следует ставить обнуление этих
переменных ( и ), иначе можно
получить ошибку считывания сообщений из очереди MQRC_NO_MSG_AVAILABLE, несмотря на то, что сообщения в очереди
имеются.
Рассмотрим на примере, как работает с и на практике.
Пусть в некоторую очередь поступили сообщения в определенном порядке,
как указано в таблице ниже.
| Номер сообщения | ||
|---|---|---|
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 1 | 3 |
| 4 | 2 | 1 |
| 5 | 2 | 2 |
| 6 | 3 | 1 |
Теперь варьируя и , можно читать самые разные
сообщения.
читает первое доступное
сообщение независимо от и , то есть сообщение 1.
может прочитать только одно
сообщение, имеющее значение , это сообщение 3.
читает первое сообщение со
значением , это сообщение 4.
может прочитать только одно сообщение,
имеющее уникальное сочетание и , это сообщение
5.
Таким образом, если никакие другие сообщения не поступят в очередь,
то в очереди после четырех останутся 2 сообщения. Здесь уместен
вопрос о производительности работы менеджера очередей при таком
поиске сообщений. Если число сообщений в очереди не превышает 100, то
время поиска будет незначительным, оно практически не зависит от
числа сообщений. Но если число сообщений в очереди превысит 1000, то
менеджер будет сканировать очередь и время поиска будет заметным. На
OS/390 или (но
не одновременно) как индекс и это существенно ускорит поиск нужного
сообщения в очереди. Следует также отметить, что на других платформах
(AS/400, HP_UX, AIX, Sun Solaris, Windows) в версии 5.3 MQMO_MATCH_MSG_ID
и MQMO_MATCH_CORREL_ID. Если эти опции соответствия не будут
определены, то и будут игнорироваться, как если бы
использовались опции MQMI_NONE и MQCI_NONE и будет извлекаться
очередное сообщение.
Сформулируем одно общее правило при разработке программ с контролем доставки сообщений
с помощью и : если приложение перемещает сообщение из
входной очереди в выходную, то входного сообщения перемещается
в выходного сообщения и создается новый уникальный
идентификатор выходного сообщения. На уровне псевдокода это
правило можно записать следующим образом:
INPUT_MSG_DESC.MsgId = MQMI_NONE;
INPUT_MSG_DESC.CorrelId = MQCI_NONE;
MQGET (..........);
/* Обработка входного сообщения */
OUT_MSG_DESC.MsgId = MQMI_NONE;
OUT_MSG_DESC.CorrelId = INPUT_MSG_DESC.MsgId;
MQPUT (..........);
Разрабатывая логику в цепи программ-обработчиков сообщений на разных
платформах следует стремиться к тому, чтобы содержал
исходного запроса, а выходного сообщения содержал уникальный
идентификатор, полученный наиболее важной в функциональном смысле
выходной программой. На основе такой логики легко получить
подтверждение на выходе, что исходный запрос был отработан, и
промежуточные программы проставили свой в выходном сообщении.
Это позволяет осуществить контроль времени прохождения запроса и
получения сообщения-отчета.
В завершение раздела, уж если речь пошла о контроле доставки
сообщений, необходимо отметить возможность использования полей context (контекст) в дескрипторе сообщения для контроля авторизации
пользователя и обеспечения безопасности. Группа полей context состоит
из 8 полей, их значения по умолчанию приводятся в таблице ниже.
| Поле | Размер | Значение | Поле | Размер | Значение |
|---|---|---|---|---|---|
| UserIdentifier | CHAR12 | Имя пользователя | PutAppName | CHAR28 | Имя приложения |
| AccountingToken | BYTE32 | Учетный номер приложения-отправителя | PutDate | CHAR8 | YYYYMMDD |
| AppIdentityData | CHAR32 | Пробелы | PutTime | CHAR8 | HHMMSSTH |
| PutApplType | LONG | UNIX и т.п. | AppOriginData | CHAR4 | пробелы |
Используя UserIdentifier и AccountingToken, а также опции для работы
с полями для альтернативной авторизации,
позволяющей одному пользователю посылать сообщения от имени другого,
можно контролировать авторизацию пользователей, запускающих задачи.
Эти права устанавливает . Об этом не следует забывать при разработке приложений.
До настоящего момента говорилось о
Группа сообщений это одно или несколько
MsgId и CorrelId ; MsgId и CorrelId .Пусть необходимо объединить в группу три сообщения. Для этого в
дескрипторе сообщений полю MQMD_GroupId (24 байта) нужно присвоить
уникальный идентификатор и записать его в каждом сообщении.
Следующему полю MQMD_MsgSeqNumber присвоить значения 1, 2 и 3 для
первого, второго и третьего сообщений, соответственно, как показано
это на рис.11.1. Если сообщение не включено в группу, то для него будет NULL (MQGI_NONE), а в MsgSeqNumber будет значение 1. MsgSeqNumber во время
выполнения функции , если в PMO задана опция MQPMO_LOGICAL_ORDER. В этом случае также понадобится установить
значение в новое поле дескриптора сообщений: MQMD.MsgFlags, которое
может иметь значения MQMF_MSG_IN_GROUP и MQMF_LAST_MSG_IN_GROUP. Если
в было установлено значение MQGI_NONE, то для будет
сгенерирован новый уникальный идентификатор при использовании MQPMO_LOGICAL_ORDER. И еще одно правило для вывода группы сообщений:
PERSISTENCE и NOT_PERSISTENCE не должны
перемешиваться в одной группе.
Для пояснения изложенного следует привести фрагмент программы, использующей вывод сообщений в виде группы.
if ((CompCode == MQCC_OK) ||
(CompCode == MQCC_WARNING))
{
pmo.Options = MQPMO_LOGICAL_ORDER +
MQPMO_SYNCPOINT ;
memcpy(md.GroupId, "1",
sizeof(md.GroupId));
memcpy(md. MsgFlags, "MQMF_MSG_IN_GROUP",
sizeof(md. MsgFlags));
//memcpy(md.MsgSeqNumber, 1,
// sizeof(md.MsgSeqNumber));
//memcpy(md.MsgFlags, MQMF_SEGMENT,
// sizeof(md.MsgFlags));
/* блок формирования buffer */
MQPUT1(Hcon, odR, md, pmo, messlen,
buffer, CompCode, Reason);
if (Reason != MQRC_NONE)
printf("MQPUT1 ended with reason
code %ld\n", Reason);
}
/* конец цикла чтения поступающих сообщений */
memcpy(md. MsgFlags,
"MQMF_ LAST_MSG_IN_GROUP",
sizeof(md. MsgFlags));
/* блок формирования buffer */
MQPUT1(Hcon, odR, md, pmo, messlen,
buffer, CompCode, Reason);
if (Reason != MQRC_NONE)
printf("MQPUT1 ended with
reason code %ld\n", Reason);
(рис 11.1) Группа сообщений и сегментация сообщенияПроцесс извлечения группы во многом будет зависеть от значений, заданных в опции GMO.
Если задать значение MQGMO_ALL_MSGS_AVAILABLE, то сообщения будут
доступны для чтения только, когда все сообщения группы поступят в
очередь. Если задана опция MQGMO_LOGICAL_ORDER, то сообщения можно
извлекать в порядке, определяемом MsgSeqNumber и только по одному
сообщению из группы. Если опция MQGMO_LOGICAL_ORDER не задана, то
приложение должно выбирать сообщения двумя способами:
GroupId и MsgSeqNumber, либо переключаться на MQGMO_LOGICAL_ORDER, либо переслать сообщения дальше.MatchOption, если они заданы:MQMO_MATCH_MSG_ID,MQMO_MATCH_CORREL_ID,MQMO_MATCH_GROUP_ID,MQMO_MATCH_MSG_SEQ_NUMBER.Например, опция CORREL_ID означает, что должно выбираться сообщение,
корреляционный идентификатор которого, соответствует значению поля параметра MsgDesc функции . Это согласование является
дополнением к любому другому соответствию, заданному в MatchOption.
Если корреляционный идентификатор есть MQCI_NONE, то это равносильно
тому, что опция MQMO_MATCH_CORREL_ID не определена.
Теперь следует дать расширенное определение сегмента сообщения.
Сегментом сообщения называется , MsgSeqNumber и Offset. Из лекции 8 известно, что Offset это поле формата MQLONG в структурах MQOD и MQMD. Таким
образом, одно
Разбиение на сегменты ( MsgFlag значение MQMF_SEGMENTATION_ALLOWED, и менеджер очередей
определил, что сообщение является слишком большим для его MaxMsgLength или для значения параметра MaxMsgLength для очереди, то
тогда менеджер осуществляет
Реассемблирование с помощью менеджера осуществляется, если при чтении
сообщений командой была задана GMO-опция: MQGMO_COMPLETE_MESSAGE. В этом случае извлекаются все сегменты, и
только полное MQRC_UOW_AVAILABLE ) или
приложения (опция MQRC_UOW_NOT_AVAILABLE ).
Для опцию MQPMO_LOGICAL_ORDER и проследить за правильным включением
в поле MsgFlags флажков MQMF_SEGMENT и MQMF_LAST_SEGMENT. Менеджер
очередей позаботиться о назначении и поддержании , MsgSeqNumber и Offset.
Реассемблирование с помощью приложения осуществляется, если при
чтении сообщений командой была задана GMO-опция: MQGMO_COMPLETE_MESSAGE и флажок MsgFlags имеет значение MQMF_SEGMENTATION_ALLOWED. В этом случае приложение извлекает
сегменты самостоятельно в зависимости от ограничений на свой размер
буфера. Опции MQGMO_ALL_MSGS_AVAILABLE и MQGMO_ALL_SEGMENTS_AVAILABLE
позволят начать работу, если все сообщения или все сегменты поступят
в очередь. Как только первое сообщение поступит в очередь, можно
использовать MQGMO_LOGICAL_ORDER опцию, чтобы быть уверенным, что все
последующие сегменты MQGMO_LOGICAL_ORDER не задана, то приложение может выбирать
сообщения на основе опций соответствия MatchOption, как это делалось
для групп:
MQMO_MATCH_MSG_ID,MQMO_MATCH_CORREL_ID,MQMO_MATCH_GROUP_ID,MQMO_MATCH_MSG_SEQ_NUMBER,MQMO_MATCH_OFFSET.Приложение может определить окончание работы с сегментом с помощью
возвращаемого значения поля MQGMO.SegmentStatus, которое может
принимать следующие значения: MQSS_SEGMENT, MQSS_LAST_SEGMENT, MQSS_NOT_A_SEGMENT.
В завершение раздела можно привести примеры использования функций и для вывода и чтения
/* пример использования функции MQPUT */
PMO.Options = MQGMO_LOGICAL_ORDER +
MQGMO_SYNCPOINT
/* помещаем в очередь первое логическое
сообщение*/
MQPUT (MQMF_MSG_IN GROUP + MQMF_SEGMENT)
MQPUT (MQMF_MSG_IN GROUP + MQMF_SEGMENT)
MQPUT (MQMF_MSG_IN GROUP + MQMF_LAST_SEGMENT)
/* помещаем в очередь второе логическое
сообщение */
MQPUT (MQMF_MSG_IN GROUP + MQMFSEGMENT)
/* помещаем в очередь третье логическое
сообщение */
MQPUT (MQMF_LAST_MSG_IN GROUP + MQMF_SEGMENT)
MQPUT (MQMF_ LAST_MSG_IN GROUP +
MQMF_LAST_SEGMENT)
MQCMIT
/* пример использования функции MQGET */
GMO.Options = MQGMO_LOGICAL_ORDER +
MQGMO_SYNCPOINT +
MQGMO_ALL_MSGS_AVAILABLE +
MQGMO_WAIT
Do while (Not MQGS_LAST_MSG_IN GROUP and
Not MQSS_LAST_SEGMENT)
MQGET (........)
/* обработка сегмента или законченного
логического сообщения */
End
MQCMIT
Иногда возникает потребность применять mqexit.dll с точкой входа MsgExit. Для работы программы,
ее надо прописать в том канале, через который идут сообщения:
| Message Exit Name: | mqexit(MsgExit) |
| Message Exit Data: | \mydir\mqexit.ini |
С помощью программы это может быть сделано командой:
alter chl(mychannel) chltype(mychannel_type)
msgexit(' mqexit(MsgExit)')
msgdata('\mydir\mqexit.ini')
Программа mqexit.dll и настроечный файл mqexit.ini помещаются в
стандартной директории (mydir ) для C:\Program Files\IBM\WebSphere MQ\Exits\, для UNIX HP_UX
это /var/mqm/exits/, аналогичные пути существуют для других
платформ).
Настроечный файл mqexit.ini может содержать имя лог файла, имя
буферного файла для записи входящего сообщения; имя очереди, в
которую перекладывается содержимое буферного файла; имя менеджера,
ведь менеджеров может быть несколько на одном сервере.
В учебных целях стоит упростить задачу, не использовать
mqexit.ini-файл и записать имя лог-файла непосредственно в Message
Exit Data (например, C:\TEMP\mqexit.log ), а с сообщениями пусть
разбирается прежняя программа-обработчик, читающая входящие сообщения
из очереди. При этом предполагается, что используется менеджер
очередей по умолчанию. В этом случае программа mqexit.dll для
Windows выглядит следующим образом.
/* Листинг программы mqexit.dll */
/************************************************************************/
/* Program name: mqexit */
/* Description: This is a sample message exit function record all messages */
/* This function will work on any channels, e.g. send, receive, requester, etc. */
/* This function logs all channel activities into the Channel Log file. */
/* Please see Intercommunication documentation to specify the Channel Message Exit */
/* Authors: Vladimir Makushkin, Moscow, Russia */
/* email: vmakushkin@mail.ru */
/* Disclaimer: this program has been tested under HP_UX and Windows to the */
/* authors' satisfaction. You may use this program at your own risk. Author are not */
/* responsible for any unexpected results generated by this program. */
/************************************************************************/
/* standard headers */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <locale.h>
#include <sys/types.h>
#include <sys/timeb.h>
#include <time.h>
/* MQSeries headers */
#include <cmqc.h>
#include <cmqxc.h>
char timebuf??(130??);
char Channel_Logfile??(129??);
char MsgBufFile??(129??);
char ini_filename??(129??);
FILE * OST = 0;
#define DBGPRINTF(x) { \
OST=fopen(Channel_Logfile,"a+"); \
if(OST) fprintf x; \
fclose(OST); \
}
void writer (long *, char *); /* prototype for file writer function */
void getINI( void );
void MQENTRY MQStart(void) {;}
void MQENTRY MsgExit( PMQCXP pChannelExitParams,
PMQCD pChannelDefinition,
PMQLONG pDataLength,
PMQLONG pAgentBufferLength,
PMQBYTE AgentBuffer,
PMQLONG pExitBufferLength,
PMQPTR pExitBufferAddr)
{
short i;
struct timeb t;
char millisec??(25??);
time_t clock = time( (time_t*) NULL);
struct tm *tmptr = localtime(clock);
strcpy(timebuf, asctime(tmptr));
ftime( t );
memset( millisec, 0, sizeof( millisec ) );
sprintf( millisec, " Time:%ld.%d", t.time, t.millitm );
strcat( timebuf, millisec );
if ( strlen(pChannelExitParams->ExitData) )
{
memset( ini_filename, 0, sizeof( ini_filename ) );
strncpy( ini_filename, pChannelExitParams->ExitData, MQ_EXIT_DATA_LENGTH );
/*------------------------*/
/* remove space from data */
/*------------------------*/
for ( i = MQ_EXIT_DATA_LENGTH - 1; i > 0; i-- )
{
if ( ini_filename??(i??) != ' ' ini_filename??(i??) )
break;
ini_filename??(i??) = 0;
}
}
getINI();
void writer(long *lngth, char *bf)
{
long msgsize;
int n;
FILE *fp;
getINI();
fp = fopen(MsgBufFile, "ab");
msgsize = *lngth;
n = fwrite(msgsize, sizeof(msgsize), 1, fp); /* put record length */
n = fwrite(bf, msgsize, 1, fp); /* and message buffer */
fclose(fp);
}
/*---------------------------------------------------------
getINI() - read information from INI file
-----------------------------------------------------------*/
void getINI( void )
{
FILE *ini_fp;
char *ptr, *name_ptr;
char field_val??(129??), Ini_s??(129??), fn??(129??);
short len;
memset( Ini_s, 0, sizeof( Ini_s ) );
memset( field_val, 0, sizeof( field_val ) );
memset( MsgBufFile, 0, sizeof ( MsgBufFile ) );
memset( Channel_Logfile, 0, sizeof( Channel_Logfile ) );
if ( strlen( ini_filename ) ) /* if Channel Exit defined INI filename */
strcpy( fn, ini_filename ); /* use it */
else /* otherwise use default INI filename */
strcpy( fn, INI_FILENAME );
if ( (ini_fp = fopen( fn, "r" )) == NULL )
{
fprintf(stderr, "\nUnable to open %s. Error #: %d", fn, errno );
return;
}
/*-----------------------------*/
/* read the INI file until eof */
/*-----------------------------*/
while ( (ptr = fgets( Ini_s, sizeof(Ini_s) - 1, ini_fp )) != NULL )
{
len = strlen( Ini_s );
if ( Ini_s??(len -1??) == LINEFEED )
Ini_s??(len-1??) = 0; /* null out '\n' */
if ( (name_ptr = strchr( Ini_s, EQUAL )) == NULL ) /* no '=' found */
continue;
ptr = name_ptr + 1;
*name_ptr = 0; /* set null for s[] */
strcpy( field_val, ptr );
fclose( ini_fp );
return;
}
В качестве комментария к программе надо отметить, что обязательным
является соблюдение трех рекомендаций для Windows из документа
-
MQStart и ChannelExit программы mqexit.c:#include <cmqc.h>
#include <cmqxc.h>
void MQStart() {;}
/* dummy entry point - for
consistency only */
void MQENTRY ChannelExit (
PMQCXP pChannelExitParms,
PMQCD pChannelDefinition,
PMQLONG pDataLength,
PMQLONG pAgentBufferLength,
PMQVOID pAgentBuffer,
PMQLONG pExitBufferLength,
PMQPTR pExitBufferAddr)
{
... Insert code here
}
MQMVX.LIB. В
настройках проекта для генерации C/C++ кода изменить выпадающее меню
с "Use Run-.DEF файл ( mqexit.def ):LIBRARY mqexit
DESCRIPTION 'Provides Retry
and Channel exits'
HEAPSIZE 4096
STACKSIZE 8192
EXPORTS ChannelExit
И после этого проходящие по каналу сообщения начинают записываться в
файл, определенный в Message Exit Data, включая заголовок и данные
сообщения. Скорость прохождения сообщений с использованием
В заключение лекции следует отметить, что остались механизмы, которые не нашли отражение в этой книге и могли бы представлять интерес для читателя-программиста, а именно:
С этими вопросами читателю предлагается ознакомиться самостоятельно по документации.
Ключевым моментом интеграции приложений является использование
WebSphere Business Integration Message Broker $$\text{\textregistered}$$, сокращенно WBI
Message Broker или просто WBI Broker (ранее - IBM
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.