【问题标题】:Sending an int over TCP (C-programming)通过 TCP 发送 int(C 编程)
【发布时间】:2010-12-16 19:09:15
【问题描述】:

我有一个服务器和一个客户端程序(都在同一台机器上运行)。客户端能够向服务器发送一个带有“ID”、“size”等成员的结构。然后我希望服务器将 ID 成员(只是一个整数)作为 ACK 发送回客户端以进行验证,但是尽管能够毫无问题地发送结构,但我还是无法弄清楚..

这是来自 server.c 的代码:

/* having just recieved the struct */

int ACK_ID = struct_buffer->message_ID;
result = send(CLIENT_socket, &ACK_ID, sizeof(int), 0);

if (result == -1) {
    close(SERVER_socket);
    printf("\n\t[ERROR] Failed to send ACK.\n");
    exit(EXIT_FAILURE);
}

这是来自 client.c 的代码:

// Recieve ACK from server
int ACK_ID;
com_result = read(CLIENT_socket, &ACK_ID, sizeof(int), 0);

if ((com_result == -1) || (ACK_ID != metablocks[index].message_ID)) {
    printf("\n\t[ERROR] Failed to send metadata. ACK: %i\n", ACK_ID);
}

当我尝试运行它时,我从 client.c 得到以下输出:

[错误] 无法发送元数据。确认:14

当然,服务器告诉我它发送 ACK 失败。我尝试发送的 ID 整数的值应该是 1,但接收到的是 14。我在这里做错了什么?

更新
所以我只是尝试了Shawley先生的建议,并得到了这个错误信息:

部分读取:未定义错误:0

首先我完全尝试了他写的内容,但后来我注意到代码将com_resultsizeof(int) 进行比较。所以我认为这是一个错字,并尝试在比较中用ACK_ID 变量替换com_result。结果一样。

更新 2
刚刚失败时在服务器上加了一个perror(),得到如下错误信息:

错误的文件描述符

我在此操作中使用与接收结构时使用的相同的套接字。这是来自 server.c 的扩展代码示例:

// Recieve connection
CLIENT_socket = accept(SERVER_socket, (struct sockaddr *)&CLIENT_address, &CLIENT_address_length);
if (CLIENT_socket == -1) {
    close(SERVER_socket);
    printf("\n\t[ERROR] Failed to accept client connection.\n");
    exit(EXIT_FAILURE);
}

printf("\n\tClient connected!\n");

int data_size;

// Read meta data from connection
data_size = sizeof(struct msg_meta);
result = read(CLIENT_socket, &meta_buffer_char, data_size, 0);
meta_buffer = (struct msg_meta *) meta_buffer_char;

if (result == -1) {
    close(SERVER_socket);
    printf("\n\t[ERROR] Failed to read from connection.\n");
    perror("\n\tRead");
    exit(EXIT_FAILURE);
} else if (result > 0) {
    printf("\n\tMessage recieved.\n");
    printf("\n");
}

// Send ACK back to client
int ACK_ID = meta_buffer->message_ID;
result = send(CLIENT_socket, &ACK_ID, sizeof(int), 0);

if (result == -1) {
    printf("\n\t[ERROR] Failed to send ACK.");
    perror("\n\tSend");
    printf("\n");
    close(SERVER_socket);
    exit(EXIT_FAILURE);
}

// Close sockets
close(SERVER_socket);
close(CLIENT_socket);

【问题讨论】:

  • 旁白:在发送和接收(分别)一个 int 时使用 htonlntohl 以确保它具有正确的字节顺序。
  • 在打印网络错误(如果有)之前无法确定。您还可以验证发送到服务器的原始结构实际上是否包含您期望的值。
  • @outis: 不,看同一个人的问题stackoverflow.com/questions/1734819/…。他知道他在便携性方面处于犯罪状态。用 little-endian 整数定义有线协议没有任何问题,如果他想要 big-endian 客户端或服务器,他只需要更改它(或开始字节交换)。
  • 结构在服务器上被验证。我知道我正在将正确的数据发送回客户端..
  • Shawley 先生假设您的发送成功,但您的问题是 服务器告诉我它发送 ACK 失败 在服务器端也尝试perror。 (顺便说一句,如果服务器发送 ACK 失败,部分读取是相当奇怪的!)

标签: c macos tcp sockets integer


【解决方案1】:

您需要检查发送所产生的错误。您应该包含错误库#include <cerrno>,然后检查全局errno 的值。有多种宏声明发生了哪种类型的错误。这将为您提供更多信息来调试它无法发送 ACK 的确切原因。

