深入探索MicroSIP网络电话软件设计与实现

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:MicroSIP电话是一款基于SIP协议的开源网络电话软件,支持互联网音频和视频通话。本文详细介绍了SIP协议的基础知识,网络电话(VoIP)技术的简述,以及MicroSIP软件的源码结构和关键功能模块,包括用户界面、账户管理、呼叫控制和多媒体处理等。开发者可以利用这些资源深入学习SIP协议和VoIP技术,而普通用户则能享受到一个高效易用的网络电话服务。 MicroSIP电话

1. SIP协议基础知识

SIP协议简介

会话初始化协议(SIP)是一种应用层控制协议,用于创建、修改和终止两个或多个参与者之间的会话。这些会话可能包括互联网电话呼叫、多媒体分发或在线游戏。

SIP的核心组件

SIP的核心组件包括用户代理(User Agent),代理服务器(Proxy Server),注册服务器(Registrar),重定向服务器(Redirect Server)以及位置服务器(Location Server)。用户代理负责发起和终结呼叫,而代理服务器则转发请求并管理会话。

SIP协议的主要特点

SIP协议的主要特点包括可扩展性、可重用性和可扩展性。它具有独立于传输机制的能力,并支持多种网络地址格式和终端类型。此外,SIP支持多种类型的呼叫,如单播、组播以及多媒体会议。

以上为 SIP 协议的基础知识概览,通过本章内容,读者可以对 SIP 协议有一个基础的认识,为后续深入学习 SIP 在 VoIP 中的应用打下基础。

2. 网络电话(VoIP)技术介绍

2.1 VoIP的基本概念

2.1.1 VoIP的定义

Voice over Internet Protocol (VoIP) 是一种通过互联网或其他 IP 网络进行语音通信的技术。与传统的电话服务不同,VoIP 通过数字信号处理技术将模拟信号(人的语音)转换为数据包,在 IP 网络上传输,然后再将数据包还原为模拟信号,以供接收方接听。这种技术允许用户通过互联网进行免费或低成本的语音通话,同时还支持视频通信、即时消息以及各种多媒体数据的传输。

VoIP 之所以受到广泛的关注和应用,一方面是因为它可以大幅降低通信成本,尤其是国际长途电话费用。另一方面,VoIP 技术提供了更加丰富的通信手段和更高的灵活性,用户可以利用个人电脑、智能手机或其他设备随时随地进行通信,不受地理位置的限制。

2.1.2 VoIP的工作原理

VoIP 的工作原理大致可以分为以下几个步骤:

  1. 语音采集与编码 :用户使用麦克风进行通话时,模拟的语音信号首先被转换为数字信号。这一步骤通过模数转换器(ADC)实现。转换得到的数字信号还需要通过编码器压缩,以减少数据包的大小,便于在网络上传输。

  2. 数据包封装与传输 :压缩后的数据被封装在 IP 数据包中。这些数据包通过 IP 网络(如互联网)传输到目标地址。传输过程中可能使用 UDP 或 TCP 协议,其中 UDP 由于其较低的延迟,通常被优先考虑用于实时语音通信。

  3. 数据包解包与解码 :当数据包到达接收方后,接收方的 VoIP 应用程序会对数据包进行解包,并将解压后的数字信号通过解码器转换回模拟信号,最后由扬声器播放出来。

  4. 实时传输控制 :由于网络条件的不断变化和数据包可能发生的丢失或错序,VoIP 系统通常会采用一些实时传输控制协议(如 RTP, RTCP)来保证语音通信的流畅性和质量。

2.2 VoIP的关键技术

2.2.1 语音编码技术

语音编码技术是 VoIP 系统中的核心技术之一,负责将模拟的语音信号转换为数字信号,并进行压缩编码以便高效传输。常见的语音编码技术包括 G.711、G.722、G.723.1、G.729 等。这些编码标准各有特点,例如:

  • G.711 :是一种固定比特率的编码方式,分为 A 律和 μ 律两种,常用于电话系统。它的语音质量较好,但数据率较高(64kbps)。
  • G.722 :是一种宽带语音编码技术,提供宽频带语音质量,适用于音频会议,数据率介于 48kbps 至 64kbps。
  • G.723.1 :是一种低比特率的编码方式,主要用于视频会议和多媒体通信中,支持 5.3kbps 或 6.3kbps 的低数据率,但以牺牲一定的语音质量为代价。

  • G.729 :是一种低数据率的编码方式,主要用于减少带宽占用,数据率为 8kbps,但需要更高的计算资源。

2.2.2 传输控制技术

为了实现高质量的实时通信,VoIP 系统必须有效管理数据包的传输。实时传输协议(RTP)和实时控制协议(RTCP)是常用的 VoIP 传输控制技术。

  • RTP :提供端到端的网络传输功能,负责携带音频和视频数据。RTP 数据包包含时间戳和序列号,用以支持同步和重建原始媒体流。

  • RTCP :与 RTP 协同工作,负责监控服务质量(QoS)并提供反馈,让通信双方能够对网络状况进行评估和适应,例如通过反馈调整编码器的比特率。

2.2.3 服务质量(QoS)保证

为了确保 VoIP 通信的质量,网络服务质量(Quality of Service, QoS)保证是不可或缺的一环。QoS 涉及的措施包括:

  • 带宽管理 :预留或优先保障语音数据的带宽,以减少数据包丢弃和延迟。
  • 流量整形和调度 :通过流量整形(如令牌桶算法)和调度技术(如 WFQ)控制数据包的发送速率和顺序。
  • 拥塞控制 :当网络出现拥塞时,系统可以减少数据发送量以降低延迟,并通过丢包和重传机制保证通信的可靠性。

2.3 SIP协议在VoIP中的作用

2.3.1 SIP协议架构

SIP(Session Initiation Protocol)是一种信令协议,用于在 IP 网络上创建、修改、终止通信会话。SIP 会话可以是电话呼叫、多媒体会议、在线游戏会话等。SIP 的设计灵感来源于 HTTP,是基于文本的请求响应协议,具有良好的扩展性和兼容性。

SIP 协议包括以下主要组件:

  • 用户代理(User Agent, UA) :位于终端设备上,用来发起和处理 SIP 会话。例如,当您使用 SIP 软件电话进行通话时,该软件就是用户代理。
  • 代理服务器(Proxy Server) :负责接收 SIP 请求,进行路由决策并转发请求。代理服务器还可以提供诸如认证、访问控制和请求重定向等服务。
  • 注册服务器(Registrar) :维护用户位置信息数据库,允许用户登录和注销。
  • 重定向服务器(Redirect Server) :在用户不可达时提供新地址信息,然后 UA 可以向新地址发送请求。
  • 位置服务器(Location Server) :提供用户的当前位置信息,该信息通常由注册服务器维护。

2.3.2 SIP消息格式与处理机制

SIP 消息格式借鉴了 HTTP,采用请求(Request)和响应(Response)的结构。主要的 SIP 消息类型包括 INVITE、ACK、BYE、OPTIONS、REGISTER 等。每种消息类型都有明确的用途,例如:

  • INVITE :发起一个会话请求,邀请另一方参与。
  • ACK :确认会话已经建立,特别是在 INVITE 请求之后。
  • BYE :终止一个已存在的会话。
  • OPTIONS :查询对方是否支持 SIP 协议以及会话参数。
  • REGISTER :用户向注册服务器注册当前位置信息,以便接收呼叫。

SIP 交互流程以三次握手过程为典型,即 INVITE->ACK->BYE。当一个 UA 发起 INVITE 请求时,对方 UA 会回应一个响应,通常包括 1xx (临时响应)、2xx (成功响应)、3xx (重定向)、4xx (客户端错误)、5xx (服务器错误) 和 6xx (全局失败)。若成功,发送者会回以 ACK 确认,并在会话结束时发送 BYE 请求终止会话。

处理机制上,SIP 依赖于 SIP 消息头部字段进行会话控制,其中一些关键字段包括 To、From、Call-ID、CSeq、Contact、Via 等,这些字段提供了通信双方的标识信息、消息序列号、传输路径以及联系信息等重要信息,确保 SIP 通信的正确性和高效性。

3. MicroSIP源码结构与功能

3.1 MicroSIP的软件架构概述

