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);//创建一个服务端且端口为8686Socket 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;}@Overridepublic 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@Overridepublic 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 获取并清除。

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


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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前端 JS 经典:数字变化动画

1. 需求 给你一个数字&#xff0c;当这个数字变化时&#xff0c;有一个动画的过渡效果。 2. 思路 首先我们要知道两个数字变化需要多少秒&#xff0c;然后变化的范围&#xff0c;算出变化的速度。记住开始变化的时间&#xff0c;然后通过 requestAnimationFrame 函数&#x…

centos 7.8 安装sql server 2019

1.系统环境 centos 7.8 2.数据库安装文件准备 下载 SQL Server 2019 (15.x) Red Hat 存储库配置文件 sudo curl -o /etc/yum.repos.d/mssql-server.repo https://packages.microsoft.com/config/rhel/7/mssql-server-2019.repo 采用yum源进行不安装下载,这时yum 会自动检测…

算法竞赛数论杂题

menji 和 gcd 题目&#xff1a; 一开始以为是只有l不确定&#xff0c;r是确定的&#xff0c;这样的话我们可以枚举r的所有约数&#xff0c;然后对其每个约数x进行判断&#xff0c;判断是否满足题意&#xff0c;具体做法是先让l % x如果 0则该约数可行&#xff0c;如果不可行…

【机器学习】:线性回归模型学习路线

Hi~&#xff01;这里是奋斗的小羊&#xff0c;很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~~ &#x1f4a5;&#x1f4a5;个人主页&#xff1a;奋斗的小羊 &#x1f4a5;&#x1f4a5;所属专栏&#xff1a;C语言 &#x1f680;本系列文章为个人学习…

教你python自动识别图文验证码的解决方案!

验证码识别解决方案 对于web应用程序来讲&#xff0c;处于安全性考虑&#xff0c;在登录的时候&#xff0c;都会设置验证码&#xff0c;验证码的类型种类繁多&#xff0c;有图片中辨别数字字母的&#xff0c;有点击图片中指定的文字的&#xff0c;也有算术计算结果的&#xff0…

成都爱尔李晓峰主任讲解眼角多出一层“膜”是什么?怎么治

眼角边突然发现长出来一层皮一层膜一样的东西&#xff0c;肉色挡在眼白上呈三角形&#xff0c;这到底是什么&#xff1f; 一种常见眼科疾病“翼状胬肉”&#xff0c;因其形状像昆虫的翅膀而得名的&#xff0c;它是受外界剌激而引起的一种慢性炎症性病变。 覆盖在眼睛表面的那…

JUC并发编程第十三章——读写锁、邮戳锁

本章路线总纲 无锁——>独占锁——>读写锁——>邮戳锁 1 关于锁的面试题 你知道Java里面有那些锁你说说你用过的锁&#xff0c;锁饥饿问题是什么&#xff1f;有没有比读写锁更快的锁StampedLock知道吗&#xff1f;&#xff08;邮戳锁/票据锁&#xff09;ReentrantR…

使用自定义注解进行权限校验

一&#xff0c;前言 对于一些重复性的操作我们可以用提取为util的方式进行处理&#xff0c;但也可以更简便一些&#xff0c;比如自定义个注解进行。选择看这篇文章的小伙伴想必都对注解不陌生&#xff0c;但是可能对它的工作原理不太清楚。这里我们用注解实现对接口的权限校验…

Wireshark v4 修改版安装教程(免费开源的网络嗅探抓包工具)

前言 Wireshark&#xff08;前称Ethereal&#xff09;是一款免费开源的网络嗅探抓包工具&#xff0c;世界上最流行的网络协议分析器&#xff01;网络封包分析软件的功能是撷取网络封包&#xff0c;并尽可能显示出最为详细的网络封包资料。Wireshark网络抓包工具使用WinPCAP作为…

基于GWO-CNN-LSTM数据时间序列预测(多输入单输出)-多维时间序列模型-MATLAB实现

基于GWO-CNN-LSTM数据时间序列预测(多输入单输出)-多维时间序列模型-MATLAB实现 基于灰狼优化&#xff08;Grey Wolf Optimizer, GWO&#xff09;、卷积神经网络&#xff08;Convolutional Neural Network, CNN&#xff09;和长短期记忆网络&#xff08;Long Short-Term Memor…

