【问题标题】:Problem with multiple messages in TCP server [duplicate]TCP服务器中的多条消息问题[重复]
【发布时间】:2021-09-16 09:12:23
【问题描述】:

如果这个问题看起来微不足道,我深表歉意,但我是 C 和套接字编程的初学者。另外,英语不是我的母语。

我最近失败了一个要求我在 C 中实现一个接受 ipv4 和 ipv6 的 TCP 服务器客户端的 uni 作业,其中客户端将发送消息以添加、删除、列出和查询链表中的整数对存储在服务器中。所以,我用下面的代码制作了服务器和客户端(不包括链表和函数 strsplit 的代码):

服务器(servidor.c)

void func(int sockfd)
{
    struct Node* posts = NULL;
    char client_message[MAX];
    char server_message[MAX];
    char* str;
    char **p;
    int X, Y, i, j, k;
    int n = 0;
    char sX[5];
    char sY[5];

    for (;;)
    {
        recv(sockfd, client_message, sizeof(client_message), 0);

        printf("> %s", client_message);
        strcpy(str, client_message);
        p = strsplit(str, " ");

        memset(server_message, 0, MAX);

        if(strncmp("kill", p[0], 4) == 0)
        {
            break;
        }
        else if(strncmp("add", p[0], 3) == 0)
        {
            if(n == 50)
            {
                strncat(server_message, "limit exceeded", 14);
            }
            else
            {
                strncat(server_message, p[1], 4);
                strncat(server_message, " ", 1);
                X = atoi(p[1]);

                strtok(p[2], "\n");
                strncat(server_message, p[2], 4);
                strncat(server_message, " ", 1);
                Y = atoi(p[2]);

                i = push(&posts, X, Y);

                if(i == 0)
                {
                    strncat(server_message, "added", 5);
                    n++;
                }
                else
                {
                    strncat(server_message, "already exists", 14);
                }

                free(p);
            }
        }
        else if(strncmp("rm", p[0], 2) == 0)
        {
            strncat(server_message, p[1], 4);
            strncat(server_message, " ", 1);
            X = atoi(p[1]);

            strtok(p[2], "\n");
            strncat(server_message, p[2], 4);
            strncat(server_message, " ", 1);
            Y = atoi(p[2]);

            i = deleteNode(&posts, X, Y);

            if(i == 0)
            {
                strncat(server_message, "removed", 7);
                n--;
            }
            else
            {
                strncat(server_message, "does not exist", 14);
            }

            free(p);
        }
        else if(strncmp("list", p[0], 4) == 0)
        {
            if(posts == NULL)
            {
                strncat(server_message, "none", 4);
            }
            else
            {
                struct Node *temp = posts;

                while(temp != NULL)
                {
                    bzero(sX, 4);
                    bzero(sY, 4);

                    snprintf(sX, sizeof(sX), "%d",temp->X);
                    snprintf(sY, sizeof(sY), "%d",temp->Y);

                    strncat(server_message, sX, 4);
                    strncat(server_message, " ", 1);
                    strncat(server_message, sY, 4);
                    strncat(server_message, " ", 1);

                    temp = temp->next;
                }

                free(temp);
            }
        }
        else if(strncmp("query", p[0], 5) == 0)
        {
            if(posts == NULL)
            {
                strncat(server_message, "none", 4);
            }
            else
            {
                struct Node *temp1 = posts;
                k = 19998;

                X = atoi(p[1]);
                strtok(p[2], "\n");
                Y = atoi(p[2]);

                while(temp1 != NULL)
                {
                    i = abs(temp1->X - X);
                    j = abs(temp1->Y - Y);
                    i = i + j;

                    if(i <= k)
                    {
                        snprintf(sX, sizeof(sX), "%d",temp1->X);
                        snprintf(sY, sizeof(sY), "%d",temp1->Y);
                        k = i;
                    }

                    temp1 = temp1->next;
                }

                strncat(server_message, sX, 4);
                strncat(server_message, " ", 1);
                strncat(server_message, sY, 4);
                strncat(server_message, " ", 1);

                free(temp1);
            }
        }

        strncat(server_message, "\n", 1);
        printf("%s", server_message);

        send(sockfd, server_message, sizeof(server_message), 0);
    }
 }

