C/C++ 服务器程序(从入门到精通)

2023-05-16

Windows 服务被设计用于需要在后台运行的应用程序以及实现没有用户交互的任务。为了学习这种控制台应用程序的基础知识,C(不是C++)是最佳选择。本文将建立并实现一个简单的服务程序,其功能是查询系统中可用物理内存数量,然后将结果写入一个文本文件。最后,你可以用所学知识编写自己的 Windows 服务。

当初我写第一个 NT 服务时,我到 MSDN 上找例子。在那里我找到了一篇 Nigel Thompson 写的文章:“Creating a Simple Win32 Service in C++”,这篇文章附带一个 C++ 例子。虽然这篇文章很好地解释了服务的开发过程,但是,我仍然感觉缺少我需要的重要信息。我想理解通过什么框架,调用什么函数,以及何时调用,但 C++ 在这方面没有让我轻松多少。面向对象的方法固然方便,但由于用类对底层 Win32 函数调用进行了封装,它不利于学习服务程序的基本知识。这就是为什么我觉得 C 更加适合于编写初级服务程序或者实现简单后台任务的服务。在你对服务程序有了充分透彻的理解之后,用 C++ 编写才能游刃有余。当我离开原来的工作岗位,不得不向另一个人转移我的知识的时候,利用我用 C 所写的例子就非常容易解释 NT 服务之所以然。

服务是一个运行在后台并实现勿需用户交互的任务的控制台程序。Windows NT/2000/XP 操作系统提供为服务程序提供专门的支持。人们可以用服务控制面板来配置安装好的服务程序,也就是 Windows 2000/XP 控制面板|管理工具中的“服务”(或在“开始”|“运行”对话框中输入 services.msc /s――译者注)。可以将服务配置成操作系统启动时自动启动,这样你就不必每次再重启系统后还要手动启动服务。

本文将首先解释如何创建一个定期查询可用物理内存并将结果写入某个文本文件的服务。然后指导你完成生成,安装和实现服务的整个过程。


第一步:主函数和全局定义

首先,包含所需的头文件。例子要调用 Win32 函数(windows.h)和磁盘文件写入(stdio.h):

#include < windows.h >
#include < stdio.h >

接着,定义两个常量:

#define SLEEP_TIME 5000
#define LOGFILE "C:\\MyServices\\memstatus.txt"

SLEEP_TIME 指定两次连续查询可用内存之间的毫秒间隔。在第二步中编写服务工作循环的时候要使用该常量。

LOGFILE 定义日志文件的路径,你将会用 WriteToLog 函数将内存查询的结果输出到该文件,WriteToLog 函数定义如下: 
复制代码

int WriteToLog(char* str)
{
    FILE* log;
    log = fopen(LOGFILE, "a+");
    if (log == NULL)
    return -1;
    fprintf(log, "%s\n", str);
    fclose(log);
    return 0;
}
  •  

声明几个全局变量,以便在程序的多个函数之间共享它们值。此外,做一个函数的前向定义:

SERVICE_STATUS ServiceStatus;
SERVICE_STATUS_HANDLE hStatus;
void ServiceMain(int argc, char** argv);
void ControlHandler(DWORD request);
int InitService();

现在,准备工作已经就绪,你可以开始编码了。服务程序控制台程序的一个子集。因此,开始你可以定义一个 main 函数,它是程序的入口点。对于服务程序来说,main 的代码令人惊讶地简短,因为它只创建分派表并启动控制分派机。 

void main()
{
    SERVICE_TABLE_ENTRY ServiceTable[2];
    ServiceTable[0].lpServiceName = "MemoryStatus";
    ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;

    ServiceTable[1].lpServiceName = NULL;
    ServiceTable[1].lpServiceProc = NULL;

// 启动服务的控制分派机线程
    StartServiceCtrlDispatcher(ServiceTable);
}

一个程序可能包含若干个服务。每一个服务都必须列于专门的分派表中(为此该程序定义了一个 ServiceTable 结构数组)。这个表中的每一项都要在 SERVICE_TABLE_ENTRY 结构之中。它有两个域:

lpServiceName: 指向表示服务名称字符串的指针;当定义了多个服务时,那么这个域必须指定;