【计算机视觉(11)】

基于Python的OpenCV基础入门——图像梯度变换 图像梯度变换Sobel算子Scharr算子Laplacian算子 图像梯度变换的代码实现以及效果图 图像梯度变换 图像梯度变换可以用于边缘检测、特征提取、增强图像和压缩图像等多种任务。图图像梯度可以把图像看成二维离散函数&#xff0c;图像…

什么是进程?

目录 进程 进程的特征, 概念 我们下面先简单介绍一下什么是进程 接下来看看一个程序的运行过程 进程的组成 进程的状态和转换 进程的状态 进程状态的转换 ​编辑 进程的组织方式 进程控制 如何实现进程控制 为什么进程控制的过程需要一气呵成? 进程控制的实现…

前端初学java

目录 java术语 JDK Javac Java Jdb Jhat JVM JRE JAR JDK下载 运行java文件 字面量 隐式转换 强制转换 注意 运算符 &&、||、&、| Switch 程序入口 String[] args 数组 静态初始化 动态初始化 变量初始化 Java内存 方法 重载 Final 包 …

智警杯数据库学习(1)

CentOS中安装MySQL数据库 检测系统是否自带安装 MySQL 首先检查是否自带mysql rpm -qa | grep mysql 如果有删除 rpm -e mysq 未安装&#xff0c;开始安装 进入software目录&#xff0c;解压安装包mysql5.7.25 cd /root/software tar -xvf mysql-5.7.25-1.el7.x86_64.rp…

【决战欧洲杯巅峰】欧洲杯含金量比世界杯高吗?有走地数据分析软件吗?

关于欧洲杯和世界杯的含金量对比&#xff0c;这是一个相当主观的问题&#xff0c;因为两者的价值和重要性很大程度上取决于个人的喜好和观点。但我可以从一些关键方面来为你提供比较的视角。 首先&#xff0c;从参赛队伍和竞技水平来看&#xff0c;世界杯无疑是全球范围内最具…

[渗透测试学习] SolarLab-HackTheBox

SolarLab-HackTheBox 信息搜集 nmap扫描端口 nmap -sV -v 10.10.11.16扫描结果如下 PORT STATE SERVICE VERSION 80/tcp open http nginx 1.24.0 135/tcp open msrpc Microsoft Windows RPC 139/tcp open netbios-ssn Microsoft Windows n…

C/S、B/S架构(详解)

一、CS、BS架构定义 CS架构&#xff08;Client-Server Architecture&#xff09;是一种分布式计算模型&#xff0c;其中客户端和服务器之间通过网络进行通信。在这种架构中&#xff0c;客户端负责向服务器发送请求&#xff0c;并接收服务器返回的响应。服务器则负责处理客户端的…

浅谈RC4

一、什么叫RC4&#xff1f;优点和缺点 RC4是对称密码&#xff08;加密解密使用同一个密钥&#xff09;算法中的流密码&#xff08;一个字节一个字节的进行加密&#xff09;加密算法。 优点&#xff1a;简单、灵活、作用范围广&#xff0c;速度快 缺点&#xff1a;安全性能较差&…

Pytorch编写Transformer

本文参考自https://github.com/datawhalechina/learn-nlp-with-transformers/blob/main/docs/ 在学习了图解Transformer以后&#xff0c;需要用Pytorch编写Transformer&#xff0c;下面是写代码的过程中的总结&#xff0c;结构根据图解Transformer进行说明。 import numpy as …

前字节员工自爆:我原腾讯一哥们,跳槽去小公司做小领导,就签了竞业,又从小公司离职去了对手公司,结果被发现了,小公司要他赔80万

“世界那么大&#xff0c;我想去看看”&#xff0c;这句曾经火遍网络的辞职宣言&#xff0c;说出了多少职场人心中的渴望。然而&#xff0c;当我们真的迈出跳槽那一步时&#xff0c;才发现&#xff0c;现实远比想象中残酷得多。 最近&#xff0c;一起前字节跳动员工爆料的事件…