3.1.1 MicroSIP的模块划分

MicroSIP是一个开源的SIP电话客户端,它拥有一个清晰的模块化架构,使其成为一个高效、灵活的VoIP解决方案。在深入了解其源码之前,我们需要先理解其主要模块划分。MicroSIP的模块化设计允许它在不同层面上进行功能扩展和维护。以下是MicroSIP几个核心模块的简要介绍:

  • 用户界面(UI)模块 :负责提供用户与软件交互的界面,包括呼叫操作、状态显示、设置调整等。
  • SIP协议栈模块 :处理SIP协议相关的消息和会话管理,是MicroSIP的核心通信模块。
  • 媒体处理模块 :负责音频和视频的采集、编解码和传输。
  • 账户管理模块 :用于存储和管理用户账户信息,包括账户注册、认证等。
  • 消息处理模块 :负责SIP消息的发送和接收,以及消息内容的解析和处理。
  • 配置管理模块 :管理用户偏好设置和软件配置,提供了保存和读取配置文件的功能。
3.1.2 源码文件的组织结构

MicroSIP的源码采用模块化组织,每个模块下的代码进一步细化为多个源文件和头文件,这有利于开发者快速定位到想要研究或修改的代码段。下面是源码文件组织结构的一个概览:

  • src目录 :主要存放源代码文件。
  • include目录 :存放源代码中需要引用的头文件。
  • lib目录 :存放MicroSIP依赖的库文件。
  • res目录 :资源文件,包括图标、声音文件、语言包等。

在src目录下,我们可以找到各个模块对应的源文件,例如:

  • microsipDlg.cpp和microsipDlg.h :实现主对话框,是用户界面模块的核心。
  • sipMsg.h和sipMsg.cpp :定义了SIP消息的数据结构和处理函数。
  • sdp.h和sdp.cpp :实现会话描述协议(SDP)的解析和构建。
  • audio.h和audio.cpp :音频设备的封装和音频流的处理。

3.2 MicroSIP核心功能分析

3.2.1 实时通话处理

实时通话处理是VoIP应用的核心功能,MicroSIP通过其SIP协议栈模块实现此功能。该模块能够处理以下通话流程中的关键步骤:

  • 会话初始化 :处理SIP INVITE请求,完成呼叫的建立。
  • 会话协商 :通过SDP协商媒体参数,如编码格式、端口等。
  • 媒体传输 :音频和视频数据的实时传输。
  • 会话终止 :处理BYE请求,结束通话会话。

上述功能的实现依赖于以下源码关键部分:

// 示例代码:处理SIP INVITE请求
void processInvite(SIPDialog* pDialog, SIPMessage* pMsg)
{
    // 分析 INVITE 请求的 SDP,决定是否接受呼叫
    // ...
    // 如果接受呼叫,则发送 200 OK 响应并开始会话
    // ...
}
3.2.2 账户管理与配置

账户管理允许用户在软件中设置和管理SIP账户。这一功能主要通过账户管理模块实现,支持添加、删除、修改SIP账户信息,以及账户的注册和认证。

  • 账户存储 :账户信息以文件形式保存,或使用加密存储,确保信息安全。
  • 账户注册 :向SIP服务器发送注册请求,维护账户的在线状态。
  • 认证处理 :处理来自SIP服务器的认证挑战,并响应。

实现账户管理功能的部分源码示例如下:

// 示例代码:账户注册逻辑
void registerAccount(SIPAccount* pAccount)
{
    // 发送 REGISTER 请求到SIP服务器
    // ...
    // 接收服务器响应并处理
    // ...
}
3.2.3 消息传递与管理

消息传递与管理模块处理SIP消息的发送和接收,负责解析和生成SIP消息。该模块支持以下功能:

  • 消息的解析和构造 :解析接收的SIP消息,构造要发送的消息。
  • 消息类型的处理 :如INVITE、REGISTER、BYE、OPTIONS等。
  • 消息状态的反馈 :根据SIP协议,提供相应的响应消息。

示例代码展示了如何在接收INVITE消息后,构造并发送200 OK响应:

// 示例代码:处理收到的INVITE消息
void handleInviteResponse(SIPAccount* pAccount, SIPMessage* pMsg)
{
    // 如果接受邀请,则构造200 OK响应
    SIPMessage response;
    response.initResponse(pMsg, 200);
    // 发送响应
    sendSIPMessage(pAccount, response);
}

在本章中,我们介绍了MicroSIP的软件架构和核心功能,以及实现这些功能的源码关键部分。下一章,我们将深入分析MicroSIP的关键源码文件,探究其内部工作机制。

4. MicroSIP关键源码文件解析

4.1 microsipDlg.cpp:主对话框实现

4.1.1 对话框界面设计

microsipDlg.cpp 文件中的代码主要负责MicroSIP客户端的主对话框界面设计。这个界面是用户交互的第一窗口,它需要提供清晰的导航和友好的用户体验。在源码中,首先会初始化对话框窗口,设置窗口的标题、大小和位置。接着,通过一系列的控件添加到对话框中,如输入框、按钮、列表框等,来形成完整的用户界面。

// 示例代码片段,展示对话框初始化
void CmicrosipDlg::OnInitDialog()
{
    CDialogEx::OnInitDialog();
    // 窗口大小调整
    CRect rect;
    GetClientRect(&rect);
    SetWindowPos(&CWnd::wndTop, 0, 0, rect.Width(), rect.Height(), SWP_NOZORDER);

    // 控件添加和布局设置
    m_btnDial = new CButton();
    m_btnDial->Create(_T("Dial"), WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON, CRect(10, 10, 100, 30), this, 101);
    // 其他控件的创建和布局代码省略...

    // 初始化对话框事件处理
    UpdateWindow();
}

4.1.2 用户交互逻辑实现

microsipDlg.cpp 文件中还包含了用户交互逻辑的实现。比如,用户点击“Dial”按钮时,触发的事件处理函数会读取输入框中的电话号码,并调用相应的函数发起呼叫。每次用户操作都可能涉及到更深层次的交互,代码需要处理好各种输入验证和异常情况,确保软件的健壮性。

// 示例代码片段,展示按钮点击事件处理
void CmicrosipDlg::OnBnClickedDial()
{
    CString strNumber;
    GetDlgItemText(IDC_EDIT_NUMBER, strNumber); // 获取输入框中的号码
    if (strNumber.IsEmpty())
    {
        AfxMessageBox(_T("Please enter a number to dial."), MB_OK);
        return;
    }
    // 调用函数发起呼叫
    Dial(strNumber);
    UpdateWindow(); // 更新界面显示
}

4.2 MessagesDlg.cpp:消息处理功能

4.2.1 消息界面元素

MessagesDlg.cpp 文件中,代码将处理消息的发送与接收,并将其反映在用户界面上。这涉及到各种状态提示、消息展示以及用户操作的响应。消息界面元素的设计需要考虑易用性、可读性和实时性,使用户能够轻松地发送和查看消息。

// 示例代码片段,展示消息框界面元素初始化
void CMessagesDlg::OnInitDialog()
{
    CDialogEx::OnInitDialog();
    // 设置消息显示区域
    m编辑框控件ID, &m_editMessage);
    // 其他界面元素初始化代码省略...
}

4.2.2 消息发送与接收机制

消息发送与接收机制是MicroSIP软件中核心的通信功能之一。在 MessagesDlg.cpp 文件中,当用户输入消息并点击发送按钮后,程序会将消息内容打包并通过SIP协议发送给指定的接收者。同时,程序也需要监听来自其他SIP客户端的消息,并将接收到的消息展示给用户。

// 示例代码片段,展示消息发送处理
void CMessagesDlg::OnBnClickedSend()
{
    CString strMessage;
    GetDlgItemText(IDC_EDIT_MESSAGE, strMessage); // 获取用户输入的消息
    if (!strMessage.IsEmpty())
    {
        SendMessage(strMessage); // 发送消息
    }
    SetDlgItemText(IDC_EDIT_MESSAGE, _T("")); // 清空消息框
    UpdateWindow();
}

4.3 ClosableTabCtrl.cpp:可关闭标签控件实现

4.3.1 标签控件的自定义

