jSMPPメソッドUDH、SAR、ペイロードを使用したSMS送信

当社はメールとSMSに従事しています。 初期段階では、中間APIを使用してSMSを送信しました。 会社は成長しており、顧客が増えているため、smppプロトコルを介してSMSを送信するための独自のソフトウェアを作成することにしました。 これにより、一連のバイトをプロバイダーに送信することができ、すでに国や国内の事業者間でトラフィックが分散されていました。



SMSを送信するための利用可能な無料のライブラリを確認した後、選択はjsmppに委ねられましたこれに加えて、使用に関する情報は主にjsmppによってgoogleからソートされました 。 ロシアのtytsの SMPPプロトコル自体の説明。



この記事で、SMPPサービスを作成するときの生活が楽になることを願っています。



はじめに



SMS送信の段階的な分析から始めましょう。 SMS送信ロジックは次のとおりです。



1.クライアントは、送信したいSMSを(jsonの形式で)送信します。



{ "sms_id": "test_sms_123", "sender": "  ", "phone": "380959999900", "text_body": "!   1000000$!", "flash": 0 }
      
      





flashパラメーターは、これがflash smsではないことを示しています。



2.データを受信すると、プロバイダーにデータを送信する方法、つまりUDH、SAR、Payloadの準備を開始します。 それはすべて、プロバイダーがサポートする方法に依存します。



3.データをプロバイダーに転送すると、プロバイダーによるSMS受信を識別する行が返されます。この行をtransaction_idと呼びます。



4. SMSを送信すると、プロバイダーには48時間以内に最終ステータス(配信済み、未配信など)を返す権利が与えられます。 したがって、 sms_idと受信したtransaction_idを保存する必要があります。



5. SMSが受信者に配信されると、プロバイダーはtransaction_idとステータス(配信済み)を送信します。



ステータス



ステータスに関する2つの記事をアドバイスします。10のステータスのみ説明され、名前が付けられています。ここに 、ステータスコードの完全なリストとその完全な説明があります。



jSmpp APIのMaven依存関係:



  <dependency> <groupId>com.googlecode.jsmpp</groupId> <artifactId>jsmpp</artifactId> <version>2.1.0-RELEASE</version> </dependency>
      
      





作業する主なクラスについて説明します。





ペイロードの送信方法



これを送信する最も簡単な方法はペイロードです。 SMSを送信する期間に関係なく、1つのデータパケットでSMSを送信します。 プロバイダー自体がSMSを部分に分割します。 パーツは、受信者の電話で既に接着されています。 それから、SMSの送信の実装のレビューを開始します。



まず、プロバイダーに接続する必要があります。 これを行うには、セッションとそのリスナー、および送信されたSMSのステータスの受信に応答するリスナーを作成する必要があります。 以下は、作成したSmppServerクラスにデータが保存されているプロバイダーに接続するためcreateSmppSessionメソッドの例です。 ログイン、パスワード、IP、ポートなどのデータが含まれています。



SMPP接続を作成する方法
 protected SMPPSession session; protected SmppServer server; private ExecutorService receiveTask; private SessionStateListener stateListener; ... // some of your code public void createSmppSession() { StopWatchHires watchHires = new StopWatchHires(); watchHires.start(); log.info("Start to create SMPPSession {}", sessionName); try { session = new SMPPSession(); session.connectAndBind(server.getHostname(), server.getPort(), new BindParameter( BindType.BIND_TRX, server.getSystemId(), server.getPassword(), server.getSystemType(), TypeOfNumber.UNKNOWN, NumberingPlanIndicator.UNKNOWN, null)); stateListener = new SessionStateListenerImpl(); session.addSessionStateListener(stateListener); session.setMessageReceiverListener(new SmsReceiverListenerImpl()); session.setTransactionTimer(TRANSACTION_TIMER); if (Objects.isNull(receiveTask) || receiveTask.isShutdown() || receiveTask.isTerminated()) { this.receiveTask = Executors.newCachedThreadPool(); } watchHires.stop(); log.info("Open smpp session id {}, state {}, duration {} for {}", session.getSessionId(), session.getSessionState().name(), watchHires.toHiresString(), sessionName); } catch (IOException e) { watchHires.stop(); if (SmppServerConnectResponse.contains(e.getMessage())) { log.error("Exception while SMPP session creating. Reason: {}. Duration {}, {}", e.getMessage(), watchHires.toHiresString(), sessionName); close(); return; } else if (e instanceof UnknownHostException) { log.error("Exception while SMPP session creating. Unknown hostname {}, duration {}, {}", e.getMessage(), watchHires.toHiresString(), sessionName); close(); return; } else { log.error("Failed to connect SMPP session for {}, duration {}, Because {}", sessionName, watchHires.toHiresString(), e.getMessage()); } } if (!isConnected()) { reconnect(); } }
      
      







