C/通过套接字发送time_t的最安全方法

c/safest way to send time_t over socket

本文关键字:方法 安全 time 套接字      更新时间:2023-10-16

>我已经设置了一个C++服务器/客户端环境,并尝试将time_t值从服务器发送到客户端(在任何服务器中都很有用)。但我遇到了一个令人头疼的问题:time_t似乎不属于任何尺寸规格。我想知道通过网络发送time_t的最安全(更便携)的方式是什么。

这是一个小例子:

time_t T = time(NULL);
unsigned char * P = (unsigned char *)&T;
// ... Convert it to network byte order, etc.
// Here, 's' would be the socket, and 'S'
// the size of the data that is going to
// be sent
send(s, P, S, 0);

我有两个问题:

  • time_t不是固定大小的
  • time_t可以是有符号或无符号整型数据类型的 typedef

我如何处理这两个问题,以便我可以在(几乎)任何架构之间安全地发送它?

提前谢谢。

编辑:在看到相同的三个答案后,我发现在这里回应更好。对不起,我之前没有澄清这一点。我更喜欢以"纯"字节发送它,尽管将其作为字符串发送不是问题。据我了解,我需要(再次)记住主机系统中time_t数据类型的符号和大小,不是吗?(谢谢和抱歉)

首先,我认为您需要决定是否要处理 C 标准使time_t的含义过于模糊的问题(它不一定以秒为单位表示,甚至没有任何有意义的数字属性,如顺序/比较)。这与每个现有和历史实现的行为相反,其中time_t以秒为单位。C 和 POSIX 也允许time_t为浮点类型;据我所知,没有实现使用它,并且在 POSIX 上它会相当有害,因为time_t的值必须是整数,就像它在struct timespec等中使用的方式一样。

如果你决定假设time_t总是自纪元以来的整数秒数,即这些值对于系统之间的交换是有意义的,那么这只是格式化它们的问题。最安全的做法是简单地转换为一个整数类型,该整数类型足够大,可以存储任何有意义的值,并且在所有系统上的大小都相同:这将是int64_t。然后使用您用于序列化int64_t的任何正常方法,以不受字节序差异的影响。

另一方面,如果您想"绝对"便携,您应该计算自己的"纪元"time_t值(标准纪元或您自己选择的纪元),然后使用difftime转换为表示"自纪元以来的秒数"的双精度,并用snprintf(buf, sizeof buf, "%.0f", diff)格式化double。请注意,在可移植 C 中计算纪元的time_t值实际上非常困难,因为大多数标准函数都以本地时间工作,而您需要通用时间。你可以用gmtimemktimelocaltime函数来做一些技巧来解决这个问题,但这并不平凡......

您可以发送由strftimegmtime结合使用生成的文本表示。该表示形式将比二进制表示形式大一点,但并不大。例如,格式字符串"%Y%j%H%M%S"生成时刻的 13 字节表示形式(不包括 NUL 字符)。

编辑:忘记我之前使用ctime的建议;它使用localtime,因此只有在客户端和服务器在同一时区时才有效。显然,asctime是不安全的,所以使用strftime.

您可以以文本形式发送它,具有固定数量的数字。这样,您就不必担心符号,大小不兼容甚至字节顺序。

更新:似乎有一个库可以提供您正在寻找的内容,请查看 Apache Portable Runtime,特别是这个关于时间例程的页面。除此之外,我想说我的回答仍然提供了一种手动实现的方法,前提是所有系统都符合 POSIX.1-2001。

我现在遇到了类似的问题,我认为有一些关于如何解决这个问题的说明可能会很好。请注意,这里的解决方案应该是 POSIX.1-2001 兼容的(在 Ubuntu 14.04 上,man tzsetman localtime提供了这样的信息,我真的没有使用过其他来源)。

使用localtime将从呼叫time获得的数据转换为struct tm(请参阅time.h):

struct tm {
int tm_sec;         /* seconds */
int tm_min;         /* minutes */
int tm_hour;        /* hours */
int tm_mday;        /* day of the month */
int tm_mon;         /* month */
int tm_year;        /* year */
int tm_wday;        /* day of the week */
int tm_yday;        /* day in the year */
int tm_isdst;       /* daylight saving time */
};

请注意,localtime会将声明为的变量设置为(再次,请参阅time.h)

extern long timezone; //seconds West of UTC 

由于现在已知所有类型,因此您可以编写自己的转换工具以使此数据网络可移植。需要考虑的事项:

  • (最好在您的软件 init 中),您必须确定您的intlong有多少字节(当然,使用sizeof)
  • 根据该信息,您可以对struct tm的每个整数字段进行主机到网络的转换(使用htonlhtons- 对于 64 位类型,您必须编写自己的)并将该信息发送到客户端(以您喜欢的任何方式)。
    如果您有时区差异,您还需要转换/发送时区信息,即存储在变量timezone中的数据(见上文)。
  • 然后,客户将不得不

    1. 将接收到的数据转换为客户端字节顺序
    2. 写入struct tm(如果涉及时区,请将收到的时区数据写入long)
    3. 如果涉及时区,请将接收到的时区数据写入long并计算时区差(计算接收到的timezone数据的差异以及通过在本地调用tzset得到的内容...),然后调整在步骤 2 中创建的struct tm变量。
    4. 获得根据客户端本地时区调整的struct tm后,使用mktime将数据转换回time_t
    5. 考虑时区差异(如果有)。