VRPN-main函数分析(2/2).md

VRPN-main函数分析(2/2)

上文分析了main函数的主要流程,这里详细分析几个重要的函数。

VRPN服务链接建立:vrpn_create_server_connection

    connection =
        vrpn_create_server_connection(con_name.str().c_str(), g_inLogName, g_outLogName);

函数原型如下:

vrpn_Connection *
vrpn_create_server_connection(const char *cname,
                              const char *local_in_logfile_name,
                              const char *local_out_logfile_name)
  • 参数:
    • cname:指定建立链接的地址,可以建立本地TCP/UDP类型,或loopback类型,或MPI类型,默认是”:3883”,将绑定本机的3883端口。
    • local_in_logfile_name:日志输入文件名,默认标准输入1。
    • local_out_logfile_name:日志输出文件名,默认标准输出2。
  • 返回值:如果成功,返回一个指向该其子类vrpn_Connection_IP的链接,如果失败,则返回NULL;

这个接口会根据传入的第一个参数来建立链接,启动vrpn服务时若没有带命令参数则使用本机3883端口来监听客户端的连接。

vrpn_Connection *
vrpn_create_server_connection(const char *cname,
                              const char *local_in_logfile_name,
                              const char *local_out_logfile_name)
{
    vrpn_Connection *c = NULL;
    ...(//======这里有一大段根据NIC指定的参数,即cname做字符串解析,省略======)
        else {
            // Find machine name and port number.  Port number returns default
            // if there is not one specified.  If the machine name is zero
            // length
            // (just got :port) then use NULL, which means the default NIC.
            char *machine = vrpn_copy_machine_name(location);
            if (strlen(machine) == 0) {
                delete[] machine;
                machine = NULL;
            }
            unsigned short port =
                static_cast<unsigned short>(vrpn_get_port_number(location));
            //建立VRPN链接
            c = new vrpn_Connection_IP(port, local_in_logfile_name,
                                       local_out_logfile_name, machine);
            printf("%s:machine=%s,port=%d\n",__func__,machine,port);
            if (machine) {
                delete[] machine;
            }
        }
    }
    delete[] location;

    if (!c) { // creation failed
        fprintf(stderr, "vrpn_create_server_connection(): Could not create new "
                        "connection.");
        return NULL;
    }
    //设置d_autoDeleteStatus为true,即当引用计数为0时,vrpn_connect
    //链接将会被delete掉
    c->setAutoDeleteStatus(true); 
    //添加引用计数
    c->addReference(); 

    return c;
}

上面代码中主要是调用vrpn_Connection_IP的构造函数来建立连接,其代码内容如下:

vrpn_Connection_IP::vrpn_Connection_IP(
    unsigned short listen_port_no, const char *local_in_logfile_name,
    const char *local_out_logfile_name, const char *NIC_IPaddress,
    vrpn_Endpoint_IP *(*epa)(vrpn_Connection *, vrpn_int32 *))
    : vrpn_Connection(local_in_logfile_name, local_out_logfile_name, epa)
    , listen_udp_sock(INVALID_SOCKET)
    , listen_tcp_sock(INVALID_SOCKET)
    , d_NIC_IP(NULL)
{
    //printf("%s:listen_port_no:%d,NIC_IPaddress:%s\n",__func__,listen_port_no,NIC_IPaddress);
    // 参数填充,如果启动vrpn没填充-NIC参数,默认NIC为NULL
    if (NIC_IPaddress != NULL) {
        char *IP = new char[strlen(NIC_IPaddress) + 1];
        strcpy(IP, NIC_IPaddress);
        d_NIC_IP = IP;
    }

    //初始化WINDOW SOCKET API,注册处理UDP消息的回调函数,handle_UDP_message
    vrpn_Connection_IP::init();

    //调用WSA来创建、绑定UDP/TCP socket
    listen_udp_sock = ::open_udp_socket(&listen_port_no, NIC_IPaddress);
    listen_tcp_sock = ::open_tcp_socket(&listen_port_no, NIC_IPaddress);
    if ((listen_udp_sock == INVALID_SOCKET) ||
        (listen_tcp_sock == INVALID_SOCKET)) {
        connectionStatus = BROKEN;
        return;
        // fprintf(stderr, "BROKEN -
        // vrpn_Connection_IP::vrpn_Connection_I{.\n");
    }
    else {
        connectionStatus = LISTEN;
// fprintf(stderr, "LISTEN - vrpn_Connection_IP::vrpn_Connection_IP.\n");
#ifdef VERBOSE
        printf("vrpn: Listening for requests on port %d\n", listen_port_no);
#endif
    }

    //使TCP套接字进入监听状态
    if (listen(listen_tcp_sock, 1)) {
        fprintf(stderr, "Couldn't listen on TCP listening socket.\n");
        connectionStatus = BROKEN;
        return;
    }
    //清理UDP套接字的缓冲数据
    flush_udp_socket(listen_udp_sock);
    //将连接添加到vrpn连接管理器当中。
    vrpn_ConnectionManager::instance().addConnection(this, NULL);
}

