Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to handle bind_transrecivier command status 5 #250

Open
dotsinspace opened this issue Feb 21, 2024 · 5 comments
Open

How to handle bind_transrecivier command status 5 #250

dotsinspace opened this issue Feb 21, 2024 · 5 comments

Comments

@dotsinspace
Copy link

I have Pipeline ( Just imagine to ends of pipe with source smpp and destination smpp ) where destiantion smpp sometimes drops the connection for some reason and some time when i restart my server i start getting bind transrecivier flag with command status 5. Now how can i make sure to unbind destination smpp before sending bind request.

@elhananjair
Copy link

I am exactly facing the same issue, although I don't receive an Already bound response from SMSC, the bind_transiever request doesn't work on my end.

There is session.unbind() function but I don't think that can be executed on smpp client side, instead, it is used by the SMPP server side to clean up sessions.

@dotsinspace
Copy link
Author

Mine is working fine now because i have extened it with Proxy and then making sure that server reconnect when ever possible on connect drops.

@elhananjair
Copy link

@dotsinspace proxy? how does that help in reconnecting to the SMSC again? Can you share the idea, please?
I had a hard time reconnecting when the connection got closed, I couldn't distinguish if it was only an unbind issue or the close of the connection.

@dotsinspace
Copy link
Author

dotsinspace commented Mar 10, 2024

Here you go.

/*
 * IMPORTS
 */
import Smpp from './node_modules/smpp' // Npm: SMPP library.
import Redis from 'ioredis' // Npm: Redis library.
import _ from 'underscore' // Npm: Utility library.
import { create as DeePool } from 'deepool' // Npm: Connection pool library.
import { Queue, Worker } from 'bullmq' // Npm: BullMQ library.


/*
 * PACKAGES
 */
import Tag from 'tag'


/*
 * GLOBALS
 */
const _functionName = 'SmppManager -> Session'


/*
 * EXPORTS
 */
export default new Proxy(Smpp, {
  'get': (__target, __name) => {
    /*
     * If the property is a function, return a function that will create a new connection pool.
     * and property name is connect
     */
    if (_.isFunction(__target[__name]) && 'connect' === __name) {
      // Return pooled object.
      return ($options, __cb) => {
        // Local variable.
        let _Session

        // Variable assignment.
        _Session = __target[__name]($options, r => {
          // Send unbind pdu.
          _Session.unbind()

          // Bind given session.
          _Session.bind_transceiver({ 'system_id': $options.username, 'password': $options.password }, j => (_Session.isBounded = 0 === j.command_status))

          // Call the callback.
          __cb(r)
        })

        // Update context.
        _Session.context = $options.context

        // Only proceed if session context is context
        if (_Session && !_Session.context.isContext) return new Error('MISSING__CONTEXT')

        // Create bull queue for given connection.
        _Session.connection = new Redis({ 'connectTimeout': 10000, 'maxRetriesPerRequest': null })
        _Session.id = String.random(32)
        _Session.reconnectAttempts = 0
        _Session.Queue = new Queue(_Session.id, { 'connection': _Session.connection })
        _Session.Worker = new Worker(_Session.id, __job => _Session.isBounded ? _Session.submit_sm(__job.data) : new Error('SESSION_NOT_BOUNDED'), { 'connection': _Session.connection })
        _Session.Reconnect = () => {
          // Increment reconnect attempts
          _Session.reconnectAttempts += 1

          // Unbind previous connection.
          _Session.unbind()

          // Calculate reconnect delay with backoff factor
          const calculatedDelay = ($options.reconnectBackoffFactor ** (_Session.reconnectAttempts - 1)) * $options.initialReconnectDelay
          const reconnectDelay = Math.min(calculatedDelay, 10)

          // Check if max reconnection attempts reached
          if (_Session.reconnectAttempts <= $options.maxReconnectAttempts) {
            // Reconnect socket with a delay.
            const reconnectTimeout = setTimeout(() => {
              // Check if the session is already connected; if yes, no need to reconnect
              if (_Session.closed) {
                // Reconnect session.
                _Session.connect()
                _Session.resume()
              }

              // Clear timeout.
              clearTimeout(reconnectTimeout)

              // Clear the reconnect timeout ID
              _Session.reconnectTimeoutId = void 0
            }, reconnectDelay)

            // Save the reconnect timeout ID
            _Session.reconnectTimeoutId = reconnectTimeout
          } else {
            // Destroy the existing session
            _Session.close(() => _Session.destroy())

            // Clear timeout.
            clearTimeout(_Session.reconnectTimeoutId)

            // Reinitialize the session
            _Session = _Session.connect($options, () => {
            // Make sure to unbind the previous session
              _Session.unbind()

              // Bind given session.
              _Session.bind_transceiver({ 'system_id': $options.username, 'password': $options.password }, j => (_Session.isBounded = 0 === j.command_status))
            })
          }
        }

        // Event handlers.
        _Session.on('enquire_link', __pdu => { _Session.send(__pdu.response()) })
        _Session.on('unbind', () => { _Session.unbind_resp(); _Session.close() })
        _Session.on('pdu', j => 0 === j.command_status ? void 0 : _Session.context.Debug({ 'message': JSON.stringify({ ip: _Session.remoteAddress, ...j }) }, _functionName))
        _Session.on('debug', (e, j, k) => _Session.context.Pubsub.publish(Tag.Smpp.DebuggingIpPort(_Session.remoteAddress, _Session.remotePort), { ..._Session, 'logs': { 'type': e, 'msg': j, 'payload': k } }))
        _Session.socket.on('timeout', () => { _Session.context.Debug({ 'message': `Smpp session with id: ${_Session.remoteAddress} got timeout.` }, _functionName) })
        _Session.socket.on('close', () => { _Session.context.Debug({ 'message': `Smpp session with id: ${_Session.remoteAddress} got closed.` }, _functionName) })
        _Session.socket.on('error', e => { _Session.context.Debug({ 'message': `Smpp session with id: ${_Session.remoteAddress} got error.`, 'error': e }, _functionName); _Session.Reconnect() })

        /*
         * If user hasn't asked for pooling.
         * then return session as is.
         */
        if ($options.noPooling) return _Session

        // Const assignment.
        const _DeePooledTarget_ = DeePool(() => _Session)

        // Increase the pool size.
        _DeePooledTarget_.grow($options.sessionAllowed)

        // Return the pooled object.
        return _DeePooledTarget_
      }
    }

    // Otherwise, return the property.
    return __target[__name]
  }
})

Ability:

  1. Bull MQ for QUEUE
  2. Deepool for pooling
  3. No Connection Drop only Destroy will going to clean and drop the connection.
  4. Auto reconnects on connection drop.
  5. Make sure to use 2 minimum session incase when SMSE takes time to update itself if connection is dropped.

@elhananjair
Copy link

@dotsinspace thank you for sharing. It's different from the way I am implementing it, but I will use it as a reference thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants