Linux网络编程:shutdown() 与 close() 函数详解:剖析 shutdown()、close() 函数的实现原理、参数说明和使用技巧

简介: Linux网络编程:shutdown() 与 close() 函数详解:剖析 shutdown()、close() 函数的实现原理、参数说明和使用技巧

前言

简介

在网络编程中,套接字是一个重要的概念。套接字是一种用于网络通信的接口,它可以实现进程之间的通信和数据传输。在使用套接字进行网络编程时,关闭套接字是一个必要的操作。关闭套接字可以释放资源,避免程序出现内存泄漏等问题。在关闭套接字时,我们通常会用到 close() 和 shutdown() 函数。这两个函数虽然都能够关闭套接字,但是它们的使用方式和作用有所不同。


目的

本篇博客的目的是详细介绍 close() 和 shutdown() 函数的区别,以及如何选择使用哪个函数。同时,我们还会介绍一些关闭套接字时需要注意的事项。如果您正在进行网络编程,那么这篇博客一定会对您有所帮助。


close()函数

原型

#include<unistd.h> 
int close(int sockfd); //返回成功为0,出错为-1.

close 一个套接字的默认行为是把套接字标记为已关闭,然后立即返回到调用进程,该套接字描述符不能再由调用进程使用,也就是说它不能再作为read或write的第一个参数,然而TCP将尝试发送已排队等待发送到对端的任何数据,发送完毕后发生的是正常的TCP连接终止序列

在多进程并发服务器中,父子进程共享着套接字,套接字描述符引用计数记录着共享着的进程个数,当父进程或某一子进程close掉套接字时,描述符引用计数会相应的减一,当引用计数仍大于零时,这个close调用就不会引发TCP的四路握手断连过程。


作用

close() 函数用于关闭套接字,释放套接字占用的资源。在网络编程中,使用 close() 函数可以防止套接字资源泄漏,提高程序的稳定性和可靠性。


关闭方式

close() 函数会关闭套接字的所有功能,包括读、写和监听。它会立即关闭套接字,并释放所有相关资源。如果在关闭套接字之前还有未发送的数据,这些数据会被丢弃。


影响范围

close() 函数只会影响当前套接字,不会影响其他套接字。关闭套接字后,其他套接字仍然可以正常工作。


产生的信号

调用 close() 函数不会产生任何信号。


使用示例

下面是一个使用 close() 函数关闭套接字的示例:

#include <sys/socket.h>
#include <unistd.h>
int sockfd; // 套接字描述符
// 关闭套接字
int ret = close(sockfd);
if (ret == -1) {
   // 处理异常情况
}

在上面的代码中,close() 函数会关闭 sockfd 套接字,并释放所有相关资源。如果关闭成功,函数会返回 0,否则会返回 -1。


shutdown()函数

原型

#include<sys/socket.h> 
int shutdown(int sockfd,int howto); //返回成功为0,出错为-1.</span>
  • 参数说明

该函数的行为依赖于howto的值

  • SHUT_RD:值为0,关闭连接的读端。
  • SHUT_WR:值为1,关闭连接的写这一半。
  • SHUT_RDWR:值为2,连接的读和写都关闭。

终止网络连接的通用方法是调用close函数。但使用shutdown能更好的控制断连过程(使用第二个参数)。


作用

shutdown() 函数用于关闭套接字的读或写功能。它可以在套接字关闭之前,确保所有的数据都已经发送或接收完毕。此外,>shutdown() 函数还可以控制套接字的关闭方式,包括关闭读、写或读写两个方向。


关闭方式

shutdown() 函数有三种关闭方式:

SHUT_RD:关闭套接字的读功能,即禁止从套接字中读取数据。

SHUT_WR:关闭套接字的写功能,即禁止向套接字中写入数据。

SHUT_RDWR:关闭套接字的读写功能,即禁止从套接字中读取数据和向套接字中写入数据。


