本文重点介绍C#中环组的开发。 任何呼叫中心的有效性不仅取决于运营商的行为,还取决于呼叫中心的技术背景。 学习完本指南后,您将能够创建振铃组分机(即“虚拟软件电话”),该分机可用于根据预定义的振铃组策略将传入呼叫转移到选定的呼叫中心座席之一。
下载源代码:
Softphone-RingGroup-RingOneByOne.zip
内容

  • 主题的重要性
  • 有关呼叫中心的技术背景的更多信息
  • 环组开发简介
  • 开发/第1部分:构建软件电话
  • 开发/第2部分:构建其他功能
  • 开发/第3部分:使用“一对一”策略构建环网组
  • 测试
  • 结论
  • 进一步的阅读和参考

主题的重要性
呼叫中心或呼叫中心是一个集中式办公室,用于通过电话接收或传输大量请求[1]。 换句话说:“呼叫中心是一个物理场所,通常由一定数量的计算机自动化来由组织来处理客户和其他电话” [2]。 先前定义中的“ 通常具有一定程度的计算机自动化 ”部分表示,成功的呼叫中心基于人工和某种技术设备的合作。 因此,适当的IT基础架构至关重要。
专家们说, 精心挑选员工是成功建立呼叫中心的第一步。 但是大多数技能都是可以学习的。 在第一线支持,服务台和服务台培训期间 ,讲师经常提请操作员注意以下几点:

  • 要有礼貌和礼貌
  • 主动
  • 首先把事情放在首位–让优先事项推动举措
  • 双赢思考–对您的客户有利的对您也有利,等等。

这些是成功呼叫中心必不可少的非常重要的人为因素。 另外,某些管理工具,例如绩效评估和对员工的定期反馈,奖励,积极的强化等,是有效呼叫中心不可或缺的要素。 但是如果没有适当的技术,所有人类知识都是徒劳的。 无论是小型呼叫中心还是大型呼叫中心,要获得更多利润并建立客户忠诚度,员工都必须使用先进的硬件和软件设备
在外包的印度呼叫中心和大型的美国呼叫中心中,有什么共同点( 图1 )? 是的,有很多东西,但是最重要的是核心技术。 在这两个地方,运营商都使用VoIP台式电话,软电话,头戴式耳机,以及:具有典型呼叫中心功能(例如,呼叫转移,振铃组,呼叫记录,呼叫排队,电话会议等)的高级PBX系统,可以使某些任务更多由于自动化,操作简单。

图1:技术设备也是小型和大型呼叫中心的关键因素[3]
有关呼叫中心的技术背景的更多信息
呼叫中心可以同时处理大量呼叫 ,并将其转发给负责处理这些呼叫的人员。 但是,在当今快速发展的世界中,VoIP技术,高性能计算机和智能软件应用程序几乎是必不可少的。 如今的呼叫中心使用的是比老式的巨大“盒子”更多的最新设备 ,而老式的“盒子”却有很多电线伸出来。
我们可以将呼叫中心分为两种主要类型,如下所示[1]:

  • 虚拟呼叫中心:在这种呼叫中心模型中,运营商通常向在其自己的数据中心托管呼叫中心电话设备的供应商支付月费或年费。 代理商通过传统的PSTN电话线或VoIP(互联网协议语音)连接到供应商的设备。
  • 基于前提的呼叫中心:此模型与先前的模型相反。 在这种情况下,呼叫中心已建立在PBX(专用分支交换)中,该交换机由呼叫中心运营商自己拥有,托管和维护。 该PBX提供高级功能,例如呼叫排队,ACD(自动呼叫分配),IVR(交互式语音响应)或SBR(基于技能的路由)–这是一种呼叫辅助策略,用于将呼入呼叫分配给最适合的座席,而不是简单地选择下一个可用的代理)。

如果使用虚拟呼叫中心,则功能是固定的,不能无限扩展它们。 但是,在基于内部的呼叫中心的情况下,您将有机会通过开发所需的功能来改进系统。 实际上,只有想象力(当然还有公司的需求和条件)限制了这种改进。
环组开发简介
在本文中,您将看到后一种情况的示例。 确切地说,本指南逐步演示了环组的创建。 该铃声组将是安装在集团电话中的“虚拟” 分机 。 此分机基于软件电话 (因此,即已注册到PBX)。 PBX为该分机分配电话号码。 呼叫时,该分机尝试将来电转移到其他分机之一(即转移到先前已在PBX上注册的振铃组成员之一)。 振铃组策略确定哪个成员将接受呼叫。
恐怕以前的线程包含一些新的(或令人恐惧的)表达式……不管是不是,为了完整起见,让我澄清一下此解决方案的基本要素。

  • 什么是振铃组:在现代电信时代,呼叫路由允许在有来电时有多个分机振铃。 它称为环组。 环组通常在PBX中显示为“虚拟分机”。 在配置振铃组时,可以定义哪些电话分机属于该振铃组。 这样,当有“虚拟分机”的来电时,所有所属分机将在振铃组内同时或依次振铃。
  • “分机”的含义是什么:在商务电话中,电话分机可能是指与PBX相连的内部电话网络中的电话。 在集团电话内,用户仅需拨打分机号码即可直接与其他任何用户联系。 对于呼入电话,拨打公司电话号码后,总机接线员或自动值班人员可以要求首选分机的号码。 如果将外部号码分配给各个分机[5],则也可以通过直接呼入完成呼叫。
  • 什么是软件电话:软件电话是一种特殊的计算机软件,可以充当虚拟电话,使人们可以使用其PC或笔记本电脑通过Internet与他人进行通信。
  • 什么是呼叫转移:呼叫转移是一种电信机制,允许用户通过使用转移按钮或开关挂钩并拨打所需的位置[6],将现有电话转移到另一个电话或话务台。
  • 什么是振铃组策略:振铃组策略确定在有来电时如何通知组成员的方式。 在呼叫转移之前,环网组需要通知组成员需要进行呼叫转移。 要选择在振铃组内接受呼叫的成员,可以使用几种策略。 让我们看一些示例:可以配置为同时呼叫振铃组中的所有分机(即所有成员的电话)。 还可以实现的是,环群分机将以预先设置的顺序一一呼叫成员。 另一种常用的策略是当环组的延伸以随机顺序被调用时的情况。 (在该项目中,将演示“一对一”策略。)

基于以上内容,让我们总结一下该项目需要哪些开发:

  • 构建能够接收电话的虚拟(或控制台)软件电话
  • 构建所需的附加功能,例如呼叫转移和多个呼叫管理
  • 用“一对一”策略建立环网组

因此,我使用了以下3类

  1. Softphone.cs:它介绍了如何使用可注册到PBX的C#开发软件电话。
  2. Program.cs:此类负责处理用户事件。 它介绍了如何将软件电话注册到PBX。
  3. RingGroupCallHandler.cs:此类说明如何创建必要的铃声组策略。 由于此类分开,因此应用程序可以使用其实例同时处理多个传入呼叫。

首先,您将需要一个新的Visual C#控制台应用程序 ,因为该应用程序实际上是“虚拟软件电话”,因此一个控制台应用程序就足够了。 为了更好地理解,我将本文的其余部分分为三个主要部分。 所有这三个部分都逐步介绍了一个类的实现。
现在,已经用尽了理论和发展背景,“保持冷静,开始编程!” :)
开发/第1部分:构建软件电话
你准备好了吗? 当然! :)好吧,让我们从Softphone.cs类开始。 第一步,使用以下行添加所需的内容。 (作为C#.NET VoIP库,已经使用了Ozeki VoIP SIP SDK- 请记住,此代码是使用此SDK的预先编写的VoIP组件编写的。因此,如果要尝试使用它们,则需要安装它的免费试用版。 [7])

using System;
using Ozeki.VoIP;
using Ozeki.VoIP.SDK;

现在需要软件电话电话线对象。 您可以从ISoftPhoneIPhoneLine接口获取它们,如下所示:

ISoftPhone _softphone;
IPhoneLine _phoneLine;

在构造函数中,您需要使用默认参数来初始化此软件电话。 5000和10000参数是指端口范围:第一个数字是最小端口号( minPortRange ),另一个是最大端口号( maxPortRange )。 为了实现对来电的持续监控,您需要订阅IncomingCall事件。 请查看以下上述步骤:

public Softphone(){_softphone = SoftPhoneFactory.CreateSoftPhone(5000, 10000);_softphone.IncomingCall += softphone_IncomingCall;}

通过订阅IncomigCall事件,将通知您是否有来电。 如果电话线的状态发生更改,则PhoneLineStateChange事件(当然会在订阅后)通知您。 请查看以下上述步骤:

public event EventHandler<VoIPEventArgs<IPhoneCall>> IncomigCall;
public event EventHandler<RegistrationStateChangedArgs> PhoneLineStateChanged;

通过使用Register方法,可以将软件电话注册到PBX。 为此,应创建一个需要SIP帐户的电话线。 为了能够为您的软件电话创建SIP帐户,您需要指定以下参数:

  • registrationRequired:为了能够接收来电,您需要为此参数设置“ true”值。
  • displayName:这是要在被叫客户端上显示的名称。
  • userName:这是要呼叫此软件电话的其他客户端要拨打的号码。
  • authenticationId:这是集团电话的标识符(如登录名)。
  • registerPassword:这是用于注册到PBX的authenticationId的密码。
  • domainHost:这是一个域名,即您要注册的集团电话的IP地址。
  • domainPort:这是您要注册的集团电话的端口号。

创建必要的SIP帐户后,您可以创建电话线以能够与PBX通信。 还需要收听电话线状态的变化。 最后, RegisterPhoneLine方法可用于将电话线注册到软件电话。
请查看以下上述步骤:

public void Register(bool registrationRequired, string displayName, string userName, string authenticationId, string registerPassword, string domainHost, int domainPort){try{var account = new SIPAccount(registrationRequired, displayName, userName, authenticationId, registerPassword, domainHost, domainPort);Console.WriteLine("\nCreating SIP account {0}", account); _phoneLine = _softphone.CreatePhoneLine(account);Console.WriteLine("Phoneline created."); _phoneLine.RegistrationStateChanged += _phoneLine_RegistrationStateChanged; _softphone.RegisterPhoneLine(_phoneLine);}catch (Exception ex){Console.WriteLine("Error during SIP registration" + ex.ToString());}}

电话线的注册状态更改时将调用此方法:

void _phoneLine_RegistrationStateChanged(object sender, RegistrationStateChangedArgs e)
{var handler = PhoneLineStateChanged;if (handler != null)handler(this, e);
}

这将在有来电时被调用:

void softphone_IncomingCall(object sender, VoIPEventArgs<IPhoneCall> e)
{var handler = IncomigCall;if (handler != null)handler(this, e);
}

调用时,可以使用以下代码片段创建调用对象:

public IPhoneCall CreateCall(string member)
{return _softphone.CreateCallObject(_phoneLine, member);
}

这是Softphone.cs的实现的结尾。 我了解您是否现在想休息一下。 休息一下,让我们继续编码Program.cs ! :)
开发/第2部分:构建其他功能
完成软件电话的创建后,让我们继续使用Program.cs进行编程。 此类介绍了Softphone对象的用法,处理控制台事件,与用户进行交互以及使用Softphone类提供的机会。 (您将看到,所有内容都是在相互通信的单独方法中处理的。)
Main方法下面可以看到您需要通过调用初始化方法来初始化软件电话的位置。 (代码段末尾的BlockExit方法-不允许应用程序退出。)

