close函数概念和使用案例
在 socket 编程中,close
函数是用于释放套接字资源的核心系统调用,其作用是终止套接字的连接并释放相关内核资源(如文件描述符、缓冲区等)。它是 UNIX 系统中通用的文件描述符关闭函数,不仅适用于 socket,也适用于普通文件、管道等,但在网络编程中有着特殊的语义和注意事项。
一、函数原型与头文件
#include <unistd.h> // 必须包含的头文件
int close(int fd);
二、参数解析:fd
(文件描述符)
- 含义:需要关闭的套接字描述符(由
socket()
或accept()
函数返回的整数)。 - 取值范围:
有效范围通常是0 ~ OPEN_MAX
(系统限制的最大文件描述符数量,可通过getrlimit
查看)。
常见有效取值为非负整数(0
通常是标准输入,1
标准输出,2
标准错误,套接字描述符一般从3
开始)。 - 约束:
- 必须是已打开且未关闭的有效套接字描述符(否则会触发
EBADF
错误)。 - 不能是已关闭的描述符(重复关闭会导致未定义行为,通常返回错误)。
- 必须是已打开且未关闭的有效套接字描述符(否则会触发
三、返回值解析
- 成功:返回
0
,表示套接字资源已被成功释放。 - 失败:返回
-1
,并设置全局变量errno
表示具体错误原因(可通过perror()
或strerror(errno)
打印)。
常见错误码(errno
)及含义:
错误码 | 含义 | 典型场景 |
---|---|---|
EBADF | 无效的文件描述符(fd 未打开或已关闭) | 对已关闭的套接字调用 close ;fd 为负数 |
EINTR | 调用被信号中断 | 关闭过程中收到信号(如 SIGINT ) |
EIO | I/O 错误(罕见) | 关闭时发生底层 I/O 异常 |
四、close
在 socket 中的核心行为
close
对套接字的操作本质是“释放资源 + 终止连接”,但具体行为因协议(TCP/UDP)和连接状态而异:
1. 对 TCP 套接字的影响(面向连接)
TCP 是面向连接的协议,close
会触发双向关闭流程(四次挥手):
- 关闭后,套接字不再允许读写操作(调用
read
/write
会返回错误)。 - 内核会尽力发送发送缓冲区中剩余的数据,并等待对方确认(确保数据不丢失)。
- 释放套接字的文件描述符,使其可被系统重新分配给新的
open
或socket
调用。
注意:TCP 的 close
是“全双工关闭”——既关闭读方向,也关闭写方向。如果需要只关闭一个方向(如只禁止发送但保留接收),需使用 shutdown
函数(而非 close
)。
2. 对 UDP 套接字的影响(无连接)
UDP 是无连接协议,没有“挥手”过程,close
的行为更简单:
- 直接释放套接字资源(发送/接收缓冲区被清空)。
- 后续无法再通过该套接字发送或接收数据。
3. 与 shutdown
的关键区别
close
和 shutdown
都可终止连接,但核心差异在于“资源释放”和“方向控制”:
特性 | close(int fd) | shutdown(int fd, int how) |
---|---|---|
资源释放 | 释放文件描述符(引用计数减 1,为 0 时真正关闭) | 不释放文件描述符,仅关闭连接方向 |
方向控制 | 全双工关闭(读写均不可用) | 可选择关闭读(SHUT_RD )、写(SHUT_WR )或全关(SHUT_RDWR ) |
多进程共享 | 仅最后一个 close 会真正关闭连接(因引用计数) | 无论引用计数,立即关闭指定方向 |
五、使用场景与最佳实践
1. 客户端主动断开连接
客户端完成数据交互后,需调用 close
释放套接字,避免资源泄漏:
// 客户端示例:发送数据后关闭连接
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
send(sockfd, "hello", 5, 0); // 发送数据
close(sockfd); // 完成后关闭套接字,释放资源
2. 服务器关闭客户端连接
服务器处理完一个客户端请求后,需关闭对应客户端的套接字(保留监听套接字继续接受新连接):
// 服务器示例:处理客户端后关闭连接
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
bind(listen_fd, ...);
listen(listen_fd, 10);while (1) {int client_fd = accept(listen_fd, ...); // 接受新连接// 处理客户端请求(如 read/write)close(client_fd); // 处理完后关闭客户端套接字
}
3. 异常场景下的资源释放
当网络出错(如连接超时、对方断开)时,需主动关闭套接字,避免资源泄漏:
// 错误处理示例
ssize_t n = recv(client_fd, buf, sizeof(buf), 0);
if (n <= 0) { // 连接关闭或出错if (n < 0) perror("recv error");close(client_fd); // 释放错误连接的资源
}
六、注意事项
-
避免重复关闭:对同一套接字多次调用
close
会导致EBADF
错误(第二次关闭时fd
已无效)。close(sockfd); close(sockfd); // 错误:重复关闭,返回 -1,errno=EBADF
-
TIME_WAIT
状态的影响:
TCP 连接中,主动关闭的一方会进入TIME_WAIT
状态(默认约 60 秒),确保最后一个确认包被接收。此时close
已返回,但端口可能暂时无法重用(需结合SO_REUSEADDR
选项解决)。 -
引用计数问题:
若多个进程/线程共享同一套接字(如fork
后子进程继承fd
),close
仅减少引用计数,只有最后一次close
才会真正关闭连接。如需强制关闭,需用shutdown
。 -
错误处理不可忽略:
虽然close
失败概率较低,但在关键场景(如服务器长期运行)中需检查返回值,避免资源泄漏:if (close(sockfd) == -1) {perror("close failed"); // 记录错误日志 }
总结
close
函数是 socket 编程中释放资源的“最后一步”,其核心作用是终止套接字连接并释放文件描述符。在使用时需注意:
- 区分 TCP(双向关闭、四次挥手)和 UDP(直接释放)的不同行为;
- 与
shutdown
配合使用(如需单方向关闭); - 避免重复关闭,正确处理错误以防止资源泄漏。
合理使用 close
是编写健壮网络程序的基础。