提问人:BigBurger 提问时间:11/7/2023 最后编辑:BigBurger 更新时间:11/14/2023 访问量:87
通过 Unix 套接字传递数据的正确方法是什么?
What is the correct way of passing data through Unix sockets?
问:
我正在做一个个人项目,试图更好地理解Unix上的进程间通信。我有两个用 C 编译的二进制文件,我正在尝试使用 Unix 套接字将数据从一个进程传递到另一个进程。
我想使我的发送/接收函数尽可能通用,以便能够使用相同的消息结构传递任何类型的数据(int、char、复杂结构):
enum DataType
{
INT_TYPE,
FLOAT_TYPE,
CHAR_TYPE,
STRUCT_TYPE,
};
struct Message
{
int identifier;
enum DataType data_type;
void* data;
size_t data_length;
};
这是我想出的发送函数:
ssize_t Send_message(const int pSocket, struct Message pMessage)
{
// Send the message over the socket
ssize_t bytes_sent = send(pSocket, &pMessage, sizeof(struct Message), 0);
if (bytes_sent == -1)
{
perror("Error in ipc.c, Send_message: Error sending message");
return -1;
}
if (bytes_sent != sizeof(struct Message))
{
fprintf(stderr, "Error in ipc.c, Send_message: Incomplete message sent\n");
return -1;
}
if(pMessage.data_length > 0 && pMessage.data != NULL)
{
bytes_sent += send(pSocket, pMessage.data, pMessage.data_length, 0);
if (bytes_sent == -1)
{
perror("Error in ipc.c, Send_message: Error sending message");
return -1;
}
if (bytes_sent != pMessage.data_length + sizeof(struct Message))
{
fprintf(stderr, "Error in ipc.c, Send_message: Incomplete message sent\n");
return -1;
}
}
printf("\nSent message with Request Type : %d, Identifier :%d, Data Lenght : %d \n", pMessage.request_type, pMessage.identifier, pMessage.data_length);
return bytes_sent;
}
不过,尽可能通用的最好方法是将我想要传递的数据转换为空白*,然后在接收端将其转换回正确的类型。发送过程示例:
struct Message response;
// ** Input here response.identifier
// ** Input here response.data_type
// ** Input here response.data_length
char *string_val = "HELLO WORLD";
int int_val = 42;
if(received_message.data_type == CHAR_TYPE)
{
response.data = (void*)string_val;
}
if(received_message.data_type == INT_TYPE)
{
response.data = (void*)&int_val ;
}
Send_message(pSocket, response);
这非常适合基本类型。但是,如果我想传递复杂的结构,例如:
typedef struct {
int subparam1;
float subparam2;
char * subparam3;
} SubConfiguration;
SubConfiguration subconf;
// ** Fill in the struct
response.data = (void*)&subconf;
Send_message(pSocket, response);
-- 编辑 添加Receive_message以接收消息
ssize_t Receive_message(const int pSocket, struct Message *pMessage)
{
// Receive the message into the buffer
ssize_t bytes_received = recv(pSocket, pMessage, sizeof(struct Message), 0);
if (bytes_received != sizeof(struct Message))
{
perror("\n Error in ipc.c, Receive_message: Error receiving message");
return -1;
}
if(pMessage->data_length > 0 )
{
pMessage->data = malloc(pMessage->data_length);
bytes_received += recv(pSocket, pMessage->data, pMessage->data_length, 0);
if (bytes_received != pMessage->data_length + sizeof(struct Message))
{
perror("\n Error in ipc.c, Receive_message: Error receiving message");
return -1;
}
}
printf("\nReceived message with Request Type : %d, Identifier :%d, Data Lenght : %d \n", pMessage->request_type, pMessage->identifier, pMessage->data_length);
return bytes_received;
}
现在我在接收端得到的只是结构的 int 和 float 值。我输入的字符*无法访问。
我的问题是:是否有可能做我想做的事情?我做错了什么?我开始考虑集成像 protobuf 这样的协议缓冲区来正确序列化和反序列化我的数据:在我的情况下有必要吗?
答:
“现代”(这意味着基本上每个系统不仅仅是过去 40 年左右开发的微控制器)系统确实具有虚拟内存。这意味着每个进程都有自己的虚拟地址范围,独立于其他进程。
如果一个进程,让我们调用进程 A,需要内存,进程 A 必须从内核请求它(在 unix 上可以使用系统调用)。然后,内核(或以后,如果使用延迟分配)为进程 A 保留物理内存。假设物理地址从 0x12345600 开始,但进程 A 可能不会使用指向地址 0x12345600 的指针访问它,但使用虚拟地址,假设它是地址0xABCDEC00。CPU 会自动将虚拟地址0xABCDEC00转换为进程 A 的物理0x12345600。mmap()
现在,当进程 A 将指向地址的指针发送到进程 B 0xABCDEC00时。当进程 B 想要访问 0xABCDEC00 时,该地址上没有映射进程 B 的物理地址,从而导致段错误。或者进程 B 确实在地址 0xABCDEC00 上映射了某些内容(else),然后访问它而不是物理地址0x12345600(导致不可预测的行为,这就是为什么在 C 中访问此地址会导致 UB)。
这就是为什么在接收器中指向无处或一些不相关的数据的原因。这是行不通的。void* data;
也许您读过虚拟内存、地址转换和 MMU(内存管理单元)的信息。
如何避免这种情况:
您可以将数据写入套接字中。这意味着您要传输的所有数据都包含在 or 调用中。write()
send()
或者,您可以保留共享内存(也可以使用 )。如果操作正确,则可以将指向该共享内存的指针发送到进程 B,进程 B 可以访问它。mmap()
我想使我的发送/接收函数尽可能通用,以便能够使用相同的消息结构传递任何类型的数据(int、char、复杂结构):
这可能不是最好的主意,因为这增加了您可以避免的大量复杂性。除了你的意思是你使用字节流(本质上是套接字、管道和文件),这是非常通用的,但你不必编写任何新函数,因为已经存在的函数可以做到这一点。
评论
mmap
exec()
exec
exec
评论
send(pSocket, &pMessage, sizeof(struct Message), 0)
发送包含成员的 a。接收器如何处理该指针?struct Message
void* data;
char
char *
SubConfiguration
STRUCT_TYPE
INT_TYPE,, FLAOT_TYPE, STRING_TYPE
float, int
struct