背景

在linux网络编程中,经常需要编写关闭socket的代码,比如心跳检测失败需要关闭重连;网络报异常需要关闭重连。但究竟关闭操作做了什么,却不太清楚。目前项目使用Netty框架来实现的网络编程,查看netty源码可以得知,netty最终是调用了java Nio的close接口做的关闭操作,那么想研究清楚这个close操作究竟做了什么,可以从两个方向入手,这两个方向也是从下至上的。

  1. 搞清楚如果使用C/C++编程,应该调用哪个系统调用函数?函数内部做了什么,涉及到什么TCP/IP的协议参数
  2. 搞清楚java nio在调用close方法时,究竟使用了哪个系统调用?

本文首先解决的是第一步,搞清楚系统调用相关的知识。

相关系统调用

Linux平台下,提供了两个系统调用函数供开发人员使用:

  • close函数
  • shutdown函数
close函数
int close(int sockfd);

这个函数的具体行为由一个TCP/IP套接字选项控制:SO_LINGER

SO_LINGER的在头文件<sys/socket.h>中定义如下:

struct linger{
int l_onoff;
int l_linger;
}

根据这个选项参数的不同,close的逻辑如下:

1)l_onoff=0,l_linger=1或者0时(这个是默认选项)

  • close会立即返回,0为成功-1为失败
  • 调用进程在该套接字上不能再发送或接收请求
  • 接收缓冲区中的数据将会被抛弃
  • 如果发送缓冲区中还有数据,会由操作系统在后台继续发送
  • 如果套接字的引用计数变为0,则发送FIN表示关闭
    • 引用计数:进程和子进程可以共享一个套接字,每当一个进程做了close操作,引用计数就会减1
  • 最后释放套接字的系统资源

2)l_onoff=1,l_linger=0时

  • close会立即返回
  • 调用进程在该套接字上不能再发送或接收请求
  • 发送和接收缓冲区中的数据都会被抛弃
  • 如果套接字的引用计数变为0,则发送RST到对端,并且状态直接变成CLOSED
    • 注:RST没有超时重发机制,如果对端没有收到RST,继续发送,那么又会促使本端发送RST,直到对方收到
  • 最后释放套接字的系统资源

3)l_onoff=1,l_linger>0时

  • 如果是阻塞的socket,close函数不会立即返回;非阻塞的会立即返回
  • 调用进程在该套接字上不能再发送或接收请求
  • 接收缓冲区中的数据将会被抛弃
  • 如果发送缓冲区中还有数据,会由操作系统在后台继续发送
  • 如果套接字的引用计数变为0,则发送FIN表示关闭,在套接字状态编程CLOSED前,如果超时时间到,返回EWOULDBLOCK错误
  • 最后释放套接字的系统资源

总结一下:

默认情况和第三种情况对比,默认情况相当于一个异步请求,并且无法得知操作结果;第三种情况,可以在超时时间范围内做close处理,发送未发送完毕的数据。第二种情况属于粗暴的关闭socket,在2MSL时间范围内如果新建立了一个“化身”(ip port dip dport都一样的套接字),可能会被前一个套接字相关的数据所影响。

注:对2MSL不理解的小伙伴,可以看下这篇博客,讲解的很清晰:

