从纯粹的解决问题的角度来看,这是一个已解决的问题,但是其潜在的机制可能对包括我在内的某些人来说很有趣。
我写了一个漂亮的基本服务器,监听给定的TCP端口。我以某种方式编写它,它可以接受多个请求,而不仅仅是一个请求,然后通过while
循环退出。
由于我没有C ++的经验,因此我搜索了一个解决方案并根据自己的需要制作了该解决方案。有问题的代码部分如下所示:
socklen_t addrlen = sizeof(address);
while(true) {
try {
std::cerr << "Listening..." << std::endl;
if(listen(socketId, 3) < 0) {
std::cerr << "Error while listening for incoming connections" << std::endl;
throw;
}
processRequest(accept(socketId, (struct sockaddr *) &address, &addrlen));
} catch(Json::RuntimeError& e) {
std::cerr << "Json error: " << e.what() << std::endl;
} catch(...) {
std::cerr << "Unknown error" << std::endl;
throw;
}
}
此代码的问题很棘手,对您中较有经验的人可能很明显,但对我来说绝对不明显。
我向您展示了类定义的一部分:
int socketId;
sockaddr_in address;
const RequestProcessor& processor;
请注意这些变量的顺序。
使用上述方法和按此顺序排列的变量,第一个请求将顺利运行,并且软件将按预期的方式开始侦听下一个请求。
但是,当第二个请求到达时,processor
引用将被覆盖,可能是因为发生了某些内存溢出(或所谓的),这将重写processor
引用所指向的内存位置,从而在此处未显示的方法中的某处导致错误。
该解决方案很简单,但很难找出:
while(true) {
try {
std::cerr << "Listening..." << std::endl;
if(listen(socketId, 3) < 0) {
std::cerr << "Error while listening for incoming connections" << std::endl;
throw;
}
socklen_t addrlen = sizeof(address);
processRequest(accept(socketId, (struct sockaddr *) &address, &addrlen));
} catch(Json::RuntimeError& e) {
std::cerr << "Json error: " << e.what() << std::endl;
} catch(...) {
std::cerr << "Unknown error" << std::endl;
throw;
}
}
我所做的就是替换创建addrlen
变量的位置。这样可以解决问题,并具有更多的“合理外观”。
问题是:为什么移动addrlen
的初始化可以解决此问题?
答案 0 :(得分:3)
accept
使用其第三个参数(即addrlen
)作为输入和输出。输入必须是第二个自变量指向的结构的大小(即address
),输出将是地址的实际大小。
根据第三个参数的输入,如果地址不适合结构,则该地址可能会被截断。
如果accept
为addrlen
返回的值大于sizeof(address)
,则在第二次迭代中,传递给accept
的参数将不再与sizeof(address)
匹配,并且将导致未定义的行为。实际上,如果类成员按该顺序进行布局,则accept
会将address
之后的地址写入processor
。
在每次调用addrlen
之前,在每次循环迭代中直接将sizeof(address)
设置为accept
是正确的做法,并确保始终将此值用作{{1 }},即使accept
在输出中修改其值。
还要确保您正在为侦听套接字使用的协议系列使用适当的accept
类型,即sockaddr_*
代表sockaddr_in
(ipv4)或AF_INET
sockaddr_in6
(ipv6)。