lpServiceProc: 指向服务主函数的指针(服务入口点);

分派表的最后一项必须是服务名和服务主函数域的 NULL 指针,文本例子程序中只宿主一个服务,所以服务名的定义是可选的。

服务控制管理器(SCM:Services Control Manager)是一个管理系统所有服务的进程。当 SCM 启动某个服务时,它等待某个进程的主线程来调用 StartServiceCtrlDispatcher 函数。将分派表传递给 StartServiceCtrlDispatcher。这将把调用进程的主线程转换为控制分派器。该分派器启动一个新线程,该线程运行分派表中每个服务的 ServiceMain 函数(本文例子中只有一个服务)分派器还监视程序中所有服务的执行情况。然后分派器将控制请求从 SCM 传给服务。

注意:如果 StartServiceCtrlDispatcher 函数30秒没有被调用,便会报错,为了避免这种情况,我们必须在 ServiceMain 函数中(参见本文例子)或在非主函数的单独线程中初始化服务分派表。本文所描述的服务不需要防范这样的情况。

分派表中所有的服务执行完之后(例如,用户通过“服务”控制面板程序停止它们),或者发生错误时。StartServiceCtrlDispatcher 调用返回。然后主进程终止。


第二步:ServiceMain 函数

VOID ServiceMain(
    DWORD   dwNumServicesArgs,
    LPWSTR  *lpServiceArgVectors)
{
    //服务类型,创建win32服务
    ServiceStatus.dwServiceType = SERVICE_WIN32;
    //服务当前状态,在这里的时候初始化未完成,所以pending
    ServiceStatus.dwCurrentState = SERVICE_START_PENDING;
    //通知SCM服务接收哪个域,处理控制请求后面处理
    ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN
        | SERVICE_ACCEPT_STOP;//本例只接受系统关机和停止服务2种控制命令
    //终止服务并报告退出细节,初始化时不退出,将他们赋值为0
    ServiceStatus.dwWin32ExitCode =  0;
    ServiceStatus.dwServiceSpecificExitCode = 0;
    //表示初始化某个服务进程要30s以上,因为我们的实例初始化过程很短,设置为0
    ServiceStatus.dwCheckPoint = 0;
    ServiceStatus.dwWaitHint = 0;
    //为服务注册控制处理器
    hStatus = RegisterServiceCtrlHandler("Sunfeihu_MemoryStatus",Ctrlhandler);//服务名,指向controlhandlefunction指针
    if (!hStatus)
    {
        WriteToLog("RegisterServerCtrlhandler Failed");
        return;
    }
    WriteToLog("RegisterServiceCtrlHandler Sucess");
    //
    if(InitService() == -1)
    {
        //初始化失败,可能写日志文件失败
        ServiceStatus.dwCurrentState = SERVICE_STOPPED;
        ServiceStatus.dwWin32ExitCode = -1;
        SetServiceStatus(hStatus,&ServiceStatus);
        //退出serviceMain
        return;
    }
    //
    //向sCM报告运行服务状态
    ServiceStatus.dwCurrentState = SERVICE_RUNNING;
    BOOL bstatus = SetServiceStatus(hStatus,&ServiceStatus);
    if(!bstatus)
    {
        DWORD  derror = GetLastError();
        char str[100]={0};
        sprintf_s(str,100,"SetServiceStatus Failed,The error value is %d",derror);
        WriteToLog(str);
        return;
    }
    //下面就开始任务循环,添加自己希望的服务
    //这里是每隔5s查询一次可用物理内存并写入日志
    MEMORYSTATUS memstatus;
    brun = true;
    while (brun)
    {
        char str[100]={0};
        GlobalMemoryStatus(&memstatus);
        int iavailmb = memstatus.dwAvailPhys/1024/1024;
        sprintf_s(str,100,"available memory is %d MB",iavailmb);
        WriteToLog(str);
        Sleep(SLEEP_TIME);
    }
    WriteToLog("Service Stopped");
}

Listing 1 展示了 ServiceMain 的代码。该函数是服务的入口点。它运行在一个单独的线程当中,这个线程是由控制分派器创建的。ServiceMain 应该尽可能早早为服务注册控制处理器。这要通过调用 RegisterServiceCtrlHadler 函数来实现。你要将两个参数传递给此函数:服务名和指向 ControlHandlerfunction 的指针。