这是可能返回的错误值列表(错误值是在cerrno 中定义的宏),取自here

     [EACCES]           The SO_BROADCAST option is not set on the socket and a broadcast address is given as
                        the destination.

     [EAGAIN]           The socket is marked non-blocking and the requested operation would block.

     [EBADF]            An invalid descriptor is specified.

     [ECONNRESET]       A connection is forcibly closed by a peer.

     [EFAULT]           An invalid user space address is specified for a parameter.

     [EHOSTUNREACH]     The destination address specifies an unreachable host.

     [EINTR]            A signal interrupts the system call before any data is transmitted.

     [EMSGSIZE]         The socket requires that message be sent atomically, and the size of the message to
                        be sent makes this impossible.  IOV_MAX.

     [ENETDOWN]         The local network interface used to reach the destination is down.

     [ENETUNREACH]      No route to the network is present.

     [ENOBUFS]          The system is unable to allocate an internal buffer.  The operation may succeed when
                        buffers become available.

     [ENOBUFS]          The output queue for a network interface is full.  This generally indicates that the
                        interface has stopped sending, but may be caused by transient congestion.

     [ENOTSOCK]         The argument socket is not a socket.

     [EOPNOTSUPP]       socket does not support (some of) the option(s) specified in flags.

     [EPIPE]            The socket is shut down for writing or the socket is connection-mode and is no
                        longer connected.  In the latter case, and if the socket is of type SOCK_STREAM, the
                        SIGPIPE signal is generated to the calling thread.

     The sendmsg() and sendto() system calls will fail if:

     [EAFNOSUPPORT]     Addresses in the specified address family cannot be used with this socket.

     [EDESTADDRREQ]     The socket is not connection-mode and does not have its peer address set, and no
                        destination address is specified.

     [EISCONN]          A destination address was specified and the socket is already connected.

     [ENOENT]           A component of the pathname does not name an existing file or the path name is an
                        empty string.

     [ENOMEM]           Insufficient memory is available to fulfill the request.

     [ENOTCONN]         The socket is connection-mode, but is not connected.

     [ENOTDIR]          A component of the path prefix of the pathname in the socket address is not a direc-tory. directory.
                        tory.

     The send() system call will fail if:

     [EDESTADDRREQ]     The socket is not connection-mode and no peer address is set.

     [ENOTCONN]         The socket is not connected or otherwise has not had the peer pre-specified.

     The sendmsg() system call will fail if:

     [EINVAL]           The sum of the iov_len values overflows an ssize_t.

     [EMSGSIZE]         The socket requires that message be sent atomically, and the size of the message to
                        be sent makes this impossible, or the msg_iovlen member of the msghdr structure
                        pointed to by message is less than or equal to  or is greater than IOV_MAX.

【讨论】:

  • 没有我能发现的。但是没有错误标志是相当奇怪的。在这一点上,我和你一样一无所知。我会继续看的……
  • 我的意思是,除了前面提到的需要将整数转换为网络字节顺序并再次返回。
  • 能否请您发布一个显示此问题的最小源代码?
  • (我的意思是一个完整的最小测试程序。)
【解决方案2】:

我的猜测是read 失败导致com_result == -1。在这种情况下,ACK_ID 的值是未定义的堆栈垃圾。试试这个:

com_result = read(CLIENT_socket, &ACK_ID, sizeof(int), 0);
if (com_result < 0) {
    perror("read");
} else if (com_result != sizeof(int)) {
    /* handle partial read condition */
} else if (ACK_ID != metablocks[index].message_ID) {
    printf("\n\t[ERROR] Failed to receive metadata. ACK: %i\n", ACK_ID);
}

read() 可能失败或返回部分结果的原因有很多——毕竟这是 TCP。 Perror 实质上会为您调用strerror(errno) 并显示您提供的消息,并附有错误字符串。当像read()send() 这样的系统调用返回-1 时,它会将errno 设置为您可以使用perror()strerror() 显示的更具描述性的值。

更新 - 部分读取

至于部分读取问题,您通常可以通过 (1) 忽略它或 (2) 在循环中执行读取直到获得您期望的所有字节来解决这个问题。比如:

int status = 0;
char *byte_ptr = (char*)&ACK_ID;
ssize_t bytes_left = sizeof(ACK_ID);
while (bytes_left > 0) {
   ssize_t rc = read(CLIENT_socket, byte_ptr, bytes_left);
   if (rc < 0) {
       if (errno == EINTR) {
           continue; /* interrupted system call */
       }
       perror("read");
       status = -1;
       break;
   } else if (rc == 0) {
       /* EOF */
       break;
   }
   bytes_left -= rc;
   byte_ptr += rc;
}
if (status == 0) {
    if (bytes_left == 0) {
        /* safely use the value stored in ACK_ID */
    } else {
        /* handle premature socket closure */
    }
}

通常它被包装在一个通用的库函数中,以使生活更轻松。如果您还没有,我建议您阅读W. Richard Steven's UNIX Network Programming, volume 1。这正是他在 readn() 库函数中所做的。

【讨论】:

  • 部分读取不会设置errno,使用perror是徒劳的
【解决方案3】:

您可以检查send 失败的原因。例如,使用perror:

if (result == -1) {
    perror("server error while sending ack");
    ....

确保不要在失败的sendperror 之间调用任何其他函数,因为这会重置errno

【讨论】:

    【解决方案4】:

    首先,在发送 int 之前尝试使用 htonl,在接收到之后使用 ntohl。 然后,您应该始终在循环中发送和接收,因此您可以发送和接收整个数据长度。

    【讨论】:

      【解决方案5】:

      Re:更新 2 - 确保您 (a) 使用了正确的文件描述符,并且 (b) 没有 close() 或 shutdown() 套接字的写入部分。

      【讨论】:

      • 不完全确定问题出在哪里,但我猜这一定是我在关闭套接字时做错了什么。现在已经重写了,它可以工作了。
      猜你喜欢
      • 2010-12-16
      • 2015-03-03
      • 1970-01-01
      • 2013-07-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-10
      相关资源
      最近更新 更多