socket--shutdown()函数

本文对比了close()/closesocket()与shutdown()函数在断开网络连接时的不同作用。close()/closesocket()彻底关闭连接并清除套接字,而shutdown()允许选择性地关闭输入或输出流,保留部分连接能力。
调用 close()/closesocket() 函数意味着完全断开连接,即不能发送数据也不能接收数据,这种“生硬”的方式有时候会显得不太“优雅”。

图1:close()/closesocket() 断开连接

上图演示了两台正在进行双向通信的主机。主机A发送完数据后,单方面调用 close()/closesocket() 断开连接,之后主机A、B都不能再接受对方传输的数据。实际上,是完全无法调用与数据收发有关的函数。

一般情况下这不会有问题,但有些特殊时刻,需要只断开一条数据传输通道,而保留另一条。

使用 shutdown() 函数可以达到这个目的,它的原型为:
  1. int shutdown(int sock, int howto); //Linux
  2. int shutdown(SOCKET s, int howto); //Windows
sock 为需要断开的套接字,howto 为断开方式。

howto 在 Linux 下有以下取值:
  • SHUT_RD:断开输入流。套接字无法接收数据(即使输入缓冲区收到数据也被抹去),无法调用输入相关函数。
  • SHUT_WR:断开输出流。套接字无法发送数据,但如果输出缓冲区中还有未传输的数据,则将传递到目标主机。
  • SHUT_RDWR:同时断开 I/O 流。相当于分两次调用 shutdown(),其中一次以 SHUT_RD 为参数,另一次以 SHUT_WR 为参数。

howto 在 Windows 下有以下取值:
  • SD_RECEIVE:关闭接收操作,也就是断开输入流。
  • SD_SEND:关闭发送操作,也就是断开输出流。
  • SD_BOTH:同时关闭接收和发送操作。

至于什么时候需要调用 shutdown() 函数,下节我们会以文件传输为例进行讲解。
close()/closesocket()和shutdown()的区别
确切地说,close() / closesocket() 用来关闭套接字,将套接字描述符(或句柄)从内存清除,之后再也不能使用该套接字,与C语言中的 fclose() 类似。应用程序关闭套接字后,与该套接字相关的连接和缓存也失去了意义,TCP协议会自动触发关闭连接的操作。

shutdown() 用来关闭连接,而不是套接字,不管调用多少次 shutdown(),套接字依然存在,直到调用 close() / closesocket() 将套接字从内存清除。

调用 close()/closesocket() 关闭套接字时,或调用 shutdown() 关闭输出流时,都会向对方发送 FIN 包。FIN 包表示数据传输完毕,计算机收到 FIN 包就知道不会再有数据传送过来了。

