网络:进程和进程间跨主机(Host)通信。
进程发送的数据,怎么发送到网卡呢?
OS的网络管理(网络协议栈)
线路上的发送(不是我们的重点,属于通信专业的研究)
怎么让网卡发送数据?
1.填充数据
2.触发发送
网卡怎样接受数据?
入口处可以将物理数据转为数字数据
1.接收数据
2.通过中断把控制权给OS
3.OS把数据搬给进程
两台直连的主机,数据怎样发送和接受呢?
主机A
1.用数据填充网卡
2.通过网卡发送
主机B
网卡接受数据
通过中断通知OS ,转到进程
进程发送出来的数据,是原封不动的给到网卡的吗?
数据需要经过不断地封装之后才会发送的
有不同的层次去实现网络,所以现代网络都是分层去讨论的。
理论层次:OSI 7层(了解)
实际层次:TCP/IP 5层(重要)
要求:记住有哪些层次,层次前后的关系,每层的职责是什么
1.物理层physical layer:解决在物理介质中传输的问题 系统实现OS(内核态)
hub集线器
2.数据链路层data link layer:LAN内部的主机到主机,用来做局域网 系统实现(我们进行)
switch交换机
3.网络层network layer:跨局域网LAN的通讯,解决路由问题 系统实现
以上实现主机到主机 router路由器
4.传输层transmission layer:数据到达主机之后,应该分给哪个进程,进程到进程 系统实现
TCP、UDP、IP、ICMP
5.应用层 application layer:处理业务逻辑 用户实现
以上实现进程到进程 DNS、HTTP(S)协议
局域网 Local Area Network LAN:
被路由器网线直接连接起来的设备
广域网Wide Area Network WAN:
局域网组合起来形成广域网
针对LAN局域网,主机是可以直接通信的
组网:
1.主机连接主机
2.星形、总线、环形...
网络设备的存在:集线器hub、交换机switch
集线器:插座(在物理层)
消息中携带目标信息
只有目标主机接收数据,其他主机直接忽略
职责:交给数据链路层处理
数据链路层:解决一个局域网内部的数据通信问题
交换机switch:交换机是可以知道发送给哪个目标的,只会把数据交给对应的主机
交换机工作在数据链路层
路由器router:在网络层工作
路由:寻路的问题(哪条路最合适)
传输层:解决进程对 进程通信的问题
应用层:在解决通信问题的情况下,各个业务是具体怎么通信的
MAC地址:数据链路层
物理地址:绑定在网卡上的一个地址(硬件地址),MAC地址是“绝对唯一”
IP地址:网络层
一个网络内部的IP地址,不应该重复。(软件地址)
端口port:
在一个主机内部区分不同进程用的 0-65535
依赖于ip +port
通过IP地址,一定可以唯一的确定网络中的一台主机
通过port,一定可以唯一的确定主机内部的一个进程
ip:port 唯一确定网络中的一个进程
通信主题A:IP1 :port1
通信主题B : IP2 : port2
四元组:唯一确定网络中的一条通信“线路”
发送IP:发送port+目标ip:目标port
本地IP:本地port +远端ip:远端port
传输层:TCP/UDP
五元组: 协议:Ip1:port 1:ip2:port2
进程和端口的关系:
一个进程可以同时绑定bind多个端口
一个端口同一时刻,只能属于一个进程
协议:网络协议的简称,网络协议是网络通信经过的所有网络设备都必须共同遵从的一组规定。
0-1023是知名端口号,这些端口预留给服务端程序绑定广泛使用的应用层协议。
封装:
数据链路层:数据帧frame
网络层:数据包packet
传输层:
UDP:报文datagram
TCP:段segment
应用层:
请求 - 响应模型、订阅 - 推送模型
HTTP:请求、相应
一般封装的数据都是头部信息。
网络的分布式架构造成以下特点:
1.数据在整个通信线路上对于线路中的设备实际是透明的(存在数据监听和数据篡改的风险)
2.网络在发送中天生是不可靠的(不保证数据一定都能发送给对方)
3.来回的线路并不保证一致
观察数据的封装过程:OS对代码进行封装
观察封装后的数据:抓包工具wireshark
TCP:长连接 vs 短连接
一、长连接: 重复请求 响应,由于TCP是面向客户流的,
客户端: 请求1 请求 2 请求3
服务器:请求1 +请求2前一半 +请求2后一半请求3
为了避免这种问题,在进行应用层设计时,需要考虑:请求中有明确的分界线,帮助服务器划分不同的界限。
方法:
1.采用定长的方式,比如,请求的固定长度是10个字节
2.采用变长的方式
1.先发送长度,在发送请求
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
public class 长连接Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 8080); // 拨号
//有两种方式实现:设置数量或者使用特殊字符,区分每次的请求
for (int i = 0; i < 3; i++) {
InputStream is = socket.getInputStream();
Scanner scanner = new Scanner(is, "UTF-8");
OutputStream os = socket.getOutputStream();
OutputStreamWriter writer = new OutputStreamWriter(os, "UTF-8");
PrintWriter printWriter = new PrintWriter(writer);
// 发送请求
printWriter.printf("我是Java19班的\r\napple\r\n");
printWriter.flush();
// 读取响应
String header = scanner.nextLine(); // 好的
String word = scanner.nextLine(); // 苹果
System.out.println(word);
}
//socket.close(); // 挂断电话
//我们现在要做的长连接,是不挂断电话,请求新的响应
}
}
2.使用一个特殊字符,区分不同的请求
比如:每次读到两次\r\n 表示一个请求 我是软件工程的\r\napple\r\n
围绕长连接继续进化:客户端的单词来自用户输入,什么时候结束也来自用户输入。
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
public class 长连接Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 8080); // 拨号
Scanner systemInScanner = new Scanner(System.in);
// 通过 Ctrl + D 结束
// 通过 Ctrl + Z 结果
while (systemInScanner.hasNextLine()) {
String w = systemInScanner.nextLine();
InputStream is = socket.getInputStream();
Scanner socketScanner = new Scanner(is, "UTF-8");
OutputStream os = socket.getOutputStream();
OutputStreamWriter writer = new OutputStreamWriter(os, "UTF-8");
PrintWriter printWriter = new PrintWriter(writer);
// 发送请求
printWriter.printf("我是Java19班的\r\n%s\r\n", w);
printWriter.flush();
// 读取响应
String header = socketScanner.nextLine(); // 好的
String word = socketScanner.nextLine(); // 苹果
System.out.println(word);
}
//socket.close(); // 挂断电话
}
}
二、短连接:挂断电话之后重新请求 响应
TCP:面向字节流
让A送信,但是一天只送一封信,可能会堆积一堆信
abc def ghi 保证顺序不动,但不保证会不会 断开 abcde fghi
UDP:面向数据报文
职责:在网络层的基础上,实现了进程到进程的通信
UDP协议的包头信息:UDP添加的封装(解包、分用)
检验和checksum:防止数据错误,类似Hash的算法
发送端:hash(有效数据) 计算哈希值h1
接收端:hash(有效数据) 计算哈希值h2
h1 ! = h2 数据一定出错了
h1 == h2:大概率的认为,数据就是原数据
发送abc 立即送 收到的就是abc
发送:
1.填充端口
2.计算长度+填充长度
3.计算检验和 +填充检验和
4.立即将数据交给网络层(UDP协议栈内部没有发送缓冲区)
接受:
1.从网络层接受数据
2.通过计算校验和,检查数据是否出错了。若出错,直接丢弃。
3.数据没有错,根据自己内部维护Map<port,pid>
找到对应的接收进程(找不到直接丢弃)
4.看对应的进程是否准备好一块内存
如果暂时没有,需要暂时把数据暂时保存一段时间
5.如果有,相当于把数据复制到对应的内存空间 byte[] buf
UDP的特点:不可靠的、无连接的、面向数据报文。
TCP协议:
1.职责:进程到进程
可靠:承诺可靠,没有承诺安全
2.什么是可靠性:
1.TCP会尽自己最大的努力,将数据发送给对方
2.如果真的遇到发送不过去的情况,TCP至少会告诉发送进程,数据发送失败了
3.保证不会受到错误的数据(通过checksum)
4.TCP能保证收到的数据一定是有序的(按照发送进程发送时的顺序)
5.TCP会根据对方的接受能力和网络线路承载能力,进行流量的控制
3.TCP做什么保证可靠
1.确认应答机制:接收方(对方的TCP)有责任对收到的数据进行确认(应答)
2.连接管理(为了做到确认应达)
三次握手
1.哪三次
2.标志位+序列号+状态
3.状态变化
4.三次握手和应用层代码的关系
确认段:一份数据可以起到发送数据的角色,也可以起到确认的角色
发送:携带有数据,填写正确序列号SN,就是发送SEGMENT
确认:标志位ACK置1,
发送端:我们没有上帝视角、
当我发送一个数据没有收到应答时,可能发生了什么
可能情况:1.对方没有收到
2.对方收到了,并且应答了,但是应答没有发送过来
真的遇到没有收到应答(超时重传机制):1.不应该一直等下去(超时机制timeout)
2.重新发送机制
主机B能否判断出数据有重复?
根据数据中的序号检查
TCP的发送端不用关心你没有收到应答的原因是什么,采用统一的超时机制即可。
如果接收端真的收到了重复的数据,直接丢弃即可。
超时重传是无限制的吗?
会有一定的上限!
达到上限之后,发送发TCP就认为本次数据线路出现重大问题了。
1.TCP会关闭本次连接
2.TCP会通知进程(在JAVAZ中,采用异常处理IOException)
3.TCP会发送一个reset segment出去
关于超时时间的设置:一般这个时间不是一个固定的长度,大多采用的是逐步变长
10S 20S 40S 80S
站在进程的角度思考:向一个有问题的TCP线路中发送数据,多久之后,我“线程”知道线路有问题
10 +20+40+80s之后发现。
作为TCP发送方,经过一段时间之后,是可以知道线路有问题的
作为TCP接收方,无法得知线路有问题的(无法确认对方是没有数据发送还是发送失败了)
小结:TCP提供了确认应答机制(数据编号机制、超时重传的机制)
1,保证了TCP尽自己所能,把数据发送给对方
2,TCP在最终发送失败以后,通知了进程发送失败的事实
涉及TCP报头
1.TCP一个segment既可以当 发送segment,也可以当 确认segment使用。
SN+携带数据payload 发送作用
ack 标志位+ASN 确认作用
SN:本次数据的第一个字节的编号
ASN:(确认序列号)希望下次发送的第一个字节编号
1.TCP有没有发送缓冲区send buffer 有!
发送之后,可以直接丢弃吗? 不可以,可能要重发。所以,至少需要一个地方保存这些数据。
2.TCP有接受缓冲区。
3.TCP得维护好发送时的序号 SN=x,才可以用于
TCP得维护一个已经接收的数据序号 ASN= y,才可以去重
为什么TCP要设立建立连接。
1.必须确认对方存在,才能可靠的传输
2.交换一些必要的数据
SN不是直接从1 开始的,双方各自随机生成,随后随机交换
正式通信之前,需要一个阶段
1.确认对方在线
2.同步Synchronize一些基本信息
TCP的状态:表示当前连接目前的情况
建立连接阶段:
主动连接方 (打电话的角色)大部分是Client 确认对方在线+告诉自己的信息
被动连接方(接电话的角色)大部分是Server 确认对方在线+告诉自己的信息
ack =1 确认 ack =0 没有确认
syn =1 同步
fin =1 挥手
closed:虚拟状态
listen:被动连接方
服务器已启动 ,但没有真正的连接建立
SYN_RCVD:收到了SYN
SYN_SEND:发送了SYN