深入了解recv()函数

发布时间:2023-05-20

recv()函数详解:网络编程中的数据接收利器

在进行网络编程时,接收数据是至关重要的一步。而recv()函数是实现数据接收的重要函数之一,本文将详细介绍recv()函数的使用方法和常见问题。

一、recv()函数的定义和原型

recv()函数为socket数据接收的一种通用方式。它有多种不同的用法,可以在不同的协议、不同的网络类型上使用。此函数的可选参数非常多,可以控制超时、接收标志等。 recv()函数的原型如下:

#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

其中,各个参数含义如下:

  • sockfd:需要接收数据的套接字文件描述符。
  • buf:接收数据的缓冲区。
  • len:缓冲区的长度。
  • flags:调用操作标志位,可以控制接收的行为,包括超时、阻塞等等。

二、不同情形下的recv()函数

1. TCP协议下的recv()函数

以TCP协议下的情形作为示例,主要介绍三种情况下recv()函数的使用。分别是:

  • 一次性接收所有数据;
  • 限制每次接收的数据长度;
  • 非阻塞式接收数据。

(1) 一次性接收所有数据

如果服务器一次性发送了足够的数据,那么客户端就可以使用一次性接收的方法。接收完毕后,recv()函数会返回接收到的数据字节数。这时,可以通过数据的长度来判断发送是否完成。

int sockfd, n;
char buffer[MAXLINE];
bzero(&buffer, sizeof(buffer));   // 清空缓冲区
n = recv(sockfd, buffer, MAXLINE, 0);   // 接收数据
printf("Message received: %s\n", buffer);

(2) 限制每次接收的数据长度

如果数据量太大,就需要分多次接收。此时需要考虑每次接收的数据长度。

int sockfd, n;
char buffer[MAXLINE];
bzero(&buffer, sizeof(buffer));
while ((n = recv(sockfd, buffer, MAXLINE, 0)) > 0) {   // 当接收到数据时
    printf("Message received: %s\n", buffer);
    bzero(&buffer, sizeof(buffer));   // 重置缓冲区
}

(3) 非阻塞式接收数据

在实际应用中,服务器可能不会一次性发送足够的数据。此时,可以使用非阻塞模式接收数据。使用非阻塞模式时,如果没有接收到数据,recv()函数会立即返回-1,并将errno设置为EAGAINEWOULDBLOCK

#include <fcntl.h>
int sockfd, n, flags;
char buffer[MAXLINE];
bzero(&buffer, sizeof(buffer));
flags = fcntl(sockfd, F_GETFL, 0);
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);   // 将套接字设为非阻塞模式
while ((n = recv(sockfd, buffer, MAXLINE, 0)) > 0 || errno == EAGAIN) {   // 接收数据
    if (n > 0) {
        printf("Message received: %s\n", buffer);
        bzero(&buffer, sizeof(buffer));   // 重置缓冲区
    }
}
fcntl(sockfd, F_SETFL, flags);   // 将套接字设为阻塞模式

2. UDP协议下的recv()函数

UDP协议下recv()函数需要设置socket套接字为非阻塞模式,否则recv()函数会一直等待数据的到来。

#include <fcntl.h>
#include <netinet/in.h>
#include <sys/socket.h>
int sockfd, n, flags;
struct sockaddr_in servaddr, cliaddr;
char buffer[MAXLINE];
socklen_t len;
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
len = sizeof(cliaddr);
flags = fcntl(sockfd, F_GETFL, 0);
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);   // 将套接字设为非阻塞模式
while (1) {
    n = recvfrom(sockfd, buffer, MAXLINE, 0, (struct sockaddr *)&cliaddr, &len);
    if (n < 0) {
        if (errno == EAGAIN) continue;
        else break;
    }
    printf("Message received: %s\n", buffer);
    bzero(&buffer, sizeof(buffer));   // 重置缓冲区
}
fcntl(sockfd, F_SETFL, flags);   // 将套接字设为阻塞模式

三、recv()函数的注意事项

1. recv()函数在处理TCP数据流时需要注意粘包问题

由于TCP协议是面向流的,因此在发送数据流时,很可能导致两次发送的数据合并为一个数据包。如果在接收端没有处理好这个问题,就会产生错误。在接收数据的时候,需要正确地分离每一个TCP数据段,避免处理数据时发生混乱。可以通过下面的方法解决这个问题。

  • 依次读入数据,存储在缓存区。当缓存区的数据达到我们需要的长度时,开始处理这个数据。
  • 读入数据时,记录上一次读入数据的长度,然后将这个长度和当前的长度相加。如果这个值小于接收缓存长度MAXLINE,说明本次读取的数据中没有发生粘包。否则,需要将缓存区的数据截成两部分,第一部分是上一次长度和本次长度之和减去MAXLINE,第二部分从刚才的长度差处截断。

2. recv()函数在处理UDP数据时可能因为数据迟到导致接收失败

UDP协议本身不保证可靠性,也不保证数据的传输顺序。如果在UDP数据传输过程中出现网络拥塞、丢包、延迟等情况,就有可能导致接收端无法接收到数据。此时,可以使用setsockopt()函数设置UDP套接字的接收缓存大小,增加接收成功的概率。

int sockfd, n;
char buffer[MAXLINE];
bzero(&buffer, sizeof(buffer));
setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n));   // 增加接收缓存大小
while ((n = recv(sockfd, buffer, MAXLINE, 0)) > 0) {
    printf("Message received: %s\n", buffer);
    bzero(&buffer, sizeof(buffer));   // 重置缓冲区
}

3. recv()函数在处理大数据量时可能会阻塞

如果一次性接收大量数据,会导致程序阻塞。此时,可以使用非阻塞式接收数据。

四、总结

综上所述,本文对recv()函数进行了详细介绍,从定义和原型、不同情形下的使用方式、注意事项等多个方面进行了分析。通过本文的介绍,相信大家对recv()函数的使用有了更深入的理解,可以更好地进行网络编程。