TCP/IP协议
✏️作者:银河罐头
📋系列专栏:JavaEE
🌲“种一棵树最好的时间是十年前,其次是现在”
目录
- TCP/IP协议
- 应用层协议
- 自定义应用层协议
- DNS
- 传输层协议
- 端口号
- UDP协议
- UDP协议端格式
- TCP协议
- TCP协议段格式
- TCP工作机制
- 确认应答(安全机制)
- 超时重传(安全机制)
- 连接管理(安全机制)
- 滑动窗口(效率机制)
- 流量控制(安全机制)
- 拥塞控制(安全机制)
- 延时应答(效率机制)
- 捎带应答(效率机制)
- 面向字节流
- 异常情况
- UDP 和 TCP 对比
- 网络层协议
- IP协议
- 地址管理
- 路由选择
- 数据链路层协议
- 以太网
- 以太网帧格式
- MTU
TCP/IP协议
应用层协议
自定义应用层协议
- 为啥要自定义协议?
当前的应用程序要解决的业务场景是错综复杂的,不同的公司有不同的业务,不同的业务有不同的流程,业务复杂,要用程序来解决这个复杂的业务,程序也就复杂了。因此很难有一个通用的协议满足所有的业务需求。
- 怎样进行自定义协议?
1)结合需求,分析清楚请求响应(客户端服务器之间)要传递哪些信息
2)明确传递的信息是以什么样的格式来组织
可选的方案是很多的。
约定好协议得格式内容之后,客户端就能够按照这个格式构造数据并发送,服务器按照这个格式解析处理。
约定的协议的内容(传递的信息)是和业务相关性非常大的。但是协议的数据组织的格式(传递的格式)和业务关系不大.
典型的用来组织数据的格式:
- XML 标签化的数据组织格式。使用标签来表示键值对,以及树形结构
开始标签和结束标签需要成对出现,标签中间的部分就是标签的内容,内容可以是数字,字符串,还可以嵌套放别的标签。
HTML 是 XML 的特殊情况
XML中标签名字叫啥都是自定义的
- json
上述 xml 和 json 都是按照文本的方式来组织的,
优点是可读性好,用户不需要借助其他工具,肉眼能看懂。
缺点是效率不高,占用较多的网络带宽。
xml 要额外传很多标签,json 要额外传很多的key
- protobuffer (谷歌)
二进制的表示数据的方式,针对上述的数据信息,通过二进制的形式进行压缩表示了。
特点是肉眼观察不了(二进制直接用记事本打开乱码),但是占用空间小了,占用的带宽也就降低了。
DNS
域名解析系统
域名,网址。
eg:www.baidu.com
要访问网络上的服务器,需要 IP 地址。IP地址不好记,于是就使用一些简单的字符串来表示这个地址。
每个域名都对应了 1/N 个IP 地址。
最原始的做法,是使用一个 hosts 文件
C:\Windows\System32\drivers\etc
像 hash 表一样,建立了 IP 和域名之间的映射关系,当然现在打开 hosts 文件,基本都是空的, # 表示注释
当访问某个域名的时候,就自动请求下 DNS 服务器,DNS 就会进行查询,将得到的结果(具体的 IP 地址)返回给你。
如果你电脑的 DNS 服务器配置的不对,或者 DNS 服务器挂了,此时就会出现一个典型的现象:QQ 能用,但是网页打不开。
当前要求网站的域名不能重复(保证唯一)。
- 全世界这么多网站,如何保证唯一呢?
一级域名,二级域名,三级域名…
pic.sogou.com
.com(公司) 一级域名,类似的一级域名还有 org,cn,us…
sogou 二级域名,表示 搜狗这个公司
pic 三级域名
域名分级了,DNS 服务器也是分级了,有 一级域名 的 DNS 服务器,还有 二级域名,三级域名…
一般常见的就是 三级,四级左右。
www.
具体 www 算 三级还是 四级还是 五级,看域名具体咋写的,
www 是"万维网"的缩写,我们现在使用的这个网络就是 万维网
传输层协议
端口号
端口号:数据库 mysql 默认端口 3306。
端口号起到的效果就是区分一个主机上的具体的应用程序。
所以,同一台主机上,一个端口号不能被多个进程绑定。
使用 IP 地址来区分主机。
eg :进程 A 绑定了 3306,此时进程 B 也尝试绑定 3306,进程 B 绑定操作就会失败 (抛异常)
端口号是传输层协议的概念,TCP 和 UDP 协议的报头中都会包含 源端口 和 目的端口。
TCP 和 UDP 都是用 2 个字节,16 个 bit 位来表示端口号的,一个端口号的取值范围:0 ~ 65535
最初学习 C 语言,就得知道不同长度字节数表示的数据范围。
1 个字节,8 bit , -128 ~ 127 128 就是 2 ^ 7, 127 就是 2 ^ 7 - 1
0 ~ 255 255 就是 2 ^ 8 - 1
2 个字节 , 16 bit, -32768 ~ 32767 32768 就是 2 ^ 15
0 ~ 65535 65535 就是 2 ^ 16 - 1
4 个字节,32 bit, -21亿 ~ 21亿
0 ~ 42 亿 9 千万
但是自己写程序,绑定的端口号,得是从1024 起的。
0 ~ 1023 这个范围的端口称为"知名端口号/具名端口号",这些端口号已经分配给了一些知名的广泛使用的应用程序了。
那么我写代码就非要指定 0 ~ 1023 的端口号,行不行呢 ?
1.先确定你这个端口号没有被别的程序绑定
2.你有管理员权限
1023 以下的端口,也不是完全不能用,只是不建议,这些端口虽然被分配给了特定程序,但是这个程序是否在你电脑上运行着,电脑上是否安装了这些程序都是不一定的。
UDP协议
UDP 的特点:无连接,不可靠传输,面向数据报,全双工。
不可靠传输:没有任何安全机制,发送端发送数据报以后,如果因为网络故障该段无法发到对方,UDP协议层也不会 给应用层返回任何错误信息。
UDP协议端格式
UDP 协议报文结构,这张图,任何一个计算机网络的教科书上都有,而且都是这么画的,但是实际上这么画不够严谨(为了书排版方便,变形了)
UDP 就会把载荷数据(就是通过 UDP socket ,也就是 send 方法拿来的数据)再在前面拼装上几个字节的报头,相当于字符串拼接(此处是二进制的,不是文本的)
UDP 报头里包含了一些特定的属性,携带了一些重要的信息。不同的协议功能不同,报头中的属性信息不同。
对于 UDP 来说,报头 一共 8 个字节,分成 4 个部分(每部分 2 字节)
- UDP 报文长度
这个长度也是用 2 个字节表示的,2 个字节表示的范围是 0 ~ 65535(64 KB).
也就是说一个 UDP 数据报只能传输 64 KB 的数据。
报文长度是整个报文的长度,不是载荷长度。
如果 应用层数据报超过 64 KB ,怎么办?
方案 1: 就需要在应用层,通过代码的方式针对应用层数据报进行手动的分包,拆成多个包通过多个 UDP 数据报进行传输(本来是 send 一次,现在需要 send 多次了)
方案2:不用 UDP 了,换成 TCP(TCP 没有这样的限制)
- 校验和
作用是验证传输的数据是否是正确的。
网络传输,本质上就是光信号/电信号,这些可能会受到一些物理环境的影响(电场/磁场/高能射线),在这些干扰下可能会出现"比特翻转"的情况。(1->0,0->1)
一旦数据变了,对于数据的含义可能是致命的。
这种现象是客观存在的,不可避免,我们要做的就是及时识别出当前数据是否出现问题。
校验和是针对数据内容进行一系列数学运算,得到一个比较短的结果(2 个字节)。
如果数据内容一定,校验和结果就一定;如果数据变了,得到的校验和结果就变了。
是否有这样的情况,假设数据传输中出错了,但是计算的 校验和 和之前的 校验和 恰好一样?
“要发射导弹” => 0xaabb
“不要发射导弹” => 0xaabb
这个情况理论上存在,工程上出现这种情况的概率极小,忽略不计了。
如果内容相同,得到的校验和一定相同;
如果校验和相同,内容不一定相同(小概率事件)
实际网络传输过程中,往往是把数据的所有字节都参与生成校验和的运算,这样任何一个字节出问题就都能发现了。
针对网络传输的数据来说,生成校验和的算法有很多种。其中比较知名的有几个。
- CRC
循环冗余校验,把数据的每个字节,循环往上累加,如果累加溢出了,高位就不要了。
好酸但是校验结果不理想,如果数据同时变动了 2 个 bit 位,前一个字节少 1 后一个字节 多 1 这种,就会出现内容变了但是校验和没变这种情况。
- MD5
MD5 不是简单的相加,有一系列公式来进行更加复杂的数学运算。
MD5算法的特点:
1)定长:无论原始数据多长,得到的 MD5都是固定长度(4/8字节)
2)冲突概率很小:原始数据哪怕只变动一个地方,算出的 MD5 都会差别很大(让 MD5 值更分散了)
3)不可逆:通过原始数据计算 MD5 很容易,通过 MD5 还原成原始数据很难,理论上是不可实现的(计算极大)
MD5 的这些特点使得 MD5 作用更多了:校验和,作为计算 hash 值的方式,加密
- SHA1
SHA1的特点和 MD5 相同,区别是计算方式不同。
TCP协议
特点是有连接,可靠传输,面向字节流,全双工。
这里的可靠传输是 TCP 的内部机制,和编码关系不大。
TCP协议段格式
- 4位首部长度
TCP 报文 = TCP 报头(首部) header + TCP 载荷 payload/body
一个TCP 报头长度是可变的,不是像 UDP 报头固定是 8 个字节
- 选项
option (选项) , optional (可选的,可有可无的)
此处的选项是对 TCP 报文的一些属性进行解释说明的。
首部长度描述了 TCP 报头有多长,选项之前的长度是固定的(20 字节),首部长度 - 20 = 选项长度
首部长度的单位是 4 字节,
如果首部长度值是 5,表示整个 TCP 报头长度是 20 字节,没有选项;
如果首部长度值是 15 ,表示整个 TCP 报头长度是 60 字节,选项长度是 40 字节
- 保留( 6 位)
reserved
C 语言中,有一类单词,叫做关键字,除了关键字之外,还有一些单词,叫做保留字。
保留字就是现在还没使用,但是保不齐以后要是用,所以现在这占个位置你现在先别用
咱们进行程序开发的时候,其中一个重点考虑的事情就是可扩展性,有些功能可能暂时不需要,保不齐未来需要
- 16 位检验和
和 UDP 校验和同理
TCP工作机制
TCP 是一个复杂的协议,里面有很多机制,这里主要讨论 TCP 提供的 10 个比较核心的机制。
确认应答(安全机制)
TCP 是可靠传输。
可靠,不是说发送方 100% 能把消息发给接收方,而是尽可能把数据传过去,发送方能知道自己的消息是发过去了还是丢了。
确认应答是实现可靠传输的最核心机制。
举个栗子:
张三在追一个女生,有一天张三给女生发消息说"我请你吃饭好嘛?“女生回复他说"好啊”。
当张三收到"好啊"的时候,说明张三发的消息已经顺利被女生看到了(没有丢包)
如果过了好久张三没收到女生的回应,说明丢包了(也可能是已读不回doge)
这里女生回复的"好啊"就称为"应答报文",也叫作 ack(acknowledge)
TCP 进行可靠性传输,最主要考的就是这个确认应答机制。
A 给 B 发了个消息,B 收到之后就会返回一个 应答报文(ACK),此时 A 收到应答之后就知道刚才发的数据已经顺利到达 B 了。
考虑更复杂的情况(发送方发多条消息):
张三可能连续发 2 条消息,张三没等第 1 条消息的回应就发了第 2 条消息。
网络上可能存在"后发先至"的情况,这个情况下收到消息的顺序可能是有变数的。
由于"后发先至",张三先收到了"滚",后收到了"好啊"。
很明显这里应答错乱导致出现歧义。
网络中数据的"先发后至",两个主机之间,路线存在多条,数据报1和数据报2走的都是不同的路线,数据报1的转发路径上的交换机/路由器和数据报2也不一样,有的转发速率快有的转发速率慢,此时这2 个数据报的到达顺序可能会有变数。
网络中的"后发先至"是客观存在的,因此应答报文到达的顺序也是存在变数的,此时就需要考虑如何规避这种顺序错乱带来的歧义。
解决办法是给传输的数据和应答报文都进行编号。
“后发先至”:
eg: 结婚时候新郎的车队可能会后发先至
- 32位序号/确认序号
引入序号之后就不怕顺序错乱了,即使顺序上乱了,也可以通过序号来区分当前应答报文是针对哪个数据进行的。
任何一条数据(包括应答报文)都是有序号的,确认序号则是只有应答报文有(普通报文确认序号里的值无意义)。
报文是否是应答报文,取决于6个标志位中的 ACK,如果 ACK 这个标志位为 1 ,表示是应答报文,如果为 0 表示不是应答报文。
应答报文的序号仅仅是一个身份标识,不需要应答 应答报文,要是应答 应答报文就无限套娃了…
TCP 的序号是按照字节来编写的。
这一条数据是 1000 个字节,假设是从 1 开始编号,此时第一个字节序号就是 1,第 2 个字节序号就是 2…但是由于这 1000 个字节都属于同一个 TCP 报文,TCP 报头里就只记录当前的第1 个字节的序号,此处报头的序号写的是 1。接下来发第 2 条数据,此时第 2 个TCP 数据报的第 1 个字节序号是 1001,如果长度是 1000,此时最后一个字节序号是 2000,这1001~2000都属于一个 TCP 数据报,那么这个TCP 数据报报头的序号就是 1001。
TCP 的字节的序号是依次累加的,这个依次累加的过程对于后一条数据的第 1 个字节序号就是上一条数据的最后一个字节序号再加 1 。每个 TCP 数据报报头只需要填写 TCP 数据 第 1 个字节的序号即可。
TCP 知道了第 1 个字节的序号,再结合报文的长度就可以知道每个字节的序号。
确认序号的取值,是收到的数据的最后一个字节 + 1。
如上图,确认序号写的是 1001,就是在 1000的基础上 +1。
表示的含义:
- < 1001的数据都已经确认收到了。
- 主机 A 接下来应该从 1001 这个序号开始继续发送
总结:TCP 的可靠传输,最主要就是通过确认应答来保证的,通过应答报文,就可以让发送方清楚地知道传输是否成功,进一步引入了序号和确认序号,针对多组数据进行区分。
超时重传(安全机制)
前面讨论确认应答,只是讨论了顺利传输的情况。如果丢包了呢?
丢包,涉及 2 种情况:
1.发的数据丢了
2.返回的 ack 丢了
发送方看到的结果就是没有收到 ack ,不能区分是哪一种情况。
丢包是一个概率性事件,通常情况下丢包的概率是很小的,因此如果重新发一遍这个数据还是有很大可能传输成功的。
因此 TCP 就引入了 重传机制。
在丢包的时候,重新传一遍数据。
到底这次传输是丢包了,还是 ack 传的慢?
TCP 就引入了时间阈值。
发送方发出数据后,就会等待 ACK,此时开始计时。只要超过了这个时间,不管是丢包了还是 ack 在路上都视为是丢包了。
比如寒假作业,没带等于没写,虽然你写了,你超过了这个规定提交时间就当做没写处理。
超时重传:超过一定时间还没响应就重新传输。
这个超时时间具体是多少 ms ,因为这个时间是可配置的,不同系统上的默认值可能都存在差别。
如果是 ack 丢了这种情况
对于主机 B 来说,1~1000 就收到了 2 次,如果你发的这个数据是一个支付请求?(寄了)
TCP 对于这种重复数据的传输,是有去重处理的,
TCP 存在一个"接收缓冲区"这样的存储空间(接收方操作系统内核里的一段内存),
每个 TCP socket 对象都有一个接收缓冲区(其实也有一个发送缓冲区)
主机 B 收到主机 A 的数据
其实是 B 的网卡读到数据了,然后把这个数据放到 B 的对应 socket 的接收缓冲区(想象成阻塞队列),后续应用程序使用 getInputStream,进一步使用 read,从接收缓冲区里读数据。
在接收缓冲区里,根据数据的序号,TCP 很容易识别当前接收缓冲区里的这 2 条数据是否是重复的,如果重复就把后来的数据丢弃,保证 应用程序调用 read 读到的数据一定是不重复的。
去重可以理解为是一个优先级队列,有序队列。
前面说了,网络上传输的数据可能"后发先至", TCP 利用这个接收缓冲区,对收到的数据重新进行排序,是应用程序 read 的数据都是有序的(保证和发送顺序一致)
根据序号排序,序号相同就去重
eg:结婚当天新郎的车队出现"后发先至",然后车队都到了新娘家门口的时候要整队,排序
这时候我们可以利用前面提到的序列号,就可以很容易做到去重的效果。
总结:由于去重和重新排序机制的存在,发送方只要发现 ACK 没有及时到达,就会重传数据,即使重复了,即使数据乱了,接收方都能去重和排序(依赖 TCP 报头的序号)。
- 重传的数据是否可能又丢包了呢?
有可能,超时重传可能传多次,如果重传好几次都没传过去,此时再重传意义不大了。
从数学角度来理解:
假设一次传输丢包概率是 10%,传输成功概率是 90%;
如果第1次传输丢包,第 2 次传输也丢包了,概率是 10% * 10% = 1%
如果 3 次都丢了,概率是 0.1%
连续重传都丢包,此时的概率原则上讲是非常低的,如果这个情况真的出现了,只能说明此时丢包概率远远不止 10% => 你当前网络出现重大故障
因此,重传达到一定次数时,就不会继续重传,会认为网络出现故障。
接下来 TCP 会尝试 重置连接(相当于断开重连),如果重置还是失败就彻底断开连接了。
具体重传几次 N,也是可配置的,不去研究
重传的时候,第 1 次重传和第 2 次重传,超时时间间隔不一样。一般来说,重传轮次越大,超时时间间隔就越大。超时时间越大,重传频率就越低。(因为重传次数越多,说明重传成功概率越低,此时你重传的太快也是白浪费系统资源)
总结:可靠传输是 TCP 最核心的部分,TCP 的可靠传输就是通过 确认应答 + 超时重传来体现的。其中确认应答描述的是传输顺利的情况;而超时重传描述的传输出现问题的情况。
连接管理(安全机制)
什么是断开连接?
A 和 B 把自己存储的连接信息(数据结构)删除了,连接就断开了。
JDBC 是基于 TCP 建立的连接
连接(Connection)
链接(Link) 快捷方式,比如电脑开始菜单的图标就是快捷方式,比如我想打开 QQ,QQ本来是 在 C 盘某个目录里,我每次打开 QQ 都要到这个目录去找未免太麻烦所以就创建一个快捷方式,这个快捷方式就是一个文件,这个文件里保存了这个可执行文件的真实路径
管理:
就是描述了连接如何创建,如何断开。
TCP 三次握手和四次挥手
滑动窗口(效率机制)
确认应答,超时重传,连接管理,都是给 TCP 可靠性提供的支持。
引入了可靠性,也付出了相应的"代价"(传输效率)
可靠性和效率是冲突的。
因此 UDP 虽然没有 可靠性,但是传输效率比 TCP 高
所以 TCP 在竭尽可能的提高传输效率(本质上是补救措施)。再怎么提高,也不可能比 UDP 这种不考虑可靠性的效率高。
滑动窗口本质上是降低了确认应答,等待 ACK 消耗的时间。
在进行 IO 操作的时候,其实时间成本主要是 2 个部分:
1.等2.数据传输(数据拷贝)
大多数情况下,IO 操作消耗的时间大部分在"等"上面
举个栗子:
放寒假从学校回家,如果两地比较远,坐飞机回家的情况下。真正坐飞机的时间很短,但是因为机场一般离市区很远,从学校到机场要花时间,到了机场后换登机牌、托运行李、安检、候机又要花上不少时间,飞机到达目的地,取行李,从机场到家里又要花上不少时间。
这里真正坐飞机的时间就相当于 数据传输的时间,较短。
而其他花在路上的时间,等待时间,比较长。
具体怎么缩短?
批量发送,批量等待,把多份等待时间合并成一份。
滑动窗口的本质就是不等待的批量发送一组数据,然后使用一份等待时间来等待一组数据的多个 ack。
举个例子:
我打算去买肉夹馍,炒饭,烤肠。
方案一:先去买肉夹馍,肉夹馍做好了,再去买炒饭,炒饭做好了,再去买烤肠。
方案二:先去买肉夹馍,不等了立即去买炒饭,也不等立即去买烤肠。
方案一就是用了 3 份等待时间,而方案二只用了 1 份等待时间
把不需要等待,就能直接发送数据的最大的量,叫做"窗口大小"。上面第 2张图里"窗口大小"就是 4000。
当批量发送了 窗口大小的这些数据之后,发送方就要等待 ack 了。
- 啥时候往下发送?等待啥时候结束?
不是说等所有的 ack 都到达才往下发,而是收到一条 ack 就往下发 1 条。(这样就让我们等待的 ack 始终都是 4 条)
在上述情况下,如果丢包了咋办?
- ack 丢了
上述丢包重传的机制,叫做 “快速重传”(重传操作只重传了丢失的数据)
这个可以视为是 超时重传 机制在滑动窗口下的变形。
如果当前传输数据密集,按照滑动窗口的方式传输,此时就按照快速重传来处理丢包;
如果当前传输数据稀疏,不再按照滑动窗口的方式传输,此时就按照之前的超时重传来处理丢包。
流量控制(安全机制)
是一种干预发送的窗口大小的机制。
滑动窗口,窗口越大,传输效率越高(一份时间等的 ack 越多)。
但是窗口也不能无限大
1.完全不等 ack , 可靠性难以保证
2.窗口太大也会消耗大量的系统资源
3.发送速度太快,接收方接收不过来,发了也白发,接收缓冲区有上限。
接收方的处理能力,是一个重要的约束依据。发送方发的速度不能超过接收方的处理能力。
流量控制就是,根据接收方的处理能力,来协调发送方的发送速率。
如何衡量接收方的处理能力?
一个量化的方法:计算接收方一秒钟能处理多少个字节…(这种方式实现起来有些困难)
更简单的办法:
直接看接收方接收缓冲区里剩余空间的大小。
每次 A 给 B 发了个数据,B 就需要算一下缓存区里剩余空间,然后把这个值,通过 ack 报文返回给 A。
A 就根据这个值来决定接下来发送的速率是多少(窗口大小是多少)
- 窗口大小 16 位,是否意味着,窗口大小最大是 16 kb ?
不是!
TCP 为了让窗口更大,在选项部分引入了窗口扩展因子。
比如窗口大小已经是 64 kb,扩展因子里写了个 2 ,意思就是让 64 kb << 2 = 256kb
由于接收方缓存区剩余空间是一直在动态变化的,所以每次返回 ack 带的窗口大小都在变化,发送方也要进行动态调整。
当窗口大小为0,发送方就要暂停发送。暂停发送的等待过程中,会给 B 定期发送窗口探测报文,这个报文不携带具体的业务数据,只是为了触发 ack 查询窗口大小。
拥塞控制(安全机制)
流量控制和拥塞控制共同决定发送方的窗口大小是多少。
流量控制考虑的是接收方的处理能力。
拥塞控制描述的是传输过程中中间节点的处理能力。
木桶效应:一只水桶能装多少水取决于它最短的那块木板。
接收方的处理能力,好量化衡量。但是中间节点不好衡量。
设计 TCP 的大佬们,想出了一个相当 天才的办法,
既然不好直接量化,那么可以通过"实验"的方式,来测试出一个合适的量
拥塞窗口不是固定数值,而是一直动态变化的。随着时间的推移,逐渐到达一个动态平衡的过程。
这样既能够把问题解决,同时也能随着网络的动态变化而动态变化。
拥塞窗口和流量控制窗口,共同决定了发送方实际的发送窗口。(取拥塞窗口和流量控制窗口的较小值)
延时应答(效率机制)
就是在 滑动窗口的基础上,做点别的操作。
收到数据之后,不是立即返回 ack ,而是稍微等会再返回。等待的时间里,接收方的应用程序就能把接收缓存区的数据给消费一些,此时剩余的空间就更大了。
实际上延时应答采取的方式,就是在滑动窗口下,ack 不再每一条数据都返回了,比如上图是隔一条返回一个 ack。
实际上剩余空间大小,既取决于发送方的发送,也取决于接收方的处理。
捎带应答(效率机制)
也是提高效率的一种方式。
在延时应答的基础之上引入的捎带应答。
服务器客户端程序,最典型的模型就是,“一问一答”。业务的请求和响应。
本来是不同时机,在延时应答下,可能成为相同时机,就合并了。
而 TCP 的三次握手本身是相同时机一定会合并,此处是有一定概率合并,此处和四次挥手的合并更像。
延时应答提高了合并的概率。
面向字节流
面向字节流,引入了一件麻烦事,
- 粘包问题。
举个栗子:
接收缓冲区是把刚才收到的多个数据都放到一起了,应用程序 read 读取的时候,读到哪里才算是一个完整的应用层数据报呢?
由于 TCP 是面向字节流的,可以一次读 1 个字节也可以一次读 N 个字节。
这就导致一次读取的时候,可能读到的是半个应用层数据报,也可能是 1 个应用层数据报,还可能是多个应用层数据报。
应用程序调用 read,
如果 read 的是 7 个字节,此时正好读出的是 aaaaaaa,这是一个完整的应用层数据报;
如果 read 的是 8 个字节,此时正好读出的是 aaaaaaab,这是"一个半"的应用层数据报;
如果 read 的是 6 个字节,此时正好读出的是 aaaaaa,这是半个应用层数据报。
在 TCP 层次,没有在 socket api 中告诉我们要读几个字节,具体怎么读都是人为操作。
期望读到的是整个的应用层数据报,后续才好处理。
- 解决方案是 :约定好应用层协议即可,尤其是明确应用层数据报和应用层数据报之间的界限。
1.约定好分隔符
2.约定每个包的长度
二选一即可。
异常情况
传输过程中出现了不可抗力。
1.进程崩溃
2.主机关机(按照正常流程关机)
3.主机掉电
4.网线断开
1 和 2 是一类,3 和 4 是一类。
1.进程崩溃:进程没了,对应的 PCB 没了,对应的 文件描述符表就释放了,相当于 socket.close()。此时内核会继续完成四次挥手,此时仍然是一个正常断开的过程。
2.主机关机:主机关机要先关闭进程,然后才正式关机。(关闭进程的过程中,也和上面一样触发四次挥手)
3.主机掉电:显然是来不及挥手了。
假设是接收方掉电了。发送方仍然在传数据,发完数据要等 ack , ack 等不到…超时重传,再怎么重传还是收不到 ack。重传几次还没有应答,尝试重置 TCP 连接,(复位报文段…RST)。显然这个重置也会失败。放弃连接。(单方面放弃)
如果是发送方掉电。接收方发现没有数据了,没数据是发送方挂了?还是发送方在组织语言要稍等会再发?接收方不知道,先等。接收方需要周期性的给发送方发送一个消息(“心跳包”),确认下对方是否还工作正常。
- 心跳包(保活机制)
1.心跳是周期性的;
2.心跳如果没了就是挂了。
心跳包是一个非常常见的机制,分布式系统中还会涉及到心跳包
心跳包来确认通信双方是否工作正常。
4.网线断开:发送方尝试重传,接收方尝试发心跳包。
TCP 是个非常复杂的协议,不仅仅是这 10 个特性,这是 TCP 比较核心的特性。
了解 TCP 更多的特性,应该去翻阅 RFC 文档,翻阅操作系统内核源码
RFC 9293: Transmission Control Protocol (TCP) (ietf.org)
UDP 和 TCP 对比
TCP 的优势在于,可靠传输,绝大部分场景中,都需要可靠传输。
UDP 优势在于,更高效率,如果某些场景对于性能要求更苛刻。
同一个机房内部,服务器之间通信。网络结构相对简单,网络带宽比较充裕,转发设备也是比较好的设备。整体丢包的可能性比较小。
UDP 还有一个优势,天然支持广播。
IP 地址中有一种特殊的地址,“广播 IP”
通过 UDP 往广播 IP 上发送数据,此时该局域网内所有的设备都能够收到数据。
传输层的协议并非只有这 2 个。除了 TCP ,UDP 之外,还有一些其他的传输层协议。有的传输层协议,属于能够专门为游戏场景量身打造的(可靠性 + 效率) , 典型的协议,以 KCP 为代表的一系列协议。
网络层协议
在复杂的网络环境中确定一个合适的路径。
1.地址管理
快递系统要想要建立起来,就需要把世界上的地址能够以一定的规范定义出来。
2.路由选择
规划路径
网络层的代表,IP协议。
IP协议
IP协议报头格式如下:
IPv4协议(v4 版本的意思)
- 4 位版本
此处的取值只有 2 个。4 或 6。
IPv4,IPv6
- 4 位首部长度
描述了 IP 报头多长(IP 报头是变长的)
此处的单位也是 4 字节。
IP 报头有一个选项部分,是变长的,是可有可无的。
- 8 位服务类型(TOS)
说是 8 位,实际上只有 4 位有效,这 4 位中只有 1 位可以是 1,其他的都是 0。
4 位就表示 IP 协议的四种形态/4 种工作模式。
最小延时,最大吞吐量,最高可靠性,最小成本。
最小延时,包裹会最快速度送达。
最大吞吐量,单位时间内你可以送更多的包裹。
最高可靠性,包裹丢的概率是最小的。(和 TCP 的可靠性不是一个概念)
最小成本,让运费最低。
实际开发中就可以根据需要,来切换 IP 的模式,来达到最优的效果。
- 16位总长度(字节数)
描述了一个 IP 数据报的长度。(头 + 载荷)
这个长度减去 IP 报头长度,剩下的就是载荷长度,就是一个完整的 TCP/UDP 数据报长度。
- 16 位总长度,是否意味着一个 IP 数据报,最多只能支持 64 KB?
确实有这个限制,但是 IP 自身就支持对 包的拆分和组装。
举个栗子:
在淘宝上买了个床,快递公司怎么运?把床拆开,以散件的形式发过来,送到了家里,有师傅上门组装。
一个 IP 数据报携带的数据载荷太大了,超过了 64KB,就会在网络层针对数据进行拆分,把一个数据拆分成多个IP 数据报,再分别发送,接收方再重新组装。
比如,发送方:把 100KB 的数据交给传输层(封装),传输层交给网络层(封装),网络层就把这个 100KB 的数据拆包,比如拆成两份 64KB + 36KB。这两份再交给数据链路层,由以太网分装成两个数据帧。接收方:数据链路层针对两个数据帧进行分用,得到两个 IP 数据包交给网络层,网络层针对这俩 IP 数据包进行解析,把里面的载荷拼成一个,交给传输层
此处说是 64 KB,实际上 IP 协议拆包不一定就按照64 KB为单位进行拆分,实际的单位往往更小(取决于数据链路层的情况)
- 8位生存时间(TTL)
一个数据包在网络上能够传输的最大时间。
这个时间的单位不是秒,而是次数
一个数据报构造出来,会有一个初始的 TTL 数值(比如32或者64或者128)这个包每次经过一个路由器转发 TTL - 1,如果一直减到 0 了,还没有到达目标,此时就认为这个报永远都到不了了,就可以丢弃了。
- 8 位协议
描述了当前载荷部分内容是属于哪个协议的(TCP/UDP)
- 16 位首部检验和
此处只需要针对首部进行检验。
载荷部分(TCP/UDP数据报)自身已经有校验和了。
如果校验和不一致,直接丢弃
-
32 位源 IP 地址
-
32 位目的 IP 地址
此处看到的 IP 地址是 32 位的整数,
而日常见到的 IP 则是一串数字 , eg:111.183.72.219
点分十进制:使用 3 个"."把32位四个字节的数字给分隔开,分成四个部分,每个部分分别使用0 - 255 十进制整数表示。
地址,期望每个设备都不相同,但是32位数字只能表示42亿9千万个数字.
期望使用这个表示全世界上所有的上网设备。(智能手机,PC,服务器,路由器,其他杂七杂八的网络设备,物联网)
- 为了解决IP地址不够用的问题,想了很多办法。
-
动态分配IP地址,此时就可以省下一批 IP 地址,这个方案没有从根本上增加 IP 地址,只是提高了利用率。
-
NAT 网络地址转换。本质是使用一个 IP 代表一批设备,也能够大大提高 IP 地址的利用率。使用端口号区分。
在NAT的背景下,就把 IP 地址分成两个大类:
1)内网 IP (私有 IP) 10.* 172.16.* - 172.31.* 192.168.*
2)外网 IP (公网IP) 剩下的是公网 IP
NAT要求,公网 IP 必须是唯一的。私网 IP 可以在不同的局域网中重复出现。
如果某个私网里的设备想访问公网设备,就需要对应的 NAT 设备(路由器),把 IP 地址进行映射,从而完成网络访问。
反之,公网的设备,无法直接访问私网的设备。不同局域网的私网的设备没法直接相互访问。
cmd - ipconfig
内网 IP 只要在局域网内部不重复即可,不同局域网中则是允许重复的。
111.183.72.219 这是一个外网 IP
这些接入运营商路由器的电脑去访问外部服务器,都会被路由器替换成路由器自己的外网 IP 。
此时只要这个电脑是经过运营商路由器转发给服务器,服务器看到的源 IP 就都一样,如果是多个电脑同时访问同一个服务器,服务器的响应就会先发给路由器,路由器根据这些电脑不同的端口号来区分,决定发给哪个设备。
因此,服务器能拿到的只是路由器的 IP 不能拿到我电脑的内网 IP ,如果我电脑不主动和服务器联系,服务器就不知道我的端口,从而无法主动找到我。
NAT机制能够有效的解决 IP 不够用的问题,但是带来的副作用就是网络环境更加复杂了。
- IPv6 : 根本上解决了IP不够用的问题。
使用16字节表示IP地址,128位。
16字节表示的地址的个数:(42亿)^4
IPv6 虽然看起来非常好,但是世界上仍然是以 NAT + IPv4 + 动态分配来进行网络组建的,真正使用 IPv6的地方非常非常少。
因为 IPv6 贵,IPv6和IPv4不兼容,如果你想要支持IPv6,就需要更换路由器等网络设备,花钱,这个钱花的就没啥收益。因此大家都不愿意去升级。
IPv6 中国已经普及80%,背后原因
地址管理
IP地址分为两个部分,网络号和主机号。
网络号:标识网段,保证相互连接的两个网段具有不同的标识;
主机号:标识主机,同一网段内,主机之间具有相同的网络号,但是必须有不同的主机号。
- 一个 IP 地址前面从哪儿到哪儿是网络号,后面从哪儿到哪儿是主机号,分界线是怎么定的?
有一个单独的概念,
子网掩码
网络号不一定就是前三个字节,都是可以调整变化的。
此处的 255.255.255.0 就是子网掩码。
32位
1111 1111 1111 1111 1111 1111 0000 0000
左侧都是 1,右侧都是 0,1的部分就描述了 IP 有多少位是网络号。
对于家用设备来说,子网掩码最常见的就是 255.255.255.0
将IP地址中的主机地址全部设为0,就成为了网络号,代表这个局域网;192.168.0.0
将IP地址中的主机地址全部设为1,就成为了广播地址,用于给同一个链路中相互连接的所有 主机发送数据包;(192.168.0.255 使用udp往这个地址上发数据报,整个局域网所有设备都能收到)
127.*的IP地址用于本机环回(loop back)测试,通常是127.0.0.1
主机号为 1,比如 192.168.0.1 通常是"网关 IP"(不绝对,都可以配置)
将 IP 地址和子网掩码进行“按位与”操作(二进制相同位,与操作,两个都是1结果为1,否则为0),得到的结果就是网络号。 将子网掩码二进制按位取反,再与 IP 地址位与计算,得到的就是主机号。
路由选择
路由选择就是规划路径。
相当于你使用地图导航一下,由于网络环境非常复杂,任何一个节点(路由器)都是无法感知到网络环境的全貌。一个路由器,最多只认识它的一些"邻居"或者"邻居"的"邻居",进行一个比较长的路径的转发就比较麻烦,需要一边走一边问。
路由选择,核心思路是"问路"。每个路由器都会保存一定的周围设备的信息(路由表),每次有一个 IP 数据报经过,路由器就需要匹配路由表,看看接下来咋走,如果路由表上有匹配的项(该路由器认识路),直接按照要求走就行了;如果没有匹配的项,路由器不认识路会提供一个默认路径,大体方向是不会错的,沿着这个方向继续走,继续问。
每经过一个路由器问一次, TTL- 1,如果减到零了,还没到说明这个包就永远都到不了了,这个地址没人知道了,就被丢弃了。
六度空间理论:你和任何一个陌生人之间所间隔的人不会超过六个,也就是说,最多通过6个中间人你就能够认识任何一个陌生人。
正常情况下,像 32 这样的 TTL 足以让我们把数据报转发给世界上任何一个地址。
数据链路层协议
以太网
以太网帧格式
- 目的地址,源地址
此处不是用 IP 地址 表示了,而是用 mac 地址。
-
mac 地址
物理地址。这个是和 IP 地址完全独立的另外一套地址体系。
6 个字节,(比 IPv4 地址大很多)当前每个设备都会有唯一的 mac 地址。不是动态分配的,而是网卡出厂的时候就被设置好了的。
当前 IP 和 mac 是怎么相互配合的?
IP 用来描述整个传输过程的起点终点,mac 则是用来描述两个相邻结点的起点终点。
- 类型
- ARP 协议
通过这个协议,让某个 路由器/交换机 能够认识局域网里的其他设备。通过 ARP 协议会在 路由器/交换机 里建立出一个 表,这个表相当于一个 hash 表,能够建立出 IP 和 mac 之间的映射关系。
MTU
MTU 是数据链路层的一个数据帧能够承载数据的最大长度。(载荷长度)
载荷具体多长,和使用的物理介质有很大关系,也和数据链路层使用的协议有关系。比如以太网协议,MTU 1500字节(这个和物理层有很大关系)
正是这个 MTU 引起了 IP 协议拆包组包,IP 协议的分包组包通常不是根据 这个 IP 最大长度 64KB 来分的。大概率情况下,MTU 比 64 KB 小。
相关文章:
TCP/IP协议
✏️作者:银河罐头 📋系列专栏:JavaEE 🌲“种一棵树最好的时间是十年前,其次是现在” 目录TCP/IP协议应用层协议自定义应用层协议DNS传输层协议端口号UDP协议UDP协议端格式TCP协议TCP协议段格式TCP工作机制确认应答(安…...
Python使用异步线程池实现异步TCP服务器交互
背景: 实现客户端与服务端交互,由于效率原因,要发送与接收异步,提高效率。 需要多线程,本文用线程池管理。 common代码: import pickle import struct import timedef send_msg(conn, data):time.sleep(…...
matplotlib常用操作
文章目录1 matplotlib绘图1.1 绘图步骤2 matplotlib基本元素2.1 matplotlib 画布2.2 设置坐标轴长度和范围2.3 设置图形的线型和颜色2.4 设置图形刻度范围、刻度标签和坐标轴标签等2.4.1 设置刻度范围2.4.2 设置坐标轴刻度2.5 文本标签图例3 matplotlib的ax对象绘图4 绘制子图5…...
二分算法题
文章目录一、在排序数组中查找数字二、0~n-1中缺失的数字三、旋转数组的最小数字四、二维数组中的查找一、在排序数组中查找数字 题目传送门 法一:暴力解 直接遍历然后计数 法二:二分法求边界 看到关键字排序数组、有序数组,一定要想到二分…...
Vue+ElementUI+SpringBoot项目配合分页插件快速实现分页(简单暴力)
首先需要在项目中引入Element-UI的组件库,使用以下命令,不会引入的请自行百度。 npm i element-ui -S Element官网地址:https://element.eleme.cn/#/zh-CN/component/changelog 去Element-UI官网组件库找到合适的分页插件,并把他引…...
【回眸】牛客网刷刷刷!嵌入式软件中也会遇到的嵌入式硬件,通讯,通讯协议专题(一)
前言 最近继续刷题,看看嵌入式软件还需要了解一些嵌入式硬件中的通讯协议和常用接口协议 比如说SPI CAN I2C 通讯协议专题 1.波特率 波特率 每秒传送的字符数 * 字符位数。串口的工作模式为1个起始位,7个数据位,1个校验位,1个…...
使用Vue展示数据(动态查询)
学习内容来源:视频P4 本篇文章进度接着之前的文章进行续写 精简前后端分离项目搭建 Vue基础容器使用 目录选择组件修改表格组件修改分页组件增加后端接口前端请求数据接口页面初始化请求数据点击页码请求数据选择组件 在官方文档中选择现成的组件,放在页…...
构建数据库测试数据——mysql
建表脚本 -- 建表 CREATE TABLE test_table (id INT(11) NOT NULL AUTO_INCREMENT,varchar_col VARCHAR(50),char_col CHAR(10),text_col TEXT,tinyint_col TINYINT(4),smallint_col SMALLINT(6),mediumint_col MEDIUMINT(9),int_col INT(11),bigint_col BIGINT(20),float_col…...
你想要的Android性能优化系列:启动优化 !
App启动优化为什么要做App的启动优化?网页端存在的一个定律叫8秒定律:即指用户访问一个网站时,如果等待打开的时间超过8秒,超过70%的用户将会放弃等待。同样的,移动端也有一个8秒定律:如果一个App的启动时间…...
python3的基础入门3:基本数据类型
基本数据类型 python 中的变量不需要声明。每个变量在使用前都必须赋值,变量赋值以后该变量才会被创建。 在 Python 中,变量就是变量,它没有类型,我们所说的"类型"是变量所指的内存中对象的类型。 等号(&…...
消息队列原理与实战-学习笔记
消息队列:保存消息的一个容器,本质是个队列,但是需要支持高吞吐、高并发、高可用。 1 前世今生 1.1 业界消息队列对比 Kafka:分布式的、分区的、多副本的日志提交服务,在高吞吐场景下发挥较为出色RocketMQ:低延迟、强一致、高性…...
Linux权限相关知识(大量图文展示,及详细操作)
Linux权限相关概念 Linux下有两种用户:一种是超级用户(root)、一种是普通用户。 超级用户:可以在linux系统下做任何事情,不受限制 普通用户:在linux下做有限的事情。 超级用户的命令提示符是“#”…...
Ep_操作系统面试题-什么是协程
协程 是一种 比线程更加轻量级的存 在,一个线程可以拥有多个协程。是一个特殊的 函数 ,这个函数可以在某个地方挂起,并且可以重新在挂起处外继续运行。协程 不是被操作系统内核所管理 , 而完全是由程序所控制(也就是在…...
在C#中使用互斥量解决多线程访问共享资源的冲突问题
在阿里云上对互斥量的概述:互斥量的获取是完全互斥的,即同一时刻,互斥量只能被一个任务获取。而信号量按照起始的计数值的配置,可以存在多个任务获取同一信号量的情况,直到计数值减为0,则后续任务无法再获取…...
JavaEE进阶第六课:SpringBoot配置文件
上篇文章介绍了SpringBoot的创建和使用,这篇文章我们将会介绍SpringBoot配置文件 目录1.配置文件的作用2.配置文件的格式2.1 .properties语法2.1.1.properties的缺点2.2 .yml语法2.2.1优点分析2.2.2配置与读取对象2.2.3配置与读取集合2.2.4补充说明3.设置不同环境的…...
MySQL基础(一)SQL分类、导入、SELECT语句,运算符
目录 MySQL安装以及相关工具 SQL分类 导入数据 最基本的SELECT语句 SELECT FROM 列的别名 去除重复行 着重号 查询常数 描述表结构 过滤数据(重要) 运算符 算数运算符 比较运算符 符号运算符 非符号运算符 逻辑运算符 位运算符 MySQL安…...
反激与正激的区别
之前学习了正激开关电源,但是对于正激和反激一直不是很清楚,网上找了一篇,觉得感觉该可以,以此记录。正激和反激是两种不同的开关电源技术一、正激(1)概述正激式开关电源是指使用正激高频变压器隔离耦合能量…...
王道操作系统课代表 - 考研计算机 第四章 文件管理 究极精华总结笔记
本篇博客是考研期间学习王道课程 传送门 的笔记,以及一整年里对 操作系统 知识点的理解的总结。希望对新一届的计算机考研人提供帮助!!! 关于对 “文件管理” 章节知识点总结的十分全面,涵括了《操作系统》课程里的全部…...
前端开发规范,你真的了解吗?一起来学习一下前端开发规范,让你的代码高级起来!
代码规范 1 编码风格规范 1.1 使用ES6风格编码源码 定义变量使用let ,定义常量使用const 使用export ,import 模块化 1.2 组件 props 原子化 提供默认值 使用 type 属性校验类型 使用 props 之前先检查该 prop 是否存在 1.3 避免 this.$parent 1.4 谨慎使用 …...
Licode—基于webrtc的SFU/MCU实现
1. webrtc浅析webrtc的前世今生、编译方法、行业应用、最佳实践等技术与产业类的文章在网上卷帙浩繁,重复的内容我不再赘述。对我来讲,webrtc的概念可以有三个角度去解释:(1).一个W3C和IETF制定的标准,约定…...
开发运维工具推荐 --- 解决远程访问局域网服务的问题。开发调试推荐
一、FastNat 可为您解决的问题1. 没公网服务器,需要发布本地的站点或网络程序到公网上,供他人访问;此项功能大大方面开发人员进行远程调试,微信小程序等开发工作进行。2. 需要远程到在其他网络中的设备,但两处的网络不…...
【华为OD机试 】单词倒序(C++ Java JS Python)
文章目录 题目描述输入描述输出描述备注用例题目解析C++ 解法JavaScript算法源码Java算法源码Python解法题目描述 输入单行英文句子,里面包含英文字母,空格以及,.?三种标点符号,请将句子内每个单词进行倒序,并输出倒序后的语句。 输入描述 输入字符串S,S的长度 1 ≤ N…...
PLC 诊断故障的基本原理
(1)东欢坨选煤厂机电设备故障信号主要取自开关量信号,PLC 通过开关量的通和断对设备进行故障诊断。PLC 对开关量的识别是通过输入模块来实现的。PLC 控制设备运行时,设备中的温度、压力、急停、跑偏、速度、过热以及各种按钮和行程开关的传感器与 PLC 输入模块相连接,输入模块的…...
QT打开外部程序并嵌入Qt子窗口的缺点
首先可以参考如下文章: QT打开外部程序并嵌入Qt界面_qt界面嵌入外部应用程序_初学小白Lu的博客-CSDN博客 Qt嵌入外部程序界面初探_qt嵌入其他程序窗口_liming4675的博客-CSDN博客 QT 如何把外部程序嵌入到QT界面_qt嵌入其他程序窗口_hellokandy的博客-CSDN博客 Qt界…...
如何系统地学习 C++ 语言?
C作为具有广泛适用性的编程语言,学习C的人越来越多,但是如何系统地学习C还是个问题,下面我们一起来看一下C学习的方法有哪些吧。 首先,要学习C,最重要的就是掌握C的基础知识。 比如数据结构、算法、微积分等。这些都是…...
【数据结构】单链表
链表1.为什么存在链表2.链表的概念3.单链表的实现4.测试1.为什么存在链表 我们在学习顺序表的时候,了解到顺序表有一定的缺陷:(1)在中间插入数据和删除数据需要挪动数据,时间复杂度是O(N)&…...
Windows 右键菜单扩展容器 [开源]
今天给大家分享一个我做的小工具,可以自定义扩展右键菜单的功能来提高工作效率,效果图如下: 如上图,右键菜单多了几个我自定义的菜单: 复制文件路径 复制文件夹路径 我的工具箱 <走配置文件动态创建子菜单&#x…...
爆文制造机!小红书热榜3个方向,告诉你选题诀窍!
我们知道,不论是达人创作内容,还是品牌制定Brief,都需要提前调研筛选海量信息,这时候如果有一个自己的内容素材库,就省事多啦。按照内容需求,我们可以按3个角度划分小红书内容素材:笔记类型、竞…...
【Web安全社工篇】——水坑攻击
作者名:白昼安全主页面链接: 主页传送门创作初心: 以后赚大钱座右铭: 不要让时代的悲哀成为你的悲哀专研方向: web安全,后渗透技术每日鸡汤:努力赚钱不是因为爱钱“水坑攻击”,黑客攻…...
SpringBoot 整合 MongoDB 实现数据的增删改查!
一、介绍在 MongoDB 中有三个比较重要的名词:数据库、集合、文档!数据库(Database):和关系型数据库一样,每个数据库中有自己的用户权限,不同的项目组可以使用不同的数据库集合(Colle…...
辉县网站建设/seo网站内部优化
资深敏捷专家Lisa Crispin在最近的讲座和参与合著的《Agile Testing – A Practical Guide for Testers and Agile Teams》中分享了敏捷软件测试的七个关键成功要素,包括使用团队整体参与的方法、采用敏捷测试思维、自动化回归测试、提供并获取反馈、构建核心实…...
如何查询公司做没做网站/线上培训平台
惠普放出了webOS开源首个测试版,这次发布的测试版有两个版本,其中有个可运行在Ubuntu上的版本。官方说明能在Ubuntu11.04和12.04的32位下正常运行,暂且不支持其他桌面版和Server版。 安装:(查看官方说明) 1…...
织梦源码怎样做单页网站/谷歌seo外链
三台主机192.168.191.106(代号106) 产生日志192.168.191.107(代号107) 实现存放日志的数据库192.168.191.173(代号173) 实现日志报表1、实现rsyslog将日志记录于MySQL中(1)在107上:yum install mariadb-serversystemctl start mariadbmysql_secure_installati…...
自己做网站怎么选架构/威海seo优化公司
原标题:一起来捉妖:在线6小时被劝退?只需网络断开,跳过等待15分钟一起来捉妖这款游戏刚出来的时候就凭借着新颖的玩法吸引了大部分玩家的驻足,而且这个游戏也是让玩家们完全停不下手,不过之卿由于已经成年所…...
新校区建设网站/推广资源seo
干货 编辑:杜伟、蛋酱,来源:机器之心2021 年之后,机器学习将会对哪些领域产生前所未有的影响?在过去的数年,见证了机器学习(ML)和计算机科学领域的许多变化。按照这种长弧形的进步模…...
做网站时的尺寸/seo的中文是什么
[讨论] 这几天来封装Win7用户配置文件丢失的解决方法个人心得 prerouting 发表于 2010-5-9 16:50:46 https://www.itsk.com/thread-36634-1-4.html [讨论] 这几天来封装Win7用户配置文件丢失的解决方法个人心得 前几日发帖(http://sky123.org/thread-36378-1-1.html…...