65.9K
CodeProject 正在变化。 阅读更多。
Home

如何用 C# 构建一个简单的 SIP PBX,并扩展拨号计划功能

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (21投票s)

2014 年 3 月 5 日

CPOL

8分钟阅读

viewsIcon

68276

downloadIcon

3143

它演示了如何以最简单的方式开发一个功能齐全的 SIP PBX,并解释了如何创建诸如拨号计划之类的有用 VoIP 功能。

简介、背景信息

PBX 是 Private Branch eXchange 的缩写。它是一个将公司的分机连接到外部公共电话网络以及移动网络的系统。当今基于软件的 IP PBX 可以取代传统的硬件商务电话系统。PBX 使用 VoIP 技术,因此 IP PBX 通过 TCP/IP 协议栈为其内部网络提供音频/视频通话和即时消息通信,并与 PSTN (公共交换电话网) 进行互联以进行电话通信。

随着 VoIP 技术的日新月异,为 PBX 增加一些附加功能(如拨号计划)变得非常容易,这对于通常有大量并发呼叫的通信网络来说非常有用。拨号计划是服务器中的自动化系统,用于管理内部和外部呼叫、呼叫转移、呼叫保持和限制。

鉴于这些事实,我实现了我自己的基于 SIP (会话发起协议) 的电话系统,并决定分享我的项目。下面我将展示如何构建一个 PBX,然后创建一些有用的功能来扩展您的通信系统。(我特别推荐本文给初学者,因为我试图一步一步地解释整个项目。)

PBX 开发的基本要求

对于这项开发,由于程序代码是用 C# 编程语言编写的,您需要一个支持 C# 的 IDE(集成开发环境)。我更喜欢 Microsoft Visual Studio。您的 PC 上还需要安装 .NET Framework。

为了定义 PBX 的默认行为,我使用了 Ozeki VoIP SIP SDK 的后台支持。这样,我的项目还需要在您的 PC 上安装 Ozeki SDK。

要测试您的电话系统,您至少还需要两个 IP 电话。为此,我使用了两个软电话(X-Lite)。

创建新的 Microsoft Visual Studio 项目

  1. 打开 Visual Studio 后,您需要通过单击 文件 然后 新建项目 来创建一个新项目。
  2. 由于此 PBX 系统将是一个简单的控制台应用程序,因此在下一个窗口中,您需要选择 Visual C# 控制台应用程序 选项。
  3. 为您的新项目指定名称(例如 MyPBX)后,单击 确定 按钮。
  4. 要获取并使用 SDK 的 PBX 组件,您需要将它们添加到您的引用中。(为此,您需要右键单击 引用,然后选择 添加引用 选项。之后,浏览 SDK.dll 文件,该文件位于 SDK 安装的位置。选择 .dll 文件,然后单击 确定 按钮。)

编写代码

现在您需要为您的 PBX 创建一个新类。(右键单击项目名称,选择添加,然后选择类选项。为其提供一个名称——例如 MyPBX——然后单击确定按钮。)

要使用 SDK 组件和方法,您需要在代码中添加一些 using 语句

using Ozeki.VoIP.PBX;
using Ozeki.VoIP.PBX.Extensions;
using Ozeki.VoIP.PBX.PhoneCalls;
using Ozeki.VoIP.SIP;
using Ozeki.VoIP.Network;

代码示例 1: 新的 using 语句

创建 PBX 时,您的 PBX 类需要继承自 SDK 提供的 PBXBase 类。创建一个字符串类型的 localAddress 变量,然后是构造函数。它有三个参数:localAddressminPortRangemaxPortRange。(您的 PC 可能有多个网络地址。localAddress 是 PBX 监听连接客户端的地址。除了 IP 地址,您的 PC 还有几个其他端口,因为每个应用程序都使用不同的端口。您需要设置第一个参数表示的端口范围为 minPortRange 和最大参数 maxPortRange。这是端口的间隔。)localAddress 将等于参数 localAddress。之后,您需要向控制台输出两行:PBX Starting…,What is our local address。现在您有了一个功能齐全的 PBX,它能够注册任何 SIP 分机,并在它们之间建立电话线路(代码示例 2)。

现在您需要创建一个 OnStart 方法。首先将“PBX started”行写入控制台,然后设置监听端口(查看以下代码示例)。参数:localAddress、端口号和传输类型(UDP)。写入新的一行:Listened port number。调用 base.OnStart 方法。最后,创建 OnRegisterReceived 方法,该方法有 3 个参数(分机、SIPaddress、expires 参数),以及 OnUnregisterReceived 方法,该方法只有 1 个参数(分机)。请检查代码示例 2