int main(int argc, char *argv[])
{
    if(strncmp("v4", argv[1], 2) == 0)
    {
        int sockfd, connfd, len;
        struct sockaddr_in servaddr, cli;

        sockfd = socket(AF_INET, SOCK_STREAM, 0);

        if (sockfd == -1)
        {
            exit(0);
        }

        bzero(&servaddr, sizeof(servaddr));

        servaddr.sin_family = AF_INET;
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        servaddr.sin_port = htons(atoi(argv[2]));

        if ((bind(sockfd, (SA*)&servaddr, sizeof(servaddr))) != 0)
        {
            exit(0);
        }

        if ((listen(sockfd, 5)) != 0)
        {
            exit(0);
        }

        len = sizeof(cli);

        connfd = accept(sockfd, (SA*)&cli, &len);
        if (connfd < 0)
        {
            exit(0);
        }

        func(connfd);

        close(sockfd);
    }
    else if(strncmp("v6", argv[1], 2) == 0)
    {
        int sockfd, connfd, len;
        struct sockaddr_in6 servaddr, cli;

        sockfd = socket(AF_INET6, SOCK_STREAM, 0);

        if(sockfd == -1)
        {
            exit(0);
        }

        bzero(&servaddr, sizeof(servaddr));

        servaddr.sin6_family = AF_INET6;
        servaddr.sin6_addr = in6addr_any;
        servaddr.sin6_port = htons(atoi(argv[2]));

        if ((bind(sockfd, (SA*)&servaddr, sizeof(servaddr))) != 0)
        {
            exit(0);
        }

        if ((listen(sockfd, 5)) != 0)
        {
            exit(0);
        }

        len = sizeof(cli);

        connfd = accept(sockfd, (SA*)&cli, &len);
        if (connfd < 0)
        {
            exit(0);
        }

        func(connfd);

        close(sockfd);
    }

    return 0;
}

客户端(cliente.c)

void func(int sockfd)
{
    char buff[MAX];
    int n;
    for (;;)
    {
        bzero(buff, sizeof(buff));
        n = 0;
        while ((buff[n++] = getchar()) != '\n')
            ;
        send(sockfd, buff, sizeof(buff), 0);
        if ((strncmp(buff, "kill", 4)) == 0)
        {
            break;
        }
        bzero(buff, sizeof(buff));
        recv(sockfd, buff, sizeof(buff), 0);
        printf("> %s", buff);
    }
}

int main(int argc, char *argv[])
{
    if(strchr(argv[1], ':') != NULL)
    {
        int sockfd, connfd;
        struct sockaddr_in6 servaddr, cli;
        struct in6_addr result;

        sockfd = socket(AF_INET6, SOCK_STREAM, 0);

        if (sockfd == -1)
        {
            exit(0);
        }

        bzero(&servaddr, sizeof(servaddr));

        servaddr.sin6_family = AF_INET6;
        inet_pton(AF_INET6, argv[1], &result);
        servaddr.sin6_addr = result;
        servaddr.sin6_port = htons(atoi(argv[2]));

        if (connect(sockfd, (SA*)&servaddr, sizeof(servaddr)) != 0)
        {
            exit(0);
        }

        func(sockfd);

        close(sockfd);
    }
    else
    {
        int sockfd, connfd;
        struct sockaddr_in servaddr, cli;

        sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd == -1)
        {
            exit(0);
        }

        bzero(&servaddr, sizeof(servaddr));

        servaddr.sin_family = AF_INET;
        servaddr.sin_addr.s_addr = inet_addr(argv[1]);
        servaddr.sin_port = htons(atoi(argv[2]));

        if (connect(sockfd, (SA*)&servaddr, sizeof(servaddr)) != 0)
        {
            exit(0);
        }

        func(sockfd);

        close(sockfd);
    }

    return 0;
}

我自己测试,通过在单独的终端中打开服务器和客户端并一一发送消息,服务器似乎按预期工作。然而,我的教授却另有说法。作业失败后,他给我提供了用来给我的服务器评分的软件的python代码,如下:

run_tests.py

def run_basic_tests(exec_path, version, port, msg_type):
    address = '127.0.0.1' if version == 'v4' else '::1'
    list_dir = os.listdir('tests/in')
    list_dir = sorted(list_dir)
    for filename in list_dir:
        filename = filename.split('.')[0]
        _, result_file_path = tempfile.mkstemp()

        server = sp.Popen(
            [f'exec {exec_path} {version} {port}'], shell=True, stdout=sp.DEVNULL, stderr=sp.DEVNULL)
        ret = os.system(
            f'python3 client.py {address} {port} {msg_type} < tests/in/{filename}.in > {result_file_path}')
        ret = os.WEXITSTATUS(ret)

        if ret == 0:
            if filecmp.cmp(f'tests/out/{filename}.out', result_file_path, shallow=False):
                print(f'{filename}\t[OK]')
            else:
                print(f'{filename}\t[FAILED] (diff output)')
        else:
            print(f'{filename}\t[FAILED] (client exited with non-zero code {ret})')
        os.remove(result_file_path)
        server.kill()


if __name__ == '__main__':
    if len(sys.argv) != 3:
        print(f'usage: python3 {sys.argv[0]} <server> <port>')
        sys.exit(0)
    exec_path = sys.argv[1]
    port = int(sys.argv[2])

    if not exec_path.startswith('/'):
        print('provide the full path to the executable')
        sys.exit(0)

    for address_family in ['v4', 'v6']:
        for msg_type in ['single_msg_single_pkg', 'single_msg_multiple_pkg', 'multiple_msg_single_pkg']:
            print('Testing IP' + address_family, msg_type)
            run_basic_tests(exec_path, address_family, port, msg_type)

client.py

BUFFER = ''


def get_address_family(address):
    try:
        socket.inet_pton(socket.AF_INET, address)
        return socket.AF_INET
    except:
        pass

    try:
        socket.inet_pton(socket.AF_INET6, address)
        return socket.AF_INET6
    except:
        pass

    sys.exit(1)


def create_socket(address, port):
    address_family = get_address_family(address)
    attempts = 0
    client_socket = None
    while attempts < 5:
        try:
            client_socket = socket.socket(address_family, socket.SOCK_STREAM)
            client_socket.connect((address, port))
            break
        except socket.error:
            client_socket = None
            time.sleep(0.05)
        attempts += 1
    if not client_socket:
        sys.exit(2)
    return client_socket


# send one message
def send(client_socket, msg):
    msg = msg.encode('ascii')
    total_sent = 0
    while total_sent != len(msg):
        sent = client_socket.send(msg[total_sent:])
        if sent == 0:
            sys.exit(3)
        total_sent += sent


# receive one complete message
def receive(client_socket):
    global BUFFER
    while True:
        if '\n' in BUFFER:
            msg = BUFFER[:BUFFER.index('\n')]
            BUFFER = BUFFER[BUFFER.index('\n') + 1:]
            return msg
        try:
            data = client_socket.recv(500 - len(BUFFER)).decode()
        except socket.timeout:
            sys.exit(7)
    
        if not data:
            sys.exit(4)

        BUFFER += data
        if len(BUFFER) >= 500:
            sys.exit(5)


def run_multiple_msg_single_pkg(client_socket):
    msg_buffer = []
    for msg in sys.stdin:
        msg_buffer.append(msg)

    for i in range(0, len(msg_buffer), 2):
        if i == len(msg_buffer) - 1:
            send(client_socket, msg_buffer[i])
            if msg_buffer[i] == 'kill\n':
                client_socket.close()
                sys.exit(0)
            ret = receive(client_socket)
            print(ret)
        else:
            msg = msg_buffer[i] + msg_buffer[i + 1]
            send(client_socket, msg)
            if msg_buffer[i] == 'kill\n':
                client_socket.close()
                sys.exit(0)
            ret = receive(client_socket)
            print(ret)
            if msg_buffer[i + 1] == 'kill\n':
                client_socket.close()
                sys.exit(0)
            ret = receive(client_socket)
            print(ret)


