Skip to content

Twitter cloudhopper

Sayapin Alexander edited this page Jun 21, 2013 · 20 revisions

Twitter Cloudhopper - это набор библиотек для работы с SMPP (Short message pear to pear), GSM, кодировками и т.д.

Набор библиотек располагается в репозиториях:

Данные библиотеки присутствуют в основном репозитории Maven.

В качестве SMPP-сервера будем использовать SMPPsim от Selenium Software http://www.seleniumsoftware.com/downloads.html

Базовый репозиторий с зависимостями для Cloud hopper можно скачать здесь: https://github.com/wizardjedi/my-spring-learning/tree/pres_0_9_0 (ветка pres_0_9_0)

Создание простейшего SMPP-клиента

Создадим простой SMPP-клиент, который будет уметь создавать подключение к SMPP-сервера и отправлять одно сообщение с помощью пакета SUBMIT_SM.

Репозиторий проекта расположен по адресу https://github.com/wizardjedi/my-spring-learning/tree/pres_0_9_1 (ветка pres_0_9_1) на основе базового приложения для Cloud hopper.

Для начала создадим основной класс приложения.

package com.a1systems;

public class App {
	public static void main(String[] args) {
		
	}
}

Для проведения тестов необходимо, чтобы SMPPsim был запущен. Скачаем SMPPsim и распакуем архив в какую-нибудь директорию.

В дистрибутиве SMPPsim уже сделаны базовые настройки.

Посмотрим настройки для SystemID и паролей пользователей.

$ cat conf/smppsim.props | grep -P '(SYSTEM_IDS|PASSWORDS)'
SYSTEM_IDS=smppclient1,smppclient2
PASSWORDS=password,password

В SMPPsim созданы 2 пользователя с данными авторизации smppclient1 / password и smppclient2 / password.

Запустим SMPPsim.

$ sudo ./startsmppsim.sh 
SMPPSim is starting....
2013.06.12 11:47:19 127 INFO    1 ==============================================================
2013.06.12 11:47:19 677 INFO    1 =  SMPPSim Copyright (C) 2006 Selenium Software Ltd
2013.06.12 11:47:19 677 INFO    1 =  SMPPSim comes with ABSOLUTELY NO WARRANTY; for details
2013.06.12 11:47:19 677 INFO    1 =  read the license.txt file that was included in the SMPPSim distribution
2013.06.12 11:47:19 677 INFO    1 =  This is free software, and you are welcome to redistribute it under
2013.06.12 11:47:19 677 INFO    1 =  certain conditions; Again, see license.txt for details or read the GNU

После запуска становится доступной WEB-консоль SMPPsim по адресу: http://127.0.0.1:88/

В данный момент мы будем пользоваться только классами по умолчанию.

Создадим SMPP-клиент.

DefaultSmppClient client = new DefaultSmppClient();

SMPP-клиент - это основной объект для подключения в SMPP-серверу. У данного объекта есть метод bind(SmppSessionConfiguration sessionConfiguration), в который необходимо передать данные конфигурации.

Установим параметры для сессии: хост, порт, тип подключения (BIND_TRANSCEIVER), system id и пароль.

SmppSessionConfiguration sessionConfig = new SmppSessionConfiguration();

sessionConfig.setType(SmppBindType.TRANSCEIVER);
sessionConfig.setHost("127.0.0.1");
sessionConfig.setPort(2775);
sessionConfig.setSystemId("smppclient1");
sessionConfig.setPassword("password");

После того как были установлены данные конфигурации можно провести операцию bind. Метод bind может генерировать ряд исключений, которые необходимо обработать. В нашем случае, вся обработка будет сводиться к печати исключения на консоль. В качестве результата, в случае успешной попытки bind'а, smpp-клиент возвращает объект сессии, в рамках которой и можно посылать PDU.

try {
	SmppSession session = client.bind(sessionConfig);
} catch (SmppTimeoutException ex) {
	Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
} catch (SmppChannelException ex) {
	Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
} catch (SmppBindException ex) {
	Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
} catch (UnrecoverablePduException ex) {
	Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
} catch (InterruptedException ex) {
	Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
}

После того как сессия создана можно приступать к отправке сообщения. Для создания объекта сообщения реализуем метод createSubmitSm, который будет собирать SubmitSm в соответствии с переданными параметрами.

Тогда отправка сообщения будет иметь вид:

SubmitSm sm = createSubmitSm("Test", "79111234567", "Привет землянин!", "UCS-2");

session.submit(sm, TimeUnit.SECONDS.toMillis(60));

createSubmitSm создаёт объект типа SubmitSm: в качестве имени отправителя используется строка Test, в качестве получателя 79111234567, а текст сообщения (Привет землянин!) будет закодирован в кодировке UCS2.

Таймаут на операцию submit установим равным 60 секундам.

Рассмотрим метод createSubmitSm.

public static SubmitSm createSubmitSm(String src, String dst, String text, String charset) throws SmppInvalidArgumentException {
	SubmitSm sm = new SubmitSm();

	// For alpha numeric will use
	// TON=5
	// NPI=0
	sm.setSourceAddress(new Address((byte)5, (byte)0, src));

	// For national numbers will use
	// TON=1
	// NPI=1
	sm.setDestAddress(new Address((byte)1, (byte)1, dst));

	// Set datacoding to UCS-2
	sm.setDataCoding((byte)8);

	// Encode text
	sm.setShortMessage(CharsetUtil.encode(text, charset));

	return sm;
}

После отправки сообщения необходимо корректно завершить сессию и закрыть клиент.

TimeUnit.SECONDS.sleep(10);

session.close();
session.destroy();

client.destroy();

Теперь добавим вывод сообщений на консоль об операциях.

Результирующий App.java будет выглядеть так:

package com.a1systems;

import com.cloudhopper.commons.charset.CharsetUtil;
import com.cloudhopper.smpp.SmppBindType;
import com.cloudhopper.smpp.SmppSession;
import com.cloudhopper.smpp.SmppSessionConfiguration;
import com.cloudhopper.smpp.impl.DefaultSmppClient;
import com.cloudhopper.smpp.pdu.SubmitSm;
import com.cloudhopper.smpp.type.Address;
import com.cloudhopper.smpp.type.RecoverablePduException;
import com.cloudhopper.smpp.type.SmppBindException;
import com.cloudhopper.smpp.type.SmppChannelException;
import com.cloudhopper.smpp.type.SmppInvalidArgumentException;
import com.cloudhopper.smpp.type.SmppTimeoutException;
import com.cloudhopper.smpp.type.UnrecoverablePduException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

public class App {
	public static void main(String[] args) {
		DefaultSmppClient client = new DefaultSmppClient();

		SmppSessionConfiguration sessionConfig = new SmppSessionConfiguration();

		sessionConfig.setType(SmppBindType.TRANSCEIVER);
		sessionConfig.setHost("127.0.0.1");
		sessionConfig.setPort(2775);
		sessionConfig.setSystemId("smppclient1");
		sessionConfig.setPassword("password");

		try {
			SmppSession session = client.bind(sessionConfig);

			SubmitSm sm = createSubmitSm("Test", "79111234567", "Привет землянин!", "UCS-2");

			System.out.println("Try to send message");

			session.submit(sm, TimeUnit.SECONDS.toMillis(60));

			System.out.println("Message sent");

			System.out.println("Wait 10 seconds");

			TimeUnit.SECONDS.sleep(10);

			System.out.println("Destroy session");

			session.close();
			session.destroy();

			System.out.println("Destroy client");

			client.destroy();

			System.out.println("Bye!");
		} catch (SmppTimeoutException ex) {
			Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
		} catch (SmppChannelException ex) {
			Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
		} catch (SmppBindException ex) {
			Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
		} catch (UnrecoverablePduException ex) {
			Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
		} catch (InterruptedException ex) {
			Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
		} catch (RecoverablePduException ex) {
			Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
		}
	}

