通过 Unix 套接字传递数据的正确方法是什么?

问题描述 投票:0回答:1

我正在开展一个个人项目,试图更好地理解 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;
}

我认为尽可能通用的最佳方法是将我想要传递的数据转换为 void*,然后在接收端转换回正确的类型。发送过程示例:

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 值。我放入的 char* 无法访问。

我的问题是:是否可以做我想做的事情?我做错了什么?我开始考虑集成像 protobuf 这样的协议缓冲区来正确序列化和反序列化我的数据:在我的情况下有必要吗?

c unix protocol-buffers ipc unix-socket
1个回答
0
投票

“现代”(这意味着基本上每个系统都不仅仅是过去 40 年左右开发的微控制器)系统确实具有虚拟内存。这意味着每个进程都有自己独立于其他进程的虚拟地址范围。

如果一个进程(让我们调用进程A)需要内存,进程A必须向内核请求内存(在unix上可以使用

mmap()
系统调用)。然后,内核(或稍后,如果使用延迟分配)为进程 A 保留物理内存。假设物理地址从 0x12345600 开始,但进程 A 可能无法使用指向地址 0x12345600 的指针来访问它,而是使用虚拟地址来访问它。是地址0xABCDEC00。 CPU 自动将进程 A 的虚拟地址 0xABCDEC00 转换为物理地址 0x12345600。

现在,当进程A将指向地址0xABCDEC00的指针发送给进程B时。当进程B想要访问0xABCDEC00时,要么没有进程B映射到该地址的物理地址,从而导致段错误。或者进程 B 确实在地址 0xABCDEC00 处映射了某些内容(其他),然后访问该地址而不是物理地址 0x12345600(导致不可预测的行为,这就是为什么在 C 中访问此地址会导致 UB)。

这就是为什么接收器中的

void* data;
要么指向任何地方,要么指向一些不相关的数据。这是行不通的。

也许您读过有关虚拟内存、地址转换和 MMU(内存管理单元)的内容。

如何避免这种情况:

您可以将数据写入套接字中。这意味着您想要传输的所有数据都包含在

write()
send()
调用中。

或者您可以保留共享内存(也可以使用

mmap()
)。如果操作正确,则可以将指向该共享内存的指针发送给进程 B,并且进程 B 可以访问它。

我想让我的发送/接收函数尽可能通用,以便能够使用相同的消息结构传递任何类型的数据(int、char、复杂结构):

这可能不是最好的主意,除非你的意思是你使用字节流(这本质上就是套接字、管道和文件),但是你不必编写任何新函数,因为已经存在的函数可以做那个。

© www.soinside.com 2019 - 2024. All rights reserved.