def run_single_msg_multiple_pkg(client_socket):
    for msg in sys.stdin:
        send(client_socket, msg[:3])
        time.sleep(0.1)
        send(client_socket, msg[3:])
        if msg == 'kill\n':
            client_socket.close()
            sys.exit(0)
        ret = receive(client_socket)
        print(ret)


def run_single_msg_single_pkg(client_socket):
    for msg in sys.stdin:
        send(client_socket, msg)
        if msg == 'kill\n':
            client_socket.close()
            sys.exit(0)
        ret = receive(client_socket)
        print(ret)


if __name__ == '__main__':
    if len(sys.argv) != 4:
        print(
            f'Usage: python3 {sys.argv[0]} <address> <port> [single_msg_single_pkg | single_msg_multiple_pkg | multiple_msg_single_pkg]')
        sys.exit(6)
    client_socket = create_socket(sys.argv[1], int(sys.argv[2]))
    client_socket.settimeout(5)

    if sys.argv[3] == 'single_msg_single_pkg':
        run_single_msg_single_pkg(client_socket)
    elif sys.argv[3] == 'single_msg_multiple_pkg':
        run_single_msg_multiple_pkg(client_socket)
    elif sys.argv[3] == 'multiple_msg_single_pkg':
        run_multiple_msg_single_pkg(client_socket)

    client_socket.close()
    sys.exit(0)

用这个软件运行我的代码,没有一个测试工作。基本上,在接收和发送第一条消息后,我的服务器会停止与客户端的通信,每次测试都失败。这可能与我如何处理代码中的字符串有关,但老实说,我在这里不知所措。至少有人可以在正确的方向上推动我吗?

【问题讨论】:

  • 重复。您假设recv 将返回完整的消息并且仅返回这个。这不是 TCP 中recv 的工作方式——因为首先在 TCP 中没有消息这样的东西。测试客户端通过发送带有多次发送的“消息”(请参阅​​run_single_msg_multiple_pkg)和在一次发送中发送多条消息(请参阅run_multiple_msg_single_pkg),故意尝试您的代码是否正确处理了这部分。
  • @Afshin:我认为老师没有错(请参阅我对您答案的评论)。

标签: python c sockets tcp


【解决方案1】:

主要问题是从 python 测试应用程序发送的字符串不是 null-terminated,但您认为它是。

在 python 中:

msg = msg.encode('ascii')

msg 以 ASCII 格式编码,但encode() 方法返回一个bytes 对象,该对象不是以空值结尾的,但是当您在服务器中收到它时:

recv(sockfd, client_message, sizeof(client_message), 0);

printf("> %s", client_message);
strcpy(str, client_message);

您的代码中有代码假定您在client_message 中收到的是一个以空字符结尾的字符串。如果您在recv() 之前将client_message 归零,您可能不会看到这个问题。

我在您的代码中看到的错误:

  • 您假设消息是空终止的。但似乎它们应该是\n 分开的。
  • 始终检查recv 的返回。您需要正确处理返回值。您可能会遇到错误,或者您可能会获得全部数据的一部分。

另外,在一个线程中运行recv循环。虽然是一个建议,但您通常总是希望这样做。

【讨论】:

  • “我认为你老师的python客户端代码(或者更好的说协议)是不正确的。” - 不需要在每条消息前加上长度来区分消息.另一种方法是有一个清晰的分隔符,就像在 HTTP、SMTP 和许多基于文本的协议中所做的那样。在这种情况下,协议在消息之间有一个明确的分隔符 - \n
  • @SteffenUllrich 是的,但需要定义。无论如何,我个人认为老师为测试提供的代码也是不正确的。所以不能完全责怪学生。
  • “但它需要被定义” - 它可能已经被定义了。 OP 没有提供完整的任务,只提供了假定相关的部分。
  • @SteffenUllrich 我在 python 代码的发送方法中看不到 \n。我不是蟒蛇专家。所以也许我还是想念它。
  • for msg in sys.stdin: ... - 每个msg 的末尾都会有一个\n。代码中有多个地方使用了\n,这清楚地表明它应该作为分隔符。参见例如receive
猜你喜欢
  • 2021-08-06
  • 2019-12-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-09-02
相关资源
最近更新 更多