static List<RingGroupCallHandler> _callHandlers;
static List<String> _members;
static int _memberCount;
static Softphone _softphone;
static void Main(string[] args)
{_softphone = new Softphone(); ShowHelp(); SipAccountInitialization(_softphone); _callHandlers = new List<RingGroupCallHandler>();   _softphone.IncomigCall += softphone_IncomigCall;_softphone.PhoneLineStateChanged += softphone_PhoneLineStateChanged;  BlockExit();
}

现在,有一个新的软件电话可以监视电话线的状态。 您已订阅以获取有关电话线状态已更改的通知。 在下面,您可以看到Softphone_PhoneLineStateChanged方法确定每种状态的处理方式。 电话线的注册状态更改时将调用此方法:

static void softphone_PhoneLineStateChanged(object sender, RegistrationStateChangedArgs e)
{Console.WriteLine("Phone line state changed to: {0}", e.State); if (e.State == RegState.RegistrationSucceeded)MemberAdder();
}

关于该类也订阅了电话线路的事件这一事实,当电话线路的状态成功注册时会通知该类,并且它开始向用户询问振铃组:成员编号,他们的电话号码。 这些被存储在字符串列表中:

private static void MemberAdder(){_members = new List<String>(); while (_memberCount == 0 || _memberCount == null){Console.Write("\nThe number of the RingGroup members: ");try{_memberCount = Int32.Parse(Console.ReadLine());}catch{Console.WriteLine("Wrong input!");}} for (int i = 0; i < _memberCount; i++){Console.Write("{0}. member's phone number: ", i + 1);string member = Console.ReadLine();_members.Add(member);} Console.WriteLine("\nWaiting for incoming calls...");}

Program.cs通过控制台窗口与用户通信时; 值得为用户提供简短的介绍信息:

static void ShowHelp(){Console.WriteLine("Hello! This is a brief introduction to this project.");Console.WriteLine("This project is about to introduce the creation of a RingGroup (with the one by one strategy), which works on the following way:");Console.WriteLine("1., the application asks the user about the number of the RingGroup members");Console.WriteLine("2., also asks the user, to enter the members' phone numbers");Console.WriteLine("3., waits for an incoming call");Console.WriteLine("4., starts to ring the first RingGroup on the list of members");Console.WriteLine("5., if the member is busy or cannot be reached, it starts to ring an other member");Console.WriteLine("6., if the call is being answered, it transfers the call and stops ringing the other members");Console.WriteLine("-------------------------------------------------------------------------------");Console.WriteLine();}

如果有传入呼叫,则将通知用户,应用程序将为该呼叫创建一个新的RingGroupCallHandler对象,订阅其Completed方法,然后将该对象添加到处理程序列表中。 此后,它调用对象的Start方法:

static void softphone_IncomigCall(object sender, VoIPEventArgs<IPhoneCall> e){Console.WriteLine("\nIncoming call from \"{0}\"!\n", e.Item.DialInfo.Dialed); var callHandler = new RingGroupCallHandler(e.Item, _softphone, _members);callHandler.Completed += callHandler_Completed; lock (_callHandlers)_callHandlers.Add(callHandler); callHandler.Start();}

当传入呼叫不再可用时(例如,由于已转移或呼叫者完成了呼叫等), 应从呼叫处理程序的列表中删除RingGroupCallHandler对象:

static void callHandler_Completed(object sender, EventArgs e){lock (_callHandlers)_callHandlers.Remove((RingGroupCallHandler)sender);}

为了能够使用先前创建的软件电话进行通信,需要设置一个SIP帐户 。 因此,应用程序应要求用户输入有效的SIP帐户详细信息,然后尝试注册到PBX。 有关详细说明,请查看以下代码库中的注释:

static void SipAccountInitialization(Softphone softphone){ var registrationRequired = true;Console.WriteLine("\nPlease set up Your SIP account:\n"); // Asks, if a registration is required to the PBX. The default value is true.Console.Write("Please set if the registration is required (true/false) (default: true): ");var regRequired = Read("Registration required", false);if (regRequired.ToLower() == "false" || regRequired.ToLower() == "no" ||regRequired.ToLower() == "n"){registrationRequired = false;}else{Console.WriteLine("Registration set to required.");}  // The SIP account needs and authentication ID, and some names as well.Console.Write("Please set Your authentication ID: ");var authenticationId = Read("Authentication ID", true); // If the user only presses the Enter button, the username will be the same as the authentication IDConsole.Write("Please set Your username (default: " + authenticationId + "): ");var userName = Read("Username", false);if (string.IsNullOrEmpty(userName))userName = authenticationId; // If the user only presses the Enter button, the display name will be the same as the authentication IDConsole.Write("Please set Your name to be displayed (default: " + authenticationId + "): ");var displayName = Read("Display name", false);if (string.IsNullOrEmpty(displayName))displayName = authenticationId; // The registration password needs to be entered.Console.Write("Please set Your registration password: ");var registerPassword = Read("Password", true); // Domain name as a string, for example an IP adress.Console.Write("Please set the domain name: ");var domainHost = Read("Domain name", true); // Port number with the as 5060 default value.Console.Write("Please set the port number (default: 5060): ");int domainPort;string port = Read("Port", false);if (string.IsNullOrEmpty(port)){domainPort = 5060;}else{domainPort = Int32.Parse(port);}  Console.WriteLine("\nCreating SIP account and trying to register...\n");softphone.Register(registrationRequired, displayName, userName, authenticationId, registerPassword, domainHost, domainPort); }

之后,需要一种帮助方法来读取输入:

private static string Read(string inputName, bool readWhileEmpty){while (true){string input = Console.ReadLine(); if (!readWhileEmpty){return input;} if (!string.IsNullOrEmpty(input)){return input;} Console.WriteLine(inputName + " cannot be empty!");Console.WriteLine(inputName + ": ");}}

最后,您需要使用BlockExit方法。 这不会让应用程序退出:

private static void BlockExit(){while (true){Thread.Sleep(10);}}

是的,现在我们也可以使用Softphone.csProgram.cs 。 如果需要,请在开始最后一个主要部分之前稍作休息! :)
开发/第3部分:使用“一对一”策略构建环网组
现在是最后一个主要部分,它演示了RingGroupCallHandler类的实现。 此类负责等待传入的呼叫,开始根据首选的振铃组策略呼叫振铃组的成员以及将呼叫转移到适当的成员。
来电时应调用Start方法。 Start方法接受呼叫,然后通过调用StartOutgoingCalls方法开始通知振铃组成员。

IPhoneCall _incomingCall;
Softphone _softPhone;
List<IPhoneCall> _calls;
ist<String> _members;
object _sync;
public RingGroupCallHandler(IPhoneCall incomingCall, Softphone softPhone, List<String> members)
{_sync = new object();_incomingCall = incomingCall;_softPhone = softPhone;_members = members; _calls = new List<IPhoneCall>();
}
public event EventHandler<VoIPEventArgs<CallError>> CallErrorOccured;
public event EventHandler Completed;
public void Start()
{_incomingCall.Answer();_incomingCall.CallStateChanged += call_CallStateChanged;StartOutgoingCalls();
}

StartOutgoingCalls方法创建呼叫对象到每个构件和CallSequencer方法将被调用。 需要订阅CallStateChanged事件。 事件发生时,应用程序将调用OutgoingCallStateChanged方法。 (当接听来电时将使用它,并且应将其转移到振铃组中的座席。)

void StartOutgoingCalls(){foreach (var member in _members){var call = _softPhone.CreateCall(member);call.CallStateChanged += OutgoingCallStateChanged;_calls.Add(call);} CallSequencer();}