它指示控制分派器调用 ControlHandler 函数处理 SCM 控制请求。注册完控制处理器之后,获得状态句柄(hStatus)。通过调用 SetServiceStatus 函数,用 hStatus 向 SCM 报告服务的状态。 
Listing 1 展示了如何指定服务特征和其当前状态来初始化 ServiceStatus 结构,ServiceStatus 结构的每个域都有其用途:

dwServiceType:指示服务类型,创建 Win32 服务。赋值 SERVICE_WIN32;

dwCurrentState:指定服务的当前状态。因为服务的初始化在这里没有完成,所以这里的状态为 SERVICE_START_PENDING;

dwControlsAccepted:这个域通知 SCM 服务接受哪个域。本文例子是允许 STOP 和 SHUTDOWN 请求。处理控制请求将在第三步讨论;

dwWin32ExitCode 和 dwServiceSpecificExitCode:这两个域在你终止服务并报告退出细节时很有用。初始化服务时并不退出,因此,它们的值为 0;

dwCheckPoint 和 dwWaitHint:这两个域表示初始化某个服务进程时要30秒以上。本文例子服务的初始化过程很短,所以这两个域的值都为 0。

调用 SetServiceStatus 函数向 SCM 报告服务的状态时。要提供 hStatus 句柄和 ServiceStatus 结构。注意 ServiceStatus 一个全局变量,所以你可以跨多个函数使用它。ServiceMain 函数中,你给结构的几个域赋值,它们在服务运行的整个过程中都保持不变,比如:dwServiceType。

在报告了服务状态之后,你可以调用 InitService 函数来完成初始化。这个函数只是添加一个说明性字符串到日志文件。如下面代码所示: 
复制代码

// 服务初始化

int InitService()
{
   int result;
   result = WriteToLog("Monitoring started.");
   return(result);
}

复制代码

在 ServiceMain 中,检查 InitService 函数的返回值。如果初始化有错(因为有可能写日志文件失败),则将服务状态置为终止并退出 ServiceMain: 
复制代码

error = InitService();
if (error)
{
// 初始化失败,终止服务
   ServiceStatus.dwCurrentState = SERVICE_STOPPED;
   ServiceStatus.dwWin32ExitCode = -1;
   SetServiceStatus(hStatus, &ServiceStatus);
   // 退出 ServiceMain
   return;
}

复制代码

如果初始化成功,则向 SCM 报告状态: 
1.// 向 SCM 报告运行状态 
2.ServiceStatus.dwCurrentState = SERVICE_RUNNING; 
3.SetServiceStatus (hStatus, &ServiceStatus);

接着,启动工作循环。每五秒钟查询一个可用物理内存并将结果写入日志文件。

如 Listing 1 所示,循环一直到服务的状态为 SERVICE_RUNNING 或日志文件写入出错为止。状态可能在 ControlHandler 函数响应 SCM 控制请求时修改。


第三步:处理控制请求

在第二步中,你用 ServiceMain 函数注册了控制处理器函数。控制处理器与处理各种 Windows 消息的窗口回调函数非常类似。它检查 SCM 发送了什么请求并采取相应行动。

每次你调用 SetServiceStatus 函数的时候,必须指定服务接收 STOP 和 SHUTDOWN 请求。Listing 2 示范了如何在 ControlHandler 函数中处理它们。

STOP 请求是 SCM 终止服务的时候发送的。例如,如果用户在“服务”控制面板中手动终止服务。SHUTDOWN 请求是关闭机器时,由 SCM 发送给所有运行中服务的请求。两种情况的处理方式相同:

写日志文件,监视停止;

向 SCM 报告 SERVICE_STOPPED 状态;

由于 ServiceStatus 结构对于整个程序而言为全局量,ServiceStatus 中的工作循环在当前状态改变或服务终止后停止。其它的控制请求如:PAUSE 和 CONTINUE 在本文的例子没有处理。

控制处理器函数必须报告服务状态,即便 SCM 每次发送控制请求的时候状态保持相同。因此,不管响应什么请求,都要调用 SetServiceStatus。


