我想通过 C 中的 TCP 套接字连接向我的 Binder 类发送消息。我必须使用 write( ) 方法。在一条消息中发送所有信息的最佳方法是什么?
3 回答
如果您的数据是相关的,您可以在单独的标头中创建一个结构,并在客户端和服务器代码中使用它,并发送此结构的变量。如果它不相关,那么我不确定您为什么需要将它们作为一条消息发送。
如果您想通过一次写入和一次读取来传输和接收数据,那么您必须使用数据报套接字。由于数据报套接字是无连接的,因此您不能使用write
/ read
。相反,您使用sendto
/recvfrom
或sendmsg
/ recvmsg
。由于数据报套接字是不可靠的,因此您必须实现自己的协议来容忍乱序数据传递和数据丢失。
如果您不想处理数据报套接字的不可靠特性,那么您需要一个流套接字。由于连接了流套接字,因此传输的数据得到保证并且有序。如果您发送的数据始终具有相同的大小,那么您可以通过对send
调用使用阻塞模式然后在调用中传递MSG_WAITALL
来模拟数据报recv
。
#define MY_MSG_SIZE 9876
int my_msg_send (int sock, const void *msg) {
int r, sent = 0;
do {
r = send(sock, (const char *)msg + sent, MY_MSG_SIZE - sent,
MSG_NOSIGNAL);
if (r <= 0) {
if (r < 0 && errno == EINTR) continue;
break;
}
sent += r;
} while (sent < MY_MSG_SIZE);
if (sent) return sent;
return r;
}
int my_msg_recv (int sock, void *msg) {
int r, rcvd = 0;
do {
r = recv(sock, (char *)msg + rcvd, MY_MSG_SIZE - rcvd, MSG_WAITALL);
if (r <= 0) {
if (r < 0 && errno == EINTR) continue;
break;
}
rcvd += r;
while (rcvd < MY_MSG_SIZE);
if (rcvd) return rcvd;
return r;
}
请注意,该软件仍然必须处理某些错误情况。在 的情况下EINTR
,需要重试 I/O 操作。对于其他错误,数据的交付或检索可能不完整。但一般来说,对于阻塞套接字,我们期望上面的do
-while
循环只进行一次迭代。
如果您的消息并不总是相同的大小,那么您需要一种方法来构建消息。框架消息意味着您需要一种方法来检测消息的开头和结尾。也许通过流式套接字构建消息的最简单方法是在消息之前加上它的大小。然后接收者将首先读出大小,然后读取消息的其余部分。您应该能够轻松地调整示例代码my_msg_send
并my_msg_recv
执行此操作。
最后,还有你的信息本身的问题。如果消息的大小并不总是相同,这可能意味着消息中有一个或多个可变长度记录。示例是值数组或字符串。如果发送者和接收者都同意记录的顺序,那么在每个可变长度记录之前加上它的长度就足够了。因此,假设您的消息具有以下结构:
struct my_data {
const char *name;
int address;
int *types;
int number_of_types;
};
然后你可以代表这样的一个实例struct my_data
:
NAME_LEN : 4 bytes
NAME : NAME_LEN bytes
ADDRESS : 4 bytes
TYPES_LEN : 4 bytes
TYPES : TYPES_LEN * 4 bytes
NAME_LEN
将从 获得strlen(msg->name)
,并将从TYPES_LEN
获得其值msg->number_of_types
。当您发送消息时,前面会加上上面消息的总长度。
我们一直使用 32 位数量来表示长度,这可能足以满足您的目的。通过套接字传输数字时,发送方和接收方必须就数字的字节顺序达成一致。即,数字 1 表示为0.0.0.1
还是表示为1.0.0.0
。这通常可以使用网络字节排序来处理,后者使用前者。套接字头文件提供了htonl
将 32 位值从主机的本机字节顺序转换为网络字节顺序的宏。这在存储一个值时使用,比如NAME_LEN
. 接收方将使用相应的宏ntohl
将传输的值恢复为主机使用的表示。如果主机本机字节顺序已经与网络字节顺序匹配,那么宏当然可以是无操作的。在异构环境中发送和接收数据时,使用这些宏特别重要,因为发送方和接收方可能具有不同的主机字节顺序。