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)。