第四步:安装和配置服务

程序编好了,将之编译成 exe 文件。本文例子创建的文件叫 MemoryStatus.exe,将它拷贝到 C:\MyServices 文件夹。为了在机器上安装这个服务,需要用 SC.EXE 可执行文件,它是 Win32 Platform SDK 中附带的一个工具。(译者注:Visaul Studio .NET 2003 IDE 环境中也有这个工具,具体存放位置在:C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\Tools\Bin\winnt)。使用这个实用工具可以安装和移除服务。其它控制操作将通过服务控制面板来完成。以下是用命令行安装 MemoryStatus 服务的方法: 
sc create MemoryStatus binpath= c:\MyServices\MemoryStatus.exe

发出此创建命令。指定服务名和二进制文件的路径(注意 binpath= 和路径之间的那个空格)。

MemoryStatus 的启动类型是手动,也就是说根据需要来启动这个服务。右键单击该服务,然后选择上下文菜单中的“属性”菜单项,此时显示该服务的属性窗口。在这里可以修改启动类型以及其它设置。你还可以从“常规”标签中启动/停止服务。以下是从系统中移除服务的方法: 
sc delete MemoryStatus

指定 “delete” 选项和服务名。此服务将被标记为删除,下次西通重启后,该服务将被完全移除。


第五步:测试服务

从服务控制面板启动 MemoryStatus 服务。如果初始化不出错,表示启动成功。过一会儿将服务停止。检查一下 C:\MyServices 文件夹中 memstatus.txt 文件的服务输出。在我的机器上输出是这样的: 
1.Monitoring started. 
2.273469440 
3.273379328 
4.273133568 
5.273084416 
6.Monitoring stopped.

为了测试 MemoryStatus 服务在出错情况下的行为,可以将 memstatus.txt 文件设置成只读。这样一来,服务应该无法启动。

去掉只读属性,启动服务,在将文件设成只读。服务将停止执行,因为此时日志文件写入失败。如果你更新服务控制面板的内容,会发现服务状态是已经停止。

开发更大更好的服务程序

理解 Win32 服务的基本概念,使你能更好地用 C++ 来设计包装类。包装类隐藏了对底层 Win32 函数的调用并提供了一种舒适的通用接口。修改 MemoryStatus 程序代码,创建满足自己需要的服务!为了实现比本文例子所示范的更复杂的任务,你可以创建多线程的服务,将作业划分成几个工作者线程并从 ServiceMain 函数中监视它们的执行。


完整代码如下:

// MyServices.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <Windows.h>
#include <atltime.h>

#define  SLEEP_TIME 5000 //两次连续查询可用内存之前毫秒间隔
#define  LOGFILE "C:/MyServices/status.txt"//日志文件路径

SERVICE_STATUS                      ServiceStatus;
SERVICE_STATUS_HANDLE   hStatus;
bool brun;

int WriteToLog(char* str)
{
    FILE* fp = nullptr;
    fp = fopen(LOGFILE,"a+");
    if (fp)
    {
        CTime ct =  CTime::GetCurrentTime();
        CString strTime  = ct.Format("%Y-%m-%d,%H:%M:%S(%c) ");
        fprintf_s(fp,strTime);
        fprintf_s(fp,"%s\n",str);
        fclose(fp);
        return 0;
    }
    return - 1;
}

//服务初始化
int InitService()
{
    int result = WriteToLog("Monitoring Started");
    return result;
}

void WINAPI Ctrlhandler(DWORD request)
{
    switch (request)
    {
    case SERVICE_CONTROL_STOP:
        brun = false;
        ServiceStatus.dwCurrentState = SERVICE_STOPPED;
        break;
    case SERVICE_CONTROL_SHUTDOWN:
        brun = false;
        ServiceStatus.dwCurrentState = SERVICE_STOPPED;
        break;
    case SERVICE_CONTROL_PAUSE:
        brun = false;
        ServiceStatus.dwCurrentState = SERVICE_PAUSED;
        break;
    case SERVICE_CONTROL_CONTINUE:
        brun = false;
        ServiceStatus.dwCurrentState = SERVICE_RUNNING;
        break;
    default:
        break;
    }
    //向SCM报告“SERVICE_STOPPED”状态
    SetServiceStatus(hStatus,&ServiceStatus);
}

