recv
?函数是在 Linux C 语言网络编程中用于从已连接的套接字接收数据的函数。它通常与 TCP 连接一起使用,但也可以用于 UDP(尽管对于 UDP,更常使用?recvfrom
,因为它还可以接收发送方的地址信息)。
recv
?函数在?<sys/socket.h>
?中定义,其函数原型如下:
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
sockfd:
这是一个已打开的套接字描述符,它标识了要从其接收数据的网络连接。对于 TCP,这个套接字通常是通过?socket
?函数创建的,并且已经通过?connect
?函数与远程服务器建立了连接,或者通过?accept
?函数接受了来自客户端的连接。
buf:
这是一个指向缓冲区的指针,该缓冲区用于存储从套接字接收到的数据。在调用?recv
?函数之前,应确保该缓冲区有足够的空间来存储要接收的数据。
len:
这是缓冲区?buf
?的长度,以字节为单位。它指定了缓冲区中可以存储的最大数据量。
flags:
这是一个整数值,用于传递特殊的接收标志给底层协议。这些标志可以修改?recv
?函数的行为。通常,这个参数被设置为 0,表示使用标准的接收行为。然而,一些可能的标志包括:
MSG_PEEK
:查看数据,但不从套接字的接收队列中移除。MSG_WAITALL
:等待直到接收到完整的?len
?字节数据,或者发生错误。然而,这个标志的行为可能因实现而异,并且不建议在阻塞模式下使用。MSG_OOB
:仅接收带外数据(out-of-band data)。MSG_DONTWAIT
:非阻塞模式操作(等效于使用非阻塞套接字)。MSG_ERRQUEUE
:获取扩展的错误信息(较少使用)。recv
?函数返回实际接收到的字节数。这个数字可能小于?len
?参数指定的长度,这取决于发送方发送的数据量、套接字的接收缓冲区中的数据量以及网络条件。recv
?函数返回 0。recv
?函数返回 -1,并设置全局变量?errno
?以指示错误类型。当?recv
?函数返回 -1 时,可以检查?errno
?来确定错误的原因。一些常见的错误包括:
EWOULDBLOCK
?或?EAGAIN
:套接字是非阻塞的,并且没有数据可供立即接收。ECONNRESET
:连接被对端重置。ENOTCONN
:套接字未连接到远程地址。EINTR
:接收操作被中断,通常是因为接收到了一个信号。EBADF
:提供的套接字描述符不是有效的或不支持接收操作。阻塞与非阻塞:根据套接字的配置,recv
?函数可以表现为阻塞或非阻塞。在阻塞模式下,recv
?会等待直到有数据可以接收或发生错误。在非阻塞模式下,如果没有数据可供接收,recv
?会立即返回?EWOULDBLOCK
?或?EAGAIN
?错误。
多次接收:由于 TCP 的流性质,一次?recv
?调用可能不会接收到发送方发送的所有数据。因此,可能需要多次调用?recv
?来接收完整的消息或数据流。
数据边界:recv
?函数不保证按发送方发送的原始边界接收数据。应用程序需要自己处理消息的边界问题,通常通过在消息前加上长度字段或使用特定的分隔符。
关闭连接:当对端关闭连接时,recv
?函数将返回 0,表示没有更多的数据可以接收。这是正常关闭连接的指示。
性能考虑:与?send
?类似,频繁地接收小块数据可能不如一次性接收大块数据高效。应用程序应优化数据传输以提高性能。
在使用?recv
?函数时,应处理可能的错误和异常情况,并确保正确地管理套接字和数据缓冲区。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main() {
// 假定 sockfd 是已经连接好的套接字
int sockfd = /* socket(...) */;
// 用于存储接收数据的缓冲区
char buffer[1024];
// 清空缓冲区
memset(buffer, 0, sizeof(buffer));
// 接收数据
int bytes_received = recv(sockfd, buffer, sizeof(buffer), 0);
if (bytes_received < 0) {
// 处理错误
perror("recv failed");
} else if (bytes_received == 0) {
// 对方已经关闭了连接
printf("Peer has performed an orderly shutdown\n");
} else {
// 打印接收到的数据
printf("Received (%d bytes): %.*s\n", bytes_received, bytes_received, buffer);
}
// 关闭套接字
close(sockfd);
return 0;
}
在上面的代码示例中,`sockfd` 应已经是一个成功连接的 socket—这意味着在 TCP 的情况下,应该在客户端使用?connect
?或在服务器端使用?accept
?来获得?sockfd
。
在实际应用程序中,通常会将?recv
?放在某个循环中以持续接收数据。当?recv
?返回 0 表示对方已经关闭了连接,接收循环就应该结束。还需要处理各种可能出现的错误。
perror
?函数是 C 语言中用来报告?errno
?错误的常用方法。当系统调用或库函数出错时,它们通常会设置全局变量?errno
?来指示错误的类型。perror
?函数可以读取当前的?errno
?值,并打印出一条描述该错误的消息到标准错误输出(stderr)。
perror
?函数的原型如下:
void perror(const char *str);
其中?str
?是一个字符串指针,它通常被用来提供关于出错上下文的额外信息。这个字符串会被打印出来,后面紧跟着一个冒号、一个空格和由?errno
?值对应的错误消息。
例如,如果你调用了一个可能失败的函数(如?open
),你可以立即调用?perror
?来打印出任何发生的错误:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
int main()
{
int fd = open("nonexistent_file.txt", O_RDONLY);
if (fd == -1)
{
perror("Error opening file");
return EXIT_FAILURE;
}
// ... 其他代码 ...
return EXIT_SUCCESS;
}
如果文件?nonexistent_file.txt
?不存在,open
?函数会失败,并设置?errno
。然后?perror
?会打印出类似以下的消息:
Error opening file: No such file or directory
这里,“Error opening file” 是传递给?perror
?的字符串,而 “No such file or directory” 是与?errno
?值对应的系统错误消息。注意,errno
?的值在调用另一个可能设置?errno
?的函数之前应当被认为是未定义的,除非你明确地知道它不会被改变。因此,通常在调用可能出错的函数之后立即检查?errno
?并使用?perror
?或其他方法来报告错误是一个好习惯。