1.互联网
有了互联网的出现 我们就可以足不出户的实现看电影、购物等等操作
我们认知中可能的互联网模型
较为真实的互联网模型
那么数据是如何从一个设备传递到另外一个设备的呢?
2.网络互联模型
统共有三种:
3.TCP/IP协议
TCP/IP是一群协议 里面不仅仅包括TCP和IP两种协议 还有其他很多的协议 这不过这两种协议是最早通过的协议 所以以他们的名字命名整个的协议家族
4.网络分层
由于我们现在是学习阶段 所以主要用的是学习研究的网络互联模型 那么该模型的具体分层是怎样的呢
5.HTTP请求过程
我们可以看到 http请求是经历了一系列的装箱、拆箱的过程 最后才传递给指定主机的
具体的过程 可以参考一下我讲的:
首先HTTP请求从应用层发送给运输层的话 那么运输层就会为其添加TCP首部
接着到达网络层的时候 该层会为其提供IP首部
接着到达数据链路层的时候 该层会为其提供首尾的mac地址
以此类推……
我们可能会有这些疑惑?
1.有人认为不需要经历那么多层才将数据发送到目标主机手中 这是错误的
首先运输层在提供了端口号 端口号用于识别同一台主机上的应用 如果同一台主机上存在多种应用的话 那么使用端口号就可以清楚的知道数据将发送给哪一个应用
还有网络层提供了ip信息 这样我们才能够知道数据最终发送的目标ip是什么
还有数据链路层提供了mac地址 这个mac地址就是用于标识网卡的 如果一台设备上存在多个网卡的话 那么我到底是发送给谁呢 只有清楚的知道mac地址 我才能够清楚的指导发送给哪一个网卡
2.那么为什么客户端、服务器端的层数那么多 而路由器的层数却那么少呢
这是因为路由器的作用就在于寻找接下来到目标主机的最短路径 而ip作为主机的标识 是由网络层提供的 我们只需要拆箱到网络层即可获取到ip信息 所以这就是为什么路由器不需要提供5层的原因
6.TCP vs UDP
在运输层中 有两种常见的协议 分别是TCP和UDP
这两种协议的不同点如下所示
TCP连接性体现在两台设备之间需要拥有一个管道进行连接 而UDP则不需要 这样的特点造就了TCP的每一个连接只能是一对一的通信 而UDP却可以进行一对多的通信
下图是TCP首部占用的空间大小明细
下图则是UDP首部占用的空间大小明细
对比之下 我们就可以发现 UDP的首部占用较小 TCP的首部占用较大
由于TCP是可靠传输 所以他的传输效率肯定是较慢 资源消耗肯定是较大
而且诸如浏览器、文件传输、邮件发送等都需要依赖TCP的可靠传输 因为TCP不丢包的缘故 所以可以保证接收到的文件、邮件等不会出现残缺
但是视频、直播这种需要实现实时互动的东西 一定使用的是UDP协议 不然的话 可能会产生诸如前几秒丢包的东西在当前展示 这就不是我们所希望的实时效果
7.TCP-三次握手-建立连接
TCP的三次握手和四次挥手都是为了建立或者释放连接 而UDP是不涉及连接的 所以UDP是不涉及建立连接和释放连接时的三次握手和四次挥手的
SYN = 1表示发送方想要建立连接 比如在第一次握手中表示客户端想要建立连接 第二次握手中表示服务器想要建立连接
ACK = 1表示接收方确认了发送方的连接请求 比如在第二次握手中表示服务器确认了客户端的连接请求 而第三次握手中则表示客户端确认了服务器的连接请求
seq = x是用于标志发送方发送的消息 即消息的序列号 有个特点就是发送方发送的消息是逐次递增1的(也不一定 因为连接建立以后 1的位置就要替换成所发送数据的字节数)
ack = x + 1表示接收方下一次想要接收的序列号(但是ack值中不一定是+1 在建立连接的过程中 由于没有发送数据 所以+1 在连接建立以后 就真正开始发送数据了 所以建立连接以后+1位置就要替换成所发送的数据占用的字节数 但是有一点是没变的 就是他一定是下一条希望接收到的消息的序列号)
同步表示连接请求的意思
整体的意思就是:
第一次握手中 客户端发送了一条消息给服务器 消息中 SYN = 1表示客户端想要建立连接 seq = x表示这条消息的序列号
第二次握手中 服务器发送了消息给客户端 消息中 SYN = 1表示服务器也想要建立连接 ACK = 1表示服务器确认了客户端的连接请求 seq = y表示这条消息的序列号 ack = x + 1表示服务器下一次希望接收到的客户端的消息的序列号
第三次握手中 客户端发送了消息给服务器 消息中 ACK = 1表示客户端确认了服务器的连接请求 seq = x + 1表示该条消息的序列号 ack = y + 1表示客户端下一次希望接收到的服务器的消息的序列号
8.TCP-四次挥手-释放连接
FIN = 1表示发送方想要关闭连接
ACK = 1表示接收方确认了发送方的关闭连接请求
seq = u标志了发送方所发送的消息 即消息的序列号 并且是逐条递增的 递增的量取决于你处于哪一个阶段 如果你处于建立或者关闭连接的阶段 那么递增量为1 如果你处于连接已建立后的阶段 那么递增量为接收方所接受的数据所占用的字节数
ack = v表示接收方下一次希望接收到的发送方发送的消息的序列号
第一次挥手中 客户端向服务器发送了一条消息 消息中 FIN = 1表示客户端想要关闭连接 ACK = 1表示客户端确认了服务器的消息 seq = u标志了这条消息 ack = v表示客户端下一次希望接收的服务器的消息的序列号
第二次挥手中 服务器向客户端发送了一条消息 消息中 ACK = 1表示服务器确认了客户端的关闭连接请求 seq = v标志了这条消息 ack = u + 1表示服务器下一次希望接收到的客户端的消息的序列号
第三次挥手中 客户端向服务器发送了一条消息 消息中 FIN = 1表示服务器希望关闭连接 ACK = 1也表示服务器确认了客户端发送的关闭连接请求 seq = w标志了这条消息 ack = u + 1表示服务器下一次希望收到的客户端的消息的序列号
第四次挥手中 客户端向服务器发送了一条消息 消息中 ACK = 1表示客户端确认了服务器的关闭连接请求 seq = u + 1标志了这条消息 ack = w + 1表示客户端下一次希望收到的服务器的消息的序列号
9.HTTP补充
平时如果想要访问域名的话 比如www.baidu.com 那么你首先得知道相关的ip地址 那如何获取指定域名的关联ip地址呢 就是从dns服务器手中获取 一台主机会关联多个dns服务器 如果你询问的这台服务器不知道的话 那么他就会在dns服务器中进行询问 直到知道为止 而且这次知道了就会对这个ip地址进行缓存
10.Socket编程
1.用代码模拟三次握手
注意:要先运行服务器 在运行客户端 不然的话 会报ConnectExcpetion 因为你只有先让服务器处于监听状态 监听客户端发送过来的连接请求才是正确的
编写服务器代码的流程:
首先设置一个ServerSocket 用于开启服务器的监听状态
接着设置一个Socket 用于服务器和客户端之间建立连接 一般创建了之后就会建立连接 如果没有收到连接请求的话 那么就会陷入阻塞状态
接着定义了一个ByteArrayOutputStream 用于进行字节输出的 然后他最后会将数据储存到缓存区中
接着定义了一个字节输入流(需要通过socket获取 因为这样才能够知道读取的对象是谁 socket对象显然是有关于客户端的) 用于读取客户端发送过来的数据
并且用一个数组进行装载
然后调用ByteArrayOutputStream的toByteArray 将缓存区中的数据储存到字节数组中
接着我们可以通过Socket对象获取到发送方客户端的ip以及发送的数据
最后需要关闭所定义的资源(资源就是实现了AutoCloseable接口的类)
编写客户端代码的流程:
首先定义一个Socket 并且需要指定接收方服务器的端口以及ip地址
接着我们需要发送数据给接收方 期间需要定义一个字节输出流(需要通过socket获取 这个socket显然是有关于服务器的)
发送数据以后 需要关闭之前定义的资源
public class TCPServer {public static void main(String[] args) throws IOException {// 定义一个服务器相关的Socket对象 指定服务器的端口号 服务器的ip默认就是所在主机的ip 并且让服务器处于监听状态ServerSocket serverSocket = new ServerSocket(222);// 然后通过以上的服务器Socket对象获取Socket对象 起到一个阻塞状态 如果没有接收到连接请求的话 那么就会一直处于阻塞状态 一旦接收到了连接请求的话 那么就会解除阻塞状态 并且建立服务器和客户端之间的连接Socket socket = serverSocket.accept();// 我们指定一下输出的载体为数组 也就是定义一个字节数组输出流ByteArrayOutputStream baos = new ByteArrayOutputStream();// 然后定义一个字节输入流InputStream is = socket.getInputStream();// 需要定义一个字节数组作为输入的载体byte[] buffer = new byte[8192];int len = 0;// 用于统计有效的输入字节数while((len = is.read(buffer)) != -1) {baos.write(buffer, 0, len);}// 然后我们需要将通过baos输出到服务器缓存中的数据移动到数组中byte[] bytes = baos.toByteArray();String string = new String(bytes, "UTF-8");System.out.format("服务器接收到了来自%s的数据:%s%n", socket.getInetAddress(), string);serverSocket.close();socket.close();baos.close();is.close();}
}
public class TCPClient {public static void main(String[] args) throws UnknownHostException, IOException {// 定义一个Socket对象 并且指定接收方服务器的ip地址以及端口号Socket socket = new Socket("127.0.0.1", 222);// 接着创建一个字节输出流 用于向指定服务器进行字节的输出String string = "哈哈哈";OutputStream os = socket.getOutputStream();// 通过socket对象获取字节输出流 意图是在向指定socket对象进行输出操作os.write(string.getBytes("UTF-8"));// 由于Socket和OutputStream都实现了AutoCloseable接口 所以都属于资源 所以需要关闭 这边我们就进行手动关闭操作了socket.close();os.close();}
}