	public static SubmitSm createSubmitSm(String src, String dst, String text, String charset) throws SmppInvalidArgumentException {
		SubmitSm sm = new SubmitSm();

		// For alpha numeric will use
		// TON=5
		// NPI=0
		sm.setSourceAddress(new Address((byte)5, (byte)0, src));

		// For national numbers will use
		// TON=1
		// NPI=1
		sm.setDestAddress(new Address((byte)1, (byte)1, dst));

		// Set datacoding to UCS-2
		sm.setDataCoding((byte)8);

		// Encode text
		sm.setShortMessage(CharsetUtil.encode(text, charset));

		return sm;
	}
}

Запустим проект и посмотрим на вывод:

$ mvn clean compile exec:java -Dexec.mainClass=com.a1systems.App
...
Try to send message
Message sent
Wait 10 seconds
Destroy session
Destroy client
Bye!
...

Данный текст свидетельствует о том, что сообщение было отправлено, на отправку был получен положительный SUBMIT_SM_RESP, сессия была закрыта и smpp-клиент был закрыт успешно.

В консоле SMPPsim можно увидеть следующие строки:

2013.06.12 13:02:11 244 INFO    14 : Standard SUBMIT_SM:
2013.06.12 13:02:11 244 INFO    14 Hex dump (80) bytes:
2013.06.12 13:02:11 244 INFO    14 00000050:00000004:00000000:00000002:
2013.06.12 13:02:11 245 INFO    14 00050054:65737400:01013739:31313132:
2013.06.12 13:02:11 245 INFO    14 33343536:37000000:00000000:00080020:
2013.06.12 13:02:11 245 INFO    14 041F0440:04380432:04350442:00200437:
2013.06.12 13:02:11 245 INFO    14 0435043C:043B044F:043D0438:043D0021:
2013.06.12 13:02:11 245 INFO    14 
2013.06.12 13:02:11 246 INFO    14 cmd_len=80,cmd_id=4,cmd_status=0,seq_no=2,service_type=,source_addr_ton=5
2013.06.12 13:02:11 246 INFO    14 source_addr_npi=0,source_addr=Test,dest_addr_ton=1,dest_addr_npi=1
2013.06.12 13:02:11 246 INFO    14 dest_addr=79111234567,esm_class=0,protocol_ID=0,priority_flag=0
2013.06.12 13:02:11 246 INFO    14 schedule_delivery_time=,validity_period=,registered_delivery_flag=0
2013.06.12 13:02:11 246 INFO    14 replace_if_present_flag=0,data_coding=8,sm_default_msg_id=0,sm_length=32
2013.06.12 13:02:11 246 INFO    14 short_message=@825B 75<;O=8=!
2013.06.12 13:02:11 247 INFO    14  
2013.06.12 13:02:11 247 INFO    14 Validity period is not set: defaulting to 5 minutes from now
2013.06.12 13:02:11 247 INFO    14 Generated default validity period=130612130711000+
2013.06.12 13:02:11 248 INFO    14 :SUBMIT_SM_RESP:
2013.06.12 13:02:11 248 INFO    21 Assessing state of 1 messages in the OutboundQueue
2013.06.12 13:02:11 248 INFO    14 Hex dump (18) bytes:
2013.06.12 13:02:11 248 INFO    14 00000012:80000004:00000000:00000002:
2013.06.12 13:02:11 248 INFO    14 3300
2013.06.12 13:02:11 249 INFO    14 cmd_len=0,cmd_id=-2147483644,cmd_status=0,seq_no=2,message_id=3

Что свидетельствует об успешной обработке сообщения SMPPsim'ом.

Добавление логирования событий

Cloudhopper уже содержит в себе логирование операций через slf4j, поэтому для включения логирования достаточно добавить в зависимости slf4j и logback.

pom.xml

<properties>
	<slf4j.version>1.7.5</slf4j.version>
	<logback.version>1.0.13</logback.version>
</properties>
 
<dependencies>
	<!-- logback -->
	<dependency>
		<groupId>ch.qos.logback</groupId>
		<artifactId>logback-core</artifactId>
		<version>${logback.version}</version>
	</dependency>
	<dependency>
		<groupId>ch.qos.logback</groupId>
		<artifactId>logback-classic</artifactId>
		<version>${logback.version}</version>
	</dependency>
	<dependency>
		<groupId>ch.qos.logback</groupId>
		<artifactId>logback-access</artifactId>
		<version>${logback.version}</version>
	</dependency>
 
	<!-- SLF4J -->
	<dependency>
		<groupId>org.slf4j</groupId>
		<artifactId>jcl-over-slf4j</artifactId>
		<version>${slf4j.version}</version>
	</dependency>
	<dependency>
		<groupId>org.slf4j</groupId>
		<artifactId>jul-to-slf4j</artifactId>
		<version>${slf4j.version}</version>
	</dependency>
	<dependency>
		<groupId>org.slf4j</groupId>
		<artifactId>slf4j-api</artifactId>
		<version>${slf4j.version}</version>
		<type>jar</type>
	</dependency>
</dependencies>

logbacl.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
	<contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
		<resetJUL>true</resetJUL>
	</contextListener>
 
	<jmxConfigurator />
 
	<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
		<encoder>
			<pattern>%.-19date;%.-1level;%logger;%msg%n</pattern>
		</encoder>
	</appender>
 
	<logger name="org.springframework" level="info" />
 
	<root level="debug">
		<appender-ref ref="console" />
	</root>
</configuration>

Перезапустим приложение и посмотри на вывод.

$ mvn clean compile exec:java -Dexec.mainClass=com.a1systems.App
...
2013-06-12 13:19:02;W;com.cloudhopper.smpp.impl.DefaultSmppClient;Session configuration did not have a name set - skipping threadRenamer in pipeline
2013-06-12 13:19:02;I;com.cloudhopper.smpp.impl.DefaultSmppSession;sync send PDU: (bind_transceiver: 0x0000002A 0x00000009 0x00000000 0x00000001) (body: systemId [smppclient1] password [password] systemType [] interfaceVersion [0x34] addressRange (0x00 0x00 [])) (opts: )
2013-06-12 13:19:02;I;com.cloudhopper.smpp.impl.DefaultSmppSession;received PDU: (bind_transceiver_resp: 0x00000018 0x80000009 0x00000000 0x00000001 result: "OK") (body: systemId [SMPPSim]) (opts: )
Try to send message
2013-06-12 13:19:02;I;com.cloudhopper.smpp.impl.DefaultSmppSession;sync send PDU: (submit_sm: 0x00000050 0x00000004 0x00000000 0x00000002) (body: (serviceType [] sourceAddr [0x05 0x00 [Test]] destAddr [0x01 0x01 [79111234567]] esmCls [0x00] regDlvry [0x00] dcs [0x08] message [041F04400438043204350442002004370435043C043B044F043D0438043D0021])) (opts: )
2013-06-12 13:19:02;I;com.cloudhopper.smpp.impl.DefaultSmppSession;received PDU: (submit_sm_resp: 0x00000012 0x80000004 0x00000000 0x00000002 result: "OK") (body: (messageId [4])) (opts: )
Message sent
Wait 10 seconds
Destroy session
2013-06-12 13:19:12;I;com.cloudhopper.smpp.impl.DefaultSmppSession;Successfully closed
2013-06-12 13:19:12;D;com.cloudhopper.smpp.impl.DefaultSmppSession;Unbind/close was requested, ignoring channelClosed event
Destroy client
Bye!
...

Из вывода видно, что cloudhopper логирует отправки пакетов, а так же hex dump содержимого пакетов.

Код приложения лежит в ветке pres_0_9_2 по адресу https://github.com/wizardjedi/my-spring-learning/tree/pres_0_9_2 .

Обработка поступающих пакетов

Для обработки поступающих пакетов необходимо реализовать специализированный класс, который реализует интерфейс SmppSessionHandler.

Для начала переведём всё логирование на slf4jи отключим внутреннее логирование cloudhoper.

Отключим логирование пакетов и логирование hex-дампов пакетов.

LoggingOptions loggingOptions = new LoggingOptions();

loggingOptions.setLogPdu(false);
loggingOptions.setLogBytes(false);

sessionConfig.setLoggingOptions(loggingOptions);

