一、OSI网络模型
1.1、网络的7层架构
1.1.1、七层架构的网络图
1.1.2 七层架构的功能和作用
1、物理层
主要定义物理设备标准,如网线的接口类型、光纤的接口类型、各种传输介质的传输速率 等 作用:传输比特流(就是由 1、0 转化为电流强弱来进行传输,到达目的地后在转化为 1、0,也就是我们常说的模数转换与数模转换)
2、数据链路层
作用:主要将从物理层接收的数据进行 MAC 地址(网卡的地址)的封装与解封装
3、网络层
作用:主要将从下层接收到的数据进行 IP 地址(例 192.168.0.1)的封装与解封装。在这一层工 作的设备是路由器,常把这一层的数据叫做数据包。
4、传输层
作用:主要是将从下层接收的数据进行分段进行传输,到达目的地址后在进行重组。 常常把这一层数据叫做段。
5、会话层
作用:通过传输层(端口号:传输端口与接收端口)建立数据传输的通路。主要在你的系统之间 发起会话或或者接受会话请求(设备之间需要互相认识可以是 IP 也可以是 MAC 或者是主机名)
6、表示层
作用:主要是进行对接收的数据进行解释、加密与解密、压缩与解压缩等(也就是把计算机能够 识别的东西转换成人能够能识别的东西(如图片、声音等))
7、应用层
作用:主要是一些终端的应用,为应用进程提供服务,比如说FTP(各种文件下载),WEB(IE浏览),QQ之类的(你就把它理解成我们在电脑屏幕上可以看到的东西.就 是终端应用)。
二、IO模型
2.1、五种IO模型(基于Linux系统)
1、阻塞IO模型
阻塞IO中从用户发起调用开始,数据等待、数据拷贝都是需要用户进程,直到数据拷贝完成返回,用户进程才可以继续执行,否则一直阻塞。
2、非阻塞模型
非阻塞IO中进程通过反复调用IO函数,判断数据是否准备好,采用轮询,占用CPU。
3、IO复用模型
主要使用复用器:select、poll、epoll 一个进程可以监听多个事件,能实现对多个端口进行监听,多个连接共享一个等待机制。
4、信号驱动IO模型
首先需要开启套接字信号驱动功能,通过系统调用sinaction执行信号处理函数,信号处理函数直接返回,进程继续工作,当数据准备就绪,生成一个siglo信号,通知应用程序取数据。
5、异步IO模型
信号驱动IO由内核通知进程开始一个IO操作,在用户进程进行IO操作的时候是需要等待的,所以是同步过程。 异步IO模型是由内核通知IO何时已经完成,进程不需要进行IO处理,所以是异步的。
三、BIO模型
3.1、BIO介绍
- BIO(Block IO):同步阻塞;
- 在JDK1.4之前建立网络链接基本采用BIO模型;
- BIO流程简单,在服务端创建一个ServerSocket去监听等待连接,客户端创建一个Socket去连接服务端,建立连接之后,客户端和服务端就可以进行网络数据的传输;
- 同步阻塞
- 在BIO编程中,accept()等待客户端的连接,人客户端合适发起连接是未知的,accept等待操作一旦发起之后,需要一直阻塞等待连接直到有客户端的连接之后accept返回,整个过程式阻塞; - 除了accept之外,还有connect、read、write等方法都会进行阻塞,只有等到数据完成之后才能继续执行;
3.2、BIO编程
- 通过BIO模拟echo命令:
public class Server {
public static void main(String[] args) {
serverHandler(9999);
}
public static void serverHandler(int port) {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(port));
System.out.println("服务端绑定端口:"+port+" 并启动啦");
Socket socket = serverSocket.accept();
System.out.println("有新客户端连接:"+socket.getRemoteSocketAddress());
byte[] bytes = new byte[1024];
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
while (true) {
int len = inputStream.read(bytes);
String msg = new String(bytes,0,len);
System.out.println("客户端:"+socket.getRemoteSocketAddress()+" 发送数据:"+msg);
String msg1 = "【echo】"+msg;
outputStream.write(msg1.getBytes());
outputStream.flush();
if (msg != null && "exit".equals(msg.trim())) {
System.out.println("服务端准备结束");
break;
}
}
socket.close();
System.out.println("客户端连接已断开");
} catch (IOException e) {
e.printStackTrace();
}finally {
if (serverSocket != null) {
try {
serverSocket.close();
System.out.println("服务端已关闭");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
public class Client {
private static Scanner scanner = new Scanner(System.in);
public static void main(String[] args) {
clientHandler("127.0.0.1",9999);
}
public static void clientHandler(String ip,int port) {
try {
Socket socket = new Socket();
System.out.println("客户端启动啦");
socket.connect(new InetSocketAddress(ip,port));
System.out.println("客户端连接上服务端");
OutputStream outputStream = socket.getOutputStream();
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
while (scanner.hasNext()) {
String msg = scanner.nextLine();
if (msg == null || "".equals(msg.trim())) continue;
outputStream.write(msg.getBytes());
outputStream.flush();
int num = inputStream.read(bytes);
System.out.println(new String(bytes,0,num));
if ("exit".equals(msg)) break;
}
System.out.println("客户端即将结束");
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.3、BIO处理多用户连接
- 在accept能够接收多用户连接,通过循环来接,借助于主线程主要接收客户端的连接(accept),子线程需要进行IO读写;
3.4、多用户请求的开发
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class MutilThreadServer {
public static void main(String[] args) {
serverHandler(9999);
}
public static void serverHandler(int port) {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(port));
System.out.println("服务端绑定端口:"+port+" 并启动啦");
while (true) {
Socket socket = serverSocket.accept();
System.out.println("有新客户端连接:"+socket.getRemoteSocketAddress());
new ServerHandler(socket).start();
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if (serverSocket != null) {
try {
serverSocket.close();
System.out.println("服务端已关闭");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
public class ServerHandler extends Thread {
Socket socket;
public ServerHandler(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
byte[] bytes = new byte[1024];
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
while (true) {
int len = inputStream.read(bytes);
String msg = new String(bytes,0,len);
System.out.println("线程:"+Thread.currentThread().getName()+" 客户端:"+socket.getRemoteSocketAddress()+" 发送数据:"+msg);
String msg1 = "【echo】"+msg;
outputStream.write(msg1.getBytes());
outputStream.flush();
if (msg != null && "exit".equals(msg.trim())) {
System.out.println("线程:"+Thread.currentThread().getName()+"服务端准备结束");
break;
}
}
socket.close();
System.out.println("线程:"+Thread.currentThread().getName()+"客户端连接已断开");
} catch (Exception e){
}
}
}
3.5、传统的BIO架构
3.6、BIO支持高并发的缺点
- 线程资源是有限的,不能无限制的创建新的资源,么么对于BIO的高并发的支持是大打折扣的:
- 1、线程需要消耗的内存资源是有限的,不能无限制的创建爱你线程;
- 2、线程切换涉及上下文(是指某一时间点CPU寄存器和程序计数器的内容)的调整,这个过程也是耗时的,如果线程执行过程中线程上下文切换耗时t1,线程执行时间耗时t2,如果t1>t2,线程的执行时效率就会降低;
四、NIO模型
4.1、NIO介绍
- NIO:同步非阻塞模型;
- 关键点:采用事件驱动的思想来实现一个多路复用器,主要是来解决高并发的问题;
- NIO中存在IO复用器,一个复用器同时可以监听多个用户的连接或者读写操作,基于事件驱动;
- 一个复用器通过 一个线程来管理,意味着一个线程可以处理多个用户的请求事件;
- NIO底层本质就是采用的是IO复用模型;
![请添加图片描述](https://img-blog.csdnimg.cn/a076d331a7794f3fab6ba86c6fbd9c63.png - NIO主要的三大核心部分:Channel(通道)、Buffer(缓冲区)、Selector。传统IO基于字节流和字符流进行操作,而NIO基于Channel和Buffer进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写到通道中,Selector(选择区)用于监听多个通道的事件(比如:连接打开,数据到达),因此,单个线程可以监听多个数据通道。
- NIO和传统IO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。
- NIO和传统的IO之间的最大的一个区别就是IO是面向流的,NIO是面向缓冲区的;
4.2、NIO缓冲区
- java IO面向流意味着每次从流中读取一个或多个字节,直至读取到所有的字节;
- 不能前后移动流中的数据;
- NIO 的缓冲导向方法不同,数据读取到它稍后处理的缓冲区,需要时间可在缓冲区中前后移动;
- 虽然增加了处理过程中的灵活性,但是还需要检查是否该缓冲区中包含所有需要我们处理的数据,并要确保在更多的数据被读入缓冲区时,不能覆盖缓冲区中还未处理的数据;
4.3、NIO非阻塞
- IO中的所有流是阻塞的;
- 当一个线程调用read()或write()方法时,该线程被阻塞,直到有一些数据被读取,或数据被完全写入;
- NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是仅能得到目前可用的数据,如果目前没有数据可以用的时候,就什么都不会获取,而不是保持线程阻塞;
- 在数据变得可读取之前,该线程可以继续做其他的事情,
- 线程通常将非阻塞IO的空闲时间用在其他通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel);
Channel:通道
- Channel和IO中的Stream(流)是差不多一个等级的,只不过Stream是单向的,如InputStream,OutputStream,channel是双向的,既可以用来进行读操作也可以用来写操作;
- NIO中的Channel的主要实现有:
public abstract class FileChannel extends AbstractInterruptibleChannel
implements SeekableByteChannel, GatheringByteChannel ScatteringByteChannel
{
protected FileChannel() { }
public static FileChannel open(Path path,Set<? extends OpenOption> options,FileAttribute<?>... attrs) throws IOException
{
FileSystemProvider provider = path.getFileSystem().provider();
return provider.newFileChannel(path, options, attrs);
}
public static FileChannel open(Path path,
Set<? extends OpenOption> options,
FileAttribute<?>... attrs)
throws IOException
{
FileSystemProvider provider = path.getFileSystem().provider();
return provider.newFileChannel(path, options, attrs);
}
- DatagramChannel
- SocketChannel
- ServerSocketChannel
五、AIO模型
简介
- AIO:是指异步非阻塞
- AIO需要操作系统的支持,在Linux内核2.6版本之后增加了对真正异步IO的实现,java从JDK1.7之后支持AIO;
- JDK1.7新增了一些与文件/网络IO相关的一些API,称之为NIO2.0或者称之为AIO(Asynchronous IO),AIO最大的特征就是提供了异步功能,对于socket网络通信和文件IO都是起作用;
- 与NIO不同,当进行读写操作时,只需直接调用API提供的读写方法,这些方法均是异步操作,
- 对于读操作而言:当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序
- 对于写操作而言:当操作系统将write方法传递的流写入完成,操作系统会通知应用程序
读写操作都是异步,完成之后会主动调用回调函数 - 在JDK1.7中,在java.nio.channels包下增加了四个异步通道
- AynchronousSocketChannel - AynchronousServerSocketChannel - AynchronousFileChannel - AynchronousDatagramChannel - AIO为accept方法提供了两个版本的处理:
- future方式
- Future accept();
- 当开始接收客户端连接,当前线程需要进行网络IO,则调用该方法的Future对象的get()方法,get()方法会阻塞当前线程,
- 提交一个IO请求,会返回一个future,然后对future进行检查,判断是否完成;
- 注意点:future.get()方法是同步的,如果使用future很容易是编程进入同步编程模式,异步编程显得无用;
- callback方法
- void accept(A attachment,CompletionHandler<AsnchronousSocketChannel,? super A> handler);
- 接收客户端的请求,连接成功或者是未成功都会触发CompletionHandler对象的相应方法其中AysnchronousServerSocketChannel就代表该CompletionHandler处理器在处理连接成功时的resultCompletionHandler接口定义了两个方法;
- void completed(V result,A attachment);
- 当IO操作完成,即触发该方法,
- 参数result代表IO操作返回的对象,参数attachment代表发起IO操作时传入的附加参数;
- void failed(Throwable exc, A attachment);
- IO操作失败后触发;
- 参数exc代表IO操作失败引发的一个异常或者是错误;
- 参数attachment代表发起IO操作时传入的附加参数;
- 当提交一个IO操作的请求,并且指定一个CompletionHandler,当异步操作完成时,便发送一个通知,此时这个CompletionHandler对象的completed或者是failed方法被调用;
public class Server {
public static void main(String[] args) {
try {
AsynchronousServerSocketChannel asynchronousServerSocketChannel = AsynchronousServerSocketChannel.open();
asynchronousServerSocketChannel.bind(new InetSocketAddress(9999));
System.out.println("server端启动啦");
asynchronousServerSocketChannel.accept(null,new AcceptHandler(asynchronousServerSocketChannel));
while (true) {
Thread.sleep(1000);
System.out.println("循环中...");
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class AcceptHandler implements CompletionHandler<AsynchronousSocketChannel,Object> {
private AsynchronousServerSocketChannel asynchronousServerSocketChannel;
public AcceptHandler(AsynchronousServerSocketChannel asynchronousServerSocketChannel) {
this.asynchronousServerSocketChannel = asynchronousServerSocketChannel;
}
@Override
public void completed(AsynchronousSocketChannel channel, Object server) {
try {
System.out.println("有新客户端连接:"+channel.getRemoteAddress());
} catch (IOException e) {
e.printStackTrace();
}
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
channel.read(buffer,buffer,new ReadHandler(channel));
asynchronousServerSocketChannel.accept(null,new AcceptHandler(asynchronousServerSocketChannel));
}
@Override
public void failed(Throwable exc, Object attachment) {
exc.printStackTrace();
}
}
public class ReadHandler implements CompletionHandler<Integer,ByteBuffer> {
private AsynchronousSocketChannel channel;
public ReadHandler(AsynchronousSocketChannel channel) {
this.channel = channel;
}
@Override
public void completed(Integer result, ByteBuffer buffer) {
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
String msg = new String(bytes);
System.out.println("读取到数据:"+msg);
buffer.clear();
channel.read(buffer,buffer,new ReadHandler(channel));
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
exc.printStackTrace();
}
}
BIO/NIO/AIO比较
1.BIO
- jdk1.4出来之前,我们建立网络连接采用的BIO模型;
- 流程
- 先在服务端启动一个ServerSocket,然后再客户端启动Socket来对服务端进行通信;
- 服务端调用accept方法等待接收客户端的连接请求,若接收到一个连接请求,就可以建立通信套接字在这个通信套接字上进行读写操作,此时不能再接收其它客户端连接请求,直到当前连接诶的客户端操作执行完成;
- BIO若要同时处理多个客户端请求,就必须使用多线程;
2.AIO
- 与NIO不同,进行读写操作时,只需直接调用API的read或write()方法,这两个方法异步;
- 对于读操作:当有流读取时,操作系统将可读的流传入read方法的缓冲区,并通知应用程序;
- 对于写操作:操作系统将write方法传递的流写入完毕,操作系统主动通知应用程序;
- 即read/write方法都是异步的,完成后悔主动调用回调函数。
3.AIO、NIO、AIO的适用场景
- BIO适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
- NIO适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
- AIO使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。另外,I/O属于底层操作,需要操作系统支持,并发也需要操作系统的支持,所以性能方面不同操作系统差异会比较明显。
六、TCP协议
1、TCP协议
- TCP:传输控制协议:
- 面向连接的传输层的协议,位于IP层之上应用层之下的中间层;
- 采用字节流传输数据;
2、TCP协议特点
- 面向连接:通信之前建立连接,通信结束断开连接;
- 每一条TCP连接只能是点对点的(一对一);
- 提供了可靠的交付服务,通过TCP连接传输的数据,无差错,不丢失,不重复;
- 提供双全工通信;
- 面向字节流;
- TCP首部20字节;
3、TCP编程
- 服务端
ServerSocket ss =null;
try {
ss = new ServerSocket(6666);
Socket socket = ss.accept();
System.out.println("有新客户端的连接");
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
while (true) {
String msg = in.readLine();
System.out.println("客户端接收到的数据:"+msg);
if ("exit".equals(msg)) break;
}
in.close();
socket.close();
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
- 客户端
Socket socket = new Socket();
socket.connect(new InetSocketAddress("127.0.0.1",6666));
OutputStream os = socket.getOutputStream();
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
while (true) {
System.out.print("请输入内容:");
String msg = in.readLine();
if (msg == null || "".equals(msg.trim())) continue;
os.write((msg+"\n").getBytes());
os.flush();
if ("exit".equals(msg)) break;
}
os.close();
in.close();
socket.close();
4、TCP报文格式
- 源端口:16位
- 端口号是表示特定主机的唯一进程,是发送主机上占用的端口;
- IP地址是在传输层标识网络中不同主机,端口则标识主机上的唯一应用,socket套接字就是IP地址加端口号共同组成;
- 目的端口:16位
- 接收主机上应用占用的端口;
- 端口的表示的个数2^16;
- 序号:32位
- 报文段中第一个数据字节序号;
- TCP用序号对每个字节进行计数,用来保证到达数据顺序的编号;
- 确认号:32位
- 是下一个期望接收的TCP的分段号,相当于是对对方所发送的并且已经被本方所正确接收的分段的确认,井道ACK标志=1时有效,确认号表示期望下一个字节的序号;
- 数据偏移:4位
- 以32位(4个字节)字长为单位,在不存在可选字段时报头长度是20字节,可选字段的长度可变的,以4字节为一个字长,2^4=15即最大是60个字节;
- 保留位:6位
- 6位,必须为0;
- 标志位:6位
- 6个标志位,每个标志位值是0或1两种情况,1表示该标志位有效,依次为:URG、ACK、PSH、RST、SYN、FIN;
- URG:1表示TCP包的紧急指针域有效,用来保证TCP连接不被中断,并督促上层应用赶快处理这些数据;
- ACK:1示当前包是确认包,确认号有效,一般发送方发送数据给接收方,接收方要告诉发送方已接收到数据,此时接收方就会给定一个确认,即将ACK置为1,且填充确认号表明下一个要接受的数据序号;
- PSH:接收方应尽快将这个报文交给应用层,即Push,push操作就是在数据包到达接收端以后,立即创送给应用层,而不是在缓冲区排队;
- RST:连接复位,复位因主机奔溃或者其他原因而出现了连接出错,也可以用拒绝非法的分段或者拒绝连接请求;
- SYN:是一个同步的序号,通常和ACK合用来建立连接,也就是常说的三次握手;
- FIN:在连接结束后需要断开连接,这个字段表示发送方已经到达数据末尾,及即双方数据传输完成,将标志位置为1,连接将被断开,将开始四次挥手断开连接过程;
- 窗口:16位
- TCO的流量控制由连接的每一端通过声明窗口的大小来提供,窗口的大小为字节数,起始与确认序号字段确认的值,这个值是接收端期望接收的字节,是一个16个bit字段,窗口的大小最大65565字节;
- 校验和:16位
- 用于对分段的首部和数据进行校验,正常情况下一般为0,用于传输层差错校验;
- 紧急指针:16位
- 当标志位URG置为1时有效,紧急指针是一个正的偏移量,和序号字段中的值相加表示紧急指针的最后一个字节的序号,TCP的紧急方式是发送端向另一端发送紧急数据的一种方式;
5、TCP发送数据
- 消息是由发送方产生;
- 发送方会首先将数据放入到发送缓冲区,然后发送的时候会从缓冲区中取,接着消息从发送方的用户空间传入内核空间借助于网络传输介质完成传输,消息在发送到接收方的内核空间,接收方如果想要读取是从内容空间读取到用户空间,接收方会将接收到的数据放入接收缓冲区,让后应用程序使用的时候到缓冲区去取;
- 因为TCP本身传输的数据包大小就有限制,所以应用发出的消息包过大,TCP会把应用消息包拆分为多个TCP数据包发送出去。Negal算法的优化,当应用发送数据包太小,TCP为了减少网络请求次数的开销,它会等待多个消息包一起,打成一个TCP数据包一次发送出去。
6、TCP接收数据
- 因为TCP缓冲区里的数据都是字符流的形式,没有明确的边界,因为数据没边界,所以应用从TCP缓冲区中读取数据时就没办法指定一个或几个消息一起读,而只能选择一次读取多大的数据流,而这个数据流中就可能包含着某个消息包的一部分数据。
- TCP是面向连接的传输协议,TCP传输的数据是以流形式传输,流数据是没有明确的开始结尾边界,所以TCP也没办法判断那一段流属于哪一个消息,所以TCP的粘包、半包都是在应用层解决;
7、滑动窗口协议
- 属于TCP协议的一种应用,用于网络数据传输时流量控制以避免拥塞发生;
- 协议允许发送方在停止并等待确认前发送的多个数据分组,由于发送方不必每发一个分组就停下来等待确认,因此该协议可以加速数据传输,提高网络吞吐量。
- 本质是描述接收方的TCP数据包缓冲区大小的数据;
- 发送方根据这个数据来计算自己最多能发送多长数据,如果发送方收到的接收方的窗口大小为0的TCP报文段,那么发送方将停止发送数据,等到接收方发送窗口大小不为0的数据包的到来;
- TCP滑动窗口技术通过动态改变窗口大小来调节两台主机间数据传输。
- 每个TCP/IP主机支持全双工数据传输,因此TCP有两个滑动窗口:一个用于接收数据,另一个用于发送数据。
TCP使用ACK确认技术,其确认号指的是下一个所期待的字节。 - 假定发送方设备以每一次三个数据包的方式发送数据,也就是说,窗口大小为3。发送方发送序列号为1、2、3的三个数据包,接收方设备成功接收数据包,用序列号4确认。发送方设备收到确认,继续以窗口大小3发送数据。当接收方设备要求降低或者增大网络流量时,可以对窗口大小进行减小或者增加,本例降低窗口大小为2,每一次发送两个数据包。当接收方设备要求窗口大小为0,表明接收方已经接收了全部数据,或者接收方应用程序没有时间读取数据,要求暂停发送。发送方接收到携带窗口号为0的确认,停止这一方向的数据传输。
8、TCP协议保证数据的可靠性
1.校验和
- TCP检验和的计算与UDP一样,在计算时要加上12byte的伪首部,检验范围包括TCP首部及数据部分,但是UDP的检验和字段为可选的,而TCP中是必须有的;
- 计算方法为:在发送方将整个报文段分为多个16位的段,然后将所有段进行反码相加,将结果存放在检验和字段中,接收方用相同的方法进行计算,如最终结果为检验字段所有位是全1则正确(UDP中为0是正确),否则存在错误。
2.序列号
- TCP将每个字节的数据都进行了编号,这就是序列号。 序列号的作用:
a) 保证可靠性(当接收到的数据总少了某个序号的数据时,能马上知道) b) 保证数据的按序到达 c) 提高效率,可实现多次发送,一次确认 d) 去除重复数据 数据传输过程中的确认应答处理、重发控制以及重复控制等功能都可以通过序列号来实现
3.确认应答机制(ack)
- TCP通过确认应答机制实现可靠的数据传输。在TCP的首部中有一个标志位——ACK,此标志位表示确认号是否有效;
- 接收方对于按序到达的数据会进行确认,当标志位ACK=1时确认首部的确认字段有效。
- 进行确认时,确认字段值表示这个值之前的数据都已经按序到达了;
- 而发送方如果收到了已发送的数据的确认报文,则继续传输下一部分数据;而如果等待了一定时间还没有收到确认报文就会启动重传机制。
4.超时重传机制/快重传
- 当报文发出后在一定的时间内未收到接收方的确认,发送方就会进行重传(通常是在发出报文段后设定一个闹钟,到点了还没有收到应答则进行重传);
- 未收到确认不一定就是发送的数据包丢了,还可能是确认的ACK丢了:
- 当主机B返回应答,因为网络拥堵等原因在传送途中丢失,没有到达主机A。主机A会等待一段时间,若在等待的时间间隔内始终未能收到这个确认应答,主机B将第二次发送已接收数据的确认应答,由于主机B其实已经收到1~100的数据,当在有相同的数据到达时它会放弃。
5.连接管理机制连接可靠性
- 连接管理机制即TCP建立连接时的三次握手和断开连接时的四次挥手。
a) 拥塞控制 拥塞避免、慢开始等算法实现,上文以对拥塞控制这部分做出详细讲解,此处不赘述。 b) 流量控制 通过滑动窗口进行,上文以对滑动窗口做出详细讲解
6.UDP协议
- 用户数据报服务,是属于传输层的无连接的协议;
- 特点
- 无连接
- 尽最大努力交付
- 面向报文
- 无拥塞控制方案
- 支持一对一,一对多,多对多的交互通信
- 首部开销小,只有四个字段占8个字节
- UDP数据报有两个字段,数据字段和首部字段,首部是8个字节,由四个字段组成,每个字段都是两个字节:
源端口(16位):在需要对方回信时可选用,不需要时可全为0; - 目的端口(16位):在终点交付报文时必须使用到
长度(16位):UDP的数据报长度(单位:字节),其最小值为8,仅包含首部; - 校验和:检验UDP用户数据报在传输中是否有错,有错则丢弃;
7.UDP编程
- 服务端
import java.io.IOException;
import java.net.*;
public class UDPServer2022 {
public static void main(String[] args) throws IOException {
DatagramSocket datagramSocket = new DatagramSocket();
String info = "hello Java";
DatagramPacket datagramPacket = new DatagramPacket(info.getBytes(), info.length(), InetAddress.getByName("localhost"), 6666);
datagramSocket.send(datagramPacket);
System.out.println("已发送数据");
}
}
- 客户端
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UDPClient2022 {
public static void main(String[] args) throws IOException {
DatagramSocket datagramSocket = new DatagramSocket(6666);
byte[] bytes = new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(bytes, 1024);
System.out.println("等待接收数据");
datagramSocket.receive(datagramPacket);
String info = new String(datagramPacket.getData(), 0, datagramPacket.getLength());
System.out.println(info);
}
}
8.TCP&UDP区别
- TCP面向连接,可靠的流式服务;
- UDP无连接不可靠的数据报服务;
七、IP协议
- 作用:主要是实现终端结点之间的通信,也叫作点对点的通信;
- 分为3部分:IP寻址,路由,IP分包和组包;
- IP地址用于连接诶网络中的所有的主机能识别出进行通信的目标地址;
- 在TCP/IP通信中所有的主机或者路由器必须设定自己的IP地址,不论一台主机与哪种数据链路连接,它的IP地址形式是保持不变的;
1.IP报头格式
1.版本号(4位)
- IP的协议版本,有IP4和IP6,,对于IP4协议来说该字段就是4;
2.首部长度(4位)
3.区分服务(8位)
4.总长度(16位)
- IP协议的总长度,包括报头长度和数据长度(根据报头长度,数据长度最大可到65535个字节,实际上传送的数据达不到这么大),若是IP报文的长度超过1500字节,IP报文就会被分片
5.标识(16位)
- 由于IP报文的长度限制,超过MTU会被分片,而在交付给上层时需要将IP报文组装,字段用于表示唯一的IP报文;
6.标志(3位)
- 第一位为保留位,第二位表示该IP报文是否分片(1:表示禁止分表 0表示分片),第三位表示该报文是否为最后一个;
7. 片偏移(13位)
- 在报文分片时有效,表示该片报文应位于整个IP报文的那个位置;
8. 生存时间(8位)
- 在路由时通过一跳或者是多跳的方式来查找路径,为防止某个报文在网络中一直处于游离状态,无限循环,所以在报文中规定了报文在网络中的最多路由过路由器的数量,也就是该报文的最大跳数
协议类型(8位),表示该IP报文协议要交给上层那个协议(UDP或TCP);
2、IP地址表示形式
- IP地址(IPv4)由32位正整数表示,IP地址在计算机中以二进制方式处理;
- 将32位的IP地址以每8位为一组,分成4组,每组以"."分割,再将每组的转换成十进制如图所示:
- 点分十进制:数据分4组,每一组范围是[9~255]:如:255.255.255.255
二进制:每组8位 如: 11111111 11111111 11111111 11111111
3、IP地址划分
- IP地址划分为网络和主机两部分标识组成;
- 网络标识在数据链路层的每个段是配置的不同的值,网络标识必须宝成相互连接的每个端的地址不重复。
- 相同端连接的主机必须有相同的网络地址。
- IP地址的主机标识则不允许在同一个网段内重复出现。
- 通过网络标识和主机标识的配置在相互连接的整个网络中保证每台主机的IP地址都不会重复,即IP地址具有唯一性;
- IP包被转发到网络中的某个路由器,利用目标IP地址的网络标识进行路由。只需要关注网络标识就能判断出是否为该网段内的主机;
4、IP地址分级
- IP地址分成四级,分别为A类,B类,C类,D类,根据IP地址中第一位到第四位的比特列对网络标识和主机标识进行区分;
- A 类 IP 地址是首位以 “0” 开头的地址;
- 从第 1 位到第 8 位是它的网络标识。用十进制表示的话,0.0.0.0~127.0.0.0 是 A 类的网络地址。A 类地址的后 24 位相当于主机标识。因此,一个网段内可容纳的主机地址上限为16,777,214个;
- B 类 IP 地址是前两位 “10” 的地址
- 从第 1 位到第 16 位是它的网络标识。用十进制表示的话,
10000000 00000000~10111111 11111111 128.0.0.0~191.255.0.0 是 B 类的网络地址。B 类地址的后 16 位相当于主机标识。因此一个网段内可容纳的主机地址上限为65,534个; - C 类 IP 地址是前三位为 “110” 的地址;
- 从第 1 位到第 24 位是它的网络标识。用十进制表示的话,192.0.0.0~223.255.255.0 是 C 类的网络地址。C 类地址的后 8 位相当于主机标识。因此,一个网段内可容纳的主机地址上限为254个;
- D 类 IP 地址是前四位为 “1110” 的地址;
- 从第 1 位到第 32 位是它的网络标识。用十进制表示的话,224.0.0.0~239.255.255.255 是 D 类的网络地址。D 类地址没有主机标识,常用于多播;
- 注意点
- 分配IP地址:IP表示主机标识时,不可以全部为0或全部为1,因此全部为0只有在表示对应的网络地址或IP地址不可以获知的情况下才使用,而全部为1的主机通常作为广播地址,在分配过程中,应该去掉这两种情况
特殊地址: 0.0.0.0 不是真正意义IP地址,他是一类问题的集合:不清楚的主机和目的网络 255.255.255.255:广播地址 对于同一网段内的所有主机。这个地址不能被路由器转发 - 127.0.0.1:环回地址 本机地址,别名:“localhost”,IP进行寻址时,是换回地址不会发送到网络接口
240.0.0.0~247.255.255.255:组播地址多用于特定的成语,保留
5、子网掩码
- IP地址网络标识和主机标识受限于地址的类别,而采用“子网掩码”的识别码通过子网网络细分出比A\B\C\C更小粒度的网络。
- 子网掩码用二进制方式,他对应IP地址的网络标识部分的为全部为1,对应主机标识的部分则全为0,因此,一个IP地址可以不再受限于它的类别,而是可以通过子网掩码自由定位自己的网络标识长度,子网掩码必须是IP地址的首位开始连续的1;
- 子网掩码的两种表示形式:
- 将IP地址和子网掩码的地址分开两行表示
- 以127.20.100.52的前26位是网络地址的情况为例,如下:
IP地址:127.20.100.52 子网掩码:255.255.255.192 网络地址:127.20.100.0 - 第二种:在每个IP地址后面追加网络地址的位数用‘/’隔开,如下:
IP地址:127.20.100.52/26 网络地址:127.20.100.0 在第二种情况下网络地址可以省略后面的‘0’ 例如:127.20.0.0/26 跟127.20/26 是表示一个意思
6、路由控制
- 在数据包发送过程中,需要类似于“指明路由器或主机”的信息,以便于真正发往目标地址,保存这种信息的就是路由控制表;
- 静态路由控制:由管理员手动添加;
- 动态路由控制:路由器与其他路由器相互交换信息时自动刷新;
- 在发送 IP 包时,首先要确定 IP 包首部中的目标地址,再从路由控制表中找到与该地址具有相同网络地址的记录,根据该记录将 IP 包转发给相应的下一个路由器。如果路由控制表中存在多条相同网络地址的记录,就选择一个最为吻合的网络地址。
7、IP分包和组包
- 每种数据链路层的最大传输单元(MTU)都不尽相同;
- 每个不同类型的数据链路层的使用目的不同,承载的MTU也不相同;
- 对于主机在必要的情况下对IP分片进行相应处理,分片主要是在网络上遇到比较大的报文无法一下子发送出去才会处理,经过分片的IP数据在被重组的时候,只能由目标主机进行;
- 路径MTU发现:指从发送端主机到接收端主机之间不需要分片的最大MTU的大小,即路径中存在的所有数据链路中最小的MTU;
- 进行路径MTU发现,就可以避免在中途的路由器进行分片处理,也可以在TCP中发送最大包;
8、IPv6
- IPv6主要是为了解决IPv4地址耗尽的问题而被标准话的网络协议,IPV4地址长度是4个8位字节,即32bit
而IPv6的地址长度是IPv4的4倍,即128bit为,一般携程8个16位字节
1.特点
- IP 得知的扩大与路由控制表的聚合;
- 性能提升:包首部长度采用固定的值(40字节),不再采用首部检验码。简化首部结构,减轻路由器负担。路由器不再做分片处理。
- 支持即插即用功能。即使没有DHCP服务器也可以实现自动分配 IP 地址;
- 采用认证与加密功能。应对伪造 IP 地址的网络安全功能以及防止线路窃听的功能;
- 多播、Mobile IP 成为扩展功能
2.DNS:域名系统
- DNS:将域名解析为IP地址;
- 一个域名有多个层次组成,从上层到下层分为顶级域名。二级域名,三级域名以及四季域名,所有域名可以化成一颗域名树;
3.域名服务器分类
- 根域名服务器:解析顶级域名;
- 顶级域名服务器:解析二级域名;
- 权限域名服务器:解析区内的域名;
- 本地域名服务器:也称为默认域名服务器,可以在其中配置告诉缓冲;
- 注意:区和域概念不同,可以在一个域中划分多个区,图b在域abc.com中划分了两个区abc.com和y.abc.com;
- 因此需要两个权限域名服务器
4.域名解析过程
- 主机箱本地域名服务器解析的过程采用递归,而本地域名服务器向其他域名服务器解析可以使用递归和迭代两种方式;
- 迭代方式:本地域名服务器向一个域名服务器解析请求之后,结果返回到本地域名服务器,然后本地域名服务器继续向其他域名服务器请求解析;
- 递归方式:请求的结果不是直接返回,而是继续向前请求解析,最后的结果才会返回;
|