在Linux中,使用C语言进行网络编程时,send
函数是用于发送数据到已连接的套接字的重要函数之一。它通常用于TCP连接,但也可以用于UDP(尽管对于UDP,通常更推荐使用sendto
,因为它允许你指定目标地址和端口)。
下面是send
函数的详细解释:
函数原型
recv
函数在 <sys/socket.h>
中定义,其函数原型如下:
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
参数
-
sockfd:
这是一个已打开的套接字描述符,它标识了要通过其发送数据的网络连接。对于TCP,这个套接字通常是通过socket
函数创建的,并且已经通过connect
函数与远程服务器建立了连接。 -
buf:
这是一个指向要发送数据的缓冲区的指针。这个缓冲区包含了要通过网络发送的字节流。缓冲区中的数据在调用send
函数时并不会被自动修改。 -
len:
这是要发送数据的长度,以字节为单位。它指定了buf
指针指向的缓冲区中有效数据的量。 -
flags:
这是一个整数值,用于传递特殊的发送标志给底层协议。这些标志可以修改send
函数的行为。通常,这个参数被设置为0,表示使用标准的发送行为。然而,一些可能的标志包括:MSG_OOB
:发送带外数据(out-of-band data),这是一种紧急数据,通常用于TCP连接。MSG_DONTROUTE
:绕过标准的路由表查找,通常用于本地通信。MSG_NOSIGNAL
:请求不发送SIGPIPE
信号,如果接收端已经关闭了连接,则send
函数将返回错误而不是终止进程。
返回值
- 如果成功,
send
函数返回实际发送的字节数。这个数字可能小于len
参数指定的长度,这取决于套接字的发送缓冲区可用空间和网络条件。 - 如果出现错误,
send
函数返回-1,并设置全局变量errno
以指示错误类型。
错误处理
当send
函数返回-1时,可以检查errno
来确定错误的原因。一些常见的错误包括:
EWOULDBLOCK
或EAGAIN
:套接字是非阻塞的,并且没有足够的缓冲区空间可供立即发送数据。ECONNRESET
:连接被对端重置。EPIPE
:对端关闭了连接,并且启用了SIGPIPE
信号(除非设置了MSG_NOSIGNAL
标志)。EBADF
:提供的套接字描述符不是有效的或不支持发送操作。EINTR
:发送操作被中断,通常是因为接收到了一个信号。
注意事项
-
阻塞与非阻塞:根据套接字的配置,
send
函数可以表现为阻塞或非阻塞。在阻塞模式下,send
会等待直到有足够的缓冲区空间可以发送数据或发生错误。在非阻塞模式下,如果没有足够的缓冲区空间,send
会立即返回EWOULDBLOCK
或EAGAIN
错误。 -
多次发送:即使
send
的返回值小于请求的len
,也不意味着发送失败。在TCP中,由于流量控制和窗口大小,可能需要多次调用send
来发送所有数据。 -
数据完整性:
send
函数不保证数据的原子性。如果需要在两个进程或两台机器之间原子性地发送数据,通常需要在应用层实现额外的协议。 -
关闭连接:当对端关闭连接时,继续向其发送数据可能会导致
EPIPE
错误(如果未设置MSG_NOSIGNAL
标志)或SIGPIPE
信号。 -
性能考虑:频繁地发送小块数据可能不如一次性发送大块数据高效,因为网络传输和操作系统调用都有一定的开销。
在使用send
函数时,务必考虑上述因素,并适当地处理可能的错误和异常情况。
示例代码
#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库函数,用于打印一个描述性的错误消息到标准错误输出 stderr。它会根据全局变量 errno 的当前值来显示当前错误的文本描述。
errno 是在发生系统调用或库函数调用出错时设置的全局变量,它的值表示错误的类型。<errno.h> 头文件定义了errno 可能的错误代码(例如 EACCES、ECONNRESET 等)和对应的错误信息。
perror 的原型定义如下:
#include <stdio.h>
void perror(const char *s);
当你调用 perror 时,你可以提供一个字符串作为参数,perror会先打印这个字符串,后接一个冒号和一个空格,然后是对应 errno 值的字符串描述。最后,它会添加一个换行符。
例如:
if (write(fd, buf, count) == -1) {
perror("write failed");
// 这里还可以包含额外的错误处理代码
}
如果 write 调用失败,你将看到像 "write failed: Permission denied" 这样的错误消息被打印到标准错误输出。
如果 errno 值是0,表示没有发生错误,perror函数通常不会打印有关错误的信息。
为了确保 perror显示的是你关心的错误信息,最好在调用可能设置errno的函数之后立即使用perror— 在任何其他可能再次设置 errno 的操作之前。这是因为许多函数都可能更改 errno 值,包括成功的函数调用。通常,在检查函数调用返回值时,如果发现它出错(通常返回值是 -1 或者 NULL),马上用 perror 来查看错误原因。