この例からわかるように、 SmsReceiverListenerImplおよびSessionStateListenerImplクラスオブジェクトを使用してセッションを作成します。 1つ目は送信されたSMSのステータスを受信し、2つ目はセッションリスナーです。



onStateChangeメソッドのSessionStateListenerImplクラスは、古いセッション状態と新しいセッション状態のクラスを取得します。 この例では、セッションが接続されていない場合、再接続が試行されます。



SMPPセッションリスナー
  /** * This class will receive the notification from {@link SMPPSession} for the * state changes. It will schedule to re-initialize session. */ class SessionStateListenerImpl implements SessionStateListener { @Override public void onStateChange(SessionState newState, SessionState oldState, Object source) { if (!newState.isBound()) { log.warn("SmppSession changed status from {} to {}. {}", oldState, newState, sessionName); reconnect(); } } }
      
      







SmsReceiverListenerImplの例。 3つのメソッドonAcceptDeliverSmonAcceptAlertNotificationonAcceptDataSmをオーバーライドする必要があります。 SMSを送信するのは最初の人だけです。 彼はプロバイダーからtransaction_idを受け取ります。プロバイダーはその下でSMSとステータスを登録しています。 この例では、 SmppErrorStatusStatusTypeの 2つのクラスがあります。 それぞれ 、エラーステータスを保存し、ステータス(プロバイダーに送信されず、プロバイダーに送信されないなど)を送信する列挙クラスです。