Реализуем отображение пришедших пакетов DELIVER_SM. Для этого реализуем собственный обработчик сессии.

App.java

...
SmppSession session = client.bind(sessionConfig, new MySmppSessionHandler());
...

создадим класс обработчика сессии.

public class MySmppSessionHandler extends DefaultSmppSessionHandler {
	public static Logger log = LoggerFactory.getLogger(MySmppSessionHandler.class);

	@Override
	public PduResponse firePduRequestReceived(PduRequest pduRequest) {
		if (
			pduRequest.isRequest()
			&& pduRequest.getClass() == DeliverSm.class
		) {
			log.debug("Got DELIVER_SM");

			DeliverSm dlr = (DeliverSm)pduRequest;

			log.debug("Msg id={}", dlr.getOptionalParameter(SmppConstants.TAG_RECEIPTED_MSG_ID));
			log.debug("Status={}", dlr.getOptionalParameter(SmppConstants.TAG_MSG_STATE));

			return pduRequest.createResponse();
		}

		return super.firePduRequestReceived(pduRequest);
	}
}

Данный обработчик расширяет стандартный обработчик и переопределяет метод, который вызывается в случае, если поступил пакет.

Обработчик проверяет тип пакета и, если это DELIVIR_SM, то логирует TLV с ID сообщения и статусом.

Но отчёты о доставки будут отправляться только в том случае, если отчёт о доставке был запрошен. Поэтому добавим в метод createSubmitSm запрос на получение отчёта о доставке.

//We would like to get delivery receipt
sm.setRegisteredDelivery((byte)1);

После этого можно собрать приложение и посмотреть на вывод.

$ mvn clean compile exec:java -Dexec.mainClass=com.a1systems.App
...
2013-06-12 14:47:27;W;com.cloudhopper.smpp.impl.DefaultSmppClient;Session configuration did not have a name set - skipping threadRenamer in pipeline
2013-06-12 14:47:27;D;com.a1systems.App;Try to send message
2013-06-12 14:47:27;D;com.a1systems.App;Message sent
2013-06-12 14:47:27;D;com.a1systems.App;Wait 10 seconds
2013-06-12 14:47:32;D;com.a1systems.MySmppSessionHandler;Got DELIVER_SM
2013-06-12 14:47:32;D;com.a1systems.MySmppSessionHandler;Msg id=receipted_message_id: 0x001E 0x0003 [313400]
2013-06-12 14:47:32;D;com.a1systems.MySmppSessionHandler;Status=message_state: 0x0427 0x0001 [02]
2013-06-12 14:47:37;D;com.a1systems.App;Destroy session
2013-06-12 14:47:37;I;com.cloudhopper.smpp.impl.DefaultSmppSession;Successfully closed
2013-06-12 14:47:37;D;com.cloudhopper.smpp.impl.DefaultSmppSession;Unbind/close was requested, ignoring channelClosed event
2013-06-12 14:47:37;D;com.a1systems.App;Destroy client
2013-06-12 14:47:37;D;com.a1systems.App;Bye!

Логирование пакетов не производится, но при получении DELIVER_SM после отправки срабатывает наш обработчик, который отвечает успешным DELIVER_SM_RESP'ом и логирует отчёт о доставке.

Изменённое приложение доступно по адресу https://github.com/wizardjedi/my-spring-learning/tree/pres_0_9_3 ветка pres_0_9_3.

Отправка сообщений в асинхронном режиме

Рассмотренный способ отправки сообщений в прошлом разделе является блокирующим, то есть пока не будет получен ответ от SMSc или пока не будет достигнут предел по времени ожидания (таймаут) вызывающий поток будет заблокирован. Существует возможность отправлять сообщения не блокируя выызвающий поток. Для этого используется метод sendRequestPdu(), который принимает пакет на отправку, таймаут и признак синхронности отправки, а возвращает объект типа WindowFuture. Остановимся на каждом параметре подробнее:

  • пакет на отправку - пакет, который предполагается отправить
  • таймаут - максимальное время ожидание ответа на запроса
  • признак синхронности - true, если поток желает получить ответ на запрос и обработать его, false - если предполагается обработка в SmppSessionHandler::fireExpectedPduResponseReceived

Исходя из архитектуры приложения можно выбрать любой из типов отправки.

Рассмотрим на примере. Модифицируем наш предыдущим пример так, чтобы проиллюстрировать все 3 способа отправки сообщений.

SubmitSm sm = createSubmitSm("Test", "79111234567", "Привет землянин!", "UCS-2");

log.debug("Try to send message 1");

SubmitSmResp ssmr = session.submit(sm, TimeUnit.SECONDS.toMillis(60));

log.debug("Got response 1 {}", ssmr);

log.debug("Try to send message 2");

WindowFuture<Integer, PduRequest, PduResponse> future = session.sendRequestPdu(sm, TimeUnit.SECONDS.toMillis(60), true);

while (!future.isDone()) {
	log.debug("Not done Succes is {}", future.isSuccess());
}

log.debug("Got response 2 {}", future.getResponse());

log.debug("Done Succes status is {}", future.isSuccess());
log.debug("Response time is {}", future.getAcceptToDoneTime());

log.debug("Try to send message 3");

WindowFuture<Integer, PduRequest, PduResponse> future2 = session.sendRequestPdu(sm, TimeUnit.SECONDS.toMillis(60), false);

while (!future2.isDone()) {
	log.debug("Not done Succes is {}", future2.isSuccess());
}

log.debug("Got response 3 {}", future2.getResponse());

log.debug("Done Succes status is {}", future2.isSuccess());
log.debug("Response time is {}", future2.getAcceptToDoneTime());

log.debug("Wait 10 seconds");

Добавим обработку поступления ответа на запрос в SessionHandler.

@Override
public void fireExpectedPduResponseReceived(PduAsyncResponse pduAsyncResponse) {
	if (pduAsyncResponse.getResponse().getClass() == SubmitSmResp.class) {
		SubmitSmResp ssmr = (SubmitSmResp)pduAsyncResponse.getResponse();

		log.debug("Got response with MSG ID={} for seqnum={}", ssmr.getMessageId(), ssmr.getSequenceNumber());
	}
}

Запустим приложение и посмотрим на вывод:

$ mvn clean compile exec:java -Dexec.mainClass=com.a1systems.App
...
2013-06-14 17:36:16;W;com.a1systems.App.main();com.cloudhopper.smpp.impl.DefaultSmppClient;Session configuration did not have a name set - skipping threadRenamer in pipeline
2013-06-14 17:36:16;D;New I/O  worker #1;com.a1systems.MySmppSessionHandler;Got DELIVER_SM
2013-06-14 17:36:16;D;New I/O  worker #1;com.a1systems.MySmppSessionHandler;Msg id=null
2013-06-14 17:36:16;D;New I/O  worker #1;com.a1systems.MySmppSessionHandler;Status=null
2013-06-14 17:36:16;D;com.a1systems.App.main();com.a1systems.App;Try to send message 1
2013-06-14 17:36:21;D;New I/O  worker #1;com.a1systems.MySmppSessionHandler;Got DELIVER_SM
2013-06-14 17:36:21;D;New I/O  worker #1;com.a1systems.MySmppSessionHandler;Msg id=null
2013-06-14 17:36:21;D;New I/O  worker #1;com.a1systems.MySmppSessionHandler;Status=null
2013-06-14 17:36:26;D;com.a1systems.App.main();com.a1systems.App;Got response 1 (submit_sm_resp: 0x00000013 0x80000004 0x00000000 0x00000002 result: "OK") (body: (messageId [20])) (opts: )
2013-06-14 17:36:26;D;com.a1systems.App.main();com.a1systems.App;Try to send message 2
2013-06-14 17:36:26;D;com.a1systems.App.main();com.a1systems.App;Not done Succes is false
2013-06-14 17:36:26;D;New I/O  worker #1;com.a1systems.MySmppSessionHandler;Got DELIVER_SM
2013-06-14 17:36:26;D;com.a1systems.App.main();com.a1systems.App;Not done Succes is false
2013-06-14 17:36:26;D;New I/O  worker #1;com.a1systems.MySmppSessionHandler;Msg id=null
2013-06-14 17:36:26;D;com.a1systems.App.main();com.a1systems.App;Not done Succes is false
2013-06-14 17:36:26;D;New I/O  worker #1;com.a1systems.MySmppSessionHandler;Status=null
2013-06-14 17:36:26;D;com.a1systems.App.main();com.a1systems.App;Not done Succes is false
2013-06-14 17:36:26;D;com.a1systems.App.main();com.a1systems.App;Not done Succes is false
...
2013-06-14 17:36:26;D;com.a1systems.App.main();com.a1systems.App;Not done Succes is false
2013-06-14 17:36:26;D;com.a1systems.App.main();com.a1systems.App;Got response 2 (submit_sm_resp: 0x00000013 0x80000004 0x00000000 0x00000002 result: "OK") (body: (messageId [21])) (opts: )
2013-06-14 17:36:26;D;com.a1systems.App.main();com.a1systems.App;Done Succes status is true
2013-06-14 17:36:26;D;com.a1systems.App.main();com.a1systems.App;Response time is 9
2013-06-14 17:36:26;D;com.a1systems.App.main();com.a1systems.App;Try to send message 3
...
2013-06-14 17:36:26;D;com.a1systems.App.main();com.a1systems.App;Not done Succes is false
2013-06-14 17:36:26;D;New I/O  worker #1;com.a1systems.MySmppSessionHandler;Got DELIVER_SM
2013-06-14 17:36:26;D;com.a1systems.App.main();com.a1systems.App;Not done Succes is false
2013-06-14 17:36:26;D;New I/O  worker #1;com.a1systems.MySmppSessionHandler;Msg id=null
2013-06-14 17:36:26;D;New I/O  worker #1;com.a1systems.MySmppSessionHandler;Status=null
2013-06-14 17:36:26;D;com.a1systems.App.main();com.a1systems.App;Not done Succes is false
2013-06-14 17:36:26;D;com.a1systems.App.main();com.a1systems.App;Not done Succes is false
...
2013-06-14 17:36:26;D;com.a1systems.App.main();com.a1systems.App;Not done Succes is false
2013-06-14 17:36:26;D;com.a1systems.App.main();com.a1systems.App;Not done Succes is false
2013-06-14 17:36:26;D;New I/O  worker #1;com.a1systems.MySmppSessionHandler;Got response with MSG ID=22 for seqnum=2
2013-06-14 17:36:26;D;com.a1systems.App.main();com.a1systems.App;Got response 3 (submit_sm_resp: 0x00000013 0x80000004 0x00000000 0x00000002 result: "OK") (body: (messageId [22])) (opts: )
2013-06-14 17:36:26;D;com.a1systems.App.main();com.a1systems.App;Done Succes status is true
2013-06-14 17:36:26;D;com.a1systems.App.main();com.a1systems.App;Response time is 11
2013-06-14 17:36:26;D;com.a1systems.App.main();com.a1systems.App;Wait 10 seconds
2013-06-14 17:36:27;D;New I/O  worker #1;com.a1systems.MySmppSessionHandler;Got DELIVER_SM
2013-06-14 17:36:27;D;New I/O  worker #1;com.a1systems.MySmppSessionHandler;Msg id=null
2013-06-14 17:36:27;D;New I/O  worker #1;com.a1systems.MySmppSessionHandler;Status=null
2013-06-14 17:36:36;D;New I/O  worker #1;com.a1systems.MySmppSessionHandler;Got DELIVER_SM
2013-06-14 17:36:36;D;New I/O  worker #1;com.a1systems.MySmppSessionHandler;Msg id=null
2013-06-14 17:36:36;D;New I/O  worker #1;com.a1systems.MySmppSessionHandler;Status=null
2013-06-14 17:36:36;D;com.a1systems.App.main();com.a1systems.App;Destroy session
2013-06-14 17:36:36;I;com.a1systems.App.main();com.cloudhopper.smpp.impl.DefaultSmppSession;Successfully closed
2013-06-14 17:36:36;D;New I/O  worker #1;com.cloudhopper.smpp.impl.DefaultSmppSession;Unbind/close was requested, ignoring channelClosed event
2013-06-14 17:36:36;D;com.a1systems.App.main();com.a1systems.App;Destroy client
2013-06-14 17:36:36;D;com.a1systems.App.main();com.a1systems.App;Bye! 

Часть строк вида Not done Succes is false опущена в выводе, но данный вывод хорошо иллюстрирует, что поток был заблокирован только при первом вызове, а для второго и третьего вызова обработка потока продолжалась. Кроме того, видно, что SessionHandler работает в потоке New I/O worker #1 и в случае с отправкой №3 ответ от SMSc был обработан SessionHandlerом в потоке New I/O worker #1.

Репозиторий проекта расположен в ветке pres_0_9_4 по адресу https://github.com/wizardjedi/my-spring-learning/tree/pres_0_9_4.

Задание объектов ссылки для пакетов

Мы научились создавать объекты запроса и отправлять их асинхронно, но как, например, отличить один пакет от другого. Для этого в классе Pdu предусмотрено поле типа Object, которое повзоляет указывать или читать объект ссылки с помощью методов setReferenceObject() и getReferenceObject().

Рассмотрим как можно переписать методы, чтобы указывать объект связи.

Добавим метод помощник для логирования.

private static void log(WindowFuture<Integer, PduRequest, PduResponse> future) {
	SubmitSm req = (SubmitSm)future.getRequest();
	SubmitSmResp resp = (SubmitSmResp)future.getResponse();

	log.debug("Got response with MSG ID={} for APPID={}", resp.getMessageId(), req.getReferenceObject());
}

и внесём изменения в код отправки:

SubmitSm sm1 = createSubmitSm("Test", "79111234567", "Привет землянин!", "UCS-2");

log.debug("Try to send message");

sm1.setReferenceObject("Hello1");

WindowFuture<Integer, PduRequest, PduResponse> future = session.sendRequestPdu(sm1, TimeUnit.SECONDS.toMillis(60), false);

SubmitSm sm2 = createSubmitSm("Test", "79111234567", "Привет землянин!", "UCS-2");

sm2.setReferenceObject("Hello2");

WindowFuture<Integer, PduRequest, PduResponse> future2 = session.sendRequestPdu(sm2, TimeUnit.SECONDS.toMillis(60), false);

while (
	!future2.isDone()
	|| !future.isDone()
) {
	log.debug("Not done");
}

log(future);
log(future2);

и также изменения в SessionHandler.

@Override
public void fireExpectedPduResponseReceived(PduAsyncResponse pduAsyncResponse) {
	if (pduAsyncResponse.getResponse().getClass() == SubmitSmResp.class) {
		SubmitSm req = (SubmitSm)pduAsyncResponse.getRequest();
		log.debug("Got response for APPID={}", req.getReferenceObject());

		SubmitSmResp ssmr = (SubmitSmResp)pduAsyncResponse.getResponse();

		log.debug("Got response with MSG ID={} for seqnum={}", ssmr.getMessageId(), ssmr.getSequenceNumber());
	}
}

Запустим приложение и посмотрим на вывод:

$ mvn clean compile exec:java -Dexec.mainClass=com.a1systems.App
...
2013-06-14 18:07:33;D;com.a1systems.App.main();com.a1systems.App;Try to send message
2013-06-14 18:07:43;D;New I/O  worker #1;com.a1systems.MySmppSessionHandler;Got response for APPID=Hello1
2013-06-14 18:07:43;D;New I/O  worker #1;com.a1systems.MySmppSessionHandler;Got response with MSG ID=32 for seqnum=2
...
2013-06-14 18:07:43;D;New I/O  worker #1;com.a1systems.MySmppSessionHandler;Got response for APPID=Hello2
2013-06-14 18:07:43;D;New I/O  worker #1;com.a1systems.MySmppSessionHandler;Got response with MSG ID=33 for seqnum=3
...
2013-06-14 18:07:43;D;com.a1systems.App.main();com.a1systems.App;Got response with MSG ID=32 for APPID=Hello1
2013-06-14 18:07:43;D;com.a1systems.App.main();com.a1systems.App;Got response with MSG ID=33 for APPID=Hello2

