【问题标题】:sprintf / snprintf not correctly writing to buffersprintf / snprintf 未正确写入缓冲区
【发布时间】:2014-11-20 17:04:21
【问题描述】:

我必须编写一个包含整数的 TCP 服务器程序,它应该可以被客户端程序修改。它应该有点像银行账户。一切正常,除了一件事:

当客户端第一次连接到服务器时,它将等待欢迎消息(服务器必须是迭代的,因此它一次只能处理一个客户端)。服务器总是只发送欢迎消息的前几封信。所有其他消息都已完全正确地传输。

在第 49 行,欢迎消息首先被复制到一个字符数组,然后写入套接字。这就是错误所在...仅发送前 1-5 个字母(每次新客户端连接时都不同)。在我使用 sprintf() 将消息复制到字符数组然后将其写入套接字的其他地方,一切都按我想要的方式工作。 我也尝试过使用 snprintf(),但这也不起作用。我究竟做错了什么? :D

所以这将是客户端的示例输出:

Connected!
Waiting for welcome message...
We

之后,我可以开始向服务器输入命令。但是整个欢迎信息在两封信之后被删掉了。但如上所述,有时它只有一个字母,有时它是五个 :D。

无论如何,这是我的代码(如果有任何其他错误或我应该避免的事情,请随时告诉我:D):

客户:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h> 
#define BufferSize 99999

void error(const char *msg) {
    fprintf(stderr, "%s\n", msg);
    exit(EXIT_FAILURE);
}

int main(int argc, char *argv[]) {
    int sockfd, portno, n;
    struct sockaddr_in serv_addr;
    struct hostent *server;

    char msg[BufferSize], data[BufferSize];
    if (argc < 3) error("usage: <hostname> <port>\n");
    server = gethostbyname(argv[1]);
    if (server == NULL) error("Host not found!");
    portno = atoi(argv[2]);

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) error("socket() error");

    bzero((char *) &serv_addr, sizeof (serv_addr));
    serv_addr.sin_family = AF_INET;
    bcopy((char *) server->h_addr, (char *) &serv_addr.sin_addr.s_addr, server->h_length);
    serv_addr.sin_port = htons(portno);

    if (connect(sockfd, (struct sockaddr *) &serv_addr, sizeof (serv_addr)) < 0) error("connect() error");

    printf("Connected!\nWaiting for welcome message...\n");
    memset(msg, 0, BufferSize);
    n = read(sockfd, msg, BufferSize - 1);
    if (n < 0) error("read() error");
    printf("%s\n", msg);

    memset(data, 0, BufferSize);
    while (fgets(data, BufferSize, stdin) != NULL) {
        data[strlen(data) - 1] = '\0'; //remove trailing newline char
        n = write(sockfd, data, strlen(data) + 1);
        if (n < 0) error("write() error");

        if (strcmp(data, "exit") == 0) break;

        memset(msg, 0, BufferSize);
        n = read(sockfd, msg, BufferSize - 1);
        if (n < 0) error("read() error");
        if (n==0) error("Server shut down...");
        printf("%s\n", msg);
        memset(data, 0, BufferSize);
    }

    close(sockfd);
    return 0;
}

服务器:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <limits.h>

#define BufferSize 99999
#define ClientWaiting 100

void error(const char *msg) {
    fprintf(stderr, "%s\n", msg);
    exit(EXIT_FAILURE);
}

int main(int argc, char *argv[]) {
    int sockfd, newsockfd, portno, n, amount, balance, balOld;
    socklen_t clilen;
    char msg[BufferSize], data[BufferSize], *splitBuf[2];
    struct sockaddr_in serv_addr, cli_addr;

    balance = 0;

    if (argc < 2) error("usage: <port>");
    portno = atoi(argv[1]);

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) error("socket() error");

    memset(&serv_addr, 0, sizeof (serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(portno);

    if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof (serv_addr)) < 0) error("bind() error");

    listen(sockfd, ClientWaiting);

    while (1) {
        printf("Waiting for new client...\n");
        clilen = sizeof (cli_addr);
        newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);
        if (newsockfd < 0) error("accept() error");
        printf("New connection accepted...\n");

        memset(data, 0, BufferSize);
        sprintf(data, "Welcome!\nPlease use the following commands:\n<put, get> <positive integer>\nBalance: %d€", balance);
        n = write(newsockfd, data, strlen(msg) + 1);
        if (n < 0) error("write() error");

        while (1) {
            splitBuf[0] = NULL;
            splitBuf[1] = NULL;
            memset(data, 0, BufferSize);
            memset(msg, 0, BufferSize);
            n = read(newsockfd, msg, BufferSize - 1);
            if (n < 0) {
                fprintf(stderr, "read() error\n");
                break;
            }
            if (n == 0) {
                printf("Client disconnected...\n");
                break;
            }

            printf("Message received: %s\n", msg);

            if (strcmp(msg, "exit") == 0) break;

            splitBuf[0] = strtok(msg, " ");
            splitBuf[1] = strtok(NULL, " ");
            if (splitBuf[1] == NULL) {
                strcpy(data, "Please use the following commands:\n<put, get> <positive integer>");
            } else {
                amount = atoi(splitBuf[1]);
                if (amount <= 0) {
                    strcpy(data, "Please use the following commands:\n<put, get> <positive integer>");
                } else if (strcmp(splitBuf[0], "put") == 0) {
                    balOld = balance;
                    balance += amount;
                    if (balance < balOld) {
                        balance = INT_MAX;
                        sprintf(data, "Warning! Overflow!\nBalance: %d€", balance);
                    } else {
                        sprintf(data, "Balance: %d€", balance);
                    }
                    printf("New balance: %d€\n", balance);
                } else if (strcmp(splitBuf[0], "get") == 0) {
                    balOld = balance;
                    balance -= amount;
                    if (balance > balOld) {
                        balance = INT_MIN;
                        sprintf(data, "Warning! Underflow!\nBalance: %d€", balance);
                    } else {
                        sprintf(data, "Balance: %d€", balance);
                    }
                    printf("New balance: %d€\n", balance);
                }
            }

            n = write(newsockfd, data, strlen(data) + 1);
            if (n < 0) error("write() error");
        }
        close(newsockfd);
    }

    close(newsockfd);
    close(sockfd);
    return 0;
}