ClosableTabCtrl.cpp 文件负责实现一个可关闭的标签控件,这是MicroSIP中一种创新的用户界面设计。用户能够通过点击标签右上角的关闭按钮来移除不必要的标签页,从而提高工作效率和界面的整洁性。

// 示例代码片段,展示自定义标签控件的基本操作
void CCustomTabCtrl::DoCloseTab(int index)
{
    if (index >= 0 && index < m_nTabs)
    {
        // 移除并销毁标签页
        RemovePage(index);
        UpdateWindow();
    }
}

4.3.2 界面的动态交互

ClosableTabCtrl.cpp 文件中的代码还包含了界面的动态交互功能。标签控件的状态变化需要被实时捕捉并反馈给用户,比如当一个新的通话连接被建立时,自动创建一个标签页并打开它。程序需要有效地管理这些动态的用户界面元素。

// 示例代码片段,展示标签页的动态创建
void CCustomTabCtrl::OnNewCall()
{
    // 创建新的标签页
    int index = InsertItem(m_nTabs, _T("New Call"));
    SetCurSel(index); // 设置当前选中的标签页
    UpdateWindow();
}

4.4 VisualStylesXP.cpp:Windows XP视觉样式支持

4.4.1 视觉样式的应用与兼容性

VisualStylesXP.cpp 文件负责在程序中实现Windows XP的视觉样式。通过调用Windows API,程序能够将标准控件的外观统一更新为与Windows XP操作系统的视觉风格一致,提升软件界面的美观程度和用户体验。

// 示例代码片段,展示视觉样式的应用
void CVisualStylesXP::EnableVisualStyles()
{
    if (!IsThemeActive())
    {
        int result = InitiateSystemTheme();
        if (result != ERROR_SUCCESS)
        {
            // 错误处理代码省略...
        }
    }
}

4.4.2 样式定制对用户体验的影响

VisualStylesXP.cpp 文件中,代码还会允许用户根据个人偏好定制软件的视觉样式。这些样式会根据用户的设置动态加载并应用到软件界面上,增加了用户的参与度,并且提升了软件的个性化。

// 示例代码片段,展示样式定制功能
void CVisualStylesXP::CustomizeStyle()
{
    // 提示用户选择样式
    // 应用用户选择的样式到对话框
}

以上章节详细解读了 microsipDlg.cpp MessagesDlg.cpp ClosableTabCtrl.cpp 、和 VisualStylesXP.cpp 文件的关键源码,深入探讨了MicroSIP主对话框、消息处理、可关闭标签控件以及Windows XP视觉样式支持等核心功能的实现机制。通过具体的代码实例和详细分析,揭示了这些功能背后的逻辑和设计思路,为理解与开发类似软件功能提供参考。

5. MicroSIP高级功能开发

5.1 SettingsDlg.cpp, settings.cpp:软件配置与设置

配置文件的读写机制

MicroSIP使用XML格式文件作为其配置文件,这不仅使得配置的编辑变得简单,也便于对配置项的维护和更新。在SettingsDlg.cpp中,程序实现了配置界面的逻辑,而settings.cpp则负责配置文件的读取和写入操作。

代码块展示:
// settings.cpp 读取配置项的函数
bool LoadSettings(string filename, CSettings& settings) {
    TiXmlDocument doc(filename);
    doc.LoadFile();
    TiXmlElement* pElement = doc.FirstChildElement("microsip");
    if (pElement == nullptr) {
        return false;
    }

    // 解析配置文件,填充CSettings对象
    for (TiXmlElement* pChild = pElement->FirstChildElement("setting");
         pChild != nullptr;
         pChild = pChild->NextSiblingElement("setting")) {
        string name = pChild->Attribute("name");
        string value = pChild->GetText();
        settings.AddSetting(name, value);
    }
    return true;
}

// settings.cpp 写入配置项的函数
void SaveSettings(string filename, CSettings& settings) {
    TiXmlDocument doc;
    TiXmlElement* pElement = new TiXmlElement("microsip");
    doc.LinkEndChild(pElement);

    // 遍历CSettings对象,生成XML元素
    for (auto& setting : settings) {
        TiXmlElement* pChild = new TiXmlElement("setting");
        pChild->SetAttribute("name", setting.first.c_str());
        pChild->SetText(setting.second.c_str());
        pElement->LinkEndChild(pChild);
    }
    doc.SaveFile(filename);
}

在上述代码中,我们看到LoadSettings函数负责读取配置文件并更新CSettings对象,而SaveSettings函数则从CSettings对象中提取信息,并写回到配置文件中。解析和生成XML文件的过程中,都是以microsip为根节点,对每一个配置项都使用setting作为子节点,并包含name和value两个属性。

参数说明:
  • string filename :配置文件的路径。
  • CSettings& settings :CSettings类的引用,用于存储配置项。

配置文件的读写操作对于用户自定义软件行为至关重要,MicroSIP通过这套机制,允许用户保存他们对软件的个性化设置,比如账户信息、呼叫设置、界面布局等。

用户定制化设置的实现

用户定制化设置通常包括外观主题、快捷键、通信协议参数等。在MicroSIP中,这些设置都是通过SettingsDlg.cpp界面来呈现,用户通过图形用户界面(GUI)来更改设置,当用户按下“保存”按钮时,更改会被写入配置文件中。

代码块展示:
// SettingsDlg.cpp 中保存用户设置的处理
void CSettingsDlg::OnBnClickedButtonSave() {
    // 读取用户界面元素的设置,例如字体大小,颜色等
    CSettings settings;
    settings.AddSetting("fontSize", m_spinFontSize.GetValue());
    settings.AddSetting("colorScheme", m_comboboxColorScheme.GetCurrentString());
    // ... 其他设置项

    // 调用保存函数
    SaveSettings(m_settingsFilename, settings);
}

在这个示例中,我们看到,用户通过界面上的控件如 m_spinFontSize (数字选择框)和 m_comboboxColorScheme (下拉列表框)来选择他们的偏好。当用户点击“保存”按钮,这些设置项会通过SaveSettings函数被写入到XML配置文件中。

逻辑分析:
  • 这里通过调用 SaveSettings 函数保存用户设置。这包括从界面控件中获取设置值,创建或更新 CSettings 对象,然后将该对象写入磁盘。
  • 这种设计使得用户可以很容易地备份他们的个人设置,因为它们被存储在一个简单的XML文件中。
  • 用户自定义设置允许用户根据个人喜好来操作软件,从而提高用户体验。

5.2 Dialer.cpp:拨号器组件实现

拨号逻辑的实现

拨号器是通信软件的一个关键部分,它负责将用户输入的电话号码转换为网络命令,并发送给SIP服务器。在MicroSIP中,Dialer.cpp负责实现这一逻辑。

代码块展示:
// Dialer.cpp 发起呼叫的函数
void CDialerDlg::OnBnClickedButtonDial() {
    string number = m_editNumber.GetValue();
    if (number.empty()) {
        return;
    }

    // 构建SIP呼叫邀请消息
    string request = "INVITE sip:" + number + " SIP/2.0\r\n";
    // ... 添加其他SIP头字段

    // 发送SIP呼叫邀请消息
    SendSIPMessage(request);
}

在上面的代码示例中,我们看到 OnBnClickedButtonDial 函数通过 m_editNumber 获取用户输入的电话号码。然后,函数构建了一个简单的SIP邀请消息,并通过 SendSIPMessage 函数发送。实际的SIP消息构建过程会更加复杂,包括多个必需的SIP头字段。

参数说明:
  • string number :用户输入的电话号码。

拨号器的用户界面设计

拨号器的用户界面(UI)设计影响用户的操作便利性。MicroSIP的拨号器设计简洁,易于操作,包含数字键盘、保存常用号码、呼叫历史等功能。

代码块展示:
// 拨号器UI元素初始化(示例)
void CDialerDlg::OnInitDialog() {
    // 初始化编辑框
    m_editNumber.SubclassDlgItem(IDC_EDIT_NUMBER, this);
    m_editNumber.SetLimitText(MAX_NUMBER_LENGTH);
    // 初始化按钮
    m_btnDial.SubclassDlgItem(IDC_BUTTON_DIAL, this);
    // ... 其他UI控件初始化
}