默认情况下,close()/closesocket() 会立即向网络中发送FIN包,不管输出缓冲区中是否还有数据,而shutdown() 会等输出缓冲区中的数据传输完毕再发送FIN包。也就意味着,调用 close()/closesocket() 将丢失输出缓冲区中的数据,而调用 shutdown() 不会。
<think>我们正在讨论socket编程中的shutdown函数。根据引用[1],我们知道在Linux中有两种操作可以终止socket间的进程通信:close和shutdown。本文将重点解释shutdown函数的作用及使用详解。 首先,我们需要理解shutdown函数与close函数的区别。close函数会关闭文件描述符,并且当所有文件描述符都关闭后,才会真正关闭连接。而shutdown函数则允许我们只关闭连接的一端(读端或写端),而不影响文件描述符本身。 shutdown函数的原型如下: ```c #include <sys/socket.h> int shutdown(int sockfd, int how); ``` 参数说明: - `sockfd`:需要关闭的socket文件描述符。 - `how`:指定关闭的方式,可以是以下值之一: - `SHUT_RD`(0):关闭连接的读端(接收端)。此后,进程不能再对socket进行读操作,并且接收缓冲区中的数据都会被丢弃。同时,对端发送的数据也会被确认,然后丢弃。 - `SHUT_WR`(1):关闭连接的写端(发送端)。此时,发送缓冲区中尚未发送的数据会被发送,然后发送一个FIN包给对端,表明本端不再发送数据。但是,本端仍然可以接收数据。 - `SHUT_RDWR`(2):同时关闭连接的读端和写端,相当于分别调用`SHUT_RD`和`SHUT_WR`。 返回值:成功返回0,失败返回-1,并设置errno。 使用shutdown函数的好处: 1. 可以半关闭连接:当我们需要告诉对端本端已经发送完所有数据,但仍然希望接收对端的数据时,可以使用`SHUT_WR`。这样,对端会收到FIN,知道本端已经结束发送,但还可以继续发送数据给本端。 2. 避免close函数在多进程/多线程中的问题:当多个进程共享一个socket时,close只是减少引用计数,直到最后一个引用被关闭,连接才真正关闭。而shutdown则不同,它直接关闭连接,无论引用计数是多少。 3. 确保数据发送完毕:使用`SHUT_WR`后,发送缓冲区中的数据会被发送,然后发送FIN,这样对端可以完整接收数据。 示例场景: 假设我们有一个客户端和服务器通过TCP连接。客户端发送一个请求后,告诉服务器它已经发送完毕(关闭写端),但准备接收服务器的响应。客户端可以这样做: ```c // 客户端发送请求 write(sockfd, request, request_len); // 关闭写端,表示不再发送数据 shutdown(sockfd, SHUT_WR); // 继续读取响应 char buffer[BUFFER_SIZE]; while ((n = read(sockfd, buffer, BUFFER_SIZE)) > 0) { // 处理响应 } // 最后关闭socket close(sockfd); ``` 服务器端在收到FIN后,会知道客户端已经发送完数据,但服务器仍然可以发送响应。 注意事项: - 调用`shutdown(sockfd, SHUT_RD)`后,再读该socket会返回0(表示EOF)?实际上,在Linux中,调用`SHUT_RD`后,再读该socket会立即返回0(即EOF),即使对端可能还在发送数据。这是因为本地已经关闭了读端,不再接收任何数据。 - 调用`shutdown(sockfd, SHUT_WR)`后,对端会收到一个FIN,表示本端不再发送数据。但是本端仍然可以接收数据,直到对端也关闭了写端。 - 调用`shutdown`并不会释放文件描述符,需要调用`close`来释放。 错误处理: - 如果对一个已经关闭的socket调用shutdown,会返回-1,并设置errno为`ENOTCONN`或`EBADF`。 在非阻塞模式下,shutdown的行为相同,因为它不涉及数据的实际发送和接收(除了发送FIN),它只是修改socket的状态。 总结:shutdown函数提供了对socket连接的更精细控制,特别是在需要半关闭连接或确保数据发送完毕的场景下非常有用。 参考文献: [^1]: Linux socket编程(8):shutdown和close的区别详解及例子 相关问题: 1. shutdown函数在关闭写端后,对端还能继续发送数据吗? 2. 在多进程环境下,shutdown和close有什么不同? 3. 使用shutdown函数后,是否还需要调用close函数?</think>### socket的`shutdown()`函数详解 `shutdown()`是网络编程中用于**半关闭**TCP连接的核心函数,它允许程序精确控制连接的关闭方向(读/写),而`close()`则是完全关闭连接。以下是详细解析: --- #### 一、函数原型及参数 ```c #include <sys/socket.h> int shutdown(int sockfd, int how); ``` - **`sockfd`**:socket文件描述符 - **`how`**:关闭模式标志: - `SHUT_RD` (0):关闭**读端**(接收功能) - `SHUT_WR` (1):关闭**写端**(发送功能) - `SHUT_RDWR` (2):同时关闭读写端 --- #### 二、核心作用 1. **半关闭连接** 允许单向关闭连接(如只关闭发送但保留接收能力),这是与`close()`的最大区别[^1]。 ```c shutdown(sockfd, SHUT_WR); // 停止发送,但仍可接收数据 ``` 2. **确保数据完整性** 调用`SHUT_WR`时: - 发送缓冲区中未发送的数据会**继续传输** - 发送`FIN`包通知对端"本端停止发送" - 对端可继续发送剩余数据 3. **多进程/线程安全** 无论有多少进程共享socket,`shutdown()`会立即影响所有关联进程的连接状态[^1]。 --- #### 三、与`close()`的关键区别 | 特性 | `shutdown()` | `close()` | |--------------------|-------------------------------|-------------------------------| | **连接关闭方向** | 可选择读/写/双向 | 总是双向关闭 | | **文件描述符** | 不释放文件描述符 | 释放文件描述符 | | **多进程影响** | 立即影响所有关联进程 | 仅减少引用计数 | | **缓冲区处理** | 确保发送缓冲区数据完成传输 | 可能丢弃未发送数据 | --- #### 四、典型使用场景 1. **HTTP服务器响应结束** 发送完响应后关闭写端,但保持读端开放(应对客户端重传): ```c write(sockfd, response, resp_len); // 发送HTTP响应 shutdown(sockfd, SHUT_WR); // 停止发送,但可接收客户端请求 ``` 2. **双向通信协议终止** 有序关闭连接的四步握手: ```c shutdown(sockfd, SHUT_WR); // 本端停止发送 read(sockfd, buffer, size); // 读取对端剩余数据 // 对端执行相同操作... close(sockfd); // 最终关闭 ``` 3. **非阻塞模式下的连接重置** 当需要立即终止连接时: ```c shutdown(sockfd, SHUT_RDWR); // 立即禁用读写 ``` --- #### 五、返回值与错误处理 - **成功**:返回 `0` - **失败**:返回 `-1`,设置`errno`: - `EBADF`:无效socket描述符 - `ENOTCONN`:socket未连接 - `ENOTSOCK`:描述符不是socket --- #### 六、Python示例 ```python import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('example.com', 80)) # 发送HTTP请求后关闭写端 s.sendall(b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n") s.shutdown(socket.SHUT_WR) # 停止发送 # 继续接收响应 response = b"" while True: data = s.recv(1024) if not data: break response += data s.close() # 最终关闭 ``` > **关键点**:`shutdown()`不释放系统资源,操作完成后仍需调用`close()`[^2][^5]。 --- ### 相关问题 1. 在非阻塞socket模式下使用`shutdown()`有哪些注意事项? 2. 如何用`shutdown()`实现TCP的优雅连接终止? 3. `shutdown(SHUT_RD)`后读取socket会得到什么结果? 4. 在多线程环境中使用`shutdown()`是否安全? [^1]: Linux socket编程(8):shutdown和close的区别详解及例子 [^2]: python中socket详解_python socket函数详解 [^5]: python中socket详解_python socket函数详解
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值