【问题标题】:C++ networking, recv() fails for no reason (?)C++ 网络,recv() 无缘无故失败(?)
【发布时间】:2019-02-01 05:27:39
【问题描述】:

我正在用 C++ 编写一个聊天服务器以供自己消遣,但总的来说我对网络还很陌生。我的知识来自 Beej 的指南 this site,以及 Kurose&Ross 的 Top Down 方法。

我的问题是,我编写的代码与您在链接中找到的代码非常相似,但它不起作用。当我尝试在服务器上使用 recv() 函数时,程序失败。

我知道 recv() 可以返回任意数量的字节,但它只是给了我-1。我知道我需要一个循环来拼凑正在发送的消息,但目前还没有类似的东西。然而,客户端中的 send() 表示它发送了缓冲区中指定的数量(是的,我知道这可能有点矫枉过正,但不确定这是否是一个错误)。

在我看来,服务器似乎……以某种方式关闭了?我很确定我在某个地方搞砸了,有一行代码我真的不确定(寻找评论)。我昨天设法让它工作,但在那之后出现了问题。

我对套接字编程和任何类型的网络都很陌生,但是函数调用和诸如此类的东西来自指南,我有一种预感,问题将出在我指定端口和地址的部分,也许是字节没有到达服务器,因为我将它们发送到其他地方?但是如果accept() 有效,那不就意味着建立了TCP 连接,那之后我不应该能够使用它吗?

我正在使用最新版本的 Lubuntu linux,如果有帮助的话。

如果你发现问题,你能告诉我如何正确地做吗?无论如何,这是服务器的代码:

void start() {

    char message[1024] = "";

    int socketfd = socket( AF_INET, SOCK_STREAM, 0 ), opt = 1, new_socket;

    struct sockaddr_in address;
    struct sockaddr_storage cl_addr;
    socklen_t len = sizeof(address);

    if( socketfd != 0 ) {

        address.sin_family = AF_INET;
        address.sin_port = htons( PORT );
        address.sin_addr.s_addr = INADDR_ANY;

        if( setsockopt(socketfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(int)) < 0 ) {
            perror("Setsockopt failed");
            exit(EXIT_FAILURE);
        }

        if( bind( socketfd, (struct sockaddr*)&address, len ) < 0 ) {           
            perror("Couldn't bind to port");
            exit(EXIT_FAILURE);
        } 

        if( listen( socketfd, 3 ) < 0 ) {
            perror("Listening on port failed");
            exit(EXIT_FAILURE);
        }

        if( (new_socket = accept( socketfd, (struct sockaddr *)&cl_addr, (socklen_t*)&len  ) < 0) ) {
            perror("Couldn't accept request");
            exit(EXIT_FAILURE);
        }

        /// This is where it fails
        std::cout << recv( new_socket, message, 1024, 0) <<  std::endl;
        close(new_socket);
    }
    else {
        perror("Couldn't open socket");
        exit(EXIT_FAILURE);
    }
    close( socketfd );
}

现在是客户端:

int main(void) {

int sockfd;

struct sockaddr_in serv_addr;
char hello[1024] = "Hello";

serv_addr.sin_family = AF_INET; 
serv_addr.sin_port = htons(PORT);

/// I'm not sure about this !! 
inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr);

sockfd = socket(AF_INET, SOCK_STREAM, 0);

if( sockfd == 0 ) {
    perror("Opening socket failed");
    exit(EXIT_FAILURE);
}

if( connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) != -1 ) {
    /// Says it sent 1024, but sever doesn't recieve it, what gives ?
    int test = send( sockfd, hello, sizeof(hello), 0);
    std::cout << test << std::endl;
}
else {
    perror("Error");
    exit(EXIT_FAILURE);
}

如您所见,它与链接中的代码相似。服务器代码只是一个函数,还有其他部分的代码,比如创建用户文件,但没有一个用于网络部分,所以我不想再白发800行代码。服务器的 main() 只是我创建了一个服务器类的对象并在其上调用 start()。

我想在力所能及的地方进行改进,所以如果您发现我可以做得更好,请告诉我,无论是一般的编码风格还是其他方面。另外,这是我在这里的第一个问题,我的第一语言不是英语,请放轻松:)

【问题讨论】:

  • 当你得到-1返回码时,你可以使用std::cerr &lt;&lt; std::strerror(errno) &lt;&lt; '\n';打印出错误信息,这可能会给你一些启示。
  • sockaddr 结构可以在此处部分初始化。您应该在填充之前将它们归零。 if( socketfd != 0 ) 也不正确,因为无效的套接字值将是 -1。
  • 您显然阅读了recv() 的文档,其中说发生错误时会返回-1,因为您的代码会认真检查它。但是,该文档还说 errno 被设置为指示错误的原因。
  • 谢谢!我将返回错误消息@VTT - 类似于 memset(&serv_addr, '0', sizeof(serv_addr));工作 ?我从示例代码中复制了这个,不知道他为什么这样做。
  • 在现代C++ 中,零初始化,通常形式为“object={}”,具有将对象初始化为全零的效果,而不必转为史前 C 风格的结构清除代码。

标签: c++ sockets networking tcp recv


【解决方案1】:

这是一个括号问题。

这一行:

if( (new_socket = accept( socketfd, (struct sockaddr *)&cl_addr, (socklen_t*)&len  ) < 0) ) {

&lt; 运算符的计算优先级高于 = 运算符。

new_socket 被赋值为 accept() &lt; 0,这是一个错误的表达式,因此 new_socket 被赋值为零。

这可能是你的意思:

if (new_socket = accept( socketfd, (struct sockaddr *)&cl_addr, (socklen_t*)&len ) < 0)

但这更不容易出错:

new_socket = accept( socketfd, (struct sockaddr *)&cl_addr, (socklen_t*)&len  ); 
if (new_socket < 0) {
        perror("Couldn't accept request");
        exit(EXIT_FAILURE);
}

【讨论】:

  • 示例 1,152,815,975,467 为什么将赋值语句填充到 ifwhile 条件子句中几乎总是一个非常糟糕的主意。 节省代码行是一个无用的目标
  • 安德鲁从未说过更真实的话。这些错误非常难以发现。
  • 谢谢你,你说得对,这只是我大学老师让我做的事。 “这样做”他说,“它更具可读性”,他说......当然......
  • @AndrewHenle - 至少现在(C++17)我们可以做到if (initialization; condition)
  • @ZomborMate 不,它不是“更具可读性”。而且这样的代码也几乎排除了健壮的错误处理,您可能希望以不同的方式处理不同类型的故障。例如,msgrcv() 之类的调用在中断时将永远不会自动重新启动,因此您必须循环并检查errno 中的EINTR,以便您可以重试 - 同时还要处理所有其他非中断错误。如果他的代码不能稳健地处理错误,我为什么怀疑你的老师从来没有在凌晨 2 点接到电话?
猜你喜欢
  • 1970-01-01
  • 2011-07-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多