Компания IBM
Опубликован: 14.12.2004 | Доступ: свободный | Студентов: 1481 / 131 | Оценка: 4.36 / 3.98 | Длительность: 16:32:00
ISBN: 978-5-9556-0031-4
Специальности: Системный архитектор
Лекция 10:

Основы программирования для WebSphere MQ

< Лекция 9 || Лекция 10: 1234 || Лекция 11 >
Аннотация: Лекция посвящена написанию программ, работающих с WebSphere MQ на основе интерфейса MQI. Первая программа rewriter (модель "один к одному") предназначена для чтения сообщений из одной очереди, записи их в другую очередь и в файл на диске. Рассматривается технология разработки приложений с использованием транзакций WebSphere MQ и транзакций базы данных. Приводится программа transmit, работающая с транзакционными функциями MQBEGIN, MQCMIT, MQBACK. Третья программа лекции distlist демонстрирует работу с механизмом списков распространения (Distribution List), реализующим модель "один ко многим". Этот механизм обычно используется в случае рассылки большому количеству клиентов постоянно меняющейся информации (котировки акций, курсы валют, новости и т.п.).

Программа 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Ггц приведены в таблице ниже.

Таблица 9.1.
Язык программы\тип очереди Not Persistent Persistent
С++ 1000 сооб/сек 400 сооб/сек
Visual Basic 6.0 200 сооб/сек 140 сооб/сек

Увеличение длины сообщения не ведет к пропорциональному уменьшению скорости. Эти исследования читатель может проделать самостоятельно. Реальные приложения, работающие с базами данных, имеют скорость обработки сообщений в 3-4 раза меньше.

Возвращаясь к вопросу о стилях программирования, следует отметить, что обработка кода ошибки является обязательным атрибутом качественного программирования и об этом не следует забывать. В нашей программе дается предупредительное сообщение и делается выход из программы. Если этого не сделать, то простая описка в rewriter.ini файле приведет к зависанию программы и мучительному поиску причин такого зависания, не говоря о других более сложных ситуациях, например, когда очередь открыта эксклюзивно другим приложением.

Для версии программы gmo.WaitInterval = MQWI_UNLIMITED полезно сделать вывод на экран передаваемых сообщений, чтобы наблюдать динамику работы созданного интерфейса. Таких улучшений может быть достаточно много и мы рассмотрим две достаточно полезные модификации.

  1. Программа 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 может вызываться с передачей параметров через командную строку и эту модификацию читатель может проделать самостоятельно.

  2. Программа rewriter может быть модифицирована в программу разветвитель mqsplitter.exe: чтение сообщений из очереди 1 и запись их в очередь 2, в очередь 3 и лог-файл на диске.

Можно сделать программу mqsplitter.exe на разных языках, например, на Visual Basic 6.0 с интерфейсом, показанным на рис.9.1, и сравнить производительность программ на разных языках, реализующих один и тот же алгоритм. Такая задача будет хорошим лабораторным практикумом.

Интерфейс программы mqsplitter на VB6

увеличить изображение
Рис. 9.1. Интерфейс программы mqsplitter на VB6

Модификацию программы mqsplitter.exe читателю предлагается сделать самостоятельно и одновременно проверить идею создания "вечного двигателя". Для создания "вечного двигателя" понадобиться изменить исходный mqsplitter.ini файл следующим образом:

QUEUE_INPUT
QUEUE_OUTPUT1
QUEUE_OUTPUT2
C:\TEMP\ mqsplitter.log
->
QUEUE_INPUT
QUEUE_OUTPUT1
QUEUE_ INPUT
C:\TEMP\ mqsplitter.log

Если в очереди QUEUE_INPUT будет хотя бы одно сообщение, то ваша программа будет посылать сообщения в очередь QUEUE_OUTPUT1 до тех пор, пока не будет остановлена. Можно заложить в ini-файл программы пятый параметр: время опроса очереди, измеряемое в миллисекундах. Такая программа окажется весьма полезной при тестировании интерфейсов.

< Лекция 9 || Лекция 10: 1234 || Лекция 11 >