如果呼叫列表中有呼叫,则CallSequencer方法会逐个振铃成员(从列表中的第一个成员开始)。 如果呼叫列表中没有任何呼叫,则该方法挂断来电:

private void CallSequencer(){if (_calls.Count > 0){var call = _calls[0];Console.WriteLine("Ringing phone number \"{0}\"", call.DialInfo.Dialed);call.Start();}else{Console.WriteLine("No available RingGroup member has been found.");_incomingCall.HangUp();}}

OutgoingCallStateChanged方法通过使用AttendedTransfer方法将该呼叫者(即传入呼叫)转移到环组成员之一。 OutgoingCallStateChanged方法还负责从调用列表中删除该调用,然后调用OnCompleted方法。 当被叫忙或无法接通时 ,应用程序将再次调用CallSequencer方法。

void OutgoingCallStateChanged(object sender, CallStateChangedArgs e){var call = (IPhoneCall)sender; if (e.State == CallState.Answered){Console.WriteLine("\nCall has been accepted by {0}.", call.DialInfo.Dialed);Console.WriteLine("Call from \"{0}\" is being transferred to \"{1}\".\n", _incomingCall.DialInfo.Dialed, call.DialInfo.Dialed);_incomingCall.AttendedTransfer(call); lock (_sync){_calls.Remove(call);OnCompleted();}} if (e.State == CallState.Busy || e.State == CallState.Error){lock (_sync){if (call != null){Console.WriteLine("Ringing phone number \"{0}\" ends.", call.DialInfo.Dialed);call.HangUp(); _calls.Remove(call);CallSequencer();}}}}

应答呼叫并将其转移到振铃组成员后,将调用OnCompleted方法。 OnCompleted方法调用HangupOutgoingCalls方法,并设置Completed事件。 这表明呼叫转移已成功,或者呼叫方已在转移之前挂断了呼叫。 Completed事件还通知应用程序,呼叫处理程序无事可做。 (当传入呼叫在传输之前完成时,也会调用OnCompleted方法。)

void call_CallStateChanged(object sender, CallStateChangedArgs e)
{if (e.State.IsCallEnded()){OnCompleted();}
}
void OnCompleted()
{HangupOutgoingCalls(); var handler = Completed;if (handler != null)handler(this, EventArgs.Empty);
}

HangupOutgoingCalls方法用于挂断所有呼出电话并将其从列表中清除:

void HangupOutgoingCalls()
{foreach (var call in _calls){call.HangUp();}_calls.Clear();
}
void call_CallErrorOccured(object sender, VoIPEventArgs<CallError> e)
{Console.WriteLine("Error occured at {0}: {1}", ((IPhoneCall)sender).DialInfo.Dialed, e.Item); var handler = CallErrorOccured;if (handler != null)handler(this, e);
}

这就是开发的终点! 祝贺您的新申请! 你的工作快结束了。 现在,您只需要使用新的虚拟软件电话来测试振铃组。
测试
让我们运行该应用程序以测试新虚拟虚拟电话的振铃组功能。 请注意, 某些SIP帐户 (无论是台式VoIP电话,软件电话还是移动分机)应事先安装在PBX中,以便能够将成员添加到环组中。 例如,我在PBX中注册了三个SIP帐户(1000、2000、3000)。 (两个将成为一个环网组成员,而我将使用第三个来进行测试通话。)
按F5后,可以在控制台应用程序中看到以前编写的介绍 。 介绍之后,该应用程序会要求您设置您的SIP帐户图2 )。

