通信中如何一次性完整地接收数据

通信中如何一次性完整地接收数据

Gray(罗国辉)

 

第一节    问题来源

可能在实际开发中,特别是socket编程,串口编程等,程序员大都会遇到这样一种情况,明明发送端一次性的将一条信息发出,或者分成几次发送,但是在接收端却始终是分多次返回。如果你的write/send/sendto等函数不在一个有条件的循环中,你的read,recv等函数如果不在一个有条件的循环中,那你的程序将很不健壮,随时都有错误的可能。因为我们发送或者接收数据,都是将数据直接送给底层的buff,(当然有时也可以直接成直接使用上层buff,而不使用底层的buff,以提高效率或者满足其它要求),这样,这个buff的发送和接收将会出现很多的不定因素,一方面由协议的传输机制决定,一次发送多少位,隔多久发送一次,位间隔多少时间,协议消耗等等;另一方面则由CPU的中断系统决定,如果将要发送的一个buff刚刚发送了1/7,这时出现优先级更高的中断,此时,CPU将转向这个中断,当这个中断处理完后才能回来。这些不定因素就决定了数据接收与发送将由数据本身携带的信息来决定它的发送与接收的结果正确与否。如果你现在或者以前的程序还是单独的一个write,read就完成了,而且也没有出现过问题,那我只能说你的运气暂时比较好而以,但这并不代表这个程序是正确的,一个程序的正确是它能够处理正确的方面,也能够处理错误的方面。一个程序的生成往往是由它的正确一面开始的,然后使用若干个else来处理这些错误的方面,而这些错误的方面往往就是我们程序初稿后烦琐而漫长的调试,虽然这个调试过程可能是我们软件开发过程的重心,也是我们最为烦恼的阶段,但这也是我们提高能力,积累经验的最好时间。某些方面的程序写的再好也没什么了不起,了不起的是无论什么问题,只要自己拿在手上都能够有自己的一套分析方法去分析,将问题很清晰呈现在脑海中,然后找出最有效,最快捷的解决方案,也就是实际解决问题的能力,也就是难人可贵的“渔”,而不单单是盲目的积累“鱼”。

为什么现在企业招聘都想招有多年工作经验的人,并不是他们做事有多厉害,任何人不可能学太多东西,学的东西都是有限的,有多年工作经验的工程师常年面对各种问题,他们有自己的某种习惯,用于解决问题的习惯,他们有他们的一套奏效的办法,因为他们看过了很多种问题的解决方案。就像“熟读唐诗三百首,不会吟诗也会背”一样的道理。另一方面他们必然经历过实战,像一个有多年经验的程序员,我不相信他没有过实战经验,实战是培养一个高级工程师最有效的办法。

建议程序编写之前或者程序的调试过程中,一定要有一个程序的流程图,不必要详细的细节,但是要有框架设计流程。当你把这样一个流程图画好后,你对这个程序也就更加的明了了,你对它的把握,对它的扩展,以及对它BUG的修复都将变得更加容易。

 

第二节   如何处理

1.       问题1write/send/sendto等函数如何确保写/发送数据完整?

方式1:根据将要发送的数据字节数,循环执行write/send/sendto等函数,直到发送的字节数等于理论上想要发送的数据大小。

 

方式2:根据接收端接收数据后的返回信息判断是否发送完整,这种方式在socket编程中用的较多。

 

说明:简单的发送端处理一般采用这种方式1检验我们发送是否完整或者完成

 

2.       问题2read/recv等函数如何确保读/接收数据完整?

方式1:如果知道将要接收的数据的长度,理论长度,则可以直接根据理论长度来判断接收的数据是否完整。当然设置超时是一个程序健壮的另一表现,总之,一个程序要想它能够很稳定的运行,则必须要考虑它的各种容错处理,就像下面的程序一样,如果不设置超时,则程序将死在这里。

如下面socket编程中接收数据的例子所示:

 

intread_sock (int sockhandle, unsigned char *buf, int length){ int byte_read = -1; unsigned char *ptbuf =buf; int mlength = length; int i = 0; do { byte_read = read (sockhandle, ptbuf,mlength); if (byte_read > 0){ ptbuf = ptbuf+byte_read; mlength = mlength-byte_read;//printf("reste to read %d /n",mlength); } //printf("buffer value 0x%02X 0x%02X /n",buf[0],buf[1]);i++;//printf("waiting %d /n",i);if(i > 10000) return -1; } while (mlength > 0); return (mlength);}

 

 

方式2:根据协议判断,一般的通信都会设置一些起始位,结束位,长度位等等作为标志,用于通信识别,这时我们就可以利用这些标志位来进行数据完整性的判断,以达到完整接收数据的目的。一般我们采用两种标志来判断,一个是长度位,另一个是结束标志位。

如下面读取串口数据的例子所示,如果接收的数据中以“+CMGR:”字符串打头,则需要使用循环以等待“OK”字符结束。如果以其它字符串打头则只执行一次read函数就可以了。

 

 do{ret = read(fd,ptrheader,sizeof(RECV_BUFF));if(ret > 0){i++;if(strstr(ptrheader,CMGR) != NULL){if( (strstr(ptrheader,OK) != NULL) || (i > 1000) ){breakflag = 1;}else{do{ptrheader = ptrheader + ret;ret = read(fd,ptrheader,sizeof(RECV_BUFF));if(ret > 0){if( (strstr(ptrheader,OK) != NULL) || (i > 1000) ){breakflag = 1;}}}while(!breakflag);} }else{breakflag = 1;}}}while(!breakflag);

 

 

方式3根据接收的数据中的长度标志位,来决定应该接收多少数据才返回。这一点跟方式2大同小异,只是将判断结束标志符更改为判断数据长度是否等于数据头中的数据位标志。这里就不再写例子了(lazy …)

 

 

3.       问题3当返回的数据先后并不一致时,就像上面的数据一样,可能结束字符串“OK”虽然是最后发送的,但并不一定是最后接收到的(当然如果是分多次发送的话),因为在无论是CPU还是设置或者网络情况都可能会导致数据的顺序不一致,这样一来,我们就需要一种方式来处理,以重组我们的数据。

方式1我们一般采用的方式是每次调用read/recv返回的数据,我们需要放在一些buff中,然后对这些buff采用字符串操作函数对其进行协议重组,判断它的完整性,提出有用的完整的数据,去掉多余的信息。如果一条信息不完整,则可以直接丢弃或者报错。

 

 

 

 

说明:

1.       对于socket和串口编程,一般都会使用select检测是否有数据可读取,以避免read/recv函数在一个循环中一直轮询而高度占用CPU资源。

2.       对于多包的数据发送,一般我们会在每个包的包头加入用户协议,包括包的特殊识别位,顺序号位,包大小位等等。

你可能感兴趣的:(通信中如何一次性完整地接收数据)