通过TCP发送int(C编程)

时间:2009-11-15 14:41:29

标签: c macos tcp sockets integer

我有一台服务器和一个客户端程序(都在同一台机器上运行)。客户端能够使用诸如“ID”,“大小”等成员向服务器发送结构。然后我希望服务器将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获得以下输出:

  

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

当然服务器告诉我它无法发送ACK。我正在尝试发送的ID整数的值应为1,但它被收到为14.我在这里做错了什么?

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

  

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

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

更新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);

5 个答案:

答案 0 :(得分: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.

答案 1 :(得分:1)

我的猜测是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()来显示该值。 / p>

更新 - 部分阅读

对于部分读取问题,通常通过(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()库函数中所做的。

答案 2 :(得分:1)

Re:update 2 - 确保你是(a)使用正确的文件描述符和(b)没有关闭()或关闭()套接字的写入部分。

答案 3 :(得分:0)

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

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

请务必不要在失败的sendperror之间调用任何其他功能,因为这会重置errno

答案 4 :(得分:0)

首先,在发送int之前尝试使用htonl,并在接收之后使用ntohl。 然后,你应该总是发送和recv循环,所以你发送和recv数据的整个长度。