namespace myPBX
{
    class MyPBX : PBXBase
    {
        string localAddress;
        
        public MyPBX(string localAddress, int minPortRange, int maxPortRange) : base(minPortRange, maxPortRange){
            this.localAddress = localAssress;
            Console.WriteLine("PBX Starting...");
            Console.WriteLine("Local address: " + localAddress);
        } 
        // Now you have a fully functional PBX that is able to register any SIP extensions and can establish phone lines in between them. 
        // If a registered SIP endpoint calls an other SIP endpoint by using the SIP account information, the communication line will be established between them. 
        // This is the default behaviour of a simple SIP PBX.
        // You can extend your PBX functionality with authentication.
        // By default, the OnRegisterReceived method allows any SIP account registrations to the PBX.
        // If you want to determine which extensions you will register and which you will not, just override the OnRegisterReceived method. 
        protected override void OnStart()
        { 
            Console.WriteLine("PBX started.");
            SetListenPort(localAddress,5060,TransportType.Udp);
            
            Console.WriteLine("Listened port: 5060(UDP)");
            base.OnStart();
        } 
        
        protected override RegisterResult OnRegisterReceived(ISIPExtension extension, SIPAddress from, int expires)
        {
            Console.WriteLine("Register received from: " + extension.Account.Username);
            return base.OnRegisterReceived(extension, from, expires);
        }
        
        protected override void OnUnregisterReceived(ISIPExtension extension)
        {
            Console.WriteLine("Unregister received from: " + extension.Account.UserName);
            base.OnUnregisterReceived(extension);
        }
    }
} 

代码示例 2: PBX 类定义和 OnStart 方法

现在让我们为您的 PBX 编写 main 方法。为此,您首先需要在代码中添加一个新的 using 语句

using Ozeki.Network;

代码示例 3: 再一个 using 语句

让我们看看如何编写 main 方法

namespace myPBX
{ 
    class Program
    { 
        static void Main(string[] args)
        {
            var myPBX = new MyPBX(NetworkAddressHelper.GetLocalIP().ToString(), 20000, 20500);
            
            myPBX.Start();
            
            Console.ReadLine();
            myPBX.Stop();
        } 
    } 
} 

代码示例 4: main 方法

让我们运行应用程序。如果它正常工作,您的 PBX 就可以接受 SIP 账户注册,然后使用注册的 SIP 分机拨打和接听电话了。

您可以使用软电话测试 SIP 注册。(例如,您可以使用 X-Lite,或者与 SDK 一起自动安装的演示软电话应用程序。)如果您使用任何软电话,在提供所需的 SIP 账户详细信息后,如果注册成功,注册行将出现在您的控制台应用程序中(图 1)。

图 1: 成功的 SIP 账户注册

要拨打测试电话,您需要注册两个 SIP 账户:分机“A”和分机“B”。完成注册后,您需要使用分机“A”拨打分机“B”的电话号码。被叫方(分机“B”)将会响铃。分机“B”接听电话后,它们之间的电话线路就建立起来了,可以进行对话。

编写拨号计划

下面我将展示如何定义当有人呼叫处于 DND(请勿打扰)状态的电话号码时应该发生什么。要创建拨号计划,您首先需要创建一个新类。(右键单击项目名称,选择添加,然后选择类选项。为其提供一个名称——例如 MyDialplanProvider——然后单击确定按钮。)

在类中添加一些 using 语句

using Ozeki.VoIP.PBX.Dialplan; 
using Ozeki.VoIP.PBX.PhoneCalls;
using Ozeki.VoIP.PBX.Services; 

代码示例 5: 需要额外的 using 语句

下面您可以看到如何实现 IDialplanProvider 接口。创建一个 UserInfoContainer、一个 IExtensionContainer 和 List 类型的变量。List 将包含 dndExtensions(dnd 指的是请勿打扰)。在构造函数中,您可以进行基本设置,然后添加 IDialplanProvider 接口成员(Name 和 GetDestination 函数)。(IDialplanProvider 可用于控制呼叫转移的目的地。GetDestination 定义呼叫将转发到何处,routeParams 参数包含与呼叫相关的信息,例如被叫号码、主叫方电话号码等。)之后,您需要创建 dialedNumbercallerPhoneNumber 变量,并根据以下代码示例定义一些条件。

namespace myPBX
{
    class MyDialplanProvider : IDialplanProvider
    {
        UserInfoContainer userInfoContainer;
        IExtensionContainer extensionContainer;
        List <string> dndExtensions;
        
