【问题标题】:TCP client/server in cc语言中的TCP客户端/服务器
【发布时间】:2021-08-23 03:16:00
【问题描述】:

我创建了一个 TCP 客户端/服务器并获得了测试脚本,但是,除了短消息之外,所有测试都失败了。简单地说,脚本发送客户端通过从文件重定向到服务器读取的任意消息。但是,对于脚本随机创建的文件,它表示接收/发送端的消息不匹配。任何帮助将不胜感激,下面是客户端和服务器代码。

// server.c

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

#define QUEUE_LENGTH 10
#define RECV_BUFFER_SIZE 2048

/* TODO: server()
 * Open socket and wait for client to connect
 * Print received message to stdout
 * Return 0 on success, non-zero on failure
*/
int server(char *server_port) {
    int sockfd, new_fd;
    struct addrinfo hints, *servinfo, *p;
    struct sockaddr_storage their_addr;   // connector's address 
    socklen_t sin_size;
    int yes = 1;
    char s[INET6_ADDRSTRLEN];
    int rv;

    char buff[RECV_BUFFER_SIZE];
    int numBytes;

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE; // use my ip address

    if ((rv = getaddrinfo(NULL, server_port, &hints, &servinfo)) != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
        return 1;
    }

    // loop through all the results and bind to the first we can
    for(p = servinfo; p != NULL; p = p->ai_next) {
        if ((sockfd = socket(p->ai_family, p->ai_socktype,
                p->ai_protocol)) == -1) {
            perror("server: socket");
            continue;
        }

        if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes,
                sizeof(int)) == -1) {
            perror("setsockopt");
            exit(1);
        }

        if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
            close(sockfd);
            perror("server: bind");
            continue;
        }

        break;
    }

    freeaddrinfo(servinfo); // all done with this structure
    
    if (p == NULL) {
        fprintf(stderr, "server: failed to bind\n");
        exit(1);
    }

    if (listen(sockfd, QUEUE_LENGTH) == -1) {
        perror("listen");
        exit(1);
    }

    // printf("server: waiting for connections...\n");

    while (1) {
        sin_size = sizeof their_addr;
        if((new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size)) == -1) {
            perror("accept");
            continue;
        }

        if (!fork()) { // child process
            close(sockfd);    // child does not need the listener
            if ((numBytes = recv(new_fd, buff, RECV_BUFFER_SIZE -1, 0)) == -1) {
                perror("recv");
                exit(1);
            }

            buff[numBytes] = '\0';
            printf("%s", buff);

            close(new_fd);
            exit(0);
        }
        close(new_fd);
    }
    return 0;
}

/*
 * main():
 * Parse command-line arguments and call server function
*/
int main(int argc, char **argv) {
  char *server_port;

  if (argc != 2) {
    fprintf(stderr, "Usage: ./server-c [server port]\n");
    exit(EXIT_FAILURE);
  }

  server_port = argv[1];
  return server(server_port);
}
// client.c

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

#define SEND_BUFFER_SIZE 2048

/* TODO: client()
 * Open socket and send message from stdin.
 * Return 0 on success, non-zero on failure
*/
int client(char *server_ip, char *server_port)
{
    int sockfd;
    int status;
    struct addrinfo hints, *servinfo, *p;

    char send_buff[SEND_BUFFER_SIZE];
    int numbytes;
    char s[INET6_ADDRSTRLEN];

    // getaddrinfo
    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    if ((status = getaddrinfo(server_ip, server_port, &hints, &servinfo)) != 0)
    {
        fprintf(stderr, "getadrrinfo: %s\n", gai_strerror(status));
        return 1;
    }

    for (p = servinfo; p != NULL; p = p->ai_next)
    {
        if ((sockfd = socket(p->ai_family, p->ai_socktype,
                             p->ai_protocol)) == -1)
        {
            perror("client: socket");
            continue;
        }

        if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
            close(sockfd);
            perror("client: socket");
            continue;
        }

        break;
    }

    if (p == NULL) {
        fprintf(stderr, "client: failed to connect\n");
        return 2;
    }

    freeaddrinfo(servinfo);

    // reading from stdin into send_buff, then send
    if((numbytes = read(0, send_buff, SEND_BUFFER_SIZE)) != -1) {
        if (send(sockfd, send_buff, numbytes, 0) == -1) {
            perror("send");
            exit(1);
        }
    }

    close(sockfd);

    return 0;
}

/*
 * main()
 * Parse command-line arguments and call client function
*/
int main(int argc, char **argv)
{
    char *server_ip;
    char *server_port;

    if (argc != 3)
    {
        fprintf(stderr, "Usage: ./client-c [server IP] [server port] < [message]\n");
        exit(EXIT_FAILURE);
    }

    server_ip = argv[1];
    server_port = argv[2];
    return client(server_ip, server_port);
}

【问题讨论】:

  • 题外话:释放后不能再使用p,指针失效了!而是检查套接字。
  • 你的标准输入&lt; [message]去哪里了?您希望如何发送此输入?
  • 这里没有任何内容表明“接收/发送方的消息不匹配”。如果您假设一次发送 == 一次接收,那么您的假设是错误的。
  • 所以输入是通过文本文件,例如 ./client 127.0.0.1 55554

标签: c sockets


【解决方案1】:

2048 字节的大小超过了典型的MTU(请注意,TCP 位于 IP 之上,它本身是面向数据包的),因此数据很可能通过多个数据包发送。由于您只有一次调用recv,因此您可能会在 TCP/IP 堆栈将后续数据包的内容放在那里之前从接收缓冲区获取第一个数据包的内容。

宁可在一个循环中进行多次读取,并在接收到 0 个字节时退出循环(远程套接字关闭):

while((numBytes = recv(new_fd, buff, RECV_BUFFER_SIZE -1, 0)) > 0)
{
    buff[numBytes] = '\0';
    printf("%s", buff);
}

if(numBytes < 0)
{
    // error handling
}

【讨论】:

  • 没有“TCP 数据包”之类的东西。有 TCP 段,可以是 2^32-1 字节长,还有 IP 数据包,同上。可能您指的是 MTU,它通常但不总是
  • IP 数据报也不存在。往上看。数据报是一个UDP实体,与问题无关。实际上也没有(i)分段(ii)数据包或(iii)MTU。唯一重要的是recv() 返回时套接字接收缓冲区中的数据量。
  • @user207421 [@*%#$+!] – 首先是马虎,然后是疏忽:(我什至还没有喝过酒(或者我应该这样做以获得更好的答案??? ) – 感谢您的提示。
  • 无论如何,伙计,我已经喝过酒了 ;-) 但是小包仍然与它无关。这是一个字节流协议,仅此而已。
  • 当然可以——但是read 会获取调用时可用的那些字节。为什么这不应该发生在 底层 协议的两个数据包到达之间? recv 很肯定会在第一块数据到达时被通知唤醒,因为堆栈无法知道是否有进一步的数据包跟随以继续流。