C ++:在“ try”之外创建的类型会导致错误,但是在内部,则不会

时间:2019-10-12 19:48:33

标签: c++ sockets

从纯粹的解决问题的角度来看,这是一个已解决的问题,但是其潜在的机制可能对包括我在内的某些人来说很有趣。

我写了一个漂亮的基本服务器,监听给定的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的初始化可以解决此问题?

1 个答案:

答案 0 :(得分:3)

accept使用其第三个参数(即addrlen)作为输入和输出。输入必须是第二个自变量指向的结构的大小(即address),输出将是地址的实际大小。

根据第三个参数的输入,如果地址不适合结构,则该地址可能会被截断。

如果acceptaddrlen返回的值大于sizeof(address),则在第二次迭代中,传递给accept的参数将不再与sizeof(address)匹配,并且将导致未定义的行为。实际上,如果类成员按该顺序进行布局,则accept会将address之后的地址写入processor

在每次调用addrlen之前,在每次循环迭代中直接将sizeof(address)设置为accept是正确的做法,并确保始终将此值用作{{1 }},即使accept在输出中修改其值。

还要确保您正在为侦听套接字使用的协议系列使用适当的accept类型,即sockaddr_*代表sockaddr_in(ipv4)或AF_INET sockaddr_in6(ipv6)。