创建vrpn通用服务对象:vrpn_Generic_Server_Object

    //创建vrpn通用服务对象
    generic_server = new vrpn_Generic_Server_Object(
        connection, config_file_name, verbose, bail_on_error);
    if ((generic_server == NULL) || !generic_server->doing_okay()) {
        fprintf(stderr, "Could not start generic server, exiting\n");
        shutDown();
    }

函数原型如下:

vrpn_Generic_Server_Object::vrpn_Generic_Server_Object(
    vrpn_Connection *connection_to_use, const char *config_file_name,
    bool be_verbose, bool bail_on_open_error)
    : connection(connection_to_use)
    , d_doing_okay(true)
    , verbose(be_verbose)
    , d_bail_on_open_error(bail_on_open_error)
    , _devices(new vrpn_MainloopContainer)
  • 参数
    • connection_to_use:之前建立的vrpn链接,所有基本设备的拷贝构造函数里都会链接到这个参数指定的地方上。
    • config_file_name:配置文件,默认是“vrpn.cfg”
    • be_verbose:是否打印调试信息,默认为false。
    • bail_on_open_error:是否设置异常标志位。
  • 返回值:返回创建的对象。
vrpn_Generic_Server_Object::vrpn_Generic_Server_Object(
    vrpn_Connection *connection_to_use, const char *config_file_name,
    bool be_verbose, bool bail_on_open_error)
    : connection(connection_to_use)
    , d_doing_okay(true)
    , verbose(be_verbose)
    , d_bail_on_open_error(bail_on_open_error)
    , _devices(new vrpn_MainloopContainer)

