26、大型程序组织与网络协议应用示例

大型程序组织与网络协议应用示例

1. 程序结构概述

在开发大型程序时,将代码按模块划分是常见且有效的做法。不同的程序员小组负责不同的代码文件,这样能使程序的实现、测试、控制和管理更加高效。以下是程序结构组织的关键步骤:
1. 模块划分 :将应用需求分解为独立的模块,便于项目管理和理解程序结构。
2. 定义模块交互 :明确不同模块之间的交互方式、依赖关系以及通信机制,例如回调机制。
3. 模块服务映射 :将每个模块的服务映射到编程概念,如函数和类,这些都定义在相应的代码文件中。
4. 详细模块描述 :为每个模块提供详细的用途、服务和操作方式描述,方便维护和升级。
5. 代码文件与编译 :每个模块由多个代码文件组成,每个文件实现模块的部分服务。所有文件单独编译,由链接器将生成的代码链接起来。
6. 头文件选择 :选择可被其他模块共享的函数,将其声明放在头文件中,作为模块的接口文件。

1.1 程序元素放置规则

元素类型 放置位置
命名空间、内联函数定义、常量、枚举、类型同义词、宏、模板、类型和函数声明 头文件(使用 #ifndef 防止多重包含)
变量和函数定义 代码文件
extern 变量 可放在头文件,但建议放在使用它们的代码文件中,以明确使用位置

1.2 头文件组织方式

1.2.1 单头文件方式

将所有声明放在一个头文件中,定义放在使用它们的代码文件中。这种方式适用于小型程序,方便查找和编辑声明,但对于大型程序效率较低,因为头文件内容更改时,所有代码文件都需要重新编译,且多人修改易引入错误,文件过大也不利于阅读和维护。

1.2.2 多头文件方式

每个代码文件有自己的头文件,便于快速了解代码文件实现的程序部分和提供的服务。在大型程序中,这种方式有助于更好地控制、理解和维护程序。如果一个头文件发生变化,只需重新编译包含它的代码文件。公共信息可放在一个头文件中,方便修改。

2. 网络协议应用示例

2.1 网络协议简介

网络协议是使系统能够通信的一组规则,可通过硬件、软件或两者结合实现。其操作通常基于国际组织定义的标准,例如著名的互联网协议(IP)由 IETF 组织在 RFC 791 标准中规定。协议标准规定了系统之间交换的信息、消息格式、意义和处理程序,通常使用有限状态机来指导协议的实现。

2.2 待实现协议描述

本次实现的是 ITU - T Q.931 信令协议的简化版本,用于 ISDN 网络中呼叫的建立、维护和释放。以下是协议的主要流程:
1. 呼叫建立 :主叫用户拨号,创建 SETUP 消息,通过 ISDN 网络路由到被叫用户。如果被叫用户接受呼叫(如拿起电话),发送 CONNECT 消息回主叫用户,被叫用户回复 CONNECT_ACK,呼叫建立完成,预留电路进行数据传输。
2. 呼叫释放 :任何一方关闭连接(如挂断电话),发送 RELEASE 消息给另一方,释放电路。

2.3 应用程序设计

应用程序提供一个菜单选项,模拟双方之间的消息交换以建立和释放呼叫。每个消息由唯一代码标识,并包含特定信息。为了简化,假设每个消息只包含代码和呼叫参考值(CRV)。

2.4 模块划分与文件结构

程序的功能被划分为多个模块,每个源文件代表一个模块:
- prtcl.cpp :实现与上层和下层的接口,解析并转发接收到的消息到 fsm.cpp。
- fsm.cpp :实现协议的有限状态机,根据接收到的消息改变呼叫状态。
- send.cpp :创建消息并发送到下层,同时模拟被叫方的响应。

2.5 头文件 general.h

/* general.h */
#ifndef general_h
#define general_h

#include <iostream>
#include <vector>
#include <map>
using std::cout;
using std::cin;
using std::map;
using std::vector;

#define LOOPBACK_MODE

// Message codes according to the ITU-T standard.
const int SETUP = 0x5;
const int CONNECT = 0x7;
const int RELEASE = 0x4D;
const int RELEASE_COMPLETE = 0x5A;

struct Msg /* This structure is used as an argument in function calls. */
{
       int code;
       int CRV;
       int call_index;
       /* Normally, the structure contains many more members that correspond 
to the information elements conveyed by each message. */
};

class Call // Each call is represented by a Call object.
{
private:
       int CRV; // It holds the identifier of the call.
       int state; // It holds the state of the call.
       /* Normally, the class contains many more members related to the 
call, such as the phone numbers of the two parties and the duration of the 
call. */
public:
       Call(int crv);
       int fsm(Msg *p);
       int send_msg(Msg *p);
};

class Prtcl
{
private:
       vector<Call> calls; // We use a vector to store the calls.
       map<int, int> map_CRV; /* Mapping of the key, which is the CRV value 
of each call with the position of the object in the vector. Thus, we find the 
position of the object that corresponds to a CRV value. */
public:
       int handle_up_msg(Msg *p); /* It is used for communication with the 
upper layer. */
       int handle_low_msg(int data[]); /* It is used for communication with 
the lower layer. */
       int find_call(int CRV) const;
       void show() const {cout << "\nCalls: " << calls.size() << '\n';} /* As 
you see we can define the function in the header file. It displays the number 
of the established calls. */
};

#endif

2.6 prtcl.cpp 文件

#include "general.h"
#include "fsm.h"

Prtcl prtcl; /* I declare it as global, to show you how to use it as extern 
in other files. */

int main()
{
       int CRV, index, num, sel;
       Msg msg;

       CRV = 100; // Just, an initial value.
       while(1)
       {
              cout << "\nOperations\n";
              cout << "------------\n";

              cout << "1. Establish Call\n";
              cout << "2. Release Call\n";
              cout << "3. Show Calls\n";
              cout << "4. Exit\n";

              cout << "\nEnter choice: ";
              cin >> sel;

              switch(sel)
              {
                     case 1:
                            msg.code = SETUP;
                            msg.CRV = CRV; // Call identifier.
                            CRV++; /* For simplicity, the CRV of each call is 
incremented by one. */
                            prtcl.handle_up_msg(&msg); /* In a real 
application, the user enters information related to the call, such as the 
phone number of the called user. */
                     break;

                     case 2:
                            cout << "Enter CRV of the call to be released: ";
                            cin >> num;
                            index = prtcl.find_call(num); /* We find the 
call, that is, the object, with the same CRV as the one entered by the 
user. */
                            if(index != -1)
                            {
                                   msg.code = RELEASE;
                                   msg.CRV = num;
                                   msg.call_index = index;
                                   prtcl.handle_up_msg(&msg); /* Activate the 
call release procedure. */
                            }
                            else
                                   cout << "Non existing call\n";
                     break;

                     case 3:
                            prtcl.show();
                     break;

                     case 4:
                     return 0;

                     default:
                            cout << "\nWrong choice\n";
                     break;
              }
       }
       return 0;
}

int Prtcl::handle_up_msg(Msg *p) /* Handle the message coming from the upper 
layer. */
{
       int index;

       if(p->code == SETUP)
       {
              Call c(p->CRV); // Create an object to manage the call.
              calls.push_back(c); // Store the object.
              index = calls.size()-1;
              map_CRV[p->CRV] = index; /* Map the CRV to the position of the 
object in the vector. */
              calls[index].fsm(p); /* Forward the message to the object that 
manages the call. */
       }
       else if(p->code == RELEASE)
       {
              index = p->call_index;
              calls[index].fsm(p);
       }
       else
       {
              cout << "Upper message_" << p->code << " not supported\n";
              return -1;
       }
       return 0;
}

int Prtcl::handle_low_msg(int data[]) /* Handle the message coming from the 
lower layer. */
{
       int ret, index;
       Msg msg; /* The information coming from the lower layer is stored in 
the Msg members. */
       index = find_call(data[0]); /* We find the call, that is, the object 
which has the same CRV with the CRV contained in the message. */
       if(index == -1)
       {
              cout << "Non existing call\n";
              return -1;
       }
       msg.code = data[1];
       switch(msg.code)
       {
              case CONNECT:
                     calls[index].fsm(&msg); /* Forward the message to the 
object that manages the call. */
              break;

              case RELEASE_COMPLETE:
                     ret = calls[index].fsm(&msg); /* Here is an example to 
check the return value. The respective object is deleted. */
                     if(ret == CLEAR_CALL)
                     {
                            calls.erase(calls.begin()+index);
                            map_CRV.erase(data[0]);
                     }
              break;

              default:
                     cout << "Received message_" << msg.code << " not 
supported\n";
              return -1;
       }
       return 0;
}

int Prtcl::find_call(int CRV) const /* To find the object we use map_CRV 
which maps the CRV with its position in the vector. */
{
       auto it = map_CRV.find(CRV);
       if(it != map_CRV.end())
              return it->second;
       return -1;
}

2.7 协议状态机流程

graph TD;
    A[IDLE] -->|SETUP| B[CALL_INIT];
    B -->|CONNECT| C[ACTIVE];
    C -->|RELEASE| D[RELEASE_INIT];
    D -->|RELEASE_COMPLETE| E[CALL_RELEASED];

在后续内容中,我们将继续介绍 fsm.cpp、fsm.h 和 send.cpp 文件的内容,以及网络协议实现的测试建议和多文件程序的创建方法。

3. fsm.cpp 和 fsm.h 文件

3.1 fsm.cpp 文件

fsm.cpp 文件实现了协议的状态机。根据接收到的消息,呼叫状态会发生相应的变化。以下是 fsm.cpp 的代码:

#include "general.h"
#include "fsm.h"

Call::Call(int crv)
{
       CRV = crv;
       state = IDLE;
}

int Call::fsm(Msg *p)
{
       switch(state)
       {
              case IDLE:
                     if(p->code == SETUP)
                     {
                            state = CALL_INIT;
                            send_msg(p);
                     }
                     else
                     {
                            cout << "Unexpected message_" << p->code << " in 
state_" << state << '\n';
                            return UNKNOWN_MSG;
                     }
              break;

              case CALL_INIT:
                     if(p->code == CONNECT)
                     {
                            cout << "\nCall with CRV " << CRV << " is 
established\n";
                            state = ACTIVE;
                     }
              break;

              case ACTIVE:
                     if(p->code == RELEASE)
                     {
                            state = RELEASE_INIT;
                            send_msg(p);
                     }
              break;

              case RELEASE_INIT:
                     if(p->code == RELEASE_COMPLETE)
                     {
                            cout << "\nCall with CRV " << CRV << " is 
released\n";
                            return CLEAR_CALL;
                     }
              break;

              default:
                     cout << "Not supported state\n";
              return UNKNOWN_STATE;
       }
       return 0;
}

3.2 fsm.h 文件

fsm.h 文件定义了呼叫状态的枚举和返回值常量。以下是 fsm.h 的代码:

/* fsm.h */
#ifndef fsm_h
#define fsm_h

// Call states.
enum fsm_states {IDLE = 1, CALL_INIT, ACTIVE, RELEASE_INIT};

// Return values.
const int CLEAR_CALL = 1;
const int UNKNOWN_MSG = 2;
const int UNKNOWN_STATE = 3;

#endif

3.3 状态机状态说明

状态名称 状态说明
IDLE 初始状态,等待 SETUP 消息
CALL_INIT 呼叫初始化状态,等待 CONNECT 消息
ACTIVE 呼叫已建立状态,可进行通信
RELEASE_INIT 呼叫释放初始化状态,等待 RELEASE_COMPLETE 消息

4. send.cpp 文件

send.cpp 文件负责创建消息并将其发送到下层。为了简化,假设每个消息只包含代码和 CRV。以下是 send.cpp 的代码:

#include "general.h"

extern Prtcl prtcl;

int Call::send_msg(Msg *p)
{
       int data[2]; /* Since the message contains only its code and the CRV, 
its size is set to 2. The exercise C.11.22 shows an example of how to create 
a message. */
       data[0] = CRV;
       data[1] = p->code;

       switch(data[1])
       {
              case SETUP:
              break;

              case RELEASE:
              break;

              default:
                     cout << "Unexpected message_" << data[1] << " for 
transmission\n";
              return -1;
       }
#ifndef LOOPBACK_MODE
       /* In a real application, here is the place where we typically call a 
function of the lower layer (e.g., a function of the driver) with arguments 
the data array and its length for transmission in the network. */
       cout << "Only loopback tests are supported\n";
#else
       switch(data[1])
       {
              case SETUP:
                     data[0] = CRV;
                     data[1] = CONNECT; /* We simulate the scenario that the 
other party accepted the call and the message CONNECT is received to complete 
the call establishment. */
              break;

              case RELEASE:
                     data[0] = CRV;
                     data[1] = RELEASE_COMPLETE; /* We simulate the scenario 
that the other party closed the connection and the message RELEASE_COMPLETE 
is received to complete the release of the call. */
              break;
       }
       prtcl.handle_low_msg(data);
#endif
       return 0;
}

4.1 消息发送流程

  1. 根据传入的消息,将 CRV 和消息代码存储在 data 数组中。
  2. 检查消息代码,如果是 SETUP 或 RELEASE 消息,继续下一步;否则,输出错误信息并返回 -1。
  3. 如果未定义 LOOPBACK_MODE,输出仅支持回环测试的信息;否则,模拟对方的响应消息,并调用 prtcl.handle_low_msg(data) 处理响应消息。

5. 网络协议实现的测试建议

在实现网络协议时,测试是确保代码正确性的重要步骤。以下是一些测试建议:
1. 寻找模拟应用 :如果可以找到市场上模拟对方的应用程序,使用它来测试代码。
2. 回环测试 :如果找不到模拟应用,可以定义 LOOPBACK_MODE 宏,并在同一应用程序中添加代码来模拟对方。这样可以在本地测试大部分代码,修复错误后再部署到客户环境中。
3. 逐步添加功能 :编写模拟代码时,应逐步添加功能,先支持基本测试,再逐渐增加复杂度。

6. 多文件程序的创建方法

创建由多个文件组成的程序,取决于所使用的编译器。以 Microsoft Visual Studio 的集成开发环境(IDE)为例:
1. 创建项目 :选择类似 File->New->Project 的菜单选项来创建程序。
2. 添加文件 :使用 Project->Add_New_Item 选项添加源文件。
3. 编译文件 :可以单独编译源文件,也可以使用 Build 命令一起编译所有文件。

6.1 程序运行示例

以下是程序的运行示例:

Enter choice: 1
Call with CRV 100 is established
Enter choice: 1
Call with CRV 101 is established
Enter choice: 2
Enter CRV of the call to be released: 100
Call with CRV 100 is released

7. 总结

通过以上内容,我们了解了大型程序的组织方式,包括模块划分、头文件的使用等。同时,通过一个简化的网络协议实现示例,展示了如何使用面向对象的设计方法来开发程序。在实际开发中,合理的程序结构和有效的测试方法对于确保程序的正确性和可维护性至关重要。

7.1 关键知识点总结

知识点 描述
程序结构 将程序按模块划分,明确模块交互和服务映射,合理放置程序元素
网络协议 一组使系统能够通信的规则,可基于有限状态机实现
状态机 根据接收到的消息改变呼叫状态,实现协议的逻辑
消息发送 创建消息并发送到下层,可模拟对方响应进行测试
多文件程序 使用编译器的 IDE 创建项目和添加文件,进行编译
演示了为无线无人机电池充电设计的感应电力传输(IPT)系统 Dynamic Wireless Charging for (UAV) using Inductive Coupling 模拟了为无人机(UAV)量身定制的无线电力传输(WPT)系统。该模型演示了直流电到高频交流电的转换,通过磁共振在气隙中无线传输能量,以及整流回直流电用于电池充电。 系统拓扑包括: 输入级:使用IGBT/二极管开关连接到全桥逆变器的直流电压源(12V)。 开关控制:脉冲发生器以85 kHz(周期:1/85000秒)的开关频率运行,这是SAE J2954无线充电标准的标准频率。 耦合级:使用互感和线性变压器块来模拟具有特定耦合系数的发射(Tx)和接收(Rx)线圈。 补偿:包括串联RLC分支,用于模拟谐振补偿网络(将线圈调谐到谐振频率)。 输出级:桥式整流器(基于二极管),用于将高频交流电转换回直流电,以供负载使用。 仪器:使用示波器块进行全面的电压和电流测量,用于分析输入/输出波形和效率。 模拟详细信息: 求解器:离散Tustin/向后Euler(通过powergui)。 采样时间:50e-6秒。 4.主要特点 高频逆变:模拟85 kHz下IGBT的开关瞬态。 磁耦合:模拟无人机着陆垫和机载接收器之间的松耦合行为。 Power GUI集成:用于专用电力系统离散仿真的设置。 波形分析:预配置的范围,用于查看逆变器输出电压、初级/次级电流和整流直流电压。 5.安装使用 确保您已安装MATLAB和Simulink。 所需工具箱:必须安装Simscape Electrical(以前称为SimPowerSystems)工具箱才能运行sps_lib块。 打开文件并运行模拟。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值