和其他编程语言一样,C语言程序开发中也是有许多好用的库函数的,借助这些库函数,C语言程序员能够较为简单的开发出各种有用的程序。
例如C语言中的 socket 库允许程序员进行 TCP/IP 通信编程,要使用该库仅需包含相应的头文件,再调用相应的库函数即可,无需关心庞大繁杂的 TCP/IP 协议栈。以 recv() 函数为例,它的C语言原型如下,请看:
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
读者应注意 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。)
那么,recv() 函数用于接收数据的 buffer 参数究竟应该设置多大呢?
对于流 socket,buffer 的大小并不是特别重要,因为数据都是流式传输的,就通信协议本身而言,“数据并没有大小之分”,因此 buffer 的大小设置为实际项目需要的最大的单个消息/命令大小就可以了,简言之,就是什么大小方便,就设置成什么样的大小即可。
不过要是数据报 socket,就不能这样做了,此时应该使用足够大的 buffer 来保存应用程序级协议锁发送的最大数据报。如果当前使用的是 UDP 通信,那么一般来说,应用程序级协议不应该发送大于 1400 字节的数据包,因为过长的数据报肯定会被拆分成若干个小段分别发送。
如果 recv() 函数的 buffer 长度小于缓冲区实际的数据长度会发生什么?
对于流 socket,这个问题稍显奇怪。因为正如前文所述,谈论数据流的大小是没有意义的,数据流仅仅只是连续的字节流而已。如果 buffer 长度小于缓冲区实际的数据长度,那么 recv() 函数仅会将 buffer 填满,然后返回。缓冲区内剩余的数据可以再调用 recv() 函数得到。
不过对于数据报 socket,超出的数据就会被丢弃了。
怎样才能知道是否已经将缓冲区内实际的数据全部取出了呢?
如果使用的是流 socket,显然,仅根据 socket 通信本身,是无法得知是否已经完全接受数据。此时,需要程序员在应用程序级协议中自定义某种确定消息结尾的方法,比如一段特殊的字节序列,或者数据开头的几个用于描述总体数据长度的字节等等。
对于数据报 socket,每次调用 recv() 函数,总是返回一个完整的数据报。
小结
到这里相信读者应该明白C语言程序开发中使用 socket 库关于 recv() 函数 buffer 参数长度的基本原理了。到底有没有一种方法让 buffer 的长度随着缓冲区内数据长度动态改变呢?答案是否定的,对于C语言程序开发,我们最多能够使用 realloc() 函数重新分配 buffer 的大小。
https://stackoverflow.com/questions/2862071/how-large-should-my-recv-buffer-be-when-calling-recv-in-the-socket-library