Socket 原理和思考

众所周知Reactor是一种非常重要和应用广泛的网络编程模式,而Java NIO是Reactor模式的一个具体实现,在Netty和Redis都有对其的运用。而不管上层模式如何,底层都是走的Socket,对底层原理的了解会反哺于上层,避免空中楼阁现象。
所以本文对Socket原理及其中值得关注的点作再次梳理,最终目标还是为了理解Reactor及NIO。

Socket简介


  • Socket用于网络进程间通信,当然单机上不同进程间也行。
  • Socket位于五层网络模型中的应用层和传输层之间,是一种抽象层,也是一组接口,把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,应用层也能更便捷在网络间进行数据传输。
本文对Socket基础原理不做过多介绍,可以参考: https://blog.csdn.net/qq_39208536/article/details/137589718icon-default.png?t=N7T8https://blog.csdn.net/qq_39208536/article/details/137589718

传统Socket编程


虽然现在几乎不用再涉及原生Socket编程,但这些代码对理解原理还是有用的。
Server端:
public class MySocketServer {
    private static ExecutorService executorService = Executors.newCachedThreadPool();

    public static void main(String[] args) throws IOException, InterruptedException {
        //服务端的主线程是用来循环监听客户端请求
        ServerSocket server = new ServerSocket(8686);
        //创建一个服务端且端口为8686
        Socket client = null;
        System.out.println("服务端启动");

        //循环监听
        while (true) {
            //服务端监听到一个客户端请求
            System.out.println("阻塞等待accept....");
            client = server.accept();
            System.out.println(client.getRemoteSocketAddress() + "地址的客户端连接成功!");
            //将该客户端请求通过线程池放入HandlMsg线程中进行处理
            executorService.submit(new HandleMsg(client));
        }
    }

    public static void handle(Socket client) {
        //创建字符缓存输入流
        BufferedReader bufferedReader = null;
        //创建字符写入流
        PrintWriter printWriter = null;
        try {
            //获取客户端的输入流
            bufferedReader = new BufferedReader(new InputStreamReader(client.getInputStream()));
            //获取客户端的输出流,true是随时刷新
            printWriter = new PrintWriter(client.getOutputStream(), true);
            String inputLine = null;
            long a = System.currentTimeMillis();
            Thread.sleep(1000);

            while ((inputLine = bufferedReader.readLine()) != null) {
                printWriter.println("hello " + inputLine);
            }
            long b = System.currentTimeMillis();
            System.out.println(Thread.currentThread().getName() + "线程结束,花费了:" + (b - a) + "ms");
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        } finally {
            try {
                bufferedReader.close();
                printWriter.close();
                client.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    //一旦有新的客户端请求,创建这个线程进行处理
    private static class HandleMsg implements Runnable {
        //创建一个客户端
        Socket client;
        public HandleMsg(Socket client) {
            this.client = client;
        }
        @Override
        public void run() {
            handle(client);
        }
    }
}
Client端:
public class MySocketClient {
    public static void main(String[] args) throws IOException {
        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @SneakyThrows
                @Override
                public void run() {
                    callServer();
                }
            }).start();
        }
    }

    public static void callServer() throws IOException {
        Socket client = null;
        PrintWriter printWriter = null;
        BufferedReader bufferedReader = null;
        try {
            client = new Socket();
            // 连接超时
            client.connect(new InetSocketAddress("localhost", 8686), 100);
            // 读写超时
//            client.setSoTimeout(10);

            printWriter = new PrintWriter(client.getOutputStream(), true);
            printWriter.println(Thread.currentThread().getName());
            printWriter.flush();

            System.out.println(Thread.currentThread().getName() + " " + "等待服务端消息...");
            bufferedReader = new BufferedReader(new InputStreamReader(client.getInputStream()));            //读取服务器返回的信息并进行输出
            System.out.println(Thread.currentThread().getName() + " " + "来自服务器的信息是:" + bufferedReader.readLine());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            printWriter.close();
            bufferedReader.close();
            client.close();
        }
    }
}

创建Socket的时候操作系统创建了什么


  • Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作,Socket就是该模式的一个实现。Socket即是一种特殊的文件,一些Socket函数就是对其进行的操作。
  • 客户端或服务端Socket创建后,操作系统为其会分配:
    • 文件描述符(区别文件句柄),用于操作Socket,参考:https://blog.csdn.net/tjcwt2011/article/details/122685933 https://zhuanlan.zhihu.com/p/364617329icon-default.png?t=N7T8https://zhuanlan.zhihu.com/p/364617329 https://blog.csdn.net/tjcwt2011/article/details/122685933
    • 位于内核的发送缓冲区、接收缓冲区
    • 其他数据结构,暂不讨论。