在这个示例中, OnInitDialog 函数是对话框初始化时调用的。它为编辑框、按钮以及其他UI控件分配资源,并设置必要的属性,如输入限制和子类化。

5.3 AccountDlg.cpp:账户管理界面

账户信息的存储与管理

在VoIP通信软件中,账户信息管理是不可或缺的功能。对于MicroSIP来说,AccountDlg.cpp负责处理用户账户的存储和管理。

代码块展示:
// AccountDlg.cpp 账户列表的展示和管理
void CAccountsDlg::OnBnClickedButtonAddAccount() {
    // 创建新的账户对话框
    CAccountDlg accountDlg;
    if (accountDlg.DoModal() == IDOK) {
        // 从对话框获取账户信息,并保存
        AccountInfo info;
        if (accountDlg.GetAccountInfo(info)) {
            m_accounts.AddAccount(info);
            SaveAccounts();
        }
    }
}

上述代码展示了在用户界面上点击“添加账户”按钮后触发的事件处理。 CAccountsDlg 类中的 OnBnClickedButtonAddAccount 方法负责展示 CAccountDlg 对话框让用户输入账户信息,并在确认后将账户信息添加到本地的账户列表中。完成账户添加后,调用 SaveAccounts 方法将新账户信息保存到本地存储。

参数说明:
  • CAccountDlg accountDlg :新建的账户对话框实例。
  • m_accounts :存储账户信息的数据结构。

多账户切换与管理功能

多账户管理允许用户在不同的VoIP账户之间切换,管理各自的状态和偏好设置。MicroSIP的账户管理功能简洁明了,用户可以轻松切换账户,配置各自的属性。

代码块展示:
// AccountDlg.cpp 账户切换与管理的实现
void CAccountsDlg::OnCbnSelchangeListAccounts() {
    int index = m_listAccounts.GetCurSel();
    if (index == LB_ERR) {
        return;
    }

    // 从列表中获取当前选中的账户
    AccountInfo& currentAccount = m_accounts[index];

    // 允许用户编辑选中的账户
    // ...
    // 这里会更新界面,显示账户详细信息,用户可以对选中的账户进行修改

    // 将更新后的账户信息保存回账户列表
    m_accounts.SetAccountInfo(index, currentAccount);
    SaveAccounts();
}

在这段代码中,当用户在账户列表中选择一个账户时, OnCbnSelchangeListAccounts 函数会被调用。函数获取当前选中的账户信息,允许用户对其进行编辑,并在用户完成编辑后保存到账户列表中。

通过这种方式,MicroSIP实现了对多账户的高效管理,让用户可以方便地在不同的VoIP账户之间切换,根据需要进行呼叫和接收通话。

6. MicroSIP的附加功能与扩展性

6.1 langpack.cpp:多语言支持实现

6.1.1 本地化与国际化处理

在当今的全球化市场环境中,软件产品的本地化(Localization)和国际化(Internationalization)是实现软件全球可用性的关键步骤。对于MicroSIP这样的开源VoIP客户端而言,支持多语言能够让该软件吸引来自不同地区和文化的用户群体。在实现本地化和国际化的过程中,开发者需要确保软件界面能够适应不同语言环境下的字符显示,同时软件的功能和行为应当与地区特定的习俗和规范相吻合。

在MicroSIP源码中, langpack.cpp 文件负责实现软件的多语言支持。这个文件通常包含一个语言包加载机制,该机制允许用户安装和切换不同语言的资源文件(通常是 .lng 文件)。这些资源文件包含翻译后的文本,例如用户界面的标签、按钮名称、帮助信息以及其它可显示的字符串。

6.1.2 语言包的加载机制

语言包加载机制的核心是一个映射过程,将软件界面中的每个静态文本元素映射到一个键值对,其中键是指向实际文本的引用,而值则是实际的本地化文本。开发者为每种支持的语言创建资源文件,每个文件中都包含了这些键值对的对应关系。当用户选择一种语言时, langpack.cpp 会读取对应的资源文件,并将所有界面元素的文字更改为相应的翻译文本。

语言包的加载通常包括以下几个步骤:

  1. 检测系统语言或用户选择的语言设置。
  2. 从设定路径中加载对应语言的 .lng 文件。
  3. 将文件中的键值对读取到内存中,形成映射关系。
  4. 遍历用户界面的所有可翻译元素,使用翻译映射替换原文本。

例如,以下是一个简化的 langpack.cpp 语言包加载机制的伪代码示例:

// langpack.cpp 伪代码示例

// 加载语言包函数
bool LoadLanguagePack(const std::string& language_code) {
    // 构建资源文件的路径,例如 "lang/en.lng" 对应英语
    std::string lang_file_path = "lang/" + language_code + ".lng";
    // 加载资源文件
    // ...

    // 读取键值对映射到内存中
    // ...

    // 遍历界面元素,替换文本
    for (UIElement& element : GetAllUIElements()) {
        std::string key = element.GetTextKey(); // 获取界面元素的文本键
        std::string translated_text = FindTranslation(key); // 查找翻译文本
        element.SetText(translated_text); // 替换界面元素的文本
    }

    return true;
}

// 在主程序中调用加载语言包函数
LoadLanguagePack("en"); // 加载英语语言包

6.1.3 语言包的维护与更新

语言包不仅需要支持加载机制,还应便于维护和更新。开发者可以通过提供一个简单的编辑器或一个模板格式的 .lng 文件来让用户更容易参与翻译工作。另外,语言包的格式应当保持一致,并且易于识别键值对的映射关系,从而使得翻译的文本能够准确无误地显示在用户界面上。

随着软件版本更新或新功能的添加,语言包也应当相应地更新,以确保所有新的或修改后的文本元素都有相应的翻译。为了维护和更新的方便,开发者可以使用版本控制系统来管理语言包文件,以便追踪变更并协作更新。

6.2 Calls.cpp:通话处理模块实现

6.2.1 呼叫控制逻辑

Calls.cpp 文件是 MicroSIP 通话处理模块的核心部分,它负责实现呼叫的发起、接听、挂断以及呼叫状态的更新。呼叫控制逻辑通常包括呼叫信令的处理,它是一系列特定格式的消息,用于在通信双方之间建立、管理和结束呼叫连接。

呼叫控制逻辑必须遵循 SIP 协议的规范,并且要处理各种网络条件下的异常情况,如呼叫超时、网络中断等问题。在 MicroSIP 中,呼叫控制逻辑被封装在一系列的类和函数中,以确保代码的模块化和可维护性。

6.2.2 通话质量的监控与管理

通话质量的监控与管理是用户满意度的一个关键因素。MicroSIP 通过实时监控通话中的丢包率、延迟和抖动等参数来评估通话质量。开发者可以利用这些参数来动态调整通话的编码方式或调整网络缓冲区大小,以优化通话体验。

通话质量监控通常会涉及到一个周期性的检测机制,它会定期检查和记录相关的质量参数,并根据这些参数进行决策。在某些情况下,如果通话质量低于可接受的阈值,软件可能会提示用户降低语音质量或切换到不同的传输协议以改善通话体验。

6.2.3 代码块示例与分析

以下是 Calls.cpp 文件中的一个示例代码块,它展示了如何初始化一个通话对象,并包含对 SIP 消息的处理逻辑:

// Calls.cpp 代码示例

class Call {
public:
    // 构造函数,初始化通话对象
    Call(SipSession& session) : session_(session) {
        // ... 初始化代码 ...
    }

    // 发起呼叫
    void CallRemote(const std::string& remote_uri) {
        session_.SendRequest("INVITE", remote_uri);
    }

    // 处理收到的响应
    void OnResponse(SipResponse& response) {
        if (response.StatusCode() == 200) {
            // 呼叫成功响应处理
            HandleAnswer(response);
        } else {
            // 呼叫失败响应处理
            Handle отказ(response);
        }
    }

private:
    void HandleAnswer(SipResponse& response) {
        // ... 接听处理逻辑 ...
    }

    void HandleRefusal(SipResponse& response) {
        // ... 呼叫拒绝处理逻辑 ...
    }

    SipSession& session_;
};