VOID ServiceMain(
    DWORD   dwNumServicesArgs,
    LPWSTR  *lpServiceArgVectors)
{
    //服务类型,创建win32服务
    ServiceStatus.dwServiceType = SERVICE_WIN32;
    //服务当前状态,在这里的时候初始化未完成,所以pending
    ServiceStatus.dwCurrentState = SERVICE_START_PENDING;
    //通知SCM服务接收哪个域,处理控制请求后面处理
    ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN
        | SERVICE_ACCEPT_STOP;//本例只接受系统关机和停止服务2种控制命令
    //终止服务并报告退出细节,初始化时不退出,将他们赋值为0
    ServiceStatus.dwWin32ExitCode =  0;
    ServiceStatus.dwServiceSpecificExitCode = 0;
    //表示初始化某个服务进程要30s以上,因为我们的实例初始化过程很短,设置为0
    ServiceStatus.dwCheckPoint = 0;
    ServiceStatus.dwWaitHint = 0;
    //为服务注册控制处理器
    hStatus = RegisterServiceCtrlHandler("MemoryStatus",Ctrlhandler);//服务名,指向controlhandlefunction指针
    if (!hStatus)
    {
        WriteToLog("RegisterServerCtrlhandler Failed");
        return;
    }
    WriteToLog("RegisterServiceCtrlHandler Sucess");
    //
    if(InitService() == -1)
    {
        //初始化失败,可能写日志文件失败
        ServiceStatus.dwCurrentState = SERVICE_STOPPED;
        ServiceStatus.dwWin32ExitCode = -1;
        SetServiceStatus(hStatus,&ServiceStatus);
        //退出serviceMain
        return;
    }
    //
    //向sCM报告运行服务状态
    ServiceStatus.dwCurrentState = SERVICE_RUNNING;
    BOOL bstatus = SetServiceStatus(hStatus,&ServiceStatus);
    if(!bstatus)
    {
        DWORD  derror = GetLastError();
        char str[100]={0};
        sprintf_s(str,100,"SetServiceStatus Failed,The error value is %d",derror);
        WriteToLog(str);
        return;
    }
    //下面就开始任务循环,添加自己希望的服务
    //这里是每隔5s查询一次可用物理内存并写入日志
    MEMORYSTATUS memstatus;
    brun = true;
    while (brun)
    {
        char str[100]={0};
        GlobalMemoryStatus(&memstatus);
        int iavailmb = memstatus.dwAvailPhys/1024/1024;
        sprintf_s(str,100,"available memory is %d MB",iavailmb);
        WriteToLog(str);
        Sleep(SLEEP_TIME);
    }
    WriteToLog("Service Stopped");
}

//创建分派表并启动控制分派机
int _tmain(int argc, _TCHAR* argv[])
{
    SERVICE_TABLE_ENTRY ServiceTable[2];
    ServiceTable[0].lpServiceName = "MemoryStatus";
    ServiceTable[0].lpServiceProc = 
        (LPSERVICE_MAIN_FUNCTION)ServiceMain;

    ServiceTable[1].lpServiceName = nullptr;
    ServiceTable[1].lpServiceProc = nullptr;
    //启动服务的控制分派机线程
    StartServiceCtrlDispatcher(ServiceTable);
    return 0;
}