Socket传输数据经历的过程


网卡也是有缓冲区的,暂不讨论。

阻塞、非阻塞与同步、异步的关系


看了很多文章对这两组概念解释和对比,说的太复杂了,其实没必要,两句话就能说清楚。
首先,对于读数据recv或read(写数据同理,没写出来),分两个阶段:
  1. 等待数据可读。
  2. 系统调用讲数据从内核拷贝到用户空间。
然后,再对比那两组概念:
  • 阻塞、非阻塞是对于等待数据可读、可写时,是否死等;
  • 同步、异步是对于数据在用户空间和内核传递时,是否等待其完成;
结合这四种LinuxIO模型对比(一般讨论LinuxIO模型会有五种,其中信号驱动IO用得太少,暂不讨论。
可以得出结论: 阻塞IO、非阻塞IO、多路复用都属于同步IO!区别于异步IO
注意:我们之前说的复习Socket还是为了进一步学习NIO和Reactor模式,这里有几点需要区分原生Socket和NIO
  • 原生Socket在创建的时候也可以指定为阻塞或非阻塞模式。原生非阻塞Socket编程较复杂,比如可能需要循环判断send和recv的数据量是否完整,故一般不会轻易挑战。
  • 原生Socket也是可以直接编程实现多路复用的,参考: SOCKET编程与复用 | YuYoung's Blog
  • NIO底层实现也是操作的原生Socket,可以看作是对以上两点的包装,使用NIO来操作非阻塞IO就方便多了。

发送缓冲区和接收缓冲区


 1,send在本质上并不是向网络上发送数据,而是将应用层发送缓冲区的数据 拷贝到内核缓冲区 中,至于数据什么时候会从网卡缓冲区中真正的发到网络中,要根据TCP/IP协议栈的行为来确定。recv在本质上并不是从网络上收取数据,而是将 内核缓冲区中的数据拷贝到 应用程序的缓冲区中,也就是说从网络接收数据时,TCP/IP协议栈会把数据收下来放在内核的接收缓冲区内。
2,如果接收缓冲区一直满着堆积,没有recv读取,网卡缓冲区也满,网络发过来的数据怎么存?
只有当接收网络报文的速度大于应用程序读取报文的速度时,可能使读缓存达到了上限,这时这个缓存使用上限才会起作用。所起作用为:丢弃掉新收到的报文,防止这个TCP连接消耗太多的服务器资源。同样,当应用程序发送报文的速度大于接收对方确认ACK报文的速度时,写缓存可能达到上限,从而使send方法阻塞或失败。
3,当待发送(拷贝)的数据的长度大于发送缓冲区的长度,是如何发送的?
一次send调用,但TCP/IP协议栈可能会分多帧发送,参考:https://blog.csdn.net/aflyeaglenku/article/details/73614292
4,recv和send不一定是一一对应的,也就是说并不是send一次,就一定recv一次就接收完,有可能send一次,recv多次才接收完,也有可能send多次,一次recv就接收完了。  

缓冲区可读、可写的判断条件


1,接收低水位和发送低水位

每个套接字有一个接收低水位和一个发送低水位。他们由select函数使用。

  • 接收低水位标记:让select返回“可读”时接收缓冲区中所需的数据量。对于TCP默认值为1。
  • 发送低水位标记:让select返回“可写”时发送缓冲区中所需的可用空间。对于TCP,其默认值常为2048。

2,引用《Unix网络编程》中的可读可写条件

当满足下列条件之一时,一个套接字准备好读:

  • 该套接字接收缓冲区中的数据字节数大于等于套接字接收缓冲区低水位标记的当前大小。对这样的套接字执行读操作不会阻塞并将返回一个大于 0 的值(也就是返回准备好读入的数据)。我们可以使用 SO_RCVLOWAT 套接字选项设置该套接字的低水位标记。对于 TCP 和 UDP 套接字而言,其默认值为 1。
  • 该连接的读半部关闭(也就是接收了 FIN 的 TCP 连接)。对这样的套接字的读操作将不阻塞并返回 0 (也就是返回 EOF)。
  • 该套接字是一个监听套接字且已完成的连接数不为 0。对这样的套接字的 accept 通常不会阻塞。
  • 其上有一个套接字错误待处理。对这样的套接字的读操作将不阻塞并返回 -1(也就是返回一个错误),同时把 errno 设置成确切的错误条件。这些待处理错误也可以通过指定 SO_ERROR 套接字选项调用 getsockopt 获取并清除。

当满足下列条件之一时,一个套接字准备好写:

  • 该套接字发送缓冲区中的可用空间字节数大于等于套接字发送缓冲区低水位标记的当前大小,并且要求该套接字已连接(TCP)或者不需要连接(UDP)。这意味着如果我们把这样的套接字设置为非阻塞,写操作将不阻塞并返回一个正值(例如由传输层接收的字节数)。我们可以使用 SO_SNDLOWAT 套接字选项来设置该套接字的低水位标记。对于 TCP 和 UDP 套接字而言,其默认值通常为 2048。
  • 该连接的写半部关闭,对这样的套接字的写操作将产生 SIGPIPE 信号。
  • 使用非阻塞式 connect 的套接字已建立连接,或者已经以失败告终。
  • 其上有一个套接字错误待处理。对这样的套接字的写操作将不阻塞并返回 -1(也就是返回一个错误),同时把 errno 设置成确切的错误条件。这些待处理的错误也可以通过指定 SO_ERROR 套接字选项调用 getsockopt 获取并清除。

当缓冲区满了时,发送或接收数据会怎样?


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/725655.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【数学建模】解析几何与方程模型

文章目录 解析几何与方程模型1.几何建模思想2.Numpy在线性代数中的使用3.国赛求解3.1题目3.2 问题1求解建立模型代码求解 3.3 问题2求解 4.问题答疑Q1:什么是行列式&#xff0c;其使用场景是什么行列式的定义行列式的性质行列式的使用场景 Q2:2023B题问题一用相似三角形求解覆盖…

htb_Editorial

hack the book Editorial 端口扫描 80 22 目录扫描 /upload 是一个上传book information的页面 其中最顶上有一个可以上传书本封面的地方&#xff0c;可以从本地上传&#xff0c;也可以从远程下载 这里可能涉及ssrf和本地文件上传&#xff0c;逐一尝试 随便上传一个图片…

使用高斯混合模型(GMM)进行猫狗音频聚类(Kaggle Audio Cats and Dogs)

Audio Cats and Dogs | Kaggle 目录 一、实验目标 二、数据分析 三、实验结果 四、改进方向 一、实验目标 数据集包括164个标注为猫的.wav文件&#xff0c;总共1323秒和113个标注为狗叫声的.wav文件&#xff0c;总共598秒&#xff0c;要求判别每个音频是狗叫还是猫叫 二、…

Spark SQL函数详解:案例解析(第8天)

系列文章目录 1- Spark SQL函数定义&#xff08;掌握&#xff09; 2- Spark 原生自定义UDF函数案例解析&#xff08;掌握&#xff09; 3- Pandas自定义函数案例解析&#xff08;熟悉&#xff09; 4- Apache Arrow框架案例解析&#xff08;熟悉&#xff09; 5- spark常见面试题…

Centos 配置安装Mysql

linux安装配置mysql的方法主要有yum安装和配置安装两种&#xff0c;由于yum安装比较简单&#xff0c;但是会将文件分散到不同的目录结构下面&#xff0c;配置起来比较麻烦&#xff0c;这里主要研究一下配置安装mysql的方法 1、环境说明 centos 7.9 mysql 5.7.372、环境检查 …

ChatGPT Plus GPT-4o Claude 3 Opus合租拼车全新方式

无需自己搭建&#xff0c;登录即可用&#xff0c;国内直连访问&#xff0c;聚合多家最强大模型&#xff0c;随意选择使用。立即体验 datapipe.top 支持 OpenAI 最新 GPT-4o &#xff0c;获得快速高质量的对话&#xff0c;保证可用配额。支持多种大模型&#xff0c;GPT-4o &…

SerialChart上位机使用详解

SerialChart 上位机 软件分为三个区域&#xff1a;接收数据区&#xff0c;用于显示串口接收的数据。参数配置区&#xff0c;用于配置串口参数和显示参数。波形显示区&#xff0c;显示串口数据的波形。 在参数配置区写入串口号&#xff0c;波特率&#xff0c;通道波形颜色等&am…

高压电阻器支持牙科 X 射线成像的准确性

为了捕获患者牙齿和颌骨的足够图像&#xff0c;牙医依靠锥形束计算机断层扫描 &#xff08;CBCT&#xff09; 系统的先进 3D 成像。CBCT系统的输出对于准确诊断口腔健康问题和随后的治疗计划至关重要。为了确保这些图像的可靠性&#xff0c;CBCT系统制造商利用了Exxelia Ohmcra…

Jenkins+K8s实现持续集成(一)

镜像仓库的搭建 docker run -d \--restartalways \--name registry \-p 5000:5000 \-v /root/devops/registry/data:/var/lib/registry \registry安装完之后&#xff0c;执行下面命令可以看到镜像仓库已经安装成功 docker ps 然后在浏览器上输入下面地址进行访问 http://ip:…

一键简易桌签(带背景)-Word插件-大珩助手

问题整理&#xff1a; 如何Word中设计简易桌签&#xff1f;如何设置带背景图的桌签&#xff1f; Word大珩助手是一款功能丰富的Office Word插件&#xff0c;旨在提高用户在处理文档时的效率。它具有多种实用的功能&#xff0c;能够帮助用户轻松修改、优化和管理Word文件&…

Python酷库之旅-比翼双飞情侣库(17)

目录 一、xlwt库的由来 1、背景和需求 2、项目启动 3、功能特点 4、版本兼容性 5、与其他库的关系 6、示例和应用 7、发展历史 二、xlwt库优缺点 1、优点 1-1、简单易用 1-2、功能丰富 1-3、兼容旧版Excel 1-4、社区支持 1-5、稳定性 2、缺点 2-1、不支持.xls…

LVGL开发教程-Flex(弹性布局)

系列文章目录 知不足而奋进 望远山而前行 目录 系列文章目录 文章目录 前言 1.常用方法 2.代码实现 3.对齐方式 4.控件特殊的size 总结 前言 Flexbox布局在现代界面设计中扮演着重要角色&#xff0c;特别是在响应式和动态布局方面。LVGL&#xff08;LittlevGL&#x…

Dockerfile封装制作pytorch(tensorflow)深度学习框架 + jupyterlab服务 + ssh服务镜像

一&#xff1a;docker-hub官网寻找需求镜像 1.我们在https://hub.docker.com/官网找到要封装的pytorch基础镜像&#xff0c;这里我们以pytorch1.13.1版本为例 2.我们找到的这个devel版本的镜像&#xff08;我们需要cuda的编译工具&#xff09; pytorch版本是1.13.1&#xff0c;…

气体泄露隐患多,佛山工业可燃气体报警器年检校准来帮忙

在佛山这座工业发达的城市&#xff0c;可燃气体报警器的应用日益广泛&#xff0c;涉及化工、冶金、石油等多个领域。 然而&#xff0c;长时间的使用和恶劣的工业环境可能导致报警器的性能下降&#xff0c;甚至出现误报或漏报的情况。 因此&#xff0c;定期对可燃气体报警器进…

OPenCV实现把人形轮廓画在实时视频画面中

操作系统&#xff1a;ubuntu22.04OpenCV版本&#xff1a;OpenCV4.9IDE:Visual Studio Code编程语言&#xff1a;C11 1.功能描述 当你从摄像头读取实时视频时&#xff0c;如果想在视频的画面中画一个方框&#xff0c;或者是画一个圆&#xff0c;是很简单的事情&#xff0c;可是…

VMR,支持30+种编程语言的SDK版本管理器,支持Windows/MacOS/Linux。

官方文档地址&#xff1a;documents 官方项目地址&#xff1a;github 欢迎安装使用&#xff0c;分享转发&#xff0c;前往github star。 跨平台&#xff0c;支持Windows&#xff0c;Linux&#xff0c;MacOS支持多种语言和工具&#xff0c;省心受到lazygit的启发&#xff0c;拥…

LLM漫谈(七)| 使用PyTorch从零构建LLM

LLM是最流行AI聊天机器人的核心基础&#xff0c;比如ChatGPT、Gemini、MetaAI、Mistral AI等。在每一个LLM&#xff0c;有个核心架构&#xff1a;Transformer。我们将首先根据著名的论文“Attention is all you need”-https://arxiv.org/abs/1706.03762 来构建Transformer架构…

漏洞挖掘 | 记一次src挖掘-小程序敏感信息泄露

权当是一次漏洞挖掘的思路分享 闲言 就现在的一个web漏洞挖掘强度还是非常高的&#xff0c;所以我们不妨把我们的眼光投向一个之前可能未曾涉及到的区域———小程序 是的微信小程序&#xff0c;这玩意的防范能力和过滤能力其实对比web方向是要弱小很多的 进入正题 以下就是…

详细分析Element Plus的el-pagination基本知识(附Demo)

目录 前言1. 基本知识2. Demo3. 实战 前言 需求&#xff1a;从无到有做一个分页并且附带分页的导入导出增删改查等功能 前提一定是要先有分页&#xff0c;作为全栈玩家&#xff0c;先在前端部署一个分页的列表 相关后续的功能&#xff0c;是Java&#xff0c;推荐阅读&#x…

配置环境常规操作

一、看看显卡情况 1、看显卡驱动&#xff1a; nvidia-smi 2、验证cuda是否安装成功 nvcc -V 二、conda创建环境 conda create --name PatchCore_anomaly_detection python3.9 conda activate PatchCore_anomaly_detection 三、配置虚拟环境 cd C:\BaiduNetdiskDownload…