在上述代码示例中:

  • Call 负责管理一次呼叫的生命周期。
  • CallRemote 方法负责向指定的 URI 发起一次 INVITE 请求,以开始呼叫。
  • OnResponse 方法负责处理收到的响应,并根据响应的状态码来决定后续的行动。
  • HandleAnswer HandleRefusal 方法处理成功和失败的具体逻辑。

通过分析该代码块,我们可以看到 MicroSIP 如何通过封装调用细节来简化呼叫流程,并且通过状态码的检查来决定通话流程的不同分支,展示了呼叫控制的逻辑清晰性。

7. MicroSIP的测试与优化

7.1 软件测试流程

7.1.1 单元测试的实施

在开发过程中,单元测试是确保每个独立模块正确性的基础。对于MicroSIP而言,单元测试需要对主要的源码文件进行测试,如前面章节提到的microsipDlg.cpp, MessagesDlg.cpp等。测试时通常使用一些框架如Google Test或Catch,编写独立的测试用例来验证功能。

在测试microsipDlg.cpp时,开发者需要确保主对话框中的每个按钮、菜单项和逻辑分支都被覆盖到。例如,当用户点击拨号按钮时,程序应能正确地调起拨号界面,并处理用户输入的电话号码。

TEST_CASE("主对话框拨号功能测试") {
    microsipDlg dlg;
    dlg.OnBnClickedDial(); // 模拟点击拨号按钮
    REQUIRE(dlg.IsDialogMessage(L"正确的电话号码")); // 验证电话号码是否正确处理
}

7.1.2 集成测试与系统测试

在单元测试通过后,将各个模块集成到一起,并进行集成测试,检查模块间的交互是否符合预期。紧接着进行系统测试,确保整个软件作为一个整体时能够正常工作。

在集成测试阶段,可以模拟实际的通话场景,确保语音数据能够在MicroSIP的不同模块间正确传输。这可能涉及到网络通信、音频编解码等多个方面。

系统测试需要模拟实际用户操作,检查软件的每个功能是否能够满足用户需求,例如账户管理、消息传递、通话记录保存等。

7.2 性能优化策略

7.2.1 代码层面的优化

代码层面的优化直接影响软件性能和资源使用效率。在MicroSIP的开发中,开发者应关注于以下几个方面:

  • 循环优化:移除不必要的循环,优化循环内部的处理逻辑,减少循环次数。
  • 内存管理:合理分配和释放内存,避免内存泄漏,使用智能指针来管理资源。
  • 事件处理:减少事件处理函数中的无效计算,确保只处理与事件直接相关的操作。

例如,考虑microsipDlg.cpp中的事件处理函数,如果存在内存分配操作,应确保在适当的时机释放内存。

void microsipDlg::OnSomeEvent() {
    auto resource = std::make_unique<SomeResource>(); // 智能指针自动释放内存
    // ... 其他操作 ...
}

7.2.2 资源使用监控与管理

资源使用监控与管理是为了确保软件在运行过程中不会造成系统资源的过度消耗。在MicroSIP中,可以采用以下方法来监控资源使用:

  • 使用Windows性能计数器,监控CPU、内存使用情况。
  • 实时监控线程和进程状态,确保没有死锁或者线程资源的竞争。
  • 对于音频流的传输,监控网络带宽使用情况,保证通话质量。

通过这些监控,开发者可以发现潜在的性能瓶颈,并进行针对性的优化。

7.3 用户反馈与软件迭代

7.3.1 用户反馈的收集与分析

收集用户反馈是软件迭代开发的重要环节。在MicroSIP中,可以通过多种方式来收集用户的反馈:

  • 在软件内部设置反馈按钮,让用户直接提交问题。
  • 使用在线调查和问卷收集用户的意见和建议。
  • 在论坛、社交媒体和用户群组中收集用户的反馈。

收集到的反馈信息经过整理和分类,可以为后续的软件迭代提供方向。

7.3.2 基于反馈的软件迭代开发

根据用户反馈,软件开发团队将规划新版本的功能更新和性能改进。在迭代开发过程中,对用户反映的问题进行优先级排序,优先解决那些影响用户日常使用的关键问题。

为了确保每次迭代都有明确的改进,开发团队会制定详细的迭代计划,并通过内部评审来确保每个新功能都满足用户的实际需求。

通过这一系列的测试与优化流程,MicroSIP能够不断地提升其性能和用户体验,从而在竞争激烈的VoIP市场中保持竞争力。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:MicroSIP电话是一款基于SIP协议的开源网络电话软件,支持互联网音频和视频通话。本文详细介绍了SIP协议的基础知识,网络电话(VoIP)技术的简述,以及MicroSIP软件的源码结构和关键功能模块,包括用户界面、账户管理、呼叫控制和多媒体处理等。开发者可以利用这些资源深入学习SIP协议和VoIP技术,而普通用户则能享受到一个高效易用的网络电话服务。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

