我有一台服务器和一个客户端程序(都在同一台机器上运行)。客户端能够使用诸如“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_result
与sizeof(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);
答案 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");
....
请务必不要在失败的send
和perror
之间调用任何其他功能,因为这会重置errno
。
答案 4 :(得分:0)
首先,在发送int之前尝试使用htonl,并在接收之后使用ntohl。 然后,你应该总是发送和recv循环,所以你发送和recv数据的整个长度。