使用sc命令create服务的时候,名称好像可以随意定义,不知道是不是本来就是这样,就算源码里面写了“MemoryStatus”也无影响??

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C/C++ 服务器程序(从入门到精通) 的相关文章

  • 【Linux学习笔记】关于ubuntu开机菜单栏和任务栏不见了的有效解决方法

    一 问题描述 ubuntu开机只有桌面 xff0c 没有菜单栏和任务栏 xff0c 如下图 xff1a 二 问题解决 刚学习ubuntu xff0c 总有些像我这样不折腾就不舒服的人 xff0c 今天改了一下主题 xff0c 图标什么的 x
  • 【数据结构与算法】深入浅出递归和迭代的通用转换思想

    深入浅出递归和迭代的通用转换思想 一般来说 xff0c 能用迭代的地方就不要用递归 xff01 理论上讲 xff0c 所有的递归和迭代之间都能相互转换 xff01 刷题碰到 一天一道LeetCode 130 Surrounded Regio
  • 【unix网络编程第三版】阅读笔记(二):套接字编程简介

    unp第二章主要将了TCP和UDP的简介 xff0c 这些在 TCP IP详解 和 计算机网络 等书中有很多细致的讲解 xff0c 可以参考本人的这篇博客 计算机网络 第五版 阅读笔记之五 xff1a 运输层 xff0c 这篇博客就不再赘述
  • 带你深入理解STL之Deque容器

    在介绍STL的deque的容器之前 xff0c 我们先来总结一下vector和list的优缺点 vector在内存中是分配一段连续的内存空间进行存储 xff0c 其迭代器采用原生指针即可 xff0c 因此其支持随机访问和存储 xff0c 支
  • 带你深入理解STL之Set和Map

    在上一篇博客 带你深入理解STL之RBTree中 xff0c 讲到了STL中关于红黑树的实现 xff0c 理解起来比较复杂 xff0c 正所谓前人种树 xff0c 后人乘凉 xff0c RBTree把树都种好了 xff0c 接下来就该set
  • 一个小时开发的直播推拉流软件来了

    一 简介 目前市面上直播推流的软件有很多 xff0c 拉流也很常见 近期因为业务需要 xff0c 需要搭建一整套服务端推流 xff0c 客户端拉流的程序 随即进行了展开研究 xff0c 花了一个小时做了个基于winfrom桌面版的推拉流软件
  • Redis源码剖析--字符串t_string

    前面一直在分析Redis的底层数据结构 xff0c Redis利用这些底层结构设计了它面向用户可见的五种数据结构 xff0c 字符串 哈希 xff0c 链表 xff0c 集合和有序集合 xff0c 然后用redisObject对这五种结构进
  • Redis源码剖析--快速列表quicklist

    在RedisObject这一篇博客中 xff0c 有介绍到list结构的底层编码类型有OBJ ENCODING QUICKLIST xff0c 当时就发现这个底层数据结构被我遗漏了 昨天花了点时间补了补这个知识 xff0c 看完发现这货就跟
  • Redis源码剖析--列表list

    上一篇博客Redis源码剖析 快速列表 带大家一起剖析了quicklist这个底层数据结构的实现原理 Redis对外开放的列表list结构就是采用quicklist作为底层实现 xff08 在新版本的Redis源码中 xff0c 不再采用z
  • PX4二次开发(一:PX4架构)

    概念 本节包含有关PX4系统架构和其他核心概念的主题 目录 PX4架构 PX4飞行栈架构 事件接口 飞行模式 飞行任务 控制分配 xff08 混控Mixing xff09 PWM限制状态机 系统启动 PX4 SD卡布局 PX4系统架构 以下
  • ssh登录警告 WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!

    1 ssh远程登录警告提示信息如下 xff1a span class token function ssh span th 64 192 168 162 136 64 64 64 64 64 64 64 64 64 64 64 64 64
  • 在Mac上使用Dronekit与SITL做飞行程序的模拟调试

    背景 无人机的项目快要中期答辩了 xff0c 为了在地面站 xff08 电脑 xff09 控制无人机 xff0c 我们选择DroneKit来进行代码的书写 DroneKit是一个专门用于控制无人机的Python库 xff0c 使用这个API
  • 敏捷教练的十种能力

    1 具备神奇的 读懂一个房间 的能力 只要走进一个房间 xff0c 就能判断出不在的过程中 xff0c 房间里发生了什么事情 xff0c 能立即读出空气中蕴含的情绪 xff0c 从而判断是否一切正常 xff1b 2 关心人本身胜过关心产品
  • kubernetes: HPA解析

    什么是HPA Horizontal Pod Autoscaling可以根据指标自动伸缩一个Replication Controller Deployment 或者Replica Set中的Pod数量 HPA的工作模型 有哪些指标 目前主要分
  • ROS中生成CameraInfo消息

    前言 由于某个第三方代码需要接受CameraInfo消息 xff0c 我换了一个相机以后 xff0c 需要自己发布CameraInfo消息 网上搜了半天 xff0c 很少有介绍CameraInfo这些数据都是怎么来的的资料 xff0c 可能
  • OpenCV+aruco 生成标定格与相机位姿计算

    仅用于记录自己使用aruco过程中遇到的问题与解决方法 0 参考资料 github一个参考 xff1a https github com opencv opencv contrib blob master modules aruco sam
  • 计算机和控制领域SCI收录期刊及其影响因子

    来源 xff1a http hi baidu com daren007 blog item 0605ed97b1c50e6a55fb9608 html 031 Computer Applications amp Cybernetics No
  • 【学习总结】Kalibr标定相机与IMU

    本文仅用于记录自己学习过程 使用方法 Kalibr包括 xff1a 相机内参 xff0c 多相机外参 xff0c 已知IMU和相机内参的 相机与IMU标定 xff0c 以及扩展Kalibr支持IMU内参标定 当已知IMU内参和相机内参后 x
  • 【学习记录】Kalibr标定相机与IMU的一点记录

    一周更多的时间在搞这个Kalibr的相机与IMU的标定 xff0c 记录一些问题 xff1a 相机重投影误差 相机一定要好好标定 xff0c 如果重投影误差太大 xff0c 是优化不出来外参的 好在相机内参 xff0c 与IMU外参标定 x
  • 【学习总结】VIO初始化学习1:Monocular Visual–Inertial State Estimation With Online Initialization and Camera–IMU

    最近看了一篇论文 xff0c 很是头大 xff0c 大概看懂了个所以然 记录一下 论文 xff1a Monocular Visual Inertial State Estimation With Online Initialization