Как видно из вывода можно обращаться к объекту связи из любого места и получать соответствующий объект. В нашем случае объектом связи была строка, но в общем случае это может быть любой объект.

Пример приложения доступен в ветке pres_0_9_5 по адресу https://github.com/wizardjedi/my-spring-learning/tree/pres_0_9_5.

Создание класса клиента

Мы добавили функциональность по поддержке подключения к SMSc, настройки подключения, настройки логирования и собственный обработчик для сессии, научились отправлять сообщения синхронно и асинхронно, а также отличать пакеты ответов между собой.

Теперь пришло время создать класс, который будет скрывать детали реализации и реализует ещё повторное соединение в случае разрыва связи или невозможности подключения, а также посылку пакетов ENQUIRE_LINK для поддержки сессии поднятой. За ребинд и посылку пакета enquire_link будут отвечать соответствующие классы реализующийе интерфейс Runnable, а периодический запуск будет реализован через другой класс реализующий интерфейс Runnable.

Реализуем enumeration для представления состояния подключения.

package com.a1systems.client;

public enum ClientState {
	IDLE, BINDING, BOUND, STOPPING, STOPPED;
}
  • IDLE - состояние предстовляющее клиент до начала работы
  • BINDING - происходит подключение к СМСц или переподключение к СМСц
  • BOUND - подключение прошло успешно (необходимо отправлять ENQUIRE_LINK)
  • STOPPING - происходит отключение
  • STOPPED - клиент отключён

Реализуем класс задачи, который будет проводить отправку ENQUIRE_LINK. В случае отсутствия ENQUIRE_LINK будем переходить к попыткам переподключения.

package com.a1systems.client;

import com.cloudhopper.smpp.SmppSession;
import com.cloudhopper.smpp.pdu.EnquireLink;
import com.cloudhopper.smpp.type.RecoverablePduException;
import com.cloudhopper.smpp.type.SmppChannelException;
import com.cloudhopper.smpp.type.SmppTimeoutException;
import com.cloudhopper.smpp.type.UnrecoverablePduException;
import java.util.concurrent.TimeUnit;
import org.slf4j.LoggerFactory;

public class ElinkTask implements Runnable {
	public static final org.slf4j.Logger log = LoggerFactory.getLogger(ElinkTask.class);

	protected Client client;

	public ElinkTask(Client client) {
		this.client = client;
	}

	@Override
	public void run() {
		if (client.state == ClientState.BOUND) {
			SmppSession session = client.getSession();

			log.debug("Send elink");

			try {
				session.enquireLink(new EnquireLink(), TimeUnit.SECONDS.toMillis(10));

				log.debug("Elink sent successfull");
			} catch (RecoverablePduException ex) {
				log.debug("{}", ex);
			} catch (UnrecoverablePduException ex) {
				log.debug("{}", ex);
			} catch (SmppTimeoutException ex) {
				client.bind(); // Go to binding state

				log.debug("{}", ex);
			} catch (SmppChannelException ex) {
				log.debug("{}", ex);
			} catch (InterruptedException ex) {
				log.debug("{}", ex);
			}
		}
	}
}

Данные класс отправляет пакеты ENQUIRE_LINK, только в том случае, если клиент находится в состоянии BOUND. В случае не получения ENQUIRE_LINK_RESP задача переводит клиент в состояние BINDING для проведения дополнительных попыток подключения.

Реализуем класс для проведения процесса подключения.

package com.a1systems.client;

import com.cloudhopper.smpp.SmppClient;
import com.cloudhopper.smpp.SmppSession;
import com.cloudhopper.smpp.type.SmppBindException;
import com.cloudhopper.smpp.type.SmppChannelException;
import com.cloudhopper.smpp.type.SmppTimeoutException;
import com.cloudhopper.smpp.type.UnrecoverablePduException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RebindTask implements  Runnable {

	public static final Logger log = LoggerFactory.getLogger(RebindTask.class);

	protected Client client;

	public RebindTask(Client client) {
		this.client = client;
	}

	@Override
	public void run() {
		if (client.state == ClientState.BINDING) {
			SmppClient smppClient = client.getSmppClient();
			try {
				log.debug("Try to bind...");

				SmppSession session = smppClient.bind(client.getCfg(), client.getSessionHandler());

				client.bound(session);
			} catch (SmppTimeoutException ex) {
				log.debug("{}", ex);
			} catch (SmppChannelException ex) {
				log.debug("{}", ex);
			} catch (SmppBindException ex) {
				log.debug("{}", ex);
			} catch (UnrecoverablePduException ex) {
				log.debug("{}", ex);
			} catch (InterruptedException ex) {
				log.debug("{}", ex);
			}
		}
	}
}

Данный класс проводит попытку подключения с параметрами из объекта Client. В случае успешной попытки вызывается метод bound(), который переводим подключение в состояние BOUND, отменяет попытки подключения и запускает задачи по отправке ENQUIRE_LINK.

Теперь реализуем класс, который отвечает за подключение. Класс будет запускаться в отдельном потоке, чтобы операции были не блокирующими.

package com.a1systems.client;

import com.cloudhopper.smpp.SmppClient;
import com.cloudhopper.smpp.SmppSession;
import com.cloudhopper.smpp.SmppSessionConfiguration;
import com.cloudhopper.smpp.SmppSessionHandler;
import com.cloudhopper.smpp.impl.DefaultSmppClient;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Client implements Runnable {
	public static final Logger log = LoggerFactory.getLogger(Client.class);

	protected SmppSessionConfiguration cfg;
	protected SmppSessionHandler sessionHandler;
	protected ClientState state;

	protected volatile SmppSession session;

	protected SmppClient smppClient;

	protected ScheduledExecutorService timer;

	protected ScheduledFuture<?> elinkTask;
	protected ScheduledFuture<?> rebindTask;

	protected long rebindPeriod = 5;
	protected long elinkPeriod = 5;

	public Client(SmppSessionConfiguration cfg) {
		this.cfg = cfg;

		this.timer = Executors.newScheduledThreadPool(2);
	}

	@Override
	public void run() {
		log.debug("Creating client");

		this.state = ClientState.IDLE;

		while (this.state != ClientState.STOPPING) {
			try {
				TimeUnit.MILLISECONDS.sleep(10);
			} catch (InterruptedException ex) {
				/* */
			}
		}

		this.smppClient.destroy();

		this.state = ClientState.STOPPED;
	}

	public void start() {
		log.debug("Starting client");

		this.smppClient = new DefaultSmppClient();

		this.bind();
	}

	private void runRebindTask() {
		this.rebindTask = this.timer.scheduleAtFixedRate(new RebindTask(this), 0, getRebindPeriod(), TimeUnit.SECONDS);
	}

	private void runElinkTask() {
		this.elinkTask = this.timer.scheduleAtFixedRate(new ElinkTask(this), getElinkPeriod(), getElinkPeriod(), TimeUnit.SECONDS);
	}


	public void bind() {
		if (
			this.state == ClientState.BOUND
			|| this.state == ClientState.IDLE
		) {
			log.debug("Binding state");

			if (
				this.session != null
				&& this.session.isBound()
			) {
				this.session.close();
				this.session.destroy();
			}

			this.state = ClientState.BINDING;

			if (elinkTask != null) {
				this.elinkTask.cancel(true);
			}
			runRebindTask();
		}
	}

	public void bound(SmppSession session) {
		if (this.state == ClientState.BINDING) {
			log.debug("Bound state");

			this.state = ClientState.BOUND;

			this.session = session;

			if (rebindTask!=null) {
				this.rebindTask.cancel(true);
			}
			runElinkTask();
		}
	}

	public void stop() {
		log.debug("Stopping");

		this.state = ClientState.STOPPING;

		this.elinkTask.cancel(true);
		this.rebindTask.cancel(true);
		this.timer.shutdown();

		this.timer = null;
	}

	// getters and setters
	public long getRebindPeriod() {
		return rebindPeriod;
	}

	public void setRebindPeriod(long rebindPeriod) {
		this.rebindPeriod = rebindPeriod;
	}

	public long getElinkPeriod() {
		return elinkPeriod;
	}

	public void setElinkPeriod(long elinkPeriod) {
		this.elinkPeriod = elinkPeriod;
	}

	public SmppClient getSmppClient() {
		return smppClient;
	}

	public void setSmppClient(SmppClient smppClient) {
		this.smppClient = smppClient;
	}

	public SmppSessionConfiguration getCfg() {
		return cfg;
	}

	public void setCfg(SmppSessionConfiguration cfg) {
		this.cfg = cfg;
	}

	public SmppSessionHandler getSessionHandler() {
		return sessionHandler;
	}

	public void setSessionHandler(SmppSessionHandler sessionHandler) {
		this.sessionHandler = sessionHandler;
	}

	public SmppSession getSession() {
		return session;
	}

	public void setSession(SmppSession session) {
		this.session = session;
	}
}

