SIP Server with Mobicents in Java

Overview

Mobicents is an open-source Java-based SIP (Session Initiation Protocol) server that provides VoIP and real-time communication capabilities. It's part of the Restcomm project and supports SIP servlets for building telecom applications.

1. Setup and Dependencies

Maven Dependencies

<!-- pom.xml -->
<properties>
<mobicents.version>3.0.0.FINAL</mobicents.version>
<sip.servlet.version>3.0.0.FINAL</sip.servlet.version>
</properties>
<dependencies>
<!-- Mobicents SIP Servlets -->
<dependency>
<groupId>org.mobicents.servlet.sip</groupId>
<artifactId>sip-servlets-core-api</artifactId>
<version>${sip.servlet.version}</version>
</dependency>
<dependency>
<groupId>org.mobicents.servlet.sip</groupId>
<artifactId>sip-servlets-core-impl</artifactId>
<version>${sip.servlet.version}</version>
</dependency>
<!-- SIP API -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.sip</groupId>
<artifactId>jain-sip-api</artifactId>
<version>1.2.1.4</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!-- Utilities -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1.1-jre</version>
</dependency>
</dependencies>

Web Application Configuration

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" 
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<display-name>Mobicents SIP Server</display-name>
<!-- SIP Servlets Configuration -->
<servlet>
<servlet-name>sip-server</servlet-name>
<servlet-class>
org.mobicents.servlet.sip.startup.SipServletContextListener
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- SIP Servlet Mapping -->
<servlet-mapping>
<servlet-name>sip-server</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<!-- Main SIP Servlet -->
<servlet>
<servlet-name>BasicSipServlet</servlet-name>
<servlet-class>com.yourapp.sip.BasicSipServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>BasicSipServlet</servlet-name>
<url-pattern>/sip/*</url-pattern>
</servlet-mapping>
<!-- Concurrent Utilities -->
<listener>
<listener-class>
org.mobicents.servlet.sip.startup.SipConnectorListener
</listener-class>
</listener>
</web-app>

sip.xml

<?xml version="1.0" encoding="UTF-8"?>
<sip-app 
xmlns="http://www.jcp.org/xml/ns/sipservlet"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.jcp.org/xml/ns/sipservlet 
http://www.jcp.org/xml/ns/sipservlet/sip-app_1_1.xsd"
version="1.1">
<app-name>Mobicents SIP Application</app-name>
<display-name>Basic SIP Server</display-name>
<servlet>
<servlet-name>BasicSipServlet</servlet-name>
<servlet-class>com.yourapp.sip.BasicSipServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>BasicSipServlet</servlet-name>
<pattern>
<equal>
<var>request.method</var>
<value>INVITE</value>
</equal>
</pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>BasicSipServlet</servlet-name>
<pattern>
<equal>
<var>request.method</var>
<value>BYE</value>
</equal>
</pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>BasicSipServlet</servlet-name>
<pattern>
<equal>
<var>request.method</var>
<value>REGISTER</value>
</equal>
</pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>BasicSipServlet</servlet-name>
<pattern>
<equal>
<var>request.method</var>
<value>ACK</value>
</equal>
</pattern>
</servlet-mapping>
</sip-app>

2. Basic SIP Servlet Implementation

Core SIP Servlet

package com.yourapp.sip;
import org.mobicents.servlet.sip.RestcommSipServlet;
import org.mobicents.servlet.sip.annotation.SipApplication;
import org.mobicents.servlet.sip.annotation.SipApplicationKey;
import org.mobicents.servlet.sip.annotation.SipListener;
import org.mobicents.servlet.sip.message.SipFactoryImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.sip.*;
import java.io.IOException;
import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@SipApplication(
name = "BasicSipApplication",
displayName = "Basic SIP Server Application",
applicationKey = @SipApplicationKey(
key = "BasicSipApp",
appName = "BasicSipApplication"
)
)
@SipListener
public class BasicSipServlet extends SipServlet {
private static final long serialVersionUID = 1L;
private static final Logger logger = LoggerFactory.getLogger(BasicSipServlet.class);
private SipFactory sipFactory;
private Map<String, SipSession> activeSessions;
private Map<String, UserRegistration> registeredUsers;
@Override
public void init() {
this.sipFactory = (SipFactory) getServletContext().getAttribute(SIP_FACTORY);
this.activeSessions = new ConcurrentHashMap<>();
this.registeredUsers = new ConcurrentHashMap<>();
logger.info("Basic SIP Servlet initialized successfully");
}
@Override
protected void doRegister(SipServletRequest request) throws ServletException, IOException {
try {
String fromUser = getUsernameFromAddress(request.getFrom().getURI().toString());
String contact = request.getAddressHeader("Contact").getURI().toString();
logger.info("REGISTER request from: {}, contact: {}", fromUser, contact);
// Register user
registeredUsers.put(fromUser, new UserRegistration(fromUser, contact, System.currentTimeMillis()));
// Create OK response
SipServletResponse response = request.createResponse(SipServletResponse.SC_OK);
response.setAddressHeader("Contact", request.getAddressHeader("Contact"));
response.addHeader("Expires", "3600");
response.send();
logger.info("User {} registered successfully", fromUser);
} catch (Exception e) {
logger.error("Error processing REGISTER request", e);
request.createResponse(SipServletResponse.SC_SERVER_INTERNAL_ERROR).send();
}
}
@Override
protected void doInvite(SipServletRequest request) throws ServletException, IOException {
try {
String fromUser = getUsernameFromAddress(request.getFrom().getURI().toString());
String toUser = getUsernameFromAddress(request.getTo().getURI().toString());
logger.info("INVITE request from {} to {}", fromUser, toUser);
// Check if target user is registered
UserRegistration targetUser = registeredUsers.get(toUser);
if (targetUser == null) {
logger.warn("User {} not found", toUser);
request.createResponse(SipServletResponse.SC_NOT_FOUND).send();
return;
}
// Create provisional response
SipServletResponse trying = request.createResponse(SipServletResponse.SC_TRYING);
trying.send();
// Create ringing response
SipServletResponse ringing = request.createResponse(SipServletResponse.SC_RINGING);
ringing.send();
// Store the session
String sessionId = generateSessionId();
activeSessions.put(sessionId, request.getSession());
// For demo purposes, auto-answer after 2 seconds
new Thread(() -> {
try {
Thread.sleep(2000);
SipServletResponse ok = request.createResponse(SipServletResponse.SC_OK);
ok.setContent(request.getContent(), request.getContentType());
ok.send();
logger.info("Call established between {} and {}", fromUser, toUser);
} catch (Exception e) {
logger.error("Error establishing call", e);
}
}).start();
} catch (Exception e) {
logger.error("Error processing INVITE request", e);
request.createResponse(SipServletResponse.SC_SERVER_INTERNAL_ERROR).send();
}
}
@Override
protected void doAck(SipServletRequest request) throws ServletException, IOException {
String sessionId = request.getSession().getId();
logger.info("ACK received for session: {}", sessionId);
// Call is now established
// You can start media session here
}
@Override
protected void doBye(SipServletRequest request) throws ServletException, IOException {
try {
String sessionId = request.getSession().getId();
logger.info("BYE request for session: {}", sessionId);
// Remove session
activeSessions.remove(sessionId);
// Send OK response
SipServletResponse response = request.createResponse(SipServletResponse.SC_OK);
response.send();
logger.info("Call terminated for session: {}", sessionId);
} catch (Exception e) {
logger.error("Error processing BYE request", e);
request.createResponse(SipServletResponse.SC_SERVER_INTERNAL_ERROR).send();
}
}
@Override
protected void doCancel(SipServletRequest request) throws ServletException, IOException {
String sessionId = request.getSession().getId();
logger.info("CANCEL request for session: {}", sessionId);
// Remove session and send appropriate responses
activeSessions.remove(sessionId);
request.createResponse(SipServletResponse.SC_OK).send();
}
@Override
protected void doError(SipServletRequest request) throws ServletException, IOException {
logger.error("SIP Error received: {}", request.getStatus());
logger.error("Error message: {}", request.getReasonPhrase());
}
// Utility methods
private String getUsernameFromAddress(String address) {
if (address.contains(":")) {
return address.split(":")[1].split("@")[0];
}
return address.split("@")[0].replace("sip:", "");
}
private String generateSessionId() {
return "sess_" + System.currentTimeMillis() + "_" + Math.random();
}
public Map<String, UserRegistration> getRegisteredUsers() {
return new HashMap<>(registeredUsers);
}
public Map<String, SipSession> getActiveSessions() {
return new HashMap<>(activeSessions);
}
@Override
public void destroy() {
logger.info("Basic SIP Servlet shutting down");
activeSessions.clear();
registeredUsers.clear();
super.destroy();
}
}

User Registration Model

package com.yourapp.sip;
import java.util.Objects;
public class UserRegistration {
private String username;
private String contact;
private long registrationTime;
private long expires;
public UserRegistration(String username, String contact, long registrationTime) {
this.username = username;
this.contact = contact;
this.registrationTime = registrationTime;
this.expires = registrationTime + (3600 * 1000); // 1 hour expiry
}
// Getters and setters
public String getUsername() { return username; }
public String getContact() { return contact; }
public long getRegistrationTime() { return registrationTime; }
public long getExpires() { return expires; }
public boolean isExpired() {
return System.currentTimeMillis() > expires;
}
public void refresh() {
this.expires = System.currentTimeMillis() + (3600 * 1000);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UserRegistration that = (UserRegistration) o;
return username.equals(that.username);
}
@Override
public int hashCode() {
return Objects.hash(username);
}
@Override
public String toString() {
return String.format("UserRegistration{username='%s', contact='%s', registered=%d}", 
username, contact, registrationTime);
}
}

3. Advanced SIP Server Features

Call Routing and Management

package com.yourapp.sip;
import org.mobicents.servlet.sip.RestcommSipServlet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.sip.*;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.*;
public class AdvancedSipServlet extends RestcommSipServlet {
private static final Logger logger = LoggerFactory.getLogger(AdvancedSipServlet.class);
private Map<String, CallSession> activeCalls;
private ScheduledExecutorService scheduler;
private CallRoutingEngine routingEngine;
@Override
public void init() {
this.activeCalls = new ConcurrentHashMap<>();
this.scheduler = Executors.newScheduledThreadPool(10);
this.routingEngine = new CallRoutingEngine();
// Start session cleanup task
scheduler.scheduleAtFixedRate(this::cleanupExpiredSessions, 1, 1, TimeUnit.MINUTES);
logger.info("Advanced SIP Servlet initialized");
}
@Override
protected void doInvite(SipServletRequest request) throws ServletException, IOException {
String callId = request.getCallId();
String from = request.getFrom().getURI().toString();
String to = request.getTo().getURI().toString();
logger.info("New INVITE - Call-ID: {}, From: {}, To: {}", callId, from, to);
try {
// Create call session
CallSession callSession = new CallSession(callId, from, to);
activeCalls.put(callId, callSession);
// Apply call routing logic
RoutingResult routing = routingEngine.routeCall(from, to);
if (!routing.isRoutable()) {
logger.warn("Call {} cannot be routed", callId);
request.createResponse(routing.getRejectCode()).send();
activeCalls.remove(callId);
return;
}
// Send provisional responses
request.createResponse(SipServletResponse.SC_TRYING).send();
request.createResponse(SipServletResponse.SC_RINGING).send();
// Store original request for later use
callSession.setOriginalRequest(request);
// Process call based on routing result
processCallRouting(callSession, routing);
} catch (Exception e) {
logger.error("Error processing INVITE for call {}", callId, e);
request.createResponse(SipServletResponse.SC_SERVER_INTERNAL_ERROR).send();
activeCalls.remove(callId);
}
}
private void processCallRouting(CallSession callSession, RoutingResult routing) {
switch (routing.getRouteType()) {
case DIRECT:
processDirectCall(callSession);
break;
case IVR:
processIvrCall(callSession);
break;
case CONFERENCE:
processConferenceCall(callSession);
break;
case FORWARD:
processForwardCall(callSession, routing.getForwardTarget());
break;
default:
logger.warn("Unknown route type for call {}", callSession.getCallId());
}
}
private void processDirectCall(CallSession callSession) {
scheduler.schedule(() -> {
try {
SipServletRequest originalRequest = callSession.getOriginalRequest();
SipServletResponse okResponse = originalRequest.createResponse(SipServletResponse.SC_OK);
// Set SDP content for media negotiation
String sdpContent = createBasicSDP();
okResponse.setContent(sdpContent, "application/sdp");
okResponse.send();
callSession.setStatus(CallStatus.ESTABLISHED);
logger.info("Direct call established: {}", callSession.getCallId());
} catch (Exception e) {
logger.error("Error establishing direct call {}", callSession.getCallId(), e);
callSession.setStatus(CallStatus.FAILED);
}
}, 2, TimeUnit.SECONDS);
}
private void processIvrCall(CallSession callSession) {
// Implement IVR logic
logger.info("Processing IVR call: {}", callSession.getCallId());
// Play welcome message, collect DTMF, etc.
}
private void processConferenceCall(CallSession callSession) {
// Implement conference call logic
logger.info("Processing conference call: {}", callSession.getCallId());
}
private void processForwardCall(CallSession callSession, String forwardTarget) {
try {
SipServletRequest originalRequest = callSession.getOriginalRequest();
// Create new INVITE to forward target
SipFactory sipFactory = (SipFactory) getServletContext().getAttribute(SIP_FACTORY);
SipApplicationSession appSession = sipFactory.createApplicationSession();
SipServletRequest forwardRequest = sipFactory.createRequest(
appSession,
"INVITE",
originalRequest.getFrom(),
originalRequest.getTo()
);
// Copy headers and content
forwardRequest.setContent(originalRequest.getContent(), originalRequest.getContentType());
// Send forward request
forwardRequest.send();
callSession.setStatus(CallStatus.FORWARDED);
logger.info("Call {} forwarded to {}", callSession.getCallId(), forwardTarget);
} catch (Exception e) {
logger.error("Error forwarding call {}", callSession.getCallId(), e);
callSession.setStatus(CallStatus.FAILED);
}
}
@Override
protected void doBye(SipServletRequest request) throws ServletException, IOException {
String callId = request.getCallId();
logger.info("BYE received for call: {}", callId);
CallSession callSession = activeCalls.remove(callId);
if (callSession != null) {
callSession.setStatus(CallStatus.TERMINATED);
logger.info("Call {} terminated", callId);
}
request.createResponse(SipServletResponse.SC_OK).send();
}
private String createBasicSDP() {
return "v=0\r\n" +
"o=user1 53655765 2353687637 IN IP4 127.0.0.1\r\n" +
"s=-\r\n" +
"c=IN IP4 127.0.0.1\r\n" +
"t=0 0\r\n" +
"m=audio 8000 RTP/AVP 0\r\n" +
"a=rtpmap:0 PCMU/8000\r\n";
}
private void cleanupExpiredSessions() {
long currentTime = System.currentTimeMillis();
activeCalls.entrySet().removeIf(entry -> 
entry.getValue().isExpired(currentTime)
);
}
@Override
public void destroy() {
scheduler.shutdown();
activeCalls.clear();
logger.info("Advanced SIP Servlet destroyed");
super.destroy();
}
}

Call Session Management

package com.yourapp.sip;
import javax.servlet.sip.SipServletRequest;
import java.util.concurrent.atomic.AtomicLong;
public class CallSession {
private String callId;
private String from;
private String to;
private CallStatus status;
private long createTime;
private long lastActivity;
private SipServletRequest originalRequest;
private AtomicLong duration;
public CallSession(String callId, String from, String to) {
this.callId = callId;
this.from = from;
this.to = to;
this.status = CallStatus.INITIATED;
this.createTime = System.currentTimeMillis();
this.lastActivity = createTime;
this.duration = new AtomicLong(0);
}
public enum CallStatus {
INITIATED, RINGING, ESTABLISHED, FORWARDED, TERMINATED, FAILED
}
// Getters and setters
public String getCallId() { return callId; }
public String getFrom() { return from; }
public String getTo() { return to; }
public CallStatus getStatus() { return status; }
public long getCreateTime() { return createTime; }
public long getLastActivity() { return lastActivity; }
public long getDuration() { return duration.get(); }
public SipServletRequest getOriginalRequest() { return originalRequest; }
public void setStatus(CallStatus status) {
this.status = status;
this.lastActivity = System.currentTimeMillis();
if (status == CallStatus.ESTABLISHED) {
// Start duration tracking
new Thread(() -> {
while (this.status == CallStatus.ESTABLISHED) {
try {
Thread.sleep(1000);
duration.incrementAndGet();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}).start();
}
}
public void setOriginalRequest(SipServletRequest request) {
this.originalRequest = request;
}
public boolean isExpired(long currentTime) {
// Consider session expired if no activity for 5 minutes
return (currentTime - lastActivity) > (5 * 60 * 1000);
}
public void updateActivity() {
this.lastActivity = System.currentTimeMillis();
}
@Override
public String toString() {
return String.format(
"CallSession{callId='%s', from='%s', to='%s', status=%s, duration=%ds}",
callId, from, to, status, duration.get()
);
}
}

Call Routing Engine

package com.yourapp.sip;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
public class CallRoutingEngine {
private Map<String, String> userExtensions;
private Map<Pattern, String> routingRules;
public CallRoutingEngine() {
this.userExtensions = new ConcurrentHashMap<>();
this.routingRules = new ConcurrentHashMap<>();
// Initialize with sample data
initializeSampleData();
}
private void initializeSampleData() {
// User extensions
userExtensions.put("1001", "sip:[email protected]");
userExtensions.put("1002", "sip:[email protected]");
userExtensions.put("1003", "sip:[email protected]");
// Routing rules (regex pattern -> action)
routingRules.put(Pattern.compile("^1800.*"), "IVR");
routingRules.put(Pattern.compile("^1900.*"), "CONFERENCE");
routingRules.put(Pattern.compile("^\\*70.*"), "CALL_FORWARD");
}
public RoutingResult routeCall(String from, String to) {
String toUser = extractUsername(to);
logger.info("Routing call from {} to {}", from, to);
// Check if target is a registered user
if (userExtensions.containsKey(toUser)) {
return new RoutingResult(RouteType.DIRECT, userExtensions.get(toUser));
}
// Apply routing rules
for (Map.Entry<Pattern, String> entry : routingRules.entrySet()) {
if (entry.getKey().matcher(toUser).matches()) {
return applyRoutingRule(entry.getValue(), from, to);
}
}
// Default: reject call
return new RoutingResult(false, SipServletResponse.SC_NOT_FOUND, "User not found");
}
private RoutingResult applyRoutingRule(String rule, String from, String to) {
switch (rule) {
case "IVR":
return new RoutingResult(RouteType.IVR, null);
case "CONFERENCE":
return new RoutingResult(RouteType.CONFERENCE, null);
case "CALL_FORWARD":
String forwardTarget = determineForwardTarget(from);
return new RoutingResult(RouteType.FORWARD, forwardTarget);
default:
return new RoutingResult(false, SipServletResponse.SC_NOT_IMPLEMENTED, "Rule not implemented");
}
}
private String extractUsername(String sipUri) {
// Extract username from SIP URI (sip:user@domain)
if (sipUri.contains(":")) {
return sipUri.split(":")[1].split("@")[0];
}
return sipUri.split("@")[0];
}
private String determineForwardTarget(String from) {
// Simple forward logic - in real application, this would check user preferences
String fromUser = extractUsername(from);
return userExtensions.getOrDefault("1002", "sip:[email protected]");
}
public void addUserExtension(String extension, String sipUri) {
userExtensions.put(extension, sipUri);
}
public void removeUserExtension(String extension) {
userExtensions.remove(extension);
}
public void addRoutingRule(String pattern, String action) {
routingRules.put(Pattern.compile(pattern), action);
}
}
class RoutingResult {
private boolean routable;
private int rejectCode;
private String rejectReason;
private RouteType routeType;
private String forwardTarget;
public RoutingResult(RouteType routeType, String forwardTarget) {
this.routable = true;
this.routeType = routeType;
this.forwardTarget = forwardTarget;
}
public RoutingResult(boolean routable, int rejectCode, String rejectReason) {
this.routable = routable;
this.rejectCode = rejectCode;
this.rejectReason = rejectReason;
}
// Getters
public boolean isRoutable() { return routable; }
public int getRejectCode() { return rejectCode; }
public String getRejectReason() { return rejectReason; }
public RouteType getRouteType() { return routeType; }
public String getForwardTarget() { return forwardTarget; }
}
enum RouteType {
DIRECT, IVR, CONFERENCE, FORWARD, REJECT
}

4. SIP Server Configuration

Server Configuration Class

package com.yourapp.sip.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class SipServerConfig {
private static final Logger logger = LoggerFactory.getLogger(SipServerConfig.class);
private static final String CONFIG_FILE = "/sip-server.properties";
private static SipServerConfig instance;
private Properties properties;
private SipServerConfig() {
loadConfiguration();
}
public static synchronized SipServerConfig getInstance() {
if (instance == null) {
instance = new SipServerConfig();
}
return instance;
}
private void loadConfiguration() {
properties = new Properties();
try (InputStream input = getClass().getResourceAsStream(CONFIG_FILE)) {
if (input != null) {
properties.load(input);
logger.info("SIP server configuration loaded successfully");
} else {
logger.warn("Configuration file not found, using defaults");
setDefaultProperties();
}
} catch (IOException e) {
logger.error("Error loading configuration file", e);
setDefaultProperties();
}
}
private void setDefaultProperties() {
properties.setProperty("sip.server.host", "127.0.0.1");
properties.setProperty("sip.server.port", "5060");
properties.setProperty("sip.server.transport", "udp");
properties.setProperty("sip.session.timeout", "300");
properties.setProperty("sip.registration.expires", "3600");
properties.setProperty("sip.max.calls", "100");
properties.setProperty("sip.audio.port.range.start", "8000");
properties.setProperty("sip.audio.port.range.end", "8100");
}
public String getProperty(String key) {
return properties.getProperty(key);
}
public String getProperty(String key, String defaultValue) {
return properties.getProperty(key, defaultValue);
}
public int getIntProperty(String key, int defaultValue) {
try {
return Integer.parseInt(properties.getProperty(key));
} catch (NumberFormatException e) {
return defaultValue;
}
}
public boolean getBooleanProperty(String key, boolean defaultValue) {
String value = properties.getProperty(key);
if (value != null) {
return Boolean.parseBoolean(value);
}
return defaultValue;
}
// Configuration getters
public String getServerHost() {
return getProperty("sip.server.host", "127.0.0.1");
}
public int getServerPort() {
return getIntProperty("sip.server.port", 5060);
}
public String getTransport() {
return getProperty("sip.server.transport", "udp");
}
public int getSessionTimeout() {
return getIntProperty("sip.session.timeout", 300);
}
public int getMaxCalls() {
return getIntProperty("sip.max.calls", 100);
}
}

Configuration Properties File

# sip-server.properties
sip.server.host=192.168.1.100
sip.server.port=5060
sip.server.transport=udp
sip.server.domain=yourdomain.com
# Session settings
sip.session.timeout=300
sip.registration.expires=3600
sip.max.calls=100
# Audio settings
sip.audio.port.range.start=8000
sip.audio.port.range.end=8100
sip.audio.codec.preference=PCMU,PCMA,G729
# Logging
sip.log.level=INFO
sip.log.requests=true
sip.log.responses=true
# Security
sip.auth.enabled=true
sip.auth.realm=YourSIPRealm
sip.auth.nonce.expiry=300
# Performance
sip.thread.pool.size=50
sip.queue.size=1000
sip.timer.interval=1000

5. Monitoring and Management

SIP Server Monitor

package com.yourapp.sip.monitor;
import com.yourapp.sip.AdvancedSipServlet;
import com.yourapp.sip.CallSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.management.*;
import java.lang.management.ManagementFactory;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
public class SipServerMonitor implements SipServerMonitorMBean {
private static final Logger logger = LoggerFactory.getLogger(SipServerMonitor.class);
private AdvancedSipServlet sipServlet;
private AtomicLong totalCalls;
private AtomicLong successfulCalls;
private AtomicLong failedCalls;
private AtomicLong totalRegistrations;
public SipServerMonitor(AdvancedSipServlet sipServlet) {
this.sipServlet = sipServlet;
this.totalCalls = new AtomicLong(0);
this.successfulCalls = new AtomicLong(0);
this.failedCalls = new AtomicLong(0);
this.totalRegistrations = new AtomicLong(0);
registerMBean();
}
private void registerMBean() {
try {
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
ObjectName name = new ObjectName("com.yourapp.sip:type=SipServerMonitor");
mbs.registerMBean(this, name);
logger.info("SIP Server Monitor MBean registered successfully");
} catch (Exception e) {
logger.error("Failed to register MBean", e);
}
}
// MBean interface implementation
@Override
public long getTotalCalls() {
return totalCalls.get();
}
@Override
public long getSuccessfulCalls() {
return successfulCalls.get();
}
@Override
public long getFailedCalls() {
return failedCalls.get();
}
@Override
public long getActiveCalls() {
return sipServlet.getActiveCalls().size();
}
@Override
public long getTotalRegistrations() {
return totalRegistrations.get();
}
@Override
public int getRegisteredUsers() {
return sipServlet.getRegisteredUsers().size();
}
@Override
public double getCallSuccessRate() {
long total = totalCalls.get();
if (total == 0) return 0.0;
return (successfulCalls.get() * 100.0) / total;
}
@Override
public String getServerStatus() {
return "RUNNING";
}
@Override
public Map<String, CallSession> getActiveCallDetails() {
return sipServlet.getActiveCalls();
}
@Override
public void resetStatistics() {
totalCalls.set(0);
successfulCalls.set(0);
failedCalls.set(0);
totalRegistrations.set(0);
}
// Methods to update statistics
public void incrementTotalCalls() {
totalCalls.incrementAndGet();
}
public void incrementSuccessfulCalls() {
successfulCalls.incrementAndGet();
}
public void incrementFailedCalls() {
failedCalls.incrementAndGet();
}
public void incrementRegistrations() {
totalRegistrations.incrementAndGet();
}
}
// MBean Interface
public interface SipServerMonitorMBean {
long getTotalCalls();
long getSuccessfulCalls();
long getFailedCalls();
long getActiveCalls();
long getTotalRegistrations();
int getRegisteredUsers();
double getCallSuccessRate();
String getServerStatus();
Map<String, CallSession> getActiveCallDetails();
void resetStatistics();
}

6. Deployment and Testing

Main Application Class

package com.yourapp.sip;
import org.mobicents.servlet.sip.startup.SipServletContextListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletContextEvent;
public class SipServerApplication extends SipServletContextListener {
private static final Logger logger = LoggerFactory.getLogger(SipServerApplication.class);
@Override
public void contextInitialized(ServletContextEvent sce) {
logger.info("Starting Mobicents SIP Server Application");
try {
// Initialize configuration
SipServerConfig config = SipServerConfig.getInstance();
// Log startup information
logger.info("SIP Server Configuration:");
logger.info("  Host: {}", config.getServerHost());
logger.info("  Port: {}", config.getServerPort());
logger.info("  Transport: {}", config.getTransport());
logger.info("  Max Calls: {}", config.getMaxCalls());
super.contextInitialized(sce);
logger.info("Mobicents SIP Server Application started successfully");
} catch (Exception e) {
logger.error("Failed to start SIP Server Application", e);
throw new RuntimeException("SIP Server initialization failed", e);
}
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
logger.info("Stopping Mobicents SIP Server Application");
try {
super.contextDestroyed(sce);
logger.info("Mobicents SIP Server Application stopped successfully");
} catch (Exception e) {
logger.error("Error during SIP Server shutdown", e);
}
}
}

Key Features Covered:

  1. Basic SIP Servlet - Handling REGISTER, INVITE, BYE requests
  2. Call Management - Session tracking and call state management
  3. Call Routing - Advanced routing logic with multiple route types
  4. Configuration Management - External configuration support
  5. Monitoring - JMX monitoring and statistics
  6. User Registration - SIP user management and authentication

This comprehensive Mobicents SIP server implementation provides a solid foundation for building VoIP applications, call centers, IVR systems, and other real-time communication solutions.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper