背景:我们要讲述的是网络编程中常用的两个API:
#include <unistd.h>
int close(int fd);
#include <sys/socket.h>
int shutdown(int sockfd, int how);
以及TCP的半连接,半打开。
shutdown函数的行为依赖第二个参数区分:
SHUT_RD :reception(接受)的disallow(拒绝),也就是第一个参数的文件描述符的接受数据被阻止。
SHUT_WR :transmissions(传播,发送)的disallow,也就是发送被阻止。
SHUT_RDWR :兼而有之。
其实这些都是宏,我们完全可以使用数字,更直观:
返回值:On success, zero is returned. On error, -1 is returned, and errno is set appropriately.
具体也可以使用man 2 shutdown查看手册。
具体实现都是从内核层面执行。当然了,shutdown之后,接收缓冲区中的数据仍然可以被处理。
close是关闭掉对应的文件描述符并释放与描述符相关的资源,收不了也接不了。
注意,shutdown并不会关闭掉对应的文件描述符并释放与描述符相关的资源,只是对于可用性进行内核层面阻止,你要free这个fd,还是要使用close。当然了,这是Linux,如果是Windows,close要换成closesocket();
也就是说:shutdown() 提供了更细粒度(你可以关闭部分或者全关闭)的控制,而 close() 完全关闭套接字并释放资源。前者允许你在不关闭套接字的情况下,停止某个方向的数据传输。例如,你可以通过 SHUT_WR 选项来从内核层面停止该sockfd的发送数据能力,但仍然允许接收数据。即即shutdown() 适用于在应用层控制连接的关闭行为时使用。
另外如果还需要在TCP协议栈层面深入理解close and shutdown,详见:https://stackoverflow.com/questions/4160347/close-vs-shutdown-socket/23483487#23483487
我们再给出一点代码:
server.cc
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <cstring>int main()
{// 创建一个 TCP 套接字int server_fd = socket(AF_INET, SOCK_STREAM, 0);if (server_fd == -1){std::cerr << "Socket creation failed!" << std::endl;return -1;}// 设置服务器地址sockaddr_in server_addr{};server_addr.sin_family = AF_INET;server_addr.sin_addr.s_addr = INADDR_ANY;server_addr.sin_port = htons(12345); // 监听端口 12345// 绑定套接字到地址if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1){std::cerr << "Bind failed!" << std::endl;close(server_fd);return -1;}// 开始监听连接if (listen(server_fd, 1) == -1){std::cerr << "Listen failed!" << std::endl;close(server_fd);return -1;}std::cout << "Server is listening on port 12345..." << std::endl;// 等待客户端连接int client_fd = accept(server_fd, nullptr, nullptr);if (client_fd == -1){std::cerr << "Accept failed!" << std::endl;close(server_fd);return -1;}std::cout << "Client connected!" << std::endl;// 接收客户端发来的数据char buffer[1024];ssize_t bytes_received = recv(client_fd, buffer, sizeof(buffer), 0);if (bytes_received > 0){buffer[bytes_received] = '\0'; // 添加字符串结束符std::cout << "Received data: " << buffer << std::endl;}// 使用 shutdown() 关闭发送和接收功能if (shutdown(client_fd, SHUT_RDWR) == -1){std::cerr << "Shutdown failed!" << std::endl;close(client_fd);close(server_fd);return -1;}std::cout << "Shutdown the connection. No further send/recv operations allowed." << std::endl;// 尝试发送数据const char *msg = "Hello from server!";ssize_t bytes_sent = send(client_fd, msg, strlen(msg), 0);if (bytes_sent == -1){std::cerr << "Send failed!" << std::endl;}else{std::cout << "Sent data: " << msg << std::endl;}// 最后关闭连接close(client_fd);close(server_fd);return 0;
}
client.cc
telnet localhost 12345
server收到一条数据后,将调用shutdown,届时,将无法通信。
TCP 的半连接是指在 TCP 三次握手过程中发生的一种状态。当主机 A 向主机 B 发起连接请求时,B 响应了这个请求,但 A 并没有完成第三次握手,这种情况被称为半连接。
与此相关的概念是半连接攻击,也称为 SYN 攻击。在这种攻击中,攻击者不断发送连接请求,但不完成后续的握手过程,导致主机 B 分配的内存资源被长期占用,最终可能导致资源耗尽。
TCP的半打开?
半打开:两方建立TCP连接,然后一方关闭,而另一方并不知情,这样的连接称之为半打开连接。处于半打开的连接,如果双方不进行数据通信,确实发现不了问题,仍处于连接状态的一方不会检测另外一方已经出现异常。怎么解决?
心跳机制,具体使用定时器实现,比如让服务器每隔一段时间发送报文请求回复。当主机收到这个包后,因为本地应用程序已经关闭了它的套接字,所以主机此时会给服务端回复一个RST包:报文中没有负载且TCP头部标志中的RST位被设置的一个数据包。