前言
简介
在网络编程中,套接字是一个重要的概念。套接字是一种用于网络通信的接口,它可以实现进程之间的通信和数据传输。在使用套接字进行网络编程时,关闭套接字是一个必要的操作。关闭套接字可以释放资源,避免程序出现内存泄漏等问题。在关闭套接字时,我们通常会用到 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