和其他编程语言一样,C语言程序开发中也是有许多好用的库函数的,借助这些库函数,C语言程序员能够较为简单的开发出各种有用的程序。
调用recv()函数时,分配给buffer的问题
例如C语言中的socket库允许程序员进行TCP/IP通信编程,要使用该库仅需包含相应的头文件,再调用相应的库函数即可,无需关心庞大繁杂的TCP/IP协议栈。以recv()函数为例,它的C语言原型如下,请看:
ssize_trecv(intsockfd,void*buffer,size_tlen,intflags);
读者应注意recv()函数的第二个参数buffer,它是用于从TCP/IP通信缓冲区接收数据的内存段。作为C语言程序员,这里有一个敏感点:调用recv()函数时,分配给buffer的内存应该多大呢?
如果分配给buffer的内存过小,则当缓冲区的数据长度较长时,容易造成内存溢出或者数据截断的风险。如果分配给buffer的内存过大,则又会造成浪费。况且,到底多大算“过大”呢?
况且,到底多大算“过大”呢?那么,能否让buffer动态改变大小呢?如果缓冲区内的数据很长,则将buffer分配大一点,否则就分配小一点。遗憾的是,一般我们并不能事先得知缓冲区内的数据长度。而C语言也不支持动态类型,传递给recv()函数的buffer内存只能是事先分配好的固定大小内存。
虽然,C语言有realloc()这样的库函数用于重新分配内存大小,但是recv()函数是已经封装好的,对程序员不可见,我们不能指望recv()函数一定会自己根据数据长度重新分配合适大小的内存。所以,在使用socket库时,应该弄清楚以下几个问题:
怎样确定recv()函数的参数buffer长度?如果recv()函数的buffer长度小于缓冲区实际的数据长度会发生什么?怎样确定recv()函数是否已经将缓冲区内的数据完全取出?要回答以上几个问题,首先应该弄清楚的是当前正在使用的究竟是流(stream)socket还是数据报(datagram)socket。(一般来说,TCP通信一般使用流socket,而UDP通信一般使用数据报socket。)
buffer参数究竟应该设置多大呢?那么,recv()函数用于接收数据的buffer参数究竟应该设置多大呢?
对于流socket,buffer的大小并不是特别重要,因为数据都是流式传输的,就通信协议本身而言,“数据并没有大小之分”,因此buffer的大小设置为实际项目需要的最大的单个消息/命令大小就可以了,简言之,就是什么大小方便,就设置成什么样的大小即可。
不过要是数据报socket,就不能这样做了,此时应该使用足够大的buffer来保存应用程序级协议锁发送的最大数据报。如果当前使用的是UDP通信,那么一般来说,应用程序级协议不应该发送大于字节的数据包,因为过长的数据报肯定会被拆分成若干个小段分别发送。
如果recv()函数的buffer长度小于缓冲区实际的数据长度会发生什么?
对于流socket,这个问题稍显奇怪。因为正如前文所述,谈论数据流的大小是没有意义的,数据流仅仅只是连续的字节流而已。如果buffer长度小于缓冲区实际的数据长度,那么recv()函数仅会将buffer填满,然后返回。缓冲区内剩余的数据可以再调用recv()函数得到。
缓冲区内剩余的数据可以再调用recv()不过对于数据报socket,超出的数据就会被丢弃了。
怎样才能知道是否已经将缓冲区内实际的数据全部取出了呢?
如果使用的是流socket,显然,仅根据socket通信本身,是无法得知是否已经完全接受数据。此时,需要程序员在应用程序级协议中自定义某种确定消息结尾的方法,比如一段特殊的字节序列,或者数据开头的几个用于描述总体数据长度的字节等等。
对于数据报socket,每次调用recv()函数,总是返回一个完整的数据报。
小结
到这里相信读者应该明白C语言程序开发中使用socket库关于recv()函数buffer参数长度的基本原理了。到底有没有一种方法让buffer的长度随着缓冲区内数据长度动态改变呢?答案是否定的,对于C语言程序开发,我们最多能够使用realloc()函数重新分配buffer的大小。
点个赞再走吧欢迎在评论区一起讨论,质疑。文章都是手打原创,每天最浅显的介绍C语言、linux等嵌入式开发,喜欢我的文章就