Дополнительно необходимо внести изменения в App.java и SmppSessionHandler, чтобы создавался нужный клиент и клиент переходил в состояние попыток подключения в случае обрыва связи.

package com.a1systems;

import com.a1systems.client.Client;
import com.cloudhopper.smpp.PduAsyncResponse;
import com.cloudhopper.smpp.SmppConstants;
import com.cloudhopper.smpp.impl.DefaultSmppSessionHandler;
import com.cloudhopper.smpp.pdu.DeliverSm;
import com.cloudhopper.smpp.pdu.PduRequest;
import com.cloudhopper.smpp.pdu.PduResponse;
import com.cloudhopper.smpp.pdu.SubmitSm;
import com.cloudhopper.smpp.pdu.SubmitSmResp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MySmppSessionHandler extends DefaultSmppSessionHandler {
	public static Logger log = LoggerFactory.getLogger(MySmppSessionHandler.class);

	protected Client client;

	public MySmppSessionHandler(Client client) {
		this.client = client;
	}

	@Override
	public PduResponse firePduRequestReceived(PduRequest pduRequest) {
		if (
			pduRequest.isRequest()
			&& pduRequest.getClass() == DeliverSm.class
		) {
			log.debug("Got DELIVER_SM");

			DeliverSm dlr = (DeliverSm)pduRequest;

			log.debug("Msg id={}", dlr.getOptionalParameter(SmppConstants.TAG_RECEIPTED_MSG_ID));
			log.debug("Status={}", dlr.getOptionalParameter(SmppConstants.TAG_MSG_STATE));

			return pduRequest.createResponse();
		}

		return super.firePduRequestReceived(pduRequest);
	}

	@Override
	public void fireExpectedPduResponseReceived(PduAsyncResponse pduAsyncResponse) {
		if (pduAsyncResponse.getResponse().getClass() == SubmitSmResp.class) {
			SubmitSm req = (SubmitSm)pduAsyncResponse.getRequest();
			log.debug("Got response for APPID={}", req.getReferenceObject());

			SubmitSmResp ssmr = (SubmitSmResp)pduAsyncResponse.getResponse();

			log.debug("Got response with MSG ID={} for seqnum={}", ssmr.getMessageId(), ssmr.getSequenceNumber());
		}
	}

	@Override
	public void fireChannelUnexpectedlyClosed() {
		client.bind(); // Rebind
	}
}
package com.a1systems;

import com.a1systems.client.Client;
import com.cloudhopper.commons.charset.CharsetUtil;
import com.cloudhopper.commons.util.windowing.WindowFuture;
import com.cloudhopper.smpp.SmppBindType;
import com.cloudhopper.smpp.SmppSessionConfiguration;
import com.cloudhopper.smpp.pdu.PduRequest;
import com.cloudhopper.smpp.pdu.PduResponse;
import com.cloudhopper.smpp.pdu.SubmitSm;
import com.cloudhopper.smpp.pdu.SubmitSmResp;
import com.cloudhopper.smpp.type.Address;
import com.cloudhopper.smpp.type.LoggingOptions;
import com.cloudhopper.smpp.type.RecoverablePduException;
import com.cloudhopper.smpp.type.SmppChannelException;
import com.cloudhopper.smpp.type.SmppInvalidArgumentException;
import com.cloudhopper.smpp.type.SmppTimeoutException;
import com.cloudhopper.smpp.type.UnrecoverablePduException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class App {
	public static Logger log = LoggerFactory.getLogger(App.class);

	private static void log(WindowFuture<Integer, PduRequest, PduResponse> future) {
		SubmitSm req = (SubmitSm)future.getRequest();
		SubmitSmResp resp = (SubmitSmResp)future.getResponse();

		log.debug("Got response with MSG ID={} for APPID={}", resp.getMessageId(), req.getReferenceObject());
	}

	public static void main(String[] args) throws SmppInvalidArgumentException {
		SmppSessionConfiguration sessionConfig = new SmppSessionConfiguration();

		sessionConfig.setType(SmppBindType.TRANSCEIVER);
		sessionConfig.setHost("127.0.0.1");
		sessionConfig.setPort(2775);
		sessionConfig.setSystemId("smppclient1");
		sessionConfig.setPassword("password");

		LoggingOptions loggingOptions = new LoggingOptions();

		//loggingOptions.setLogPdu(false);
		//loggingOptions.setLogBytes(false);

		sessionConfig.setLoggingOptions(loggingOptions);

		Client client = new Client(sessionConfig);

		client.setSessionHandler(new MySmppSessionHandler(client));

		ExecutorService pool = Executors.newFixedThreadPool(2);

		pool.submit(client);

		client.start();

		log.debug("Wait to bound");

		while (
			client.getSession() == null
			|| !client.getSession().isBound()
		) {

			if (client.getSession() != null) {
				log.debug("Session is {}", client.getSession().isBound());
			} else {
				log.debug("Null session");
			}

			try {
				TimeUnit.SECONDS.sleep(1);
			} catch (InterruptedException ex) {
				java.util.logging.Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
			}
		}

		log.debug("Try to send");

		SubmitSm sm = new SubmitSm();

		sm.setSourceAddress(new Address((byte)5, (byte)0, "Test"));
		sm.setDestAddress(new Address((byte)1, (byte)1, "79111234567"));

		sm.setShortMessage(CharsetUtil.encode("Привет!", "UCS-2"));

		sm.setRegisteredDelivery((byte)1);

		sm.setDataCoding((byte)8);



		try {
			client.getSession().submit(sm, TimeUnit.SECONDS.toMillis(60));
		} catch (RecoverablePduException ex) {
			java.util.logging.Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
		} catch (UnrecoverablePduException ex) {
			java.util.logging.Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
		} catch (SmppTimeoutException ex) {
			java.util.logging.Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
		} catch (SmppChannelException ex) {
			java.util.logging.Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
		} catch (InterruptedException ex) {
			java.util.logging.Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
		}
		try {
			TimeUnit.SECONDS.sleep(30);
		} catch (InterruptedException ex) {
			java.util.logging.Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
		}

		client.stop();

		pool.shutdown();
	}
}

Производится попытка отправки сообщения и после истечения 30 секунд приложение закрывается.

Теперь можно перезапустить приложение и посмотреть на вывод.