リスナーSMSステータス
 /*The logic on this listener should be accomplish in a short time, because the deliver_sm_resp will be processed after the logic executed.*/ class SmsReceiverListenerImpl implements MessageReceiverListener { @Override public void onAcceptDeliverSm(DeliverSm deliverSm) throws ProcessRequestException { if (Objects.isNull(deliverSm)) { log.error("Smpp server return NULL delivery answer"); return; } try { // this message is delivery receipt DeliveryReceipt delReceipt = deliverSm.getShortMessageAsDeliveryReceipt(); //delReceipt.getId() must be equals transactionId from SMPPServer String transactionId = delReceipt.getId(); StatusType statusType; String subStatus; if (MessageType.SMSC_DEL_RECEIPT.containedIn(deliverSm.getEsmClass())) { // && delReceipt.getDelivered() == 1 statusType = getDeliveryStatusType(delReceipt.getFinalStatus()); SmppErrorStatus smppErrorStatus = SmppErrorStatus.contains(delReceipt.getError()); if (smppErrorStatus != null) subStatus = smppErrorStatus.name(); else subStatus = delReceipt.getError(); } else { statusType = StatusType.SMS_UNDELIVERED; // this message is regular short message log.error("Delivery SMS event has wrong receipt. Message: {}", deliverSm.getShortMessage()); subStatus = SmppErrorStatus.INVALID_FORMAT.name(); } // some providers return phone number in deliverSm.getSourceAddr() String phoneNumber = deliverSm.getDestAddress(); saveDeliveryStatus(transactionId, statusType, subStatus, phoneNumber)); log.info("Receiving delivery receipt from {} to {}, transaction id {}, status {}, subStatus {}", deliverSm.getSourceAddr(), deliverSm.getDestAddress(), transactionId, statusType, subStatus); } catch (InvalidDeliveryReceiptException e) { log.error("Exception while SMS is sending, destination address {}, {}", deliverSm.getDestAddress(), e.getMessage(), e); } } @Override public void onAcceptAlertNotification(AlertNotification alertNotification) { log.error("Error on sending SMS message: {}", alertNotification.toString()); } @Override public DataSmResult onAcceptDataSm(DataSm dataSm, Session source) throws ProcessRequestException { log.debug("Event in SmsReceiverListenerImpl.onAcceptDataSm!"); return null; } private StatusType getDeliveryStatusType(DeliveryReceiptState state) {<cut /> if (state.equals(DeliveryReceiptState.DELIVRD)) return StatusType.SMS_DELIVERED; else if (state.equals(DeliveryReceiptState.ACCEPTD)) return StatusType.ACCEPTED; else if (state.equals(DeliveryReceiptState.DELETED)) return StatusType.DELETED; else if (state.equals(DeliveryReceiptState.EXPIRED)) return StatusType.EXPIRED; else if (state.equals(DeliveryReceiptState.REJECTD)) return StatusType.REJECTED; else if (state.equals(DeliveryReceiptState.UNKNOWN)) return StatusType.UNKNOWN; else return StatusType.SMS_UNDELIVERED; } }
      
      







最後に、最も重要な方法はSMSを送信する方法です。 上記のJSONをSMSMessageオブジェクトにデシリアライズしました。したがって、このクラスのオブジェクトに会うときは、送信されたSMSに関する必要な情報がすべて含まれていることに注意してください。



以下で説明するsendSmsMessageメソッドは、 SingleSmppTransactionMessageクラスのオブジェクトを返します。このクラスには、プロバイダーによって割り当てられたtransaction_idで送信されたSMSに関するデータが含まれます。



Gsm0338クラスは、SMSのキリル文字の内容を判別するのに役立ちます。 これはプロバイダーに通知する必要があるため重要です。 このクラスは、 ドキュメントに基づいて構築されました。



EnumクラスのSmppResponseErrorは、プロバイダーのSMPPサーバーが返す可能性のあるエラーに基づいて構築されました。リンクはこちら



SMS送信方法
 public SingleSmppTransactionMessage sendSmsMessage(final SMSMessage message) { final String bodyText = message.getTextBody(); final int smsLength = bodyText.length(); OptionalParameter messagePayloadParameter; String transportId = null; String error = null; boolean isUSC2 = false; boolean isFlashSms = message.isFlash(); StopWatchHires watchHires = new StopWatchHires(); watchHires.start(); log.debug("Start to send sms id {} length {}", message.getSmsId(), smsLength); try { byte[] encoded; if ((encoded = Gsm0338.encodeInGsm0338(bodyText)) != null) { messagePayloadParameter = new OptionalParameter.OctetString( OptionalParameter.Tag.MESSAGE_PAYLOAD.code(), encoded); log.debug("Found Latin symbols in sms id {} message", message.getSmsId()); } else { isUSC2 = true; messagePayloadParameter = new OptionalParameter.OctetString( OptionalParameter.Tag.MESSAGE_PAYLOAD.code(), bodyText, "UTF-16BE"); log.debug("Found Cyrillic symbols in sms id {} message", message.getSmsId()); } GeneralDataCoding dataCoding = getDataCodingForServer(isUSC2, isFlashSms); log.debug("Selected data_coding: {}, value: {}, SMPP server type: {}", dataCoding.getAlphabet(), dataCoding.toByte(), server.getServerType()); transportId = session.submitShortMessage( "CMT", TypeOfNumber.ALPHANUMERIC, NumberingPlanIndicator.UNKNOWN, message.getSender(), TypeOfNumber.INTERNATIONAL, NumberingPlanIndicator.ISDN, message.getPhone(), ESM_CLASS, ZERO_BYTE, ONE_BYTE, null, null, rd, ZERO_BYTE, dataCoding, ZERO_BYTE, EMPTY_ARRAY, messagePayloadParameter); } catch (PDUException e) { error = e.getMessage(); // Invalid PDU parameter log.error("SMS id:{}. Invalid PDU parameter {}", message.getSmsId(), error); log.debug("Session id {}, state {}. {}", session.getSessionId(), session.getSessionState().name(), e); } catch (ResponseTimeoutException e) { error = analyseExceptionMessage(e.getMessage()); // Response timeout log.error("SMS id:{}. Response timeout: {}", message.getSmsId(), e.getMessage()); log.debug("Session id {}, state {}. {}", session.getSessionId(), session.getSessionState().name(), e); } catch (InvalidResponseException e) { error = e.getMessage(); // Invalid response log.error("SMS id:{}. Receive invalid response: {}", message.getSmsId(), error); log.debug("Session id {}, state {}. {}", session.getSessionId(), session.getSessionState().name(), e); } catch (NegativeResponseException e) { // get smpp error codes error = String.valueOf(e.getCommandStatus()); // Receiving negative response (non-zero command_status) log.error("SMS id:{}, {}. Receive negative response: {}", message.getSmsId(), message.getPhone(), e.getMessage()); log.debug("Session id {}, state {}. {}", session.getSessionId(), session.getSessionState().name(), e); } catch (IOException e) { error = analyseExceptionMessage(e.getMessage()); log.error("SMS id:{}. IO error occur {}", message.getSmsId(), e.getMessage()); log.debug("Session id {}, state {}. {}", session.getSessionId(), session.getSessionState().name(), e); } catch (Exception e) { error = e.getMessage(); log.error("SMS id:{}. Unexpected exception error occur {}", message.getSmsId(), error); log.debug("Session id {}, state {}. {}", session.getSessionId(), session.getSessionState().name(), e); } watchHires.stop(); log.info("Sms id:{} length {} sent with transaction id:{} from {} to {}, duration {}", message.getSmsId(), smsLength, transportId, message.getSender(), message.getPhone(), watchHires.toHiresString()); return new SingleSmppTransactionMessage(message, server.getId(), error, transportId); } private GeneralDataCoding getDataCodingForServer (boolean isUCS2Coding, boolean isFlashSms){ GeneralDataCoding coding; if (isFlashSms) { coding = isUCS2Coding ? UCS2_CODING : DEFAULT_CODING; } else { coding = isUCS2Coding ? UCS2_CODING_WITHOUT_CLASS : DEFAULT_CODING_WITHOUT_CLASS; } return coding; } /** * Analyze exception message for our problem with session * While schedule reconnecting session sms didn't send and didn't put to resend */ private String analyseExceptionMessage(String exMessage){ if(Objects.isNull(exMessage)) return exMessage; if (exMessage.contains("No response after waiting for")) return SmppResponseError.RECONNECT_RSPCTIMEOUT.getErrCode(); else if (exMessage.contains("Cannot submitShortMessage while")) return SmppResponseError.RECONNECT_CANNTSUBMIT.getErrCode(); else if (exMessage.contains("Failed sending submit_sm command")) return SmppResponseError.RECONNECT_FAILEDSUBMIT.getErrCode(); return exMessage; }
      
      







次の記事では、UDHを使用してSMSを送信する方法について説明します。リンクはこちらです。 このオプションでは、メッセージをバイトに変換してからサブメッセージに分割し、最初のビットに番号と番号を指定する必要があります。 楽しいでしょう。



Githubリンク 。 私の記事がSMPPサービスの開発を簡素化することを願っています。 読んでくれてありがとう。



All Articles