{
    FILE *config_file;
    verbose = true;

    if (verbose) {
        printf("Reading from config file %s\n", config_file_name);
    }
    //打开配置文件
    if ((config_file = fopen(config_file_name, "r")) == NULL) {
        perror("vrpn_Generic_Server_Object::vrpn_Generic_Server_Object(): "
               "Cannot open config file");
        fprintf(stderr, "  (filename %s)\n", config_file_name);
        d_doing_okay = false;
        return;
    }

    //保存locale变量,在解析配置文件时需要将环境设置为C环境,
    //解析完成后需要。
    std::locale const orig_locale = std::locale();


    //根据配置文件信息,来为这些设备创建程序入口。
    {
        char line[LINESIZE]; // Line read from the input file
        char *pch;
        char scrap[LINESIZE];
        char s1[LINESIZE];
        int retval;

        //逐行解析配置文件
        while (fgets(line, LINESIZE, config_file) != NULL) {

            //设置语言环境为C
            std::locale::global(std::locale("C"));

            //每行字节不超过512 - 1字节
            if (strlen(line) >= LINESIZE - 1) {
                fprintf(stderr, "vrpn_Generic_Server_Object::vrpn_Generic_"
                                "Server_Object(): Line too long in config "
                                "file: %s\n",
                        line);
                if (d_bail_on_open_error) {
                    d_doing_okay = false;
                    return;
                }
                else {
                    continue; // Skip this line
                }
            }

            //每行字符不小于3字节
            if (strlen(line) < 3) {
                continue;
            }
            bool ignore = false;
            for (int j = 0; line[j] != '\0'; j++) {
                if (line[j] == ' ' || line[j] == '\t') {
                    continue;
                }
                if (line[j] == '#') {
                    ignore = true;
                }
                break;
            }
            if (ignore) {
                continue;
            }

            // copy for strtok work
            strncpy(scrap, line, LINESIZE - 1);
            scrap[sizeof(scrap) - 1] = '\0';
    //匹配配置文件中的字符串,若匹配上,则执行安装程序,创建
    //对应的设备加入到容器中
#define VRPN_ISIT(s) !strcmp(pch = strtok(scrap, " \t"), s)
#define VRPN_CHECK(s)                                                          \
    retval = (s)(pch, line, config_file);                                      \
    if (retval && d_bail_on_open_error) {                                      \
        d_doing_okay = false;                                                  \
        return;                                                                \
    }                                                                          \
    else {                                                                     \
        continue;                                                              \
    }

            // This is a hack to get around a hard limit on how many
            // if-then-else
            // clauses you can have in the same function on Microsoft compilers.
            // We break the checking into multiple chunks as needed.
            // If any of the clauses work, we've found it.  Only if the else
            // clause
            // in the batch has not been called do we continue looking.
            bool found_it_yet = true;

            if (VRPN_ISIT("vrpn_raw_SGIBox")) {
                VRPN_CHECK(setup_raw_SGIBox);
            }
            ...(===省略其他设备===)
            else if (VRPN_ISIT("vrpn_Mouse")) {
                VRPN_CHECK(setup_Mouse);
            }
            ...(===省略其他设备===)
            else {                         // Never heard of it
                sscanf(line, "%511s", s1); // Find out the class name
                fprintf(stderr, "vrpn_server: Unknown Device: %s\n", s1);
                if (d_bail_on_open_error) {
                    d_doing_okay = false;
                    return;
                }
                else {
                    continue; // Skip this line
                }
            }
            }
        }
    }

#undef VRPN_ISIT
#undef VRPN_CHECK

    // 恢复环境变量local
    std::locale::global(orig_locale);

    //关闭配置文件
    fclose(config_file);

#ifdef SGI_BDBOX
    fprintf(stderr, "sgibox: %p\n", vrpn_special_sgibox);
#endif
}

上面代码中会根据配置文件来安装对应的程序,创建对应的设备对象,如之前demo”VRPN-体验”中vrpn_mouse:

int vrpn_Generic_Server_Object::setup_Mouse(char *&pch, char *line,
                                            FILE * /*config_file*/)
{
    char s2[LINESIZE];
    printf("%s:pch:%s\n",__func__,pch);
    VRPN_CONFIG_NEXT();

    //获取参数,例如vrpn.cfg文件中使用的是"vrpn_Mouse Mouse0",s2="Mouse0"
    if (sscanf(pch, "%511s", s2) != 1) {
        fprintf(stderr, "Bad vrpn_Mouse line: %s\n", line);
        return -1;
    }

    printf("%s:pch:%s,s2:%s\n",__func__,pch,s2);

    if (verbose) {
        printf("Opening vrpn_Mouse: %s\n", s2);
    }

    try {
        //创建以s2命名的对象,默认链接到vrpn上
        //加入到设备容器列表
        _devices->add(new vrpn_Mouse(s2, connection));
    }
    catch (...) {
        fprintf(stderr, "could not create vrpn_Mouse\n");
#ifdef linux
        fprintf(stderr, "- Is the GPM server running?\n");
        fprintf(stderr,
                "- Are you running on a linux console (not an xterm)?\n");
#endif
        return -1;
    }
    return 0;
}

mainloop

此函数非常重要,即调用设备驱动轮询状态并上报,同时服务端的数据收发也依赖此函数,在vrpn中包含通用设备对象的mainloop,以及主链接mainloop,或则其他服务的mainloop(可选)。
- 通用设备对象mainloop

    generic_server->mainloop();