影响范围

shutdown() 函数只会影响当前套接字,不会影响其他套接字。关闭套接字的读、写或读写功能后,其他套接字仍然可以正常工作。


产生的信号

调用 shutdown() 函数可能会产生 SIGPIPE 信号,这个信号表示向一个已经关闭写功能的套接字中写入数据时,会触发一个异常条件。如果不希望程序因为 SIGPIPE 信号而终止,可以使用 signal() 函数忽略该信号。

使用示例

下面是一个使用shutdown() 函数关闭套接字写功能的示例:

#include <sys/socket.h>
#include <unistd.h>
int sockfd; // 套接字描述符
// 关闭套接字写功能
int ret = shutdown(sockfd, SHUT_WR);
if (ret == -1) {
   // 处理异常情况
}

在上面的代码中,shutdown() 函数会关闭 sockfd 套接字的写功能。如果关闭成功,函数会返回 0,否则会返回 -1。


综合示例

下面将展示一个客户端例子片段来说明使用close和shutdown所带来的不同结果:

客户端有两个进程,父进程和子进程,子进程是在父进程和服务器建连之后fork出来的,子进程发送标准输入终端键盘输入数据到服务器端,知道接收到EOF标识,父进程则接受来自服务器端的响应数据。

/* First  Sample client fragment,   * 多余的代码及变量的声明已略       */     
  s=connect(...);
   if( fork() )
   {   /*      The child, it copies its stdin to the socket              */  
       while( gets(buffer) >0)  
           write(s,buf,strlen(buffer));  
           close(s);  
           exit(0);
   }  
   else {          /* The parent, it receives answers  */  
        while( (n=read(s,buffer,sizeof(buffer)){  
            do_something(n,buffer);  
            /* Connection break from the server is assumed  */  
            /* ATTENTION: deadlock here                     */  
         wait(0); /* Wait for the child to exit          */  
         exit(0);

对于这段代码,我们所期望的是子进程获取完标准终端的数据,写入套接字后close套接字,并退出,服务器端接收完数据检测到EOF(表示数据已发送完),也关闭连接,并退出。接着父进程读取完服务器端响应的数据,并退出。

然而,事实会是这样子的嘛,其实不然!子进程close套接字后,套接字对于父进程来说仍然是可读和可写的,尽管父进程永远都不会写入数据。

因此,此socket的断连过程没有发生,因此,服务器端就不会检测到EOF标识,会一直等待从客户端来的数据。

而此时父进程也不会检测到服务器端发来的EOF标识。这样服务器端和客户端陷入了死锁(deadlock)。如果用shutdown代替close,则会避免死锁的发生。


关闭套接字的注意事项

关闭顺序

在关闭套接字时,应该按照以下顺序关闭:

先关闭读功能,即调用 shutdown() 函数关闭套接字的读功能。

再关闭写功能,即调用 shutdown() 函数关闭套接字的写功能。

最后使用 close() 函数关闭套接字本身。

这样做的目的是为了确保所有的数据都已经发送或接收完毕,避免数据丢失或死锁等问题。

close() 函数不会发送任何数据,而 shutdown() 函数可以发送一个指定的信号。


关闭后的套接字状态

关闭套接字后,套接字会进入 TIME_WAIT 状态,这个状态会持续一段时间,直到所有的数据都被传输完毕。在这个状态下,套接字无法被重新使用。如果需要立即重用套接字,可以设置 SO_REUSEADDR 选项。

close() 函数不会发送任何数据,而 shutdown() 函数可以发送一个指定的信号。


超时处理

在使用 shutdown() 函数时,应该注意超时处理,避免出现死锁等问题。如果套接字中还有未发送的数据,调用 shutdown() 函数会阻塞程序,直到所有数据都被发送或者超时。为了避免阻塞程序,可以设置 SO_LINGER 选项,让套接字在超时时立即关闭。

另外,在进行网络编程时,应该根据实际情况选择使用 close() 或 shutdown() 函数。如果需要控制关闭的方式,可以使用 shutdown() 函数,否则可以使用 close() 函数。同时,应该避免在多线程环境下同时关闭同一个套接字,避免出现资源竞争等问题。

总结

close() 和 shutdown() 的区别

close() 函数会立即关闭套接字,而 shutdown() 函数可以控制关闭的方式。

close() 函数会关闭套接字的所有引用,而 shutdown() 函数可以选择关闭读、写或者全部。

close() 函数不会发送任何数据,而 shutdown() 函数可以发送一个指定的信号。

  • close函数关闭套接字ID时,如果有其他的进程共享着这个套接字,那么它仍然是打开的,这个连接仍然可以用来读和写,并且有时候这是非常重要的 ,特别是对于多进程并发服务器来说。
  • 而shutdown会切断进程共享的套接字的所有连接,不管这个套接字的引用计数是否为零,那些试图读得进程将会接收到EOF标识,那些试图写的进程将会检测到SIGPIPE信号,同时可利用shutdown的第二个参数选择断连的方式。

如何选择使用哪个函数

如果您需要立即关闭套接字并释放所有资源,那么可以使用 close() 函数。

如果您需要控制关闭的方式,并且需要增加超时处理,那么可以使用 shutdown() 函数。

如果您需要关闭套接字的读或写功能,而不关闭套接字本身,那么可以使用 shutdown() 函数。


建议

在关闭套接字时,应该先关闭读功能,再关闭写功能,最后再使用 close() 函数关闭套接字本身。

在使用 shutdown() 函数时,应该注意超时处理,避免出现死锁等问题。

在进行网络编程时,应该根据实际情况选择使用 close() 或 shutdown() 函数。


参考文献

Stevens, W. R. (1994). TCP/IP Illustrated, Volume 1: The Protocols (2nd ed.). Addison-Wesley Professional.

Forouzan, B. A. (2013). Data Communications and Networking (5th ed.). McGraw-Hill Education.

Microsoft Corporation. (2021). shutdown function (winsock). Retrieved from https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-shutdown

Microsoft Corporation. (2021). close function (winsock). Retrieved from https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-close

目录
相关文章
|
8天前
|
运维 监控 网络协议
|
12天前
|
Ubuntu Linux 虚拟化
Linux虚拟机网络配置
【10月更文挑战第25天】在 Linux 虚拟机中,网络配置是实现虚拟机与外部网络通信的关键步骤。本文介绍了四种常见的网络配置方式:桥接模式、NAT 模式、仅主机模式和自定义网络模式,每种模式都详细说明了其原理和配置步骤。通过这些配置,用户可以根据实际需求选择合适的网络模式,确保虚拟机能够顺利地进行网络通信。
|
24天前
|
网络协议 安全 Ubuntu
Linux中网络连接问题
【10月更文挑战第3天】
27 1
|
29天前
|
监控 Linux 测试技术
Linux系统命令与网络,磁盘和日志监控总结
Linux系统命令与网络,磁盘和日志监控总结
51 0
|
29天前
|
监控 Linux 测试技术
Linux系统命令与网络,磁盘和日志监控三
Linux系统命令与网络,磁盘和日志监控三
36 0
|
4月前
|
网络协议 安全 Java
Java中的网络编程:Socket编程详解
Java中的网络编程:Socket编程详解
|
4月前
|
Java 大数据
如何在Java中进行网络编程:Socket与NIO
如何在Java中进行网络编程:Socket与NIO
|
4月前
|
Java API 网络安全
Java网络编程入门
Java网络编程入门
|
4月前
|
Java API 开发者
Java网络编程基础与Socket通信实战
Java网络编程基础与Socket通信实战
|
4月前
|
网络协议 安全 Java
Java中的网络编程:Socket编程详解
Java中的网络编程:Socket编程详解