[ 为什么tcp的TIME_WAIT状态要维持2MSL

](https://www.cnblogs.com/abozhang/p/10974627.html)

shutdown函数

有一种业务场景,客户端发送数据到服务端,发送完毕后,客户端就可以关闭客户端写方向的连接了,等待服务端处理。

业务需求是保证客户端发送的数据都会被服务端应用程序接收并处理。如果使用close函数关闭连接,最多只能保证,全部数据都已经发送到了对端的接收缓冲区中(使用SO_LINGER相关配置项),但是无法确保对端的应用程序一定读取到数据(close以后,本端socket就无法读了)。

在这种业务场景下,如果需要确保服务端一定读取到了数据,可以考虑使用shutdown函数。

int shutdown(int sockfd,int howto);

执行shutdown函数,成功返回0,出错返回-1。

howto是这个函数的设置选项:

  • SHUT_RD:关闭套接字的读方向。读缓冲区中的数据都会被抛弃,如果有新数据到达,都将被ACK,并且被悄悄丢弃。
  • SHUT_WR:关闭套接字的写方向。在套接字发送缓冲区的数据都会被继续发送过去,然后发送正常的FIN开始挥手流程。
  • SHUT_RDWR:读和写两个方向都关闭

只使用shutdown函数,也无法保证满足我们上面提到的业务需求,即保证服务端应用程序是否正确读取数据。目前有两种解决方式可以实现上述业务需求:

  • shutdown后,使用read函数,等待对端的FIN发送过来,此时read函数返回0
  • 应用级别确认:完全发送数据后,再读取一个字节的数据(这个数据是客户端和服务端的自定义协议,比如:服务端完全接受数据后,可以继续发送一字节的数据,代表读取成功)

第一种方式流程图如下(摘自《Unix网络编程》):

第二种方式流程图如下(摘自《Unix网络编程》):

close函数和shutdow函数的区别
  1. close函数会计算引用计数,当计数为0时才触发挥手操作;shutdown函数则不需要判断引用计数来触发挥手操作
  2. close函数可以终止两个方向的传输,shutdown可以控制只终止一个方向的
  3. close函数会关闭资源,shutdown函数不会

网络编程-关闭连接(1)-C/C++相关系统调用的更多相关文章

  1. 聊聊iOS中网络编程长连接的那些事

    1.长连接在iOS开发中的应用 常见的短连接应用场景:一般的App的网络请求都是基于Http1.0进行的,使用的是NSURLConnection.NSURLSession或者是AFNetworking ...

  2. 【Android 应用开发】Android 网络编程 API笔记 - java.net 包相关 接口 api

    Android 网络编程相关的包 : 9 包, 20 接口, 103 类, 6 枚举, 14异常; -- Java包 : java.net 包 (6接口, 34类, 2枚举, 12异常); -- An ...

  3. Android 网络编程 API笔记 - java.net 包相关 接口 api

    Android 网络编程相关的包 : 9 包, 20 接口, 103 类, 6 枚举, 14异常; -- Java包 : java.net 包 (6接口, 34类, 2枚举, 12异常); -- An ...

  4. UNIX网络编程——TCP连接的建立和断开、滑动窗口

    一.TCP段格式: TCP的段格式如下图所示: 源端口号与目的端口号:源端口号和目的端口号,加上IP首部的源IP地址和目的IP地址唯一确定一个TCP连接. 序号:序号表示在这个报文段中的第一个数据字节 ...

  5. python网络编程--TCP连接的三次握手(三报文握手)与四次挥手

    一.TCP连接 运输连接有三个阶段: 连接建立.数据传送和连接释放. 在TCP连接建立过程中要解决以下三个问题: 1,要使每一方能够确知对方的存在. 2.要允许双方协商一些参数(如最大窗口之,是否使用 ...

  6. 三十天学不会TCP,UDP/IP网络编程-ARP -- 连接MAC和IP

    继续来做(da)推(guang)介(gao)我自己的!由于这两年接触到了比较多的这方面的知识,不想忘了,我决定把他们记录下来,所以决定在GitBook用半年时间上面写下来,这是目前写的一节,目前已完成 ...

  7. 网络编程-TCP连接的建立与终止

    TCP是一个面向连接的协议.无论哪一方向另一方发送数据之前,都必须先在双方之间建立一条连接. 1.建立连接 请求端(通常称为客户)发送一个 SYN 段指明客户打算连接的服务器的端口,以及初始序号( I ...

  8. 网络编程——TCP连接

    TCP在双方传输数据前,发送方先请求建立连接,接收方同意建立连接后才能传输数据.(打电话:先拨号,等对方同意接听后,才能交流)...高可靠性 UDP不需要建立连接(发短信).不可靠,可能出现数据丢失等 ...

  9. 网络编程-TCP连接-length

    在使用readLine方式时,常常会遇到因为没有结束标记,而导致阻塞现象. 如果不想使用结束标记,这里可以使用一个固定长度的byte[],来获取流. server package com.net.tc ...

  10. 网络编程-TCP连接-readLine

    Server: package com.net.tcp; import java.io.BufferedReader; import java.io.IOException; import java. ...

随机推荐

  1. 从零开始的Python世界生活——语法基础先导篇(Python小白零基础光速入门上手)

    从零开始的Python世界生活--语法基础先导篇(Python小白零基础光速入门上手) 1. 准备阶段 1.1 下载并安装Python 1.1.1 下载步骤: 访问Python官方网站:点击这里下载P ...

  2. [密码管理/信息安全] KeePass Java 客户端 : KeePassJava2

    序:续<KeePass:密码管理工具> [密码管理/信息安全] 密码管理工具:KeePass vs LastPass vs 1Password - 博客园/千千寰宇 [推荐] 概述 Kee ...

  3. 使用 SK Plugin 给 LLM 添加能力

    前几篇我们介绍了如何使用 SK + ollama 跟 LLM 进行基本的对话.如果只是对话的话其实不用什么 SK 也是可以的.今天让我们给 LLM 整点活,让它真的给我们干点啥. What is Pl ...

  4. 为了解决服务启动慢的问题,我为什么要给Apollo和Spring提交PR?

    最近在整理之前记录的工作笔记时,看到之前给团队内一组服务优化启动耗时记录的笔记,简单整理了一下分享出来.问题原因并不复杂,主要是如何精准测量和分析,优化后如何定量测量优化效果,说人话就是用实际数据证明 ...

  5. 第一章 Java集合框架

    ----------------------------------------------------------------------------- Java集合框架(一)-ArrayList ...

  6. Mysql死锁问题如何排查和解决

    Mysql 查询是否存在锁表有多种方式,这里只介绍一种最常用的. 1.查看正在进行中的事务SELECT * FROM information_schema.INNODB_TRX2.查看正在锁的事务SE ...

  7. Python 潮流周刊#86:Jupyter Notebook 智能编码助手(摘要)

    本周刊由 Python猫 出品,精心筛选国内外的 250+ 信息源,为你挑选最值得分享的文章.教程.开源项目.软件工具.播客和视频.热门话题等内容.愿景:帮助所有读者精进 Python 技术,并增长职 ...

  8. 数组 & 结构 & 位域 & 联合 & 枚举 & typedef

    C语言提供的五种自定义的构造数据类型: 数组: 是处理同一名字下的不同类型变量的结合体 结构: 是一种归在同一名字下相关的不同类型变量的结合,也可称为不同数据类型的集成体 位域:允许按为访问数据成员的 ...

  9. Kotlin:【字符串操作】substring、split、replace、字符串比较==与===、foreach遍历字符

    字符串的内存区域问题: 都在常量池内,相同的字符串比较属于同一引用 在字符串常量池开辟了新的内存区域,一共有三个对象,所以引用比较不相等

  10. 记一次DAC转换功能修改的解决

    最近的项目捣鼓msp430需要用到dac转换以输出模拟信号,项目代码是接手前同事的 有些地方调试不通,以前没有接触过msp430芯片,更是没搞过这种芯片io口时序驱动层的东西,甚至纯c的都是头一次作为 ...