9.close和shutdown函数

打开的套接口总是需要调用close来关闭,这样就能够回收资源。虽然大部分发行版本的linux,至少在我的fedora上面,不执行close,当进程死亡的时候打开的套接口仍然会被回收,但依赖于这点回收的话具有很大的不确定因素,因为不能保证所有的内核都会在进程死亡的时候去回收资源(绝大部分嵌入式系统上不会)。

但依靠close来关闭套接口不是一个非常有效的方法。原因一:之前曾经说过,close只是将描述字共享计数减一,只有当共享计数为0的时候,资源才会被回收。原因二是:双方通信存在了很大的不确定因素,譬如说,当server觉得无数据可发,可以close,这不代表client没有数据发送。同样的道理,当client觉得没有数据可发,可以执行close时候,server并不一定没数据可发。

当然这2个问题,如果小心控制还是可以解决的,但这并不妨碍shutdown函数出现。对于第一个问题,shutdown函数会触发套接口发送fin包,会关掉套接口,而不关此套接口上有多少个进程在使用。close只是将计数减一,只有减成0时候,才会发送fin包。另一方面,是由于关闭全双工引起,如果没有数据可发,则关闭发送即可,不一定要关闭接受功能,shutdown也提供了关闭的套接的发送或者接受(当然也可以2者都关闭)。先看下API

int shutdown(int sockfd, int howto);

第一个很好理解,第2个可以取值SHUT_RD, SHUT_WR, SHUT_RDWR也不是很难理解。

先看看close的缺陷,只看有关代码

for(;;) {
        int connfd = accept(sockfd, (struct sockaddr *)NULL, NULL);
        printf("%d\n", connfd);
        pid_t child_pid;
        if((child_pid = fork()) == 0) {
                char buf[101];
                int n = read(connfd, buf, 100);
                buf[n] = '\0';
                printf("%s", buf);
                close(connfd);
                exit(0);
        }
}

服务器端代码。当我们在子进程中调用close(connfd),而父进程没有调用close,我们看看会发生什么情况。

[root@liumengli net]# netstat -a | grep 6584
tcp        0      0 *:6584                      *:*                         LISTEN
tcp        0      0 192.168.1.235:6584          192.168.1.164:51176         CLOSE_WAIT

上面是监听套接口,对于CLOSE_WAIT状态,解释是:client完成通信,发送FIN。server接受并发送ack,进入close_wait状态,虽然子进程执行了close,但父进程没有,所以链接处于CLOSE_WAIT状态,server没有继续发送FIN。(另一个明显的特征是,比如此前描述字如果是3,如果此链接处于close_wait状态,则下一个分配的描述字将会是4。如果一直没有回收,也就是描述字所指向的内存区域没有回收,内存资源就会泄漏)。

如果在父进程添加close则出现

[root@liumengli net]# netstat -a | grep 9584
tcp        0      0 *:9584                      *:*                         LISTEN

这时就没有在看见上个链接存在的痕迹,当然也可能会出现,这还得取决于client的代码。当然我们想说明的就是close必须是减到0才会回收链接。

当我们把代码缓冲shutdown时候,就会发现即使在不在父进程中添加shutdown,依然可以达到关闭链接的效果,这个自己可以试试,我就不贴了(应该不难)。

shutdown的另一方面:

如果当我们执行shutdown(sockfd, SHUT_RD),对底层协议不会有影响, TCP窗口接受数据,但窗口不改变直至窗口满(所谓窗口不改变,也就是说接受到的数据不会传到应用层,应用程序不会接受到数据),也不会确认数据。对于UDP任何情况下都不会产生ICMP错误包。这些话不好理解,从程序来看到底会发生什么事情。

for(;;) {
        char buf[101];
        int n = read(connfd, buf, 100);
        buf[n] = '\0';
        printf("%s", buf);
        printf("n = %d\n", n);
        if(!n) {
                getchar();
                break;
        }
        if(n == 3) {
                shutdown(connfd, SHUT_RD);
        }
}

代码很简单,服务器循环不断的从套接口中读出数据,当数据长度为3时候,就关闭读。从结果来看,当关闭读以后,就再也不会从套接口中读取数据,即使你再次调用read函数,进程会一直被阻塞在read函数中,不会返回。但查看链接来看

tcp        0      0 *:9784                      *:*                         LISTEN
tcp       15      0 192.168.1.235:9784          192.168.1.164:44679         ESTABLISHED

链接仍然存在,只是没有内核缓冲区的数据没有进入应用程序缓冲区(所谓的窗口是起控制作用,滑动窗口协议是每一个网络的基本教材都会讲到的)。

由此可见shutdown(sockfd, SHUT_RD)只是在内核缓冲区到达应用缓冲区产生影响,不会对链接产生影响。

当调用shutdown(sockfd, SHUT_WR)时候,TCP将会发送FIN.

此时如果再次想套接口写入数据,会引发SIGPIPE信号并导致进程终止,但如果是接受数据,则不会出现问题。

猜你喜欢

转载自memorymyann.iteye.com/blog/631072