C套接字发送/接收缓冲区类型

C sockets send/recv buffer type

本文关键字:缓冲区 类型 套接字      更新时间:2023-10-16

我正在使用unix套接字,当我的缓冲区为char类型时,我可以发送()和recv()数据。发送和接收字符串)。我使用Beej的套接字指南,使用的示例是用于发送/接收字符串。

现在我想在一条消息中发送/接收不同类型的数据。
例如,在一条消息中,我想发送一个整数,一个字符串,一个双精度和一个浮点数。我该怎么做呢?更具体地说,我的消息"缓冲区"应该是什么类型?

send和recv的原型:

int recv (int socket, void *buffer, size_t size, int flags)
int send (int socket, void *buffer, size_t size, int flags)

我对C/c++和指针没有太多的经验,所以这可能是一个新手问题。

如果有人能指引我正确的方向,我将非常感激。由于

除非您计划发送大量数据(数千字节)并且经常(每秒几个数据包),否则我建议您将数据转换为字符串(又名"序列化数据")并以这种方式传递。它有几个好处:

  1. 它是可移植的-无论intfloatdouble是什么大小-或者字段之间的填充在结构中。
  2. 很容易调试(你可以看看数据,说它是对还是错)
  3. 发送/接收机器的字节顺序无关紧要。
另一方面,发送二进制数据是复杂的,因为你需要担心数据字段的单个大小及其内部表示(字节顺序,double如何以二进制表示,结构内数据字段的填充,不能传递指针,等等)。唯一的好处是二进制数据更加紧凑。但是,只有当您有很多kb和/或每秒发送大量数据包时,这才有意义。

您需要在发送端序列化对象,并在接收端反序列化。c++中有很多这样的库,比如Boost Serialization,如果你想重新发明轮子,你可以自己动手。

最好的方法是将想要传递的信息封装在一个结构中。然后传递结构的地址和长度。注意,buffer是一个void*,所以这允许你传递包含你的信息的结构的地址。

下面是我用来安全复制struct的一小段代码,例如工具,
struct message
{
     size_t total_len;
     size_t filename_len;
     char *filename;
     size_t text_len;
     char *text;
     char *iv;
     char *salt;
     unsigned char *hmac;
};

然后在send_message()中将messagewrite序列化为socket

bool send_message(const struct message *msg, const char *ip, const char *port)
{
    ...
   connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
   // Serialize to file
   serialize(msg, "temp");
   // Transfer the file over the network
   fp = fopen("temp", "rb");
   while((n = fread(buffer, 1, MAX_BUF_LEN, fp)) != 0)
   {
       write(sockfd, buffer, n);
   }
   fclose(fp);
   remove("temp");
   close(sockfd);
}

根据@vitaut的建议,您可以使用库来serialize,我已经实现了它作为学习经验。

要回答这个问题("我如何发送结构体?"),你只需发送一个指向结构体的指针。

struct foo
{
  int a;
  int b;
};
struct foo s;
s.a = 10;
s.b = 20;
send(socket,&s,sizeof(s),0);

真的很简单。现在,另一边必须是相同的(也就是说,系统A内存中布局的结构必须与系统B内存中布局的结构匹配)。更好的方法是使用更具体的类型和一些函数来正确排序数据:

#ifndef __GNUC__
#  define __attribute__(x)
#endif
#ifdef __SunOS
#  pragma pack(1)
#endif
struct foo
{
  uint32_t a;
  uint32_t b;
} __attribute__((packed));
#ifdef __SunOS
#  pragma pack()
#endif
struct foo s
/* to send */
s.a = htonl(10);
s.b = htonl(20);
send(socket,&s,sizeof(s),0);
/* to receive */
recv(socket,&s,sizeof(s),0);
s.a = ntohl(s.a);
s.b = ntohl(s.b);

你可以看到,当你想通过网络"可移植"地发送二进制数据时,它开始变得相当快地特定于系统,这就是为什么人们说要转换为文本,或者很可能使用已经编写好的序列化库。

这取决于它的可移植性。

最简单的方法是将所有内容转换为用逗号或分号分隔的ASCII,然后在接收端再次解析。

您也可以将其转换为网络字节顺序并以二进制形式发送。