这个函数设计是比较巧妙的,用模版,vector方式来实现,将之前添加进来的对象,全部执行他们对应的虚函数:mainloop。例如:vrpn_Mouse。

int vrpn_Generic_Server_Object::setup_Mouse(char *&pch, char *line,
                                            FILE * /*config_file*/)
{
    ...(省略无关代码)
   //创建以s2命名的对象,默认链接到vrpn上
   //加入到设备容器列表
   _devices->add(new vrpn_Mouse(s2, connection));
   ...(省略无关代码)

在vrpn_Generic_Server_Object接口中,创建并将Mouse设备放入_devices容器中,在调用generic_server->mainloop();时,会执行到_device容器中的mainloop:

inline void vrpn_MainloopContainer::mainloop()
{
    const size_t n = _vrpn.size();
    for (size_t i = 0; i < n; ++i) {
        _vrpn[i]->mainloop();
    }
}

最终调用mouse设备的mainloop函数:
void vrpn_Mouse::mainloop()

{
    //上报鼠标事件,包括按键和坐标
    get_report();
    //处理server端的ping请求
    server_mainloop();
}
  • 主链接mainloop
    这里涉及到一个问题”父类指针指向子类对象 且父类中定义了虚函数 子对象重定义了此函数 那么该对象应该调用子类对象的虚函数还是父类的虚函数”
    例如:vrpn.c中的connection->mainloop函数。
    while (!done) {
        // Let the generic object server do its thing.
        if (generic_server) {
            generic_server->mainloop();
        }

        // Send and receive all messages.
        connection->mainloop();

其中connection是指向vrpn_Connection类的指针,调用vrpn_create_server_connection时,指向了vrpn_Connection_IP类,而vrpn_Connection_IP类是vrpn_Connection类的子类。

...
vrpn_Connection *connection;//connection是指向vrpn_Connection的指针
...
c = new vrpn_Connection_IP(port, local_in_logfile_name,
                                       local_out_logfile_name, machine);    
...
    connection =
        vrpn_create_server_connection(con_name.str().c_str(), g_inLogName, g_outLogName);
...

所以connection->mainloop实际上是调用的这个函数:int vrpn_Connection_IP::mainloop(const struct timeval *pTimeout)。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据你提供的编译错误信息,出现了多个链接错误和未定义引用。 首先,错误信息指出在`ModbusConnection.cpp`文件中的`cp::ModbusConnection`构造函数和析构函数调用了基类`esf::Connection`的析构函数,但是找不到该析构函数的定义。 这个错误通常是由于链接阶段找不到基类成员函数的定义引起的。可能的原因有: 1. 基类`esf::Connection`的源文件或库文件没有正确地被编译和链接到最终的可执行文件中。请确保基类的实现文件或库文件被正确包含在编译和链接命令中。 2. 基类`esf::Connection`的析构函数没有被正确地定义和实现。请确保基类的析构函数在定义和实现时没有出错,并且在链接阶段能够找到。 3. 如果基类是一个纯虚基类(包含纯虚函数),则需要确保派生类实现了基类的纯虚函数。 另外,错误信息中还指出了对基类`esf::Connection`的虚函数表(vtable)和类型信息(typeinfo)的引用未定义。这可能是因为基类的析构函数没有被正确地定义和实现,导致编译器无法生成虚函数表和类型信息。 要解决这些问题,你可以检查以下几点: 1. 确保基类`esf::Connection`的头文件被正确地包含在相关源文件中。 2. 确保基类`esf::Connection`的实现文件被正确地编译和链接到最终的可执行文件中。 3. 检查基类`esf::Connection`的析构函数定义和实现是否正确,确保在链接阶段能够找到。 4. 如果基类是一个纯虚基类,确保派生类实现了基类的纯虚函数。 如果问题仍然存在,可能需要进一步检查编译和链接命令,以及相关的代码和上下文信息,以确定具体的解决方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值