【详解】换一个角度看Socket的数据读写
前言
以前对IO、NIO还算了解,也写过Netty的项目。但是对底层的数据传递不是很了解,一直存有这方面的疑惑。但是由于有其他事情就被打断了。前阵子因为想要了解volatile关键字的原理,学习了下JMM(Java内存模型),了解到对象数据是如何存储的。后来又想知道Tomcat是如何传递Http报文的,源码翻着翻着就到了Socket,想来Socket还有些东西没学清楚,就干脆乘着兴致查阅了不少资料。
这里就以数据读写位置为中心,整理分享一下相关内容吧。
整体视图
从“互联网” 到“本机网卡”
网卡会判断网络数据报是否是给本机的,如果是则接收,否则丢弃。它是如何判断的?数据报中有目的地址,如果为本机IP地址,则接收下来。
网卡的存储空间
网卡是有存储空间的,不过很小,只有几KB。它只能作为临时缓冲用的,一般需要存入内存。
从“本机网卡”到“内核空间”
网卡会使用DMA把数据报写入到内核空间中,这个过程不需要CPU干预。
DMA写数据是以块为单位的,也就是一堆字节。
内核空间与用户空间
内存分为两大块,用户空间和内核空间。内核空间是归属于操作系统使用的,为了安全,用户空间中的程序只能访问分配给它的地址空间,一般不能访问内核空间。
地址空间:也就是操作系统分配给进程的内存空间,它只能访问自己的内存空间,不能干预其他进程。即指针只能在一定范围内活动。地址空间是可以扩容的,这是后话了。
Socket的读写队列
每个Socket都在内核空间中都有与之相关联的读写队列(存储空间),一个读队列,一个写队列。且读队列的大小一般要大于写队列。Socket要读数据就从对应的读队列中读,写数据就写到相应的写队列。
数据报如何正确地写入到相关的Socket队列中?
换句话说,如何知道数据报是归属于哪个socket。首先IP地址肯定有了,其次TCP/UDP数据报中就有"目的端口"的字段,这自然就能映射到相关的Socket了,因为本机中的socket就是用占用的端口来彼此区分的。
Linux如何查看读写队列大小
相关信息在这两个配置文件中,内容依次是最小,默认,最大
/proc/sys/net/ipv4/tcp_rmem (读队列大小配置)
/proc/sys/net/ipv4/tcp_wmem (写队列大小配置)
从“内核空间”到“用户空间”
socket对象调用read方法,就是从内核空间中读取数据到用户空间。
系统调用
前面说了,用户空间的程序一般是不能访问内核空间的。但是程序要运行,有时候不得不访问磁盘和网络数据。于是乎,操作系统就提供一些库函数,用户程序可以调用这些库函数来间接使用操作系统的功能。
注:这里与socket相关的操作都是系统调用
如果读队列没有数据可读会怎样?
这取决于socket的mode,默认是阻塞的。也就是说,如果读队列中没有数据可读,那么当前执行这个read函数的线程将被挂起,然后等到内核空间来数据的时候再唤醒这个线程开始读数据,这就是同步阻塞。当然也有非阻塞式的,就是说,如果没有数据可读,执行线程不会被挂起,而是完成read函数,返回一个"-1"的错误码。同步非阻塞,说的就是,反复调用read函数直到成功。
待解决:内核空间如何唤醒这个线程,用的是什么机制。
读出来的数据放在哪里?
一般,我们会分配一个空间来存储,也就是创建一个byte数组来缓存读取进来的数据。为什么说是缓存?因为我们使用socket肯定不是简单的把数据读出来,肯定还要进行下一步的处理,byte数组只是用来暂时存储数据的。
IO复用的思想
前面说的,不管是同步阻塞,还是同步非阻塞。根本上都是说,线程要等到可以读写的时候,才开始读写操作。这样看来,这段等待的时间就算是浪费了。(不管你等待的方式是挂起,还是轮询),IO复用的思想就是认为,这段等待的时间可以利用起来,去执行其他socket的IO操作(当然是满足读写状态的socket)。或者说,就是只有你满足读写条件后,你准备好后,我(也就是线程)才来处理你的读写操作,而不是我来了,还要等你梳妆打扮半小时才能出发。
select、poll、epoll等函数的使用
IO复用中,一个线程同时负责多个socket连接的读写。select、poll、epoll函数简单地说,就是把满足读写状态的socket挑选出来。不同的是,它们挑选的方式不同而已。这里由于博主涉猎不深,也就不展开介绍了。
FAQ 常见问题
说是常见问题,其实只是我个人想到的,看客可能会存在的疑惑。
1.Java的socket API与window或linux底层的socket API是什么关系?
Java的socket是上层封装的API,它使得不管什么平台,都能使用同一套API。它的底层实现还是c语言的库函数。到底用哪个看运行环境,如果是window,那底层用的就是windows的socket api,否则就是linux的socket api。其实你装JDK的时候就已经确定了,因为下jdk的时候就已经选择了windows/linux。
2.如果读队列已满,发送方继续发送的数据会丢失吗?
这就涉及到TCP的拥塞控制了,当队列已满的时候,新来的数据不会被确认。没有确认收到的数据,它是会重新发的。读者可以往拥塞控制(congestion control)方向去看。
这跟拥塞控制无关,应该跟TCP滑动窗口有关。当接收方的接收窗口已满的时候,发送方不会再继续发送数据。
注:滑动窗口是缓冲队列的一部分,相当于一个游标。
3.数据发送出去后,万一丢失了呢,如果要重发数据从哪里来?
实际上,当内核空间中发送缓冲区的数据发出时,该数据并没有立即从队列中删除,也就是说它还在发送方的电脑里。只有收到接收方的确认后,该数据才会被删除。如果等待时间超过超时时间,则会重发数据。
注意:TCP协议实现是操作系统提供的,怎么移动滑动窗口,怎么保证可靠性,怎么控制端到端的流量,怎么防止网络拥塞,这些底层都已经是实现好的。
4.Socket建立连接的过程做了什么,为什么要建立连接?
很多人可能会疑惑,因为socket连接建立后,并没有建立一条实际的通信路径。熟悉TCP/IP的都知道,TCP数据报到了网络层被封装成IP数据报。这时候,数据报往哪条路走是不固定的,路由器会根据实际的网络情况进行路由。
建立连接到底都做了些什么,我暂时也不是很了解。但是我已知晓的就有序号的协商。TCP接收或发送的队列中每个字节数据都是要编号的,但是初始序号并不是0。建立连接的过程会确定双方每对"发送-接收"队列的起始序号。
参考资料
2.Network Interface Controller
4.How to find the socket buffer size of Linux?
【详解】换一个角度看Socket的数据读写的更多相关文章
- SPA路由机制详解(看不懂不要钱~~)
前言 总所周知,随着前端应用的业务功能起来越复杂,用户对于使用体验的要求越来越高,单面(SPA)成为前端应用的主流形式.而大型单页应用最显著特点之一就是采用的前端路由跳转子页面系统,通过改变页面的UR ...
- 详解 ServerSocket与Socket类
(请观看本人博文 -- <详解 网络编程>) 目录 ServerSocket与Socket ServerSocket 类: Socket类: ServerSocket与Socket 首先, ...
- Java多线程超级详解(只看这篇就够了)
多线程能够提升程序性能,也属于高薪必能核心技术栈,本篇会全面详解Java多线程.@mikechen 主要包含如下几点: 基本概念 很多人都对其中的一些概念不够明确,如同步.并发等等,让我们先建立一个数 ...
- V4L2 API详解 Buffer的准备和数据读取
1. 初始化 Memory Mapping 或 User Pointer I/O. int ioctl(int fd, int requestbuf, struct v4l2_requestbuffe ...
- 【Qt开发】V4L2 API详解 Buffer的准备和数据读取
前面主要介绍的是:V4L2 的一些设置接口,如亮度,饱和度,曝光时间,帧数,增益,白平衡等.今天看看V4L2 得到数据的几个关键ioctl,Buffer的申请和数据的抓取. 1. 初始化 Memory ...
- socket技术详解(看清socket编程)
socket编程是网络常用的编程,我们通过在网络中创建socket关键字来实现网络间的通信,通过收集大量的资料,通过这一章节,充分的了解socket编程,文章用引用了大量大神的分析,加上自己的理解,做 ...
- iOS 详解NSXMLParser方法解析XML数据方法
前一篇文章已经介绍了如何通过URL从网络上获取xml数据.下面介绍如何将获取到的数据进行解析. 下面先看看xml的数据格式吧! <?xml version="1.0" enc ...
- Linux 虚拟网络设备 veth-pair 详解,看这一篇就够了
本文首发于我的公众号 Linux云计算网络(id: cloud_dev),专注于干货分享,号内有 10T 书籍和视频资源,后台回复「1024」即可领取,欢迎大家关注,二维码文末可以扫. 前面这篇文章介 ...
- jQuery的deferred对象使用详解——实现ajax线性请求数据
最近遇到一个ajax请求数据的问题 ,就是想要请求3个不同的接口,然后请求完毕后对数据进行操作,主要问题就是不知道这3个请求誰先返回来,或者是在进行操作的时候不能保证数据都已经回来,首先想到能完成的就 ...
随机推荐
- Python开发——数据类型【字符串格式化】
字符串格式化之——% # 字符串格式化 msg = 'I am %s , My hobby is %s'%('yuan','play') print(msg) # I am yuan , My hob ...
- zabbix安装(Ubuntu)
zabbix的安装 Zabbix监控架构至少需要server,agent,web模块.mysql.web部分和server安装在同一台机器上. Zabbix安装前服务器要做时间同步(ntp) 1.创建 ...
- 数字提取——C语言
Problem Description AekdyCoin is the most powerful boy in the group ACM_DIY, whose signature is valu ...
- 找不到phpize
yum install php-devel 报错如下: 解决办法: yum install php71w-devel
- <fieldset>标签
<!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="Conten ...
- Tomcat架构解析(五)-----Tomcat的类加载机制
类加载器就是根据类的全限定名(例如com.ty.xxx.xxx)来获取此类的二进制字节流的代码模块,从而程序可以自己去获取到相关的类. 一.java中的类加载器 1.类加载器类别 java中的类加 ...
- 加盟阿里!贾扬清被曝从Facebook离职,任阿里硅谷研究院VP
3 月 2 日傍晚,知乎上爆出一则 AI 人事变动大消息——Caffe 作者贾扬清将从 Facebook 离职. 短短数小时,就有近 10 万人浏览这个问题.不仅如此,据 AI 前线爆料,贾扬清离开 ...
- Reading | 《机器学习》(周志华)(未完待续)
目录 I. 大师对人工智能和机器学习的看法 II. Introduction A. What is Machine Learning 什么是机器学习 B. Basic terms 基础术语 C. In ...
- Mysql主从复制读写分离
一.前言:为什么MySQL要做主从复制(读写分离)?通俗来讲,如果对数据库的读和写都在同一个数据库服务器中操作,业务系统性能会降低.为了提升业务系统性能,优化用户体验,可以通过做主从复制(读写分离)来 ...
- Runtime之实例总结
通过前面几篇对Runtime的讲解,本篇汇总一下Runtime实际中常用的一些场景. 1.获取类的基本信息 获取类名: const char *className = class_getName(cl ...