linux网络编程之socket(十):shutdown 与 close 函数 的区别

假设server和client 已经建立了连接,server调用了close, 发送FIN 段给client(其实不一定会发送FIN段,后面再说),此时server不能再通过socket发送和接收数据,此时client调用read,如果接收到FIN 段会返回0,但client此时还是可以write 给server的,write调用只负责把数据交给TCP发送缓冲区就可以成功返回了,所以不会出错,而server收到数据后应答一个RST段,表示服务器已经不能接收数据,连接重置,client收到RST段后无法立刻通知应用层,只把这个状态保存在TCP协议层。如果client再次调用write发数据给server,由于TCP协议层已经处于RST状态了,因此不会将数据发出,而是发一个SIGPIPE信号给应用层,SIGPIPE信号的缺省处理动作是终止程序。

有时候代码中需要连续多次调用write,可能还来不及调用read得知对方已关闭了连接就被SIGPIPE信号终止掉了,这就需要在初始化时调用sigaction处理SIGPIPE信号,对于这个信号的处理我们通常忽略即可,signal(SIGPIPE, SIG_IGN); 如果SIGPIPE信号没有导致进程异常退出(捕捉信号/忽略信号),write返回-1并且errno为EPIPE(Broken pipe)。(非阻塞地write)

 #include <unistd.h>  int close(int fd);

close 关闭了自身数据传输的两个方向。

 #include <sys/socket.h>  int shutdown(int sockfd, int how);

shutdown 可以选择关闭某个方向或者同时关闭两个方向,shutdown how = 0 or how = 1 or how = 2 (SHUT_RD or SHUT_WR or SHUT_RDWR),后两者可以保证对等方接收到一个EOF字符(即发送了一个FIN段),而不管其他进程是否已经打开了这个套接字。而close不能保证,只有当某个sockfd的引用计数为0,close 才会发送FIN段,否则只是将引用计数减1而已。也就是说只有当所有进程(可能fork多个子进程都打开了这个套接字)都关闭了这个套接字,close 才会发送FIN 段。

所以说,如果是调用shutdown how = 1 ,则意味着往一个已经发送出FIN的套接字中写是允许的,接收到FIN段仅代表对方不再发送数据,但对方还是可以读取数据的,可以让对方可以继续读取缓冲区剩余的数据。

下面使用shutdown 修改客户端程序,在前面讲过的使用select函数修改后的客户端程序基础上,修改很小一部分:

if (FD_ISSET(fd_stdin, &rset))
{

    if (fgets(sendbuf, sizeof(sendbuf), stdin) == NULL)
    {
        stdineof = 1; //表示已经输入完毕
        /* 关闭sock的写端,还能够接收数据 */
        shutdown(sock, SHUT_WR);
    }
    else
    {
        writen(sock, sendbuf, strlen(sendbuf));
        memset(sendbuf, 0, sizeof(sendbuf));

    }
}

为了测试我们想要的效果,需要在select函数修改后的服务器端程序 的 134 行代码之后,即writen 之前 sleep(4); 目的是接收到客户端数据后不马上回射回去,睡眠4s 后在客户端已经关闭连接的情况下再发送数据。

先运行服务器端程序,再运行客户端程序,在客户端标准输入,迅速敲入两行:AAAAA\n  BBBBB\n 然后按下ctrl+d 即fgets 会返回NULL,然后调用shutdown关闭写端,虽然服务器端延时才发送数据,此时客户端写端已经关闭,但还是可以读取到回射回来的数据,服务器端最后得到一个FIN段,read 返回0,打印输出 client close ,并且close(conn); 而客户端在读取服务端回射回来的两次数据后,再次read 也返回0,故打印 server connect close,break退出循环,进程顺利退出。从下面的输出还可以看出,因为延时的关系,所以不像以前那样发射一行就回射一行。

simba@ubuntu:~/Documents/code/linux_programming/UNP/socket$ ./echoser_select  recv connect ip=127.0.0.1 port=54010 fdsgfgd gfedg client close 

...........................

simba@ubuntu:~/Documents/code/linux_programming/UNP/socket$ ./echocli_select_shutdown  local ip=127.0.0.1 port=54010 fdsgfgd gfedg fdsgfgd gfedg server connect close

如果我们将客户端程序中的shutdown 改成了 close,那么当延时后服务器端发送数据给客户端时,客户端的读端和写端都已经关闭,第一次发AAAAA会返回一个RST段,根据本文前面所说,再次发BBBBB直接产生SIGPIPE信号,默认会终止进程,但因为我们已经设置了忽略SIGPIPE信号,所以服务器端进程不会被终止,但客户端也会出错,因为回到while循环开头,select阻塞等待时发现套接字的读端已经关闭,所以不能再关心可读事件了,select会返回-1,错误码是 EBADF: Bad File Descriptor。

参考:

《Linux C 编程一站式学习》

《TCP/IP详解 卷一》

《UNP》

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏左瞅瞅,右瞅瞅

nmap详解

nmap,也就是Network Mapper,最早是Linux下的网络扫描和嗅探工具包,用来扫描网上电脑开放的网络连接端,确定哪些服务运行在哪些连接端。它是网...

801
来自专栏FreeBuf

Linux内核实现多路镜像流量聚合和复制

作者 linxinsnow 应用场景 我们在进行安全性监控、测试的过程中,难免会遇到这样的问题:需要部署大量基于镜像流量的安全设备,如IPS,异常流量,数据库审...

3025
来自专栏pythonlove

Linux防火墙iptables(二)

上一篇文章我们说了一些iptables/netfilter的基础知识,本文我们来介绍一下iptables的规则编写。Iptables的规则可以概括的分为两个方面...

912
来自专栏Laoqi's Linux运维专列

keepalived工作原理和配置文件解析

3317
来自专栏图像识别与深度学习

2018-10-20Ubuntu系统计算机和Arduino通过ESP8266模块通信

二、WIFI  TCP通信--计算机为TCP Client, Makeblock Me wifi模块(ESP8266)为TCP Server

843
来自专栏A周立SpringCloud

实用技巧:Hystrix传播ThreadLocal对象(两种方案)

目前,Spring Cloud已在南京公司推广开来,不仅如此,深圳那边近期也要基于Spring Cloud新开微服务了。 于是,领导要求我出一套基于Spring...

5525
来自专栏后台全栈之路

U-Boot 中添加自定义网络通信的方法

U-boot 没有 TCP 协议栈,不支持 TCP(提出要在 U-boot 里面支持 TCP 的协议的 PM 你给我出去)。但是UDP 还是有的。使用 U-bo...

1268
来自专栏会跳舞的机器人

Nginx+Tomcat实现负载均衡

在103和117上分别部署相同的Tomcat程序,修改index.jsp页面,把内容改为各自的IP地址。

883
来自专栏皮振伟的专栏

[network][udp]你不要偷偷发包,我跟你讲

前言: 互联网后台的服务器上,通常需要运行多达数百个进程,甚至更多。 有一天,运维兄弟突然找上门,说:xx服务器上为什么要访问yy服务(例如yy服务使用UDP的...

3416
来自专栏信安之路

nmap使用指南(终极版)

2.指定范围 192.168.1.1-255 192.168.1-255.1(任意位置) 3.IPv6地址只能用规范的IPv6地址或主机名指定。 CIDR 和...

1630

扫码关注云+社区