用 C 语言的简单字节协议读/写大整数或浮点数



免责声明:问题的作者对Erlang有一般的了解,对C有基本的(但不断增加的(知识。

我正在使用 read() 函数来读取我的 port.c 程序从互操作性教程用户指南中提供的 Erlang 端口示例获得的字节("Erlang 编程"一书的第 12 章也有描述(。

但我倾向于认为这个问题根本不与 Erlang 相关,因为我得到的错误值(例如 231 而不是 999(来自 C 端。

问题是该协议不适用于超过 255 的参数(否则效果很好(。我想它与byte类型和read_exact()实现有关,但我不知道如何修复它或使其可以在其中传递float值。

为了理解这段代码,我已经读了一半的K&R书,但我仍然卡住了。

这是代码:

实际的 C 函数:

/* complex.c */
int foo(int x) {
  return x+1;
}
int bar(int y) {
  return y*2;
}

C 端口:

/* port.c */

typedef unsigned char byte;
int main() {
  int fn, arg, res;
  byte buf[100];
  while (read_cmd(buf) > 0) {
    fn = buf[0];
    arg = buf[1];
    if (fn == 1) {
      res = foo(arg);
    } else if (fn == 2) {
      res = bar(arg);
    }
    buf[0] = res;
    write_cmd(buf, 1);
  }
}

缓冲区管理:

/* erl_comm.c */
typedef unsigned char byte;
read_cmd(byte *buf)
{
  int len;
  if (read_exact(buf, 2) != 2)
    return(-1);
  len = (buf[0] << 8) | buf[1];
  return read_exact(buf, len);
}
write_cmd(byte *buf, int len)
{
  byte li;
  li = (len >> 8) & 0xff;
  write_exact(&li, 1);
  li = len & 0xff;
  write_exact(&li, 1);
  return write_exact(buf, len);
}
read_exact(byte *buf, int len)
{
  int i, got=0;
  do {
    if ((i = read(0, buf+got, len-got)) <= 0)
      return(i);
    got += i;
  } while (got<len);
  return(len);
}
write_exact(byte *buf, int len)
{
  int i, wrote = 0;
  do {
    if ((i = write(1, buf+wrote, len-wrote)) <= 0)
      return (i);
    wrote += i;
  } while (wrote<len);
  return (len);
}

Erlang port:

-module(complex1).
-export([start/1, stop/0, init/1]).
-export([foo/1, bar/1]).
start(ExtPrg) ->
    spawn(?MODULE, init, [ExtPrg]).
stop() ->
    complex ! stop.
foo(X) ->
    call_port({foo, X}).
bar(Y) ->
    call_port({bar, Y}).
call_port(Msg) ->
    complex ! {call, self(), Msg},
    receive
    {complex, Result} ->
        Result
    end.
init(ExtPrg) ->
    register(complex, self()),
    process_flag(trap_exit, true),
    Port = open_port({spawn, ExtPrg}, [{packet, 2}]),
    loop(Port).
loop(Port) ->
    receive
    {call, Caller, Msg} ->
        Port ! {self(), {command, encode(Msg)}},
        receive
        {Port, {data, Data}} ->
            Caller ! {complex, decode(Data)}
        end,
        loop(Port);
    stop ->
        Port ! {self(), close},
        receive
        {Port, closed} ->
            exit(normal)
        end;
    {'EXIT', Port, Reason} ->
        exit(port_terminated)
    end.
encode({foo, X}) -> [1, X];
encode({bar, Y}) -> [2, Y].
decode([Int]) -> Int.

我做了一个愚蠢的改变尝试

typedef unsigned char byte;

typedef int byte;

但它没有用。

实际上,有两个问题:

  • 如果我们使用大于 255 的参数调用端口(例如 foo(256)来自 Erlang 端口(,执行将在 read_cmd(( 内的 read(( 处终止,i = 0;
  • 如果我们使用小于 255 的参数调用端口,但函数的结果大于 255(比如 int foo(int x) { return x+1000; } ,那么程序不会终止,但我们得到了一些意想不到的值到我们的 Erlang 端口;

所以,问题是:我应该怎么做才能使协议适用于更大的数字甚至浮点数?

您可能遇到的任何计算机上的字节只能保存 -128 到 127(有符号(或 0 到 255(无符号(之间的值;任何其他内容都需要在两者之间封送数据。 http://www.erlang.org/documentation/doc-5.6/pdf/tutorial.pdf 是官方的 Erlang 教程,用于在 Erlang 和其他语言之间封送数据,并包含 C 语言的示例。

read_cmd 中已经存在将byte编组为较大结构的示例。

...
  int len;
  if (read_exact(buf, 2) != 2)
    return(-1);
  len = (buf[0] << 8) | buf[1];
...

这将前两个bytebuf中取出,并将它们视为一个int(一个四byte结构(。据此,double是 8 个字节,因此理论上,您应该能够应用相同的方法。你会想做这样的事情(未经测试,几乎保证不起作用,仅用于说明目的,等等(:

double marshall_eight(byte *buff) {
  int i;
  double tmp, res;
  for(i=0; i<8;i++) {
    tmp = buff[i] << 8*i;
    res += tmp;
  }
  return res;
}

假设从 Erlang 转换为字节数组而不是字符数组。

代码有点松散,最简单的答案...

while (read_cmd(buf) > 0) { 
 fn = buf[0];
    arg = buf[1];

应该是:

while (arg=read_cmd(buf) > 0) {
 fn = buf[0];

您的代码将只处理字节和整数,它不能处理浮点数和双精度数。

写入 cmds 中还有其他错误。

------------编辑-----------------以下是您要在 c 中实现的目标的示例:

 #include <stdio.h>
 #include <math.h>
 /*
  * With certain compilers  __attribute__((transparent_union)) 
  * can be used to ease handling of an unknown type.
  */
 union  u_unionalue
 {
     char                char_union;
     unsigned char       u_char_union;
     short               s_int_union;
     unsigned short      us_int_union;
     int                 int_union;
     unsigned int        u_int_union;
     long                l_int_union;
     unsigned long       ul_int_union;
     long long           ll_int_union;
     unsigned long long  ull_int_union;
     float               float_union;
     double              double_union;
     long double         l_double_union;
 };
 enum e_type
 {
     char_enum    ,
     u_char_enum  ,
     s_int_enum   ,
     us_int_enum  ,
     int_enum     ,
     u_int_enum   ,
     l_int_enum   ,
     ul_int_enum  ,
     ll_int_enum  ,
     ull_int_enum ,
     float_enum   ,
     double_enum  ,
     l_double_enum,
     last_type
 };
 struct s_type
 {
     int  type;
     char *name;
     union u_unionalue value;
     int   size;
     char *stringFormat;
 } as_typeList[]=
 /**
  * This is a quick example of how convoluted type handling can be in C. The 
  * non portable  __attribute__((transparent_union)) can be useful if the 
  * complier supports it. This helps to 
  * reduce the amount of casting, but these are the convoluted tricks that 
  * occur behind the scenes. C++ has to handle this in the compiler as well
  * as a result .. sometimes what you get is not what you expect.
  */
 {
     { char_enum    ,  "char"              ,  {.char_union=(1 << (-1 + sizeof( char ) * 8  ))}, sizeof( char ),"%+d" },
     { u_char_enum  ,  "unsigned char"     ,  {.u_char_union=-1} , sizeof( unsigned char ) ,"%+d" },
     { s_int_enum   ,  "short"             ,  {.s_int_union=((short)1 << (-1 + sizeof( short ) * 8))}  , sizeof( short ),"%+d" },
     { us_int_enum  ,  "unsigned short"    ,  {.us_int_union=-1}, sizeof( unsigned short ),"%+u"  },
     { int_enum     ,  "int"               ,  {.int_union = ((int)1<< (-1 + sizeof( int) * 8  ))}, sizeof( int), "%+i"  },
     { u_int_enum   ,  "unsigned int"      ,  {.u_int_union=-1}, sizeof( unsigned int ), "%+u"  },
     { l_int_enum   ,  "long"              ,  {.l_int_union=((long)1<< (-1 + sizeof( long) * 8  ))}, sizeof( long ), "%+li" },
     { ul_int_enum  ,  "unsigned long"     ,  {.ul_int_union=(long)-1}, sizeof( unsigned long ), "%+lu" },
     { ll_int_enum  ,  "long long"         ,  {.ll_int_union=(long long)-1 }, sizeof( long long ), "%+lli"},
     { ull_int_enum ,  "unsigned long long",  {.ull_int_union=((unsigned long long)1<< (-1 + sizeof( unsigned long long) * 8  ))}, sizeof( unsigned long long  ), "%+llu"},
     { float_enum   ,  "float"             ,  {.float_union=1e+37L}, sizeof( float ), "%+f"  },
     { double_enum  ,  "double"            ,  {.double_union=1e+37L}, sizeof( double ), "%+lf"  },
     { l_double_enum,  "long double"       ,  {.l_double_union=1e+37L}, sizeof( long double), "%+lle"}
 };

 /**
  * This is how your foo and bar functions should be organized this would 
  * allow handling for all your types. but the type is needed.
  */
 void sprintVal(struct s_type *typeVal, char*buf)
 {
     switch(typeVal->type)
     {
     case char_enum     :
         sprintf(buf, typeVal->stringFormat, typeVal->value.char_union);
         break;
     case u_char_enum   :
         sprintf(buf, typeVal->stringFormat, typeVal->value.u_char_union);
         break;
     case s_int_enum    :
         sprintf(buf, typeVal->stringFormat, typeVal->value.s_int_union);
         break;
     case us_int_enum   :
         sprintf(buf, typeVal->stringFormat, typeVal->value.us_int_union);
         break;
     case int_enum      :
         sprintf(buf, typeVal->stringFormat, typeVal->value.int_union);
         break;
     case u_int_enum    :
         sprintf(buf, typeVal->stringFormat, typeVal->value.u_int_union);
         break;
     case l_int_enum    :
         sprintf(buf, typeVal->stringFormat, typeVal->value.l_int_union);
         break;
     case ul_int_enum   :
         sprintf(buf, typeVal->stringFormat, typeVal->value.ul_int_union);
         break;
     case ll_int_enum   :
         sprintf(buf, typeVal->stringFormat, typeVal->value.ll_int_union);
         break;
     case ull_int_enum  :
         sprintf(buf, typeVal->stringFormat, typeVal->value.ull_int_union);
         break;
     case float_enum    :
         sprintf(buf, typeVal->stringFormat, typeVal->value.float_union);
         break;
     case double_enum   :
         sprintf(buf, typeVal->stringFormat, typeVal->value.double_union);
         break;
     case l_double_enum :
         sprintf(buf, typeVal->stringFormat, typeVal->value.l_double_union);
         break;
     }
 }

 void print_types()
 {
     int i=0;
     char buf[100];
     while(i < last_type )
     {
         sprintVal( &as_typeList[i], buf);
         printf( "Type: %-18s value=%-30s size=  %-dBytes n", as_typeList[i].name, buf, as_typeList[i].size );
         i++;
     };
 }
 int main(int argc, char** argv)
 {
     print_types();
     return(0);
 }

问题中最大的问题是 erlang 和 c 之间的消息传递。您当前的格式是/CMD/VALUE/,这至少应该是/CMD/TYPE/VALUE/,尽管邮件头和校验和页脚很常见。应升级类型,以便可以返回较大的结果值。因此,需要知道您正在传递的内容。

哦,如果将数据作为字符串传递,则可以在一定程度上适当地调整类型大小。如果管道末端之间存在差异,它还可以防止字节序问题。

最新更新