如何在普通C89中读取字符长度的UTF-8字符串?

我正在用普通的C89编写一个自定义的跨平台简约TCP服务器。 (但我也会接受POSIX特定的答案。)

服务器使用UTF-8字符串,但从不查看它们。 它将所有字符串视为不可变二进制blob。

但是现在我需要接受来自客户端的UTF-8字符串,它不知道如何以字节为单位计算它们的大小。 客户端只能以字符forms传输字符串长度。 (更新:客户端使用JavaScript,实际上,“字符长度”是String.length()返回的任何内容。我假设它是实际的UTF-8字符,而不是其他字符。)

我不想在我的servlets器上添加大量依赖项。 有没有一种强大而简洁的方法来读取这个数据报? (为了这个问题,让我们说它是从FILE *读取的。)

 U ; data type marker (actually read by dispatching code)  ; UTF-8 string size in characters  ; data blob 

例:

 U 7 Юникод! 

更新:

一批数据可以包含多个数据报,因此近似读取不起作用,我需要读取确切数量的字符。

实际的UTF-8数据可能包含任何字符,因此我不能选择一个字符作为终结符 – 我不想在数据中转移它。

根据这里的信息,写一个UTF-8“读者”非常容易; UTF-8的设计使得像这样的任务变得简单。

从本质上讲,您开始阅读字符,直到您阅读客户告诉您的字符数。 根据UTF-8编码定义,您知道已经阅读了整个字符,具体来说:

如果字符仅由一个字节编码,则高位为0,其他位给出代码值(在0..127范围内)。 如果字符由多个字节的序列编码,则第一个字节具有与序列中的总字节数一样多的前导’1’位,后跟一个’0’位,后续字节全部标记通过领先的“10”位模式。

好吧,JavaScript字符串的长度属性似乎计算代码点,而不是字符,正如您所看到的(但等等!它不是代码点):

 > s1='\u0061\u0301' 'á' > s2='\u00E1' 'á' > s1.length 2 > s2.length 1 > 

虽然这与V8有关。 环顾四周似乎这就是ECMAScript标准所要求的:

https://forums.teradata.com/blog/jasonstrimpel/2011/11/javascript-string-length-and-internationalizing-web-applications

另外,检查ECMA-262,在PDF的第40-41页上,它说“字符串的长度是元素的数量(即16位值)”,然后继续说明元素是UTF-16单位。 可悲的是,这不是“代码点”。 基本上,这使得字符串长度属性相当无用。 环顾四周,我发现这个:

如何在Javascript中判断字符串是否包含多字节字符?

人物? 还是代码点? 两者不一样。 Unicode很复杂。 您可以计算UTF-8字符串的所有这些不同内容:长度(以字节为单位),代码点中的长度,字符长度,字形长度以及字形集群中的长度。 对于任何给定的字符串,所有这些可能会有所不同!

我的第一个倾向是告诉破碎的客户离开。 但假设您不能这样做,您需要询问客户端究竟在计算什么。 在字节之后计算的最简单的事情是代码点 – 毕竟这是UTF-8编码的。 之后? 字符,但您需要具有组成代码点的表,以便您可以识别构成字符的代码点序列。 如果客户计算字形或字形集群,那么你就是一个受伤的世界。 但很可能客户端会计算代码点或字符。 如果它计算代码点,那么只计算具有二进制值10xxxxxx和0xxxxxxx的字节(尽管您可能希望实现足够的UTF-8以防止过长的序列)。 如果它对字符进行计数,那么您需要识别组合标记并将它们计为关联的非组合代码点的一部分。

如果你得到的长度与你得到的字节数不匹配,你有几个选择。

  1. 一次读取一个字节并将它们组合成字符,直到您获得匹配的字符数。

  2. 添加已知的终结符并完全跳过字符串大小。 只需一次读取一个字节,直到读取终止符序列。

  3. 读取标题中列出的字节数(因为这是最小数字)。 弄清楚你是否有足够的角色。 如果没有,请阅读更多内容!

如果DATA不能包含CRLF,那么您似乎可以将CRLF用作框架分隔符。 只需忽略SIZE并读取直到CRLF。

这看起来就像我需要的东西。 希望我早些发现它:

http://bjoern.hoehrmann.de/utf-8/decoder/dfa/