$ mvn clean compile exec:java -Dexec.mainClass=com.a1systems.App
...
2013-06-21 17:27:42;D;com.a1systems.App.main();com.a1systems.client.Client;Starting client
2013-06-21 17:27:42;D;pool-2-thread-1;com.a1systems.client.Client;Creating client
2013-06-21 17:27:42;D;com.a1systems.App.main();com.a1systems.client.Client;Binding state
2013-06-21 17:27:42;D;pool-1-thread-1;com.a1systems.client.RebindTask;Try to bind...
2013-06-21 17:27:42;I;pool-1-thread-1;com.cloudhopper.smpp.impl.DefaultSmppSession;sync send PDU: (bind_transceiver: 0x0000002A 0x00000009 0x00000000 0x00000001) (body: systemId [smppclient1] password [password] systemType [] interfaceVersion [0x34] addressRange (0x00 0x00 [])) (opts: )
2013-06-21 17:27:42;I;New I/O  worker #1;com.cloudhopper.smpp.impl.DefaultSmppSession;received PDU: (bind_transceiver_resp: 0x00000018 0x80000009 0x00000000 0x00000001 result: "OK") (body: systemId [SMPPSim]) (opts: )
2013-06-21 17:27:42;D;pool-1-thread-1;com.a1systems.client.Client;Bound state
2013-06-21 17:27:43;D;com.a1systems.App.main();com.a1systems.App;Try to send
2013-06-21 17:27:43;I;com.a1systems.App.main();com.cloudhopper.smpp.impl.DefaultSmppSession;sync send PDU: (submit_sm: 0x0000003E 0x00000004 0x00000000 0x00000002) (body: (serviceType [] sourceAddr [0x05 0x00 [Test]] destAddr [0x01 0x01 [79111234567]] esmCls [0x00] regDlvry [0x01] dcs [0x08] message [041F044004380432043504420021])) (opts: )
2013-06-21 17:27:43;I;New I/O  worker #1;com.cloudhopper.smpp.impl.DefaultSmppSession;received PDU: (submit_sm_resp: 0x00000012 0x80000004 0x00000000 0x00000002 result: "OK") (body: (messageId [0])) (opts: )
2013-06-21 17:27:43;I;New I/O  worker #1;com.cloudhopper.smpp.impl.DefaultSmppSession;received PDU: (deliver_sm: 0x0000003E 0x00000005 0x00000000 0x00000001) (body: (serviceType [] sourceAddr [0x05 0x00 [Test]] destAddr [0x01 0x01 [79111234567]] esmCls [0x00] regDlvry [0x01] dcs [0x08] message [041F044004380432043504420021])) (opts: )
2013-06-21 17:27:43;D;New I/O  worker #1;com.a1systems.MySmppSessionHandler;Got DELIVER_SM
2013-06-21 17:27:43;D;New I/O  worker #1;com.a1systems.MySmppSessionHandler;Msg id=null
2013-06-21 17:27:43;D;New I/O  worker #1;com.a1systems.MySmppSessionHandler;Status=null
2013-06-21 17:27:43;I;New I/O  worker #1;com.cloudhopper.smpp.impl.DefaultSmppSession;send PDU: (deliver_sm_resp: 0x00000011 0x80000005 0x00000000 0x00000001 result: "OK") (body: (messageId [])) (opts: )
2013-06-21 17:27:47;D;pool-1-thread-1;com.a1systems.client.ElinkTask;Send elink
2013-06-21 17:27:47;I;pool-1-thread-1;com.cloudhopper.smpp.impl.DefaultSmppSession;sync send PDU: (enquire_link: 0x00000010 0x00000015 0x00000000 0x00000003) (body: ) (opts: )
2013-06-21 17:27:47;I;New I/O  worker #1;com.cloudhopper.smpp.impl.DefaultSmppSession;received PDU: (enquire_link_resp: 0x00000010 0x80000015 0x00000000 0x00000003 result: "OK") (body: ) (opts: )
2013-06-21 17:27:47;D;pool-1-thread-1;com.a1systems.client.ElinkTask;Elink sent successfull
2013-06-21 17:27:48;I;New I/O  worker #1;com.cloudhopper.smpp.impl.DefaultSmppSession;received PDU: (deliver_sm: 0x0000009B 0x00000005 0x00000000 0x00000002) (body: (serviceType [] sourceAddr [0x01 0x01 [79111234567]] destAddr [0x05 0x00 [Test]] esmCls [0x04] regDlvry [0x00] dcs [0x08] message [69643A30207375623A30303120646C7672643A303031207375626D697420646174653A3133303632313137323720646F6E6520646174653A3133303632313137323720737461743A44454C49565244206572723A30303020546578743A041F044004380432043504420021])) (opts: )
2013-06-21 17:27:48;D;New I/O  worker #1;com.a1systems.MySmppSessionHandler;Got DELIVER_SM
2013-06-21 17:27:48;D;New I/O  worker #1;com.a1systems.MySmppSessionHandler;Msg id=null
2013-06-21 17:27:48;D;New I/O  worker #1;com.a1systems.MySmppSessionHandler;Status=null
2013-06-21 17:27:48;I;New I/O  worker #1;com.cloudhopper.smpp.impl.DefaultSmppSession;send PDU: (deliver_sm_resp: 0x00000011 0x80000005 0x00000000 0x00000002 result: "OK") (body: (messageId [])) (opts: )
2013-06-21 17:27:51;D;New I/O  worker #1;com.a1systems.client.Client;Binding state
2013-06-21 17:27:51;D;pool-1-thread-1;com.a1systems.client.RebindTask;Try to bind...
2013-06-21 17:27:51;D;pool-1-thread-1;com.a1systems.client.RebindTask;{}
com.cloudhopper.smpp.type.SmppChannelConnectException: Unable to connect to host [127.0.0.1] and port [2775]: Connection refused
	at com.cloudhopper.smpp.impl.DefaultSmppClient.createConnectedChannel(DefaultSmppClient.java:255) ~[ch-smpp-5.0.1.jar:5.0.1]
	at com.cloudhopper.smpp.impl.DefaultSmppClient.doOpen(DefaultSmppClient.java:212) ~[ch-smpp-5.0.1.jar:5.0.1]
	at com.cloudhopper.smpp.impl.DefaultSmppClient.bind(DefaultSmppClient.java:181) ~[ch-smpp-5.0.1.jar:5.0.1]
	at com.a1systems.client.RebindTask.run(RebindTask.java:29) ~[classes/:na]
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471) [na:1.7.0_15]
	at java.util.concurrent.FutureTask$Sync.innerRunAndReset(FutureTask.java:351) [na:1.7.0_15]
	at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:178) [na:1.7.0_15]
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:178) [na:1.7.0_15]
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) [na:1.7.0_15]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) [na:1.7.0_15]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) [na:1.7.0_15]
	at java.lang.Thread.run(Thread.java:722) [na:1.7.0_15]
Caused by: java.net.ConnectException: Connection refused
	at sun.nio.ch.SocketChannelImpl.checkConnect(Native Method) ~[na:1.7.0_15]
	at sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:692) ~[na:1.7.0_15]
	at org.jboss.netty.channel.socket.nio.NioClientSocketPipelineSink$Boss.connect(NioClientSocketPipelineSink.java:490) ~[netty-3.5.8.Final.jar:na]
	at org.jboss.netty.channel.socket.nio.NioClientSocketPipelineSink$Boss.processSelectedKeys(NioClientSocketPipelineSink.java:446) ~[netty-3.5.8.Final.jar:na]
	at org.jboss.netty.channel.socket.nio.NioClientSocketPipelineSink$Boss.run(NioClientSocketPipelineSink.java:359) ~[netty-3.5.8.Final.jar:na]
	... 3 common frames omitted
2013-06-21 17:27:56;D;pool-1-thread-2;com.a1systems.client.RebindTask;Try to bind...
2013-06-21 17:27:56;D;pool-1-thread-2;com.a1systems.client.RebindTask;{}
com.cloudhopper.smpp.type.SmppChannelConnectException: Unable to connect to host [127.0.0.1] and port [2775]: Connection refused
	at com.cloudhopper.smpp.impl.DefaultSmppClient.createConnectedChannel(DefaultSmppClient.java:255) ~[ch-smpp-5.0.1.jar:5.0.1]
	at com.cloudhopper.smpp.impl.DefaultSmppClient.doOpen(DefaultSmppClient.java:212) ~[ch-smpp-5.0.1.jar:5.0.1]
	at com.cloudhopper.smpp.impl.DefaultSmppClient.bind(DefaultSmppClient.java:181) ~[ch-smpp-5.0.1.jar:5.0.1]
	at com.a1systems.client.RebindTask.run(RebindTask.java:29) ~[classes/:na]
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471) [na:1.7.0_15]
	at java.util.concurrent.FutureTask$Sync.innerRunAndReset(FutureTask.java:351) [na:1.7.0_15]
	at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:178) [na:1.7.0_15]
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:178) [na:1.7.0_15]
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) [na:1.7.0_15]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) [na:1.7.0_15]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) [na:1.7.0_15]
	at java.lang.Thread.run(Thread.java:722) [na:1.7.0_15]