随机推荐

  • PYTHON用法第一篇:print的用法。

    hello大家好 xff0c 我是会编程的杜子腾 xff0c 今天我们来学习一下python实例 xff1a print用法 使用材料 xff1a 一台电脑 python各版本 随便一个 xff0c 尽量选python3 python文本编
  • 那些女程序员们的故事

    点击上方蓝字 关注我们 xff0c 和小伙伴一起聊技术 xff01 程序媛是程序员大军中一道美丽的风景线 xff0c 今天的这篇文章就选取了一些女程序员们的故事 xff0c 希望当所有人了解了他们的经历后 xff0c 能让这个 重男轻女 的
  • shell中脚本变量和函数变量的作用域

    原文地址 xff1a http blog csdn net ltx19860420 article details 5570902 1 shell脚本中定义的变量是global的 xff0c 其作用域从被定义的地方开始 xff0c 到she
  • 最简单易懂的10堂算法入门课——算法是什么

    算法太重要了 人工智能 xff0c 机器学习 xff0c 大数据 xff0c 这些越来越常听到的字眼 xff0c 背后其实都是一个个 算法 诸多高新科技 xff0c 似乎都离不开 算法 的 加持 科学家 工程师 技术人员 xff0c 现在如
  • Opencv之Aruco码的检测和姿态估计

    1 介绍 Aruco码是由宽黑色边框和确定其标识符 id 的内部二进制矩阵组成的正方形标记 它的黑色边框有助于其在图像中的快速检测 xff0c 内部二进制编码用于识别标记和提供错误检测和纠正 单个aruco 标记就可以提供足够的对应关系 x
  • linux与window文件通过串口传输方法(zmod传输方法)

    我们在调试linux产品时 xff0c 有的产品没有网口 xff0c 只有串口 这时nfs tfp都用不了 只能用串口来传输文件 把windows上文件通过串口传输到开发板上 开发板和电脑通过串口连接 2 使用MobaXterm工具 xff
  • CentOS 7 需要安装的常用工具,及centos安装fcitx 搜狗输入法的坑旅

    Centos常用设置 1 当最大化时隐藏标题栏 或者使用tweak tool 在字体中将标题栏字体设置为0 建议这个方法 2 添加epel源 yum y nogpgcheck install http download fedoraproj
  • 小学数学公式大全

    小学数学公式大全 第一部分 xff1a 概念 1 加法交换律 xff1a 两数相加交换加数的位置 xff0c 和不变 2 加法结合律 xff1a 三个数相加 xff0c 先把前两个数相加 xff0c 或先把后两个数相加 xff0c 再同第三
  • c++中的点号(.),冒号(:)和双冒号(::)运算符

    1 冒号 xff08 xff09 用法 xff08 1 xff09 表示机构内位域的定义 xff08 即该变量占几个bit空间 xff09 typedef struct XXX unsigned char a 4 char型的字符a占4位
  • C++ 对象和实例的区别,以及用new和不用new创建类对象区别

    起初刚学C 43 43 时 xff0c 很不习惯用new xff0c 后来看老外的程序 xff0c 发现几乎都是使用new xff0c 想一想区别也不是太大 xff0c 但是在大一点的项目设计中 xff0c 有时候不使用new的确会带来很多
  • 巫泽俊...《挑战程序设计竞赛》算法及相关书籍论点

    为什么要参加程序设计竞赛 能提高程序设计能力 xff0c 掌握技巧 减少错误 xff1b 能结识更多的同好 xff0c 交流切磋 xff1b 能更好地推销自己 xff08 大赛的前几名往往受到世界知名公司的青睐 xff09 秋叶拓哉认为 x
  • (struct)结构体变量作为函数参数调用的方法小结

    结构体变量 结构指针变量 结构数组作为函数的参数应用实例分析 struct stud long int num float score 结构体变量作为函数的参数 xff0c 修改之后的成员值不能返回到主调函数 void funvr stru
  • 搭建nginx反向代理用做内网域名转发

    基于域名的7层转发的实现 xff08 NAT 43 反向代理 xff09 在实际办公网中 xff0c 因为出口IP只有一个 xff0c 要实现对外提供服务的话就必须得做端口映射 xff0c 如果有多个服务要对外开放的话 xff0c 这只能通
  • 从平面上最近的点对,谈谈分治算法

    首先介绍一下分治 xff08 Divide and Conquer xff09 算法 xff1a 设计过程分为三个阶段 Divide xff1a 整个问题划分为多个子问题 Conquer xff1a 求解各子问题 递归调用正设计的算法 Co
  • NOIP2017 国庆郑州集训知识梳理汇总

    第一天 基础算法及数学 基本算法 递推 递归 分治 二分 倍增 贪心 递推 指通过观察 归纳 xff0c 发现较大规模问题和较小规模问题之间的关系 xff0c 用一些数学公式表达出来 在一些题解中 xff0c 和 计数DP 是指同一个概念
  • 挑战程序设计竞赛 — 知识总结

    准备篇 1 5 运行时间 概述编写的目的是面向ACM程序设计竞赛 xff0c 不可避免的要涉及复杂度和运行时间的问题 xff0c 本节给出了解决问题算法选择的依据 假设题目描述中给出的限制条件为n lt 61 1000 xff0c 针对O
  • 阿里巴巴笔试题选解

    阿里巴巴笔试题选解 9月22日 xff0c 阿里巴巴北邮站 小题 xff1a 1 有三个结点 xff0c 可以构成多少种二叉树形结构 xff1f 2 一副牌52 张 去掉大小王 xff0c 从中抽取两张牌 xff0c 一红一黑的概率是多少
  • 腾讯2014软件开发笔试题目

    腾讯2014软件开发笔试题目 9月21日 xff0c 腾讯2014软件开发校招 简答题 广州 简答题 xff1a 1 请设计一个排队系统 xff0c 能够让每个进入队伍的用户都能看到自己在 中所处的位置和变化 队伍可能随时有人加入和退出 x
  • MAVLink简介

    MAVLink简介 Mavlink协议最早由 苏黎世联邦理工学院 计算机视觉与几何实验组 的 Lorenz Meier于2009年发布 xff0c 并遵循LGPL开源协议 Mavlink协议是在串口通讯基础上的一种更高层的开源通讯协议 xf
  • C/C++ 服务器程序(从入门到精通)

    Windows 服务被设计用于需要在后台运行的应用程序以及实现没有用户交互的任务 为了学习这种控制台应用程序的基础知识 xff0c C xff08 不是C 43 43 xff09 是最佳选择 本文将建立并实现一个简单的服务程序 xff0c