图2:环组作为控制台应用程序运行后
现在,您需要配置虚拟软件电话和振铃组本身。 首先, 通过输入“ true”注册设置为必填图3 )。 此后,您需要在PBX创建一个新的SIP帐户,帐户将用作虚拟软件电话分机以用于振铃组(在我的情况下,这是编号为“ 4000”的SIP帐户)。 现在通过输入所需的数据,在新的控制台应用程序中提供相同的SIP帐户详细信息域名是集团电话的IP地址, 端口号是它的端口号(通常是5060)。 提供必要的SIP帐户详细信息后,应用程序将尝试注册到PBX 。 如果提供的信息正确,则电话线状态将更改为RegistrationSucceeded 。 此后,应用程序要求您输入环组成员数量定义一个序列 。 此顺序确定当某人拨打响铃组的电话号码时哪个响铃(即哪个电话)将响铃。 (应用程序会将来电转移到第一个成员。如果该呼叫不可用,则呼叫将转发到第二个成员,依此类推。)现在,配置完成,应用程序正在等待来电图3 )。

图3:环组控制台应用程序运行后
我的测试电话可以在下面看到( 图4 )。 我用“ 3000”打了一个电话。 我拨打了“ 4000”(这是振铃组的电话号码–它可以用作公司的中央电话号码)。 此后,第一个振铃组成员,编号为“ 2000”的分机开始振铃。 但是,此分机忙,因此第二个成员“ 1000”开始响铃。 此分机已接受呼叫,因此来电已从“ 4000”(虚拟软件电话)转移到“ 1000”(台式VoIP电话)。

图4:测试电话
这意味着您的应用程序运行良好! 恭喜您成功! :)
结论
在当今瞬息万变的世界中,公司在所有业务领域中也都需要最新的软件和硬件设备,因此在与客户和其他合作伙伴进行交流时也是如此。 呼叫中心应配备有助于自动化的有效解决方案。 如果使用VoIP技术(和IP PBX),则呼叫转移,振铃组,呼叫记录,呼叫排队,电话会议等只是可以实现的一些重要功能。 在本教程中,我想通过振铃组的示例来说明,自己开发缺少的功能以改善呼叫中心有多么容易。 我希望你喜欢它!
进一步的阅读和参考
为了撰写有关构建环网组的C#教程,我使用了以下知识库:

  1. http://en.wikipedia.org/wiki/Call_centre
  2. http://searchcrm.techtarget.com/definition/call-center
  3. http://www.imdb.com/title/tt1593756/...f_=tt_pv_mi_sm
  4. http://elderlymedicalalertsystems.co...-alert-systems
  5. http://en.wikipedia.org/wiki/Extension_%28telephone%29
  6. http://en.wikipedia.org/wiki/Call_transfer
  7. http://voip-sip-sdk.com/p_21-downloa...-sdk-voip.html
附加图片

1-call-center-little-outsourced-vs-large-call-centres.jpg (50.4 KB,2801浏览)
7-call-center-voip-ring-group-test.jpg (53.8 KB,1217视图)
8-call-中心-voip-ring-group-test-setup-sip-account-ring-group-members.jpg (52.3 KB,1400视图)
9-call-center-voip-ring-group-test-call.jpg (26.3 KB,1182视图)

From: https://bytes.com/topic/c-sharp/insights/960550-building-virtual-softphone-c-works-ring-group-your-call-center

在C#中构建一个虚拟软件电话,该软件电话可以在您的呼叫中心中作为振铃组...相关推荐

  1. c# 虚拟机加密软件_在C#中构建一个虚拟软件电话,该软件电话可以在您的呼叫中心中作为振铃组...

    c# 虚拟机加密软件 本文重点介绍C#中环组的开发. 任何呼叫中心的有效性不仅取决于运营商的行为,还取决于呼叫中心的技术背景. 学习完本指南后,您将能够创建振铃组分机(即"虚拟软件电话&qu ...

  2. 构建一个虚拟DOM并转换为真实DOM

    关于真实DOM与虚拟DOM 1.在学习虚拟DOM之前,让我们先来了解一下真实的DOM结构,这里不得不提的是关于浏览器渲染方面的知识. 当浏览器拿到一个HTML文件,首先会根据HTML文件构建出一个DO ...

  3. 用500行纯前端代码在浏览器中构建一个Tableau

    2019独角兽企业重金招聘Python工程师标准>>> 在Gartner最新的对商务智能软件的专业分析报告中,Tableau持续领跑.Microsoft因为PowerBI表现出色也处 ...

  4. 如何在 Deno 中构建一个 URL 短链生成器

    作者 | 韩亦乐       责编 | 欧阳姝黎 原文地址:How to Build a URL Shortener in Deno 原文作者:Akash Joshi 译者:hylerrix 备注:本 ...

  5. c语言编写天气预报程序,在Deno中构建一个命令行天气预报程序

    在本文中,我将通过安装Deno运行时,并创建一个命令行天气程序,该程序将把一个城市名称作为参数,并返回未来24小时的天气预报. 要为Deno编写代码,我强烈建议将Visual Studio Code与 ...

  6. 在 VirtualBox 中构建 Debian11 虚拟电脑

    文章目录 前言 一.准备工作和Debian简介 二.新建虚拟电脑 三.安装 Debian11 四.Debian11系统环境配置 配置光盘软件镜像源 配置国内镜像软件源 控制台鼠标支持 / 安装虚拟机增 ...

  7. 使用maven构建一个基于Java的spark应用程序用于统计唐诗三百首中各汉字出现的次数

    目的:统计唐诗三百首全集每个汉字出现的次数 软件需求: 下载 Eclipse ,安装Maven 安装好JDK1.8 下载并配置了hadoop-2.7.3     spark-2.2.0-bin-had ...

  8. android 自定义梯形,如何在android中构建一个梯形形状?

    这个类是一个View定义,并绘制一个梯形ShapeDrawable.因此,梯形,作为Drawable,也可用于背景. package com.stackoverflow.questions.q2576 ...

  9. js中显示一个指定html文档,JS实现选定指定HTML元素对象中指定文本内容功能示例...

    本文实例讲述了JS实现选定指定HTML元素对象中指定文本内容功能.分享给大家供大家参考,具体如下: 该功能用处多多,可以灵活运用之!主要函数如下: //选中文本中指定部分 function selec ...

最新文章

  1. php video标签使用方法,HTML_HTML5 video标签(播放器)学习笔记(一):使用入门,近有在学习html5中video标签(播 - phpStudy...
  2. 不定长参数的装包与拆包
  3. .NET中的Command(命令)模式
  4. Introspection
  5. 字符串切割(split())
  6. 对网络数据包进行嗅探
  7. centos radius mysql_FreeRadius2 MySQL配置
  8. 二极管的结构、特性、参数、稳压管的特性和参数
  9. C++读取excel表格
  10. 安装win2008R2系统并激活
  11. MCAFEE卸载软件测试初学者,win7系统完全卸载McAfee杀毒软件的两种方法
  12. 2020-08-18
  13. Unity打包后播放视频黑屏问题
  14. luci开发小插件_luci框架-LUA的一个web框架使用
  15. 微积分专项----MIT GS老师
  16. 1月末支付机构备付金总量达1.4万亿,较去年12月下滑两千多亿
  17. 推荐一款视频音频剪辑软件,可免费试用
  18. 【CCS仿真系列教程】手把手教你纯软件仿真实现音频滤波
  19. android常用控件实验报告,ui设计实验报告.doc
  20. APP风控SDK之Android APP防作弊SDK解决方案

热门文章

  1. 如何保护地球不挨撞?
  2. 斐讯N1 小钢炮系统配置 FRP 内网穿透
  3. 东芝将在家电和半导体等部门裁员约1万人
  4. Java List过滤数据
  5. Fortran基本用法小结
  6. php70w mysql.x8664_悠久防伪防窜货追溯系统php+mysql版.zip
  7. 【华为机试真题 Python实现】数组拼接
  8. 大型机学习之具体技术之-JCL练习(二)
  9. 【uniapp、微信小程序】解决引入外部字体在H5中生效,微信开发者工具中不生效的问题
  10. VR泡沫已破,巨头们重注AR能火起来?