Caused by: java.net.ConnectException: Connection refused
	at sun.nio.ch.SocketChannelImpl.checkConnect(Native Method) ~[na:1.7.0_15]
	at sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:692) ~[na:1.7.0_15]
	at org.jboss.netty.channel.socket.nio.NioClientSocketPipelineSink$Boss.connect(NioClientSocketPipelineSink.java:490) ~[netty-3.5.8.Final.jar:na]
	at org.jboss.netty.channel.socket.nio.NioClientSocketPipelineSink$Boss.processSelectedKeys(NioClientSocketPipelineSink.java:446) ~[netty-3.5.8.Final.jar:na]
	at org.jboss.netty.channel.socket.nio.NioClientSocketPipelineSink$Boss.run(NioClientSocketPipelineSink.java:359) ~[netty-3.5.8.Final.jar:na]
	at org.jboss.netty.util.ThreadRenamingRunnable.run(ThreadRenamingRunnable.java:102) ~[netty-3.5.8.Final.jar:na]
	at org.jboss.netty.util.internal.DeadLockProofWorker$1.run(DeadLockProofWorker.java:42) ~[netty-3.5.8.Final.jar:na]
	... 3 common frames omitted
2013-06-21 17:28:01;D;pool-1-thread-2;com.a1systems.client.RebindTask;Try to bind...
2013-06-21 17:28:01;D;pool-1-thread-2;com.a1systems.client.RebindTask;{}
com.cloudhopper.smpp.type.SmppChannelConnectException: Unable to connect to host [127.0.0.1] and port [2775]: Connection refused
	at com.cloudhopper.smpp.impl.DefaultSmppClient.createConnectedChannel(DefaultSmppClient.java:255) ~[ch-smpp-5.0.1.jar:5.0.1]
	at com.cloudhopper.smpp.impl.DefaultSmppClient.doOpen(DefaultSmppClient.java:212) ~[ch-smpp-5.0.1.jar:5.0.1]
	at com.cloudhopper.smpp.impl.DefaultSmppClient.bind(DefaultSmppClient.java:181) ~[ch-smpp-5.0.1.jar:5.0.1]
	at com.a1systems.client.RebindTask.run(RebindTask.java:29) ~[classes/:na]
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471) [na:1.7.0_15]
	at java.util.concurrent.FutureTask$Sync.innerRunAndReset(FutureTask.java:351) [na:1.7.0_15]
	at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:178) [na:1.7.0_15]
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:178) [na:1.7.0_15]
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) [na:1.7.0_15]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) [na:1.7.0_15]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) [na:1.7.0_15]
	at java.lang.Thread.run(Thread.java:722) [na:1.7.0_15]
Caused by: java.net.ConnectException: Connection refused
	at sun.nio.ch.SocketChannelImpl.checkConnect(Native Method) ~[na:1.7.0_15]
	at sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:692) ~[na:1.7.0_15]
	at org.jboss.netty.channel.socket.nio.NioClientSocketPipelineSink$Boss.connect(NioClientSocketPipelineSink.java:490) ~[netty-3.5.8.Final.jar:na]
	at org.jboss.netty.channel.socket.nio.NioClientSocketPipelineSink$Boss.processSelectedKeys(NioClientSocketPipelineSink.java:446) ~[netty-3.5.8.Final.jar:na]
	at org.jboss.netty.channel.socket.nio.NioClientSocketPipelineSink$Boss.run(NioClientSocketPipelineSink.java:359) ~[netty-3.5.8.Final.jar:na]
	at org.jboss.netty.util.ThreadRenamingRunnable.run(ThreadRenamingRunnable.java:102) ~[netty-3.5.8.Final.jar:na]
	at org.jboss.netty.util.internal.DeadLockProofWorker$1.run(DeadLockProofWorker.java:42) ~[netty-3.5.8.Final.jar:na]
	... 3 common frames omitted
2013-06-21 17:28:06;D;pool-1-thread-2;com.a1systems.client.RebindTask;Try to bind...
2013-06-21 17:28:06;D;pool-1-thread-2;com.a1systems.client.RebindTask;{}
com.cloudhopper.smpp.type.SmppChannelConnectException: Unable to connect to host [127.0.0.1] and port [2775]: Connection refused
	at com.cloudhopper.smpp.impl.DefaultSmppClient.createConnectedChannel(DefaultSmppClient.java:255) ~[ch-smpp-5.0.1.jar:5.0.1]
	at com.cloudhopper.smpp.impl.DefaultSmppClient.doOpen(DefaultSmppClient.java:212) ~[ch-smpp-5.0.1.jar:5.0.1]
	at com.cloudhopper.smpp.impl.DefaultSmppClient.bind(DefaultSmppClient.java:181) ~[ch-smpp-5.0.1.jar:5.0.1]
	at com.a1systems.client.RebindTask.run(RebindTask.java:29) ~[classes/:na]
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471) [na:1.7.0_15]
	at java.util.concurrent.FutureTask$Sync.innerRunAndReset(FutureTask.java:351) [na:1.7.0_15]
	at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:178) [na:1.7.0_15]
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:178) [na:1.7.0_15]
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) [na:1.7.0_15]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) [na:1.7.0_15]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) [na:1.7.0_15]
	at java.lang.Thread.run(Thread.java:722) [na:1.7.0_15]
Caused by: java.net.ConnectException: Connection refused
	at sun.nio.ch.SocketChannelImpl.checkConnect(Native Method) ~[na:1.7.0_15]
	at sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:692) ~[na:1.7.0_15]
	at org.jboss.netty.channel.socket.nio.NioClientSocketPipelineSink$Boss.connect(NioClientSocketPipelineSink.java:490) ~[netty-3.5.8.Final.jar:na]
	at org.jboss.netty.channel.socket.nio.NioClientSocketPipelineSink$Boss.processSelectedKeys(NioClientSocketPipelineSink.java:446) ~[netty-3.5.8.Final.jar:na]
	at org.jboss.netty.channel.socket.nio.NioClientSocketPipelineSink$Boss.run(NioClientSocketPipelineSink.java:359) ~[netty-3.5.8.Final.jar:na]
	at org.jboss.netty.util.ThreadRenamingRunnable.run(ThreadRenamingRunnable.java:102) ~[netty-3.5.8.Final.jar:na]
	at org.jboss.netty.util.internal.DeadLockProofWorker$1.run(DeadLockProofWorker.java:42) ~[netty-3.5.8.Final.jar:na]
	... 3 common frames omitted
2013-06-21 17:28:11;D;pool-1-thread-2;com.a1systems.client.RebindTask;Try to bind...
2013-06-21 17:28:11;W;pool-1-thread-2;com.cloudhopper.smpp.impl.DefaultSmppClient;Session configuration did not have a name set - skipping threadRenamer in pipeline
2013-06-21 17:28:11;I;pool-1-thread-2;com.cloudhopper.smpp.impl.DefaultSmppSession;sync send PDU: (bind_transceiver: 0x0000002A 0x00000009 0x00000000 0x00000001) (body: systemId [smppclient1] password [password] systemType [] interfaceVersion [0x34] addressRange (0x00 0x00 [])) (opts: )
2013-06-21 17:28:11;I;New I/O  worker #2;com.cloudhopper.smpp.impl.DefaultSmppSession;received PDU: (bind_transceiver_resp: 0x00000018 0x80000009 0x00000000 0x00000001 result: "OK") (body: systemId [SMPPSim]) (opts: )
2013-06-21 17:28:11;D;pool-1-thread-2;com.a1systems.client.Client;Bound state

Исходный код доступен по адресу: https://github.com/wizardjedi/my-spring-learning/tree/pres_0_9_6 ветка pres_0_9_6.

Clone this wiki locally