摘自:http://mbstudio.spaces.live.com/blog/cns!C898C3C40396DC11!955.entry 2007/1/30 oSIP协议栈(及eXoSIP,Ortp等)使用入门(原创更新中) (CopyLeft by Meineson | www.mbstudio.cn,原创文章,欢迎转载,但请保留出处说明!) 本文档最新版本及文中提到的相关源码及VC6工程文件请在本站找,嘿嘿~~ (首页的SkyDriver公开文件夹中,可能需要用代理才能正常访问该空间——空间绝对稳定,不会丢失文件!) (最近工作重心不在SIP开发,SO本文档也没有机会更新,有技术问题也请尽量咨询他人,本人不一定能及时回复。)   一直没空仔细研究下oSIP,最近看到其版本已经到了3.x版本,看到网上的许多帮助说明手册都过于陈旧,且很多文档内容有点误人子弟的嫌疑~~   Linux下oSIP的编译使用应该是很简单的,其Install说明文档里也介绍的比较清楚,本文主要就oSIP在Windows平台下VC6.0开发环境下的使用作出描述。   虽然oSIP的开发人员也说明了,oSIP只使用了标准C开发库,但许多人在Windows下使用oSIP时,第一步就被卡住了,得不到oSIP的LIB库和DLL库,也就没有办法将oSIP使用到自己的程序中去,所以第一步,我们将学习如何得到oSIP的静态和动态链接库,以便我们自己的程序能够使用它们来成功编译和执行我们的程序。 第一阶段: ------------------------------------------------------   先创建新工程,网上许多文档都介绍创建一个Win32动态链接库工程,我们这里也一样,创建一个空白的工程保存。   同样,将oSIP2版本3.0.1 src目录下的Osipparser2目录下的所有文件都拷到我们刚创建的工程的根目录下,在VC6上操作: Project-Add To Project-Files   将所有的源程序和头文件都加入到工程内,保存工程。   这时,我们可以尝试编译一下工程,你会得到许多错误提示信息,其内容无非是找不到osipparser2/xxxxx.h头文件之类。   处理:在Linux下,我们一般是将头文件,lib库都拷到/usr/inclue;/usr/lib之类的目录下,c源程序里直接写#include 时,能直接去找到它们,在VC里,同样的,最简单的方法就是将oSIP2源码包中的Include目录下的 osipparser2目录直接拷到我们的Windows下默认包含目录即可,这个目录在VC6的Tool-Options-Directories里设置,(当然,如果你知道这一步,也可以不用拷贝文件,直接在这里把oSIP源码包所在目录加进来就可以了),默认如果装在C盘,目录则为 C:\Program Files\Microsoft Visual Studio\VC98\Include。   这时,我们再次编译我们的工程,顺利编译,生成osipparser2.dll,这时,网上很多文档里可能直接就说,这一步也会生成libs目录,里面里osipparser2.lib文件,但我们这里没有生成:)   最简单的方法,不用深究,直接再创建一个工程,同上述创建动态链接库方法,创建一个Win32静态链接库工程,直接编译,即可得到osipparser2.lib。 ------------------------------------------------------   上面,我们得到了Osip的解析器开发库,下面再编译完整的Osip协议栈开发库,同样照上述方法,分别创建动态链接库工程和静态链接库工程,只是要拷的文件换成src下的osip目录下文件和include下的osip目录,得到osip2.dll和osip2.lib。   在编译osip2.dll这一步可能会再次得到错误,内容含义是找不到链接库,所以,我们要把前面编译得到的osipparser2.lib也拷到osip工程目录下,并在VC6中操作:   Project-Setting-Link中的Object/Library Modules: kernel32.lib user32.lib ... xxx.lib之类的内容最后增加: osipparser2.lib   保存工程后再次编译,即可成功编译osip2.dll。 ------------------------------------------------------   至此,我们得到了完整的oSIP开发库,使用时,只需在我们的程序里包含oSIP的头文件,工程的链接参数里增加osipparser2.lib和osip2.lib即可。 ------------------------------------------------------   下面我们验证一下我们得到的开发库,并大概了解一下OSIP的语法规范。   在VC里创建win32控制台程序工程,将libosip源码包的SRC目录下的Test目录内的C源程序随便拷一个到工程时,直接编译(工程设置里照前文方法在link选项里增加osip2.lib,osipparser2.lib引用我们之前成功编译得到的静态库文件)就可以运行(带参数运行,参数一般为一个文本文件,同样从Test目录的res目录里拷一个源文件同名的纯文本文件到工程目录下即可)。   该目录下的若干文件基本上是测试了Osip的一些基本功能函数,例如URI解析之类,可以大概了解一下oSIP的语法规范和调用方法,同时也能校验一下之前编译的OSIP开发库能否正常使用,成功完成本项工作后,可以进入下一步具体的oSIP的使用学习了。 ------------------------------------------------------   由于oSIP是比较底层的SIP协议栈实现,新手较难上手,而官方的示例大都是一些伪代码,需要有实际的例子程序参考学习,而最好的例子就是同样官方发布的oSIP的扩展开发库exosip2,使用exoSIP可以很方便地快速创建一个完整的SIP程序(只针对性地适用于SIP终端开发用,所以我们这里只是用它快速开发一个SIP终端,用来更方便地学习oSIP,要想真正掌握SIP的开发,需要掌握oSIP并熟读RFC文档才行,exoSIP不是我们的最终学习目的),通过成功编译运行一个自己动手开发出的程序,再由浅入深应该是初学都最好的学习方法通过对使用exosip开发库的使用创建自己的SIP程序,熟悉后再一个函数一个函数地深入学习exosip提供的接口函数,就可以深入理解osip 了,达到间接学习oSIP的目的,同时也能从eXoSIP中学习到正确使用oSIP的良好的编程风格和语法格式。   而要成功编译ExoSIP,似乎许多人被难住了,直接在XP-sp2上,用VC6,虽然你使用了eXoSIP推荐的winsock2.h,但是会得到一个 sockaddr_storage结构不能识别的错误,因为vc6自带的开发库太古董了,需要升级系统的Platform SDK,下载地址如下: http://www.microsoft.com/msdownl ... PSP2FULLInstall.htm(VC6的支持已经停止,这是VC6能使用的最新SDK)   成功安装后编译前需加OSIP_MT宏,以启用线程库,否则在程序中使用eXoSIP库时会出错,而编译时也会得到许多函数未定义的Warning提示,编译得到exosip2.lib供我们使用,当然,在此之前需要成功编译了osip2和osipparser2,而在之后的实际使用时,发现oSIP也需要增加OSIP_MT宏,否则OSIP_MT调用oSIP的线程库时会出错,所以我们需要重新编译oSIP了:),因为eXosip是基于oSIP的(同上方式创建静态和动态链接库工程,并需在Link中手工添加oSIP和oSIPparser的lib库)。 ------------------------------------------------------   创建新工程,可以是任意工程,我们从最简单的Win32控制台程序开始,为了成功使用oSIP,我们需要引用相关库,调用相关头文件,经过多次试验,发现需要引用如下的库: exosip2.lib osip2.lib osipparser2.lib WSock32.Lib IPHlpApi.Lib WS2_32.Lib Dnsapi.lib   其中,除了我们上面编译得到的三个oSIP库外,其它库都是系统库,其中有一些是新安装的Platform SDK所新提供的。   至此,我们有了一个简单的开发环境了,可以充分利用网上大量的以oSIP为基础的代码片段和官方说明文档开始具体函数功能的测试和使用了:) ------------------------------------------------------   我们先进行一个简单的纯SIP信令(不带语音连接建立)的UAC的SIP终端的程序开发的试验(即一个只能作为主叫不能作为被叫的的SIP软电话模型),我们创建一个MFC应用程序,对话框模式,照上面的说明,设置工程包含我们上面得到的oSIP的相关开发库及SDK的一些开发库,并且由于默认LIBC的冲突,需要排除MSVCRT[D]开发库(其中D代表Debug模式下,没有D表示Release模式下),直接使用eXosip的几个主要函数就可以创建一个基本的SIP软电话模型。   其主要流程为:   初始化eXosip库-启动事件监听线程-向SIP Proxy注册-向某SIP终端(电话号码)发起呼叫-建立连接-结束连接   初始化代码: int ret = 0; ret = eXosip_init (); eXosip_set_user_agent("##YouToo0.1"); if(0 != ret) { AfxMessageBox("Couldn't initialize eXosip!\n"); return false; } ret = eXosip_listen_addr (IPPROTO_UDP, NULL, 0, AF_INET, 0); if(0 != ret) { eXosip_quit (); AfxMessageBox("Couldn't initialize transport layer!\n"); return false; }   启动事件监听线程: AfxBeginThread(sip_uac,(void *)this);   向SIP Proxy注册: eXosip_clear_authentication_info(); eXosip_add_authentication_info(uname, uname, upwd, "md5", NULL); real_send_register(30);  /* 自定义函数代码请见源码 */   发起呼叫(构建假的SDP描述,实际软电话使用它构建RTP媒体连接): osip_message_t *invite = NULL; /* 呼叫发起消息体 */ int i = eXosip_call_build_initial_invite (&invite, dest_call, source_call, NULL, "## YouToo test demo!"); if (i != 0) { AfxMessageBox("Intial INVITE failed!\n"); } char localip[128]; eXosip_guess_localip (AF_INET, localip, 128); snprintf (tmp, 4096, "v=0\r\n" "o=josua 0 0 IN IP4 %s\r\n" "s=conversation\r\n" "c=IN IP4 %s\r\n" "t=0 0\r\n" "m=audio %s RTP/AVP 0 8 101\r\n" "a=rtpmap:0 PCMU/8000\r\n" "a=rtpmap:8 PCMA/8000\r\n" "a=rtpmap:101 telephone-event/8000\r\n" "a=fmtp:101 0-11\r\n", localip, localip, "9900"); osip_message_set_body (invite, tmp, strlen(tmp)); osip_message_set_content_type (invite, "application/sdp"); eXosip_lock (); i = eXosip_call_send_initial_invite (invite); eXosip_unlock ();   挂断或取消通话: int ret; ret = eXosip_call_terminate(call_id, dialog_id); if(0 != ret) { AfxMessageBox("hangup/terminate Failed!"); }   可以看到非常简单,再借助于oRTP和Mediastreamer开发库,来快速为我们的SIP软电话增加RTP和系统语音API接口交互及语音编码功能,即可以快速开发出一个可用的SIP软电话,关于oRTP和Mediastreamer的相关介绍不是本文重点,将在有空的时候考虑增加相应使用教程,文章前提到的地方可以下载基本可用的完整SIP软电话的VC源码工程文件供参考使用,完全CopyLeft,欢迎转载,但请在转载时注明作者信息,谢谢! 第二阶段: ---------------------------------------------------   得到了一个SIP软电话模型后,我们可以根据软电话的实际运行表现(结合用Ethereal抓包分析)来进行代码的分析,以达到利用eXoSIP来辅助我们学习oSIP的最终目的(如要快速开发一个可用的SIP软电话,请至前面提到的论坛去下载使用oRTP和Mediastreamer快速搭建的一个基本完整可用的SIP软电话##YouToo 0.1版本的VC源码工程文件作参考)。   现在从eXosip的初始化函数开始入手,来分析oSIP的使用,这是第二阶段,第三阶段就是深入学习oSIP的源码了,但大多数情况下应该没有必要了,因为在第二阶段就有部分涉及到第三阶段的工作了,而且oSIP的源码也就大多是一些SIP数据的语法解析和状态机的实现,能深入理解了SIP协议后,这些只是一种实现方式,没必要完全去接受,而是可以用自己的方式和风格来实现一套,比如,更轻量化更有适用目的性的方式,oSIP则只起参考作用了。   eXosip_init()是eXosip的初始化函数,我们来看看它的内部实现:   首行是定义的 osip_t *osip,这在oSIP的官方手册里我们看到,所有使用oSIP的程序都要在最开始处声明一个osip_t的指针,并使用 osip_init(&osip)来初始化这个指针,销毁这个资源使用osip_release(osip)即可。   我们可以在代码中看到很多OSIP_TRACE,这是调试输出宏调用了函数osip_trace,可以用ENABLE_TRACE宏来打开调试以方便我们开发调试。   其它就是很多的eXosip_t的全局变量eXosip的一些初始化操作,包括最上面的memset (&eXosip, 0, sizeof (eXosip))完全清空和下面的类似eXosip.user_agent = osip_strdup ("eXosip/" EXOSIP_VERSION)的exosip变量的一些初始值设置,其中有一个eXosip.j_stop_ua = 0应该是一个状态机开关,后面可以看到很多代码检测这个变量来决定是否继续流程处理,默认置成了0表示现在exosip的处理流程是就绪的,即ua是 not stop的。      osip_set_application_context (osip, &eXosip)是比较有意思的,它让下面的eXosip_set_callbacks (osip)给osip设置大量的回调函数时,能让osip能访问到eXosip这个全局变量中设置的大量程序运行时交互的信息,相当于我们在VC下开启一个线程时,给线程传入的一个void指针指向我们的MFC应用程序的当前dialog对象实例,可以用void *osip_get_application_context (osip_t * osip)这个函数来取出指针来使用,不过好象exosip中并没有用到它,可能是留给个人自已扩展的吧:)      还能看到初始化代码前面有一段WIN32平台下的SOCK的初始化代码,可以知道eXosip是用的原生的winsock api函数,也就是我们可能以前学过的用VC和WINAPI写sock程序时(不是MFC),用到的那段SOCK初始代码,还有一段有意思的代码,就是 jpipe()函数,它们返回的是一个管道,一个有2个整型数值的数组(一个进一个出),查看其代码发现,非WIN32平台是直接使用的pipe系统函数,而WIN32下则是用一对TCP的本地SOCK连接来模拟的管道,一个SOCK写一个SOCK读,这段代码是比较有参考价值的:) j = 50; while (aport++ && j-- > 0) {   raddr.sin_port = htons ((short) aport);   if (bind (s, (struct sockaddr *) &raddr, sizeof (raddr)) transactionid)); }   即,只是打印一下调试,并没有完整实现什么功能,我们学习时,完全可以用相同的方法,定义一大堆回调函数,并不忙想怎么完全实现,先都是只打印一下调试信息,看具体的应用逻辑根据抓包测试分析和看调试看程序走到了哪一步,调用了哪一个回调,来明白具体回调函数要实现什么用途,再来实现代码就方便多了,当然,如果看透了RFC文档,应该从字面就能知道各个回调函数的用途了,这是后话,不是谁都能快速完全看懂RFC的,所以我们要参考eXosip:)      我们对其中的重要的回调函数进行逐个的分析:   ---------------------------   osip_set_cb_send_message (osip, &cb_snd_message) SIP消息发送回调函数   这个函数可能是最重要的回调函数之一,消息发送,包括请求消息和回应消息,一般情况下,状态机的状态就是由它控制的,发起一个消息初始化一个状态机,回应一个消息对状态机修改,终结消息发送结束状态机……   看cb_snd_message的函数实现,要以发现,其主要代码是对参数中的要发送的消息osip_message_t * sip进行分析,找出消息要发送的真实char *host,int port的值(这些参数可以省略,但要发送消息肯定需要host和port,所以要从sip中解析),最后根据sip中解析出的传输方式是TCP还是 UDP选择最终进行消息发送处理的函数cb_udp_snd_message,cb_tcp_snd_message处理(它们的参数一致,即本函数只是补全一些省略的参数并对消息进行合法性检查)。   **毕竟eXosip是一个通用的开发库,它考虑了要支持TCP,UDP,TCPs,IPV4,IPV6,WIN32,*nix,WINCE等等多样化的复杂环境,所以,我们可以略过我们暂时不需要的部分,比如,IPV6相关的代码实现等。      由于我们大多数情况下SIP是用的UDP,所以先来看一下cb_udp_snd_message的实现,它从全局变量exosip中获取可用的 sock,并尽最大能力解析出host和port(??难道前面的函数还不够解析彻底??如最终仍无port信息则默认设置为5060),使用 osip_message_to_str (sip, &message, &length)函数将要发送的格式化的SIP消息转换成能用SOCK传输的简单数据并发送即完成消息发送,代码中有许多复杂的环境探测和错误控制等等等等,我们可以暂时不用过多关注,可以继续向下,结尾处有一个keeplive相关代码,从代码字面分析,可能是SIP的Register消息的自动重发相关代码,可以在后面再细化分析。   cb_tcp_snd_essage的函数实现要比上文的udp的实现简单很多,主要是环境探测错误控制方面,因为毕竟tcp是稳定连接的,对比一下代码,可以看到主要流程还是将SIP消息转换后,发送到从SIP消息中解析出的host和port对应的目标。      看完两个函数,可以知道,eXosip需要有两个sock,是一个数组,0是给UDP用的,1是给TCP用的,要用SOCK当然要初始化,就是下文要介绍的eXosip的网络相关的初始化了,上面的exosip_init可以看成是这个开发库的系统初始化吧:)    至些,我们应该知道了oSIP开发的SIP应用程序的消息是从哪里发出的吧,对了,就是从这个回调函数里,所谓万事开头难,就象开发WIN32应用程序时,找到了WIN32程序的main函数入口下面的工作就好办了,下面就都是为一些事件消息开发对应的处理函数而已了:)   osip_set_kill_transaction_callback 事务终结回调函数   对应ICT,IST,NICT,NIST客户/服务器注册/非注册事务状态机的终结,主要是使用osip_remove_transaction (eXosip.j_osip, tr)将当前tr事务删除,再加上一系列的清理工作,其中,NICT即客户端的非Invite事务的清理比较复杂一些,要处理的内容也比较多,可以根据实际应用的情况进行有必要的清理工作:)   cb_transport_error 传输失败处理回调   对应于上面说到的四种事务状态机,如果它们在处理时失败,则在这时进行统一处理。   从代码可知,只是在NOTIFY,SUBSCRIBE,OPTION操作失败才进行处理,其它错误可直接忽略。   osip_set_message_callback 消息发送处理回调   根据type不同,表示不同的消息发送状态   OSIP_XXX_AGAIN 重发相关消息   OSIP_ICT_INVITE_SENT 发起呼叫   OSIP_ICT_ACK_SENT ACK回应   OSIP_NICT_REGISTER_SENT 发起注册   OSIP_NICT_BYE_SENT BYE发出   OSIP_NICT_CANCEL_SENT Cancel发出   OSIP_NICT_INFO_SENT,OSIP_NICT_OPTIONS_SENT,OSIP_NICT_SUBSCRIBE_SENT,OSIP_NICT_NOTIFY_SENT,OSIP_NICT_UNKNOWN_REQUEST_SENT   我们可以看到,eXosip没有对它们作任何处理,我们可以根据自己需要,比如,重发2xx消息前记录一下日志之类的,扩展一下retransmission的处理方式,发起Invite前记录一下通话日志等等。   OSIP_ICT_STATUS_1XX_RECEIVED uac收到1xx消息,一般是表示对端正在处理中,这时,主要是设置一下事务状态机的状态值,并对会话中的osip的一些参数根据返回值进行相应设置,里面有许多条件判断,但我们常用的一般是100,180,183的判断而已,暂时可以忽略里面复杂的判断代码。   OSIP_ICT_STATUS_2XX_RECEIVED uac收到2xx消息,这里主要跟踪一下Register情况下的2xx,表示注册成功,这时会更新一下exosip的注册字段值,以便让eXosip能自动维护uac的注册,BYE的2xx回应是终结消息,Invite的2xx回应,则主要是初始化一下会话相关的数据,表示已成功建立连接。   其它4xx,5xx,6xx则分别是对应的处理,根据实现情况进行概要的查看即可。   report_event (je, sip)是代码中用来进行事件处理的一个函数,跟踪后发现,其最终是使用了我们上文提到的jpipe管道,以便在状态机外实时观测状态机内的处理信息。      OSIP_NIST_STATUS_XXX_SENT即对应于上面的uac的处理,这里是uas的对应的消息处理,相比较于uac简单一点。   前面简单介绍了一下大量的回调函数及它们的概要处理逻辑,可能会比较混乱,暂时不用管它,只需要记得一个大概的形象,知道一个SIP处理程序是通过osip_set_cb_send_message回调函数来实现真实地发送各种SIP消息,并且SIP的标准事务模型是由oSIP实现好了,我们只需要给不同的事务状态设置不同的回调处理函数来处理事务,具体的状态变化和内部逻辑不用管就可以了。   下面来说一下消息处理回调函数用到的SOCK的初始化函数,即我们上面说的除了系统初始化外的网络初始化函数eXosip_listen_addr:   从上文知道了,系统将初始化两个SOCK,一个UDP一个TCP,但查看代码发现还有第三个,TCPs的,但好象还不能实用,现在不管它,代码首先是根据传输是UDP还是TCP来设置对应的数组值,并且如果没有提供IP地址和端口号,系统会自动取出本机网络接口并创建可用的SOCK(http_port 的方式暂不用考虑)。   SOCK初始化后,如何开始SIP事务的呢?看到这个调用eXosip.j_thread = (void *) osip_thread_create (20000, _eXosip_thread, NULL),对的,这里启用了一个线程,即,eXosip是调用oSIP的线程函数(没用系统提供的线程函数,是为了跨平台)进行事务处理的状态机逻辑是在一个线程中处理的,这样就明白了为什么一直没能看到顺序执行下来的程序启动代码了,接下去看,线程实际处理函数是_eXosip_thread,这里面的代码中,我们看到了上文提到的状态机控制开关变量while (eXosip.j_stop_ua == 0),即,当j_stop_ua设置为1时,osip_thread_exit ()结束事务处理即程序终结,再接下去看,_eXosip_execute是最终的处理函数了,而且它在程序未终结情况下是一直逻辑在执行,注意,要启用oSIP的多线程宏OSIP_MT。      看到_eXosip_execute的代码中有很多时间函数和变量,仔细看,除去一些控制代码,主要处理函数是eXosip_read_message (1, lower_tv.tv_sec, lower_tv.tv_usec),即取出消息,1表示只取出一条消息,其代码量非常的大,但同样的,其中也许多的控制代码和错误检测代码,我们在查看时可以暂时忽略掉它们。   eXosip_read_message读取消息时,即没有采用sock的block也没有用非block方式,而是采用了select方式,具体应用可查询fd_set相关文档。   根据jpipe_read (eXosip.j_socketctl, buf2, 499),我们可以估计,buf2中应该是保存的我们的控制管道的数据,具体作用至些还没有表现出来,应该是用来反映一些状态机内部的警示之类的信息,实际的SIP的处理的状态机的数据是存放在buf中,使用_eXosip_recvfrom获取的,获取后sipevent = osip_parse (buf, i)解析,使用osip_find_transaction_and_add_event (eXosip.j_osip, sipevent)来查询事件对应的事务状态机,找到后就如同其注解所说明的,/* handled by oSIP ! */,即我们上文设置的那一大堆回调函数,至此,我们知道了整个SIP应用所处理的大概流程了。   如果没有找到事务状态机呢?直接丢弃吗?不是的,如果这是一个回应消息,但没有事务状态机处理它,那它是一个错误的,要进行清理后才能丢弃,而如果是一个请求,那更不能丢弃了,因为UAS事务状态机要由它来启动创建的(回应消息表示本地发出了请求消息,即UAC行为,事务状态机应是由启动UAC的代码初始化启动的),整个逻辑应该是很简单的,但eXosip的实现代码却非常多,可见其花了非常多的精力在保证会话的稳定性和应付网络复杂情况上,我们可以对其进行大量的精简来构建满足我们需求的代码实现。   先来看错误的回应消息的处理函数eXosip_process_response_out_of_transaction,可以看到其代码就是一大堆的赋值语句,XXX= NULL,即将一大堆的运行时变量清空,再调用osip_event_free清空事件,或者就是一些复杂的情况下,需要通过解析现在的运行时数据,从中分析出“可能”的正在等待回应的对端,并发送相关终结通知消息等等,可以根据实际需要进行简化。   请求事件的处理 eXosip_process_newrequest,首先是对事件进行探测,MSG_IS_INVITE、MSG_IS_ACK、 MSG_IS_REQUEST……,对事件进行所属状态机分类,随后使用_eXosip_transaction_init (&transaction,(osip_fsm_type_t) tx_type,eXosip.j_osip, evt->sip)根据探测结果进行状态机初始化,实际调用的是osip_transaction_init,初始化后即将事件入状态机 osip_transaction_add_event (transaction, evt),由状态机自动处理后调用相应回调函数处理逻辑了。当然,eXosip为方便快速开发SIP终端应用,在下面又添加了许多自动化的处理代码,来和我们在回调函数中设置的处理代码相区分。   线程调用的事件处理函数代码最后是 if (eXosip.keep_alive > 0) {   _eXosip_keep_alive (); }   这段代码印证了上文提到了,keep_alive是用来设置是否自动重新注册,由_eXosip_keep_alive函数来实现自动将eXosip全局变量中保存的注册消息解析后自动根据需要重新向SIP服务器发起Register注册。   同样,因为注册消息发起是UAC的行为,将它放在这里,可以看出来所有事件消息的事务状态机处理都是在这里,只不过这里只创建UAS的事务状态机,UAC的事务状态机的创建则要继续到下面找了,从我们的YouToo软电话代码中可知,发起呼叫和发起注册分别调用了 eXosip_call_send_initial_invite,eXosip_register_send_register这两个函数(另外用到的两个build函数则是分别构建这两个send函数要发送的SIP消息),查看这两个函数可知,UAC的事务处理状态机是在这里进行初始化的。   eXosip_register_send_register中可以看到是_eXosip_transaction_init (&transaction, NICT, eXosip.j_osip, reg)初始化UAC状态机,实际也同UAS是调用的osip_transaction_init函数,同样使用 osip_transaction_add_event (transaction, sipevent)将事件入状态机,状态机随后将自动处理调用相应回调函数处理逻辑了。   另有osip_new_outgoing_sipmessage(reg),表示发送消息,到这里,我们应该可以理解,真实的发送操作,是要到由状态机处理后,调用了消息发送回调函数才真正地将注册消息发送出去的。   同注册消息发送,它是NICT状态机,呼叫消息的发送是ICT,由eXosip_call_send_initial_invite处理,_eXosip_transaction_init (&transaction, ICT, eXosip.j_osip, invite)初始化了状态机,之前还有一个eXosip_call_init是用来初始化eXosip的一些参数的,暂时不管它,同样 osip_new_outgoing_sipmessage (invite)发送呼叫消息,但实际还是要状态机处理后调用消息发送回调函数真实发送呼叫请求函数的,osip_transaction_add_event (transaction, sipevent)则标准地,将事件入状态机,状态机将能处理随后的应用逻辑调用相应的回调函数了。   好了,作了这么多的分析,我们了解了eXosip是怎样调用oSIP来形成被我能方便地再次调用的了,可以看到,为了实现最大限度的跨平台和兼容性,代码中有大量的测试代码,宏定义和错误再处理代码,看起来非常吃力,但了解了其主要的调用框架:   初始化,回调函数设置,UAC和UAS事务处理状态机的启动,事件处理流程等,就可以基本明白了oSIP各个函数的主要作用和正确的用法了,下一步,可以参考eXosip来针对某个应用,去除掉大量暂时用不到的代码,来构建一个简单的SIP软电话和SIP服务器,来进一步深入oSIP学习应用了。  ------------------------------------------------------ [下回预告:完全基于oSIP的软电话实现及oSIP进一步学习] (CopyLeft by Meineson | www.mbstudio.cn,原创文章,欢迎转载,但请保留出处说明!) 附件为原作者提供的
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值