【问题讨论】:

  • 使用snprintf(3)(或者,在Linux上使用glibcasprintf(3))。它按记录工作。你经常需要使用它的结果。使用gcc -Wall -Wextra -g(所有警告和调试信息)编译这两个代码。并且使用调试器(例如,在两个终端中,一个用于调试您的客户端,一个用于调试您的服务器)。测试每个功能是否失败(在失败时使用perror)。
  • 嗯,使用 snprintf 我得到了完全相同的问题:欢迎消息无法正常工作,但我以相同方式复制到 char-array 的所有其他消息都可以正常工作.而且由于它是一个大小固定的字符数组,所以 asprintf() 并不真正适用,对吧? :D
  • 当你写欢迎信息时,你写的是data的内容,但是使用strlen(msg) + 1来获取长度......看起来不对。
  • Omfg 是的,这就是问题所在:D 该死的复制+粘贴哈哈 但是为什么传输的内容的长度会有所不同呢?我的意思是,如果它每次都发送一封信,那将是有道理的,因为此时 msg 将为 NULL,所以 strlen(msg) 将返回 0,加 1,然后您收到 1 个字母......但它会随机发送最多 5 个...
  • @user2336377:味精缓冲区未设置为任何内容。内存位置可能包含之前代码中的指针值之类的东西。根据具体情况,strlen() 可能会在不同的地方找到一个零字节。

标签: c sockets printf


【解决方案1】:

这个例子对于 SO 上的人来说太长了,无法调试。 (我们不是编译器和调试器。)

您的第一步必须是将程序分解为更小、更易于理解的可独立调试的部分。有几种方法可以做到这一点:

  • 添加测试您的断言的检查点
  • 将代码分解为更易于理解和独立测试的函数

例如,我看:

        splitBuf[0] = strtok(msg, " ");
        splitBuf[1] = strtok(NULL, " ");

splitBuf 是否包含您所期望的内容? (NULL 是 strtok 的有效参数吗?)

我推荐两件事: #包括

# Are my assumptions met?
assert( splitBuf[0]!=null );
assert( splitBuf[1]!=null );
#ifdef DEBUG
       printf("splitBuf[0]=%s\n", splitBuf[0]);
       printf("splitBuf[1]=%s\n", splitBuf[1]);
#endif

使用 -DDEBUG=1 进行编译以确保定义了 DEBUG,或者添加:

#define DEBUG

在文件的顶部。

其次,如果您解决较小的问题,然后独立处理并测试您对这些问题的答案,则编程会容易得多。假设您需要解析来自网络的消息并提取余额,那么您可以编写:

int parseBalance(char const* serverMessage) {
   ...
   return balance;
}

您现在可以编写测试:

void tests()
{
    // test parseBalance
    assert( 100 == parseBalance("100") )
    ... more tests
}

至少,您可以在程序开始时调用 tests() 来执行自测(阅读“单元测试”以获得更好的方法)。

如果你以这种方式编程:

  • 通常问题会变得更加明显
  • 如果您卡住并发布到 SO,您只需要发布最小的功能和测试用例。

【讨论】:

    【解决方案2】:

    我相信您的问题出在您的第一次 read() 调用中。你读了一遍,期望得到所有的欢迎信息。但这不是 TCP 的工作方式。 TCP 根据自己的内部规则将流数据放入数据包中,接收系统可以根据需要提供任意数量的数据。

    您不能依赖一次读取就获取服务器写入的所有数据。

    事实上,服务器也搞砸了。您不能期望 write 调用会写下您所说的所有内容。操作系统的套接字发送缓冲区可能已满,或者可能有信号中断调用。

    您需要发送和接收缓冲区以及函数来处理围绕读取和写入的循环,该循环一直持续到您收到完整的行或发送完整的缓冲区。

    【讨论】:

    • 嗯,这就是我们在大学里学到的方法 :D 我到底该怎么做呢?如何判断我的消息是否已完全转移?我是否必须循环 write() 并检查返回值,直到它等于我的消息的长度?对方将如何检查是否收到完整的消息?我可以循环 read() 直到我得到一个空终止符吗?
    • @user2336377:阅读有关 read() 和 write() 的文档。它们返回接收和发送的字节数。这个数字可能并不总是与您在函数调用中要求的数字相匹配。 TCP 流必须寻找换行符,或者在消息之前发送字节数,或者寻找要关闭的套接字。不,你不能循环,直到你得到一个空值。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-25
    • 1970-01-01
    • 1970-01-01
    • 2020-09-05
    • 1970-01-01
    相关资源
    最近更新 更多