        public MyDialplanProvider(UserInfoContainer userInfoContainer, IExtensionContainer extensionContainer)
        {
            dndExtensions = new List<string>();
            this.extensionContainer = extensionContainer;
            this.userInfoContainer = userInfoContainer;
        }
        
        public string Name
        {
            get { return "CustomDialplan"; }
        }
        
        public Destination GetDestination(RouteInfo routeParams)
        {
            var dialedNumber = routeParams.Destination.DialedNumber.UserName;
            var callerPhoneNumber = routeParams.CallerInfo.Owner.Account.UserName;
 
            // If the dialed number is ’*90’ and the dnd list does not contain the callerPhoneNumber then add this phone number to the list and finish the call:
            if(dialedNumber == "*90" && !dndExtension.Contains(callerPhoneNumber))
            {
                Console.WriteLine("Extension {0} added to DND list", routeParams.CallerInfo.Owner);
                <br />                dndExtensions.Add(callerPhoneNumber);
                return null;
            }
// If the dialed number is ’*91’ a then remove this phone number from the list and finish the call.
            if(dialedNumber == "*91" && !dndExtension.Contains(callerPhoneNumber))
            {
                Console.WriteLine("Extension {0} removed to DND list", routeParams.CallerInfo.Owner);
                <br />                dndExtensions.Remove(callerPhoneNumber);
                return null;
            }
// If the dialed number is in the list, else you can call the number.
            if(dndExtension.Contains(callerPhoneNumber))
            {
                return null;
            }
            return new Destination(dialedNumber);  
    }
} 

代码示例 6: IDialplanProvider 接口的实现

让我们回到您的 PBX 类,并使用 callManagerextensionContainer 变量以及您的 DialplanProvider 来修改 OnStart() 方法。

protected override void OnStart()
        {
            Console.WriteLine("PBX started.");
            SetListenPort(localAddress,5060,TransportType.Udp);
            var callManager = GetService<ICallManager>();
            var extensionContainer = GetService<IExtensionContainer>();
            callManager.DialplanProvider = new MyDialplanProvider(userInfoContainer, extensionContainer);
            Console.WriteLine("Listened port: 5060(UDP)");
            base.OnStart();
        }

代码示例 7: PBX 类中的修改

您可以使用两个软电话轻松测试您的系统。如果您从分机 101 拨打号码 *90,您的电话号码将被添加到 DND 列表,并且无法呼叫此用户。但是,如果您拨打 *91,该号码将从列表中删除,您现在可以呼叫它了(图 2)。

 

图 2: 拨号计划功能的工作原理

 

创建语音邮件功能

许多公司无法承受错过任何来电。然而,在某些情况下这是不可避免的。语音邮件服务为此问题提供了一个实用的解决方案,因为它允许呼叫者给您留言。

考虑到语音邮件开发是一个稍微广泛的话题,我认为最好将我的附加解决方案作为单独的文章来介绍。如果您有兴趣构建语音邮件功能,请学习我提供逐步实现的教程。

如何在 C# 中通过语音信箱功能改进您的 VoIP PBX
https://codeproject.org.cn/Articles/770111/How-to-improve-your-VoIP-PBX-with-voicemail-functi

创建呼叫队列功能

避免漏接电话的另一种可能解决方案是呼叫排队。通过使用此功能,您的 PBX 可以管理一个队列系统中的来电:如果没有任何可用的呼叫中心坐席,来电将被排在呼叫队列的末尾。当坐席完成当前呼叫时,队列中的第一个呼叫将被转接到该坐席。

鉴于呼叫队列的实现是一个稍微广泛的话题,我认为最好将此改进作为单独的文章来解释。如果您有兴趣开发虚拟呼叫队列扩展,请研究我下面的分步指南。

如何用 C# 开发呼叫队列来改进您的 VoIP PBX
https://codeproject.org.cn/Articles/782342/How-to-develop-a-call-queue-in-Csharp-for-improvin

摘要

总而言之,如果您使用之前编写的组件,构建自己的 SIP PBX 可以非常简单快速。在研究了更多的 SDK 后,我发现 Ozeki 的 VoIP SIP SDK 非常有效。如果您想专注于开发您的应用程序而不是担心必要的网络协议和技术细节,这是一个很好的工具。尽管我只为我的 PBX 扩展了一个附加功能(拨号计划),但当然也可以构建其他几个功能。(例如,我现在正在开发一个语音邮件解决方案——同时它已经完成,请参见上文。)

祝您好运!

参考文献

理论背景

下载所需软件

© . All rights reserved.