【JavaSE-网络部分03】网络原理-泛泛介绍各个层次

之前,我们在网络中给大家谈到过一个概念:叫做协议分层。

  • 应用层是和我们应用程序直接相关的。
  • 传输层是端对端的一个传输,不关心中间过程,只关心起点和终点。
  • 网络层是点到点传输规划传输中的中间路径。
  • 数据链路层是负责两个相邻点之间的传输。
  • 物理层是负责底层基础设施,硬件设备的。

我们的应用层传输层与我们编程的关系是最大的,应用层直接和我们的程序相关,传输层我们得去调用它里面的socket;而网络层中的内容都是在操作系统内核中调用和实现的,我们的数据链路层和物理层呢是由硬件和驱动程序实现的,他和我们的应用程序的开发没啥关系。

因此站在我们JAVA学习的角度,我们重点介绍的是应用层和传输层。那么下边的网络层,数据链路层我们只做简单介绍,而物理层我们就不介绍了。


应用层

我们先对应用层做一个泛泛的介绍,那么这里边有一个核心的协议就是HTTP协议,他是我们应用层最重要的协议,这个HTTP协议,我们放到后边去慢慢介绍。虽然在应用层中有很多大佬已经研究出来很多协议,但是呢在实际开发过程中呢,我们应用层还是需要我们自定义的,即需要自定义协议,那么不要看到协议,大家就被唬住了,其实协议呢?它就是通信双方的一个约定。

我们举一个这样的例子,假设呢我们开发了一个外卖软件,有一个功能:实现获取商家列表的功能,那么针对这样的功能,我们约定一下应用层协议。如何来约定呢?所谓的自定义协议,其实呢就是要我们做以下两件事,这两件事呢也是我们在写代码之前提前定义好的。

1.先根据我们的需求明确我们客户端和服务器之间需要传输哪些数据。

我们确定功能中客户端和服务端需要交互哪些的数据?那么这些需要交互的数据是由我们的功能需求是直接相关的。

比如呢我们有这样的一个需求:就是根据用户当前所处的位置获取方圆3km范围内的所有商家,并且呢按照距离升序排序,每一个商家的信息包括名称,图片,评分,距离等等,这样的话,我们就构成一个简化版本的需求。

请求里面的数据要求:

  • 用户所在的位置(经纬度)
  • 用户ID

响应里面的数据要求:

  • 商家列表【每个商家的名称,图片,评分,距离】

2.确定数据组织的格式。

因为我们网络通信本质上是字节流或者是字符串。

那么我们得把上述的结构化的数据能够组织成字符串,同时还能把字符串解析为结构化的数据。

  • 把结构化的数据能够组织成字符串,称之为序列化。
  • 把字符串解析为结构化数据,称之为反序列化。

那么如何来组织数据格式往往是我们程序员重点考虑的

方案一) 直接使用纯文本的格式:基于简单的分隔符的定义方式。

请求:

  • 用户ID,精度,纬度\n

响应:

  • 商家商家名称,商家图片,商家评分,商家距离;商家商家名称,商家图片,商家评分,商家距离;商家商家名称,商家图片,商家评分,商家距离\n

缺点

  • 不能直观的看到每个属性是什么意思
  • 未来如果要扩展或者说修改某一个属性都会很麻烦

方案二)基于xml格式来约定,这个是比较经典的格式,我们通过成对的标签描述某个值的含义。他有一个很好的优点呢,就是可读性比较好。

请求:

xml 复制代码
<request>
	<userid>1001</userid>
	<jingdu>45</jingdu>
	<weidu>67</weidu>
</request>

那么这样的格式,其实我们现在也很少使用,因为他也有缺点。
缺点:

  • 非常消耗资源,比如网络带宽,也就是说你本来就传输几个内容,但是你引入一堆的标签很占资源啊。
  • 格式上也不是很优雅

方案三)JSON,当前非常流行的数据组织格式。他不仅可读性好,而且消耗的带宽呢也比刚才谈到的xml更节省。

例如:

请求:

json 复制代码
{
	"userId":1001,
	"jingdu":45,
	"weidu":67
}

响应

json 复制代码
[
	{
		"accountId":2001,
		"name":"xxx",
		"image":"xxxx",
		"distance":"1.2km",
		"rank":5
	},
	{
		"accountId":2001,
		"name":"xxx",
		"image":"xxxx",
		"distance":"1.2km",
		"rank":5
	}
]

缺点

  • 还是存在冗余的信息,为了解决冗余信息,我们引入了方案四。

方案4)protobuf 这是谷歌提出的数据组织格式,它是纯二进制的数据格式,它不可读,我们无法使用肉眼解析。

它的可读性很差,但是呢传输效率是很高的。


  • 如果我们对效率要求较高的场景,我们就用protobuf。
  • 那如果对效率要求没那么高的话,我们更多考虑的是json

演示上述自定义协议的程序代码

约定好协议

1.我们先明确传递哪些数据?

请求:

  • 运算符加减乘除
  • 操作数只有两个:一个是操作数1(num1),另一个操作数2(num2)。
    响应
  • 运算结果。
    2.组织数据的传输格式【我们使用的是自定义文本格式
    比如:
    请求:
  • 运算符,操作数1,操作数2,\n

响应:

  • 结果\n

举个例子

请求:

  • +,12,10\n

响应:

  • 30\n

请求【结构化数据】

java 复制代码
package application;

/**
 * @ClassName Request
 * @Description TODO 表示一个请求【结构化数据】
 * @Author zhongge
 * @Date 2026-02-02 16:20
 * @Version 1.0
 */
public class Request {
    //运算符 支持+-*/
    private String operator;
    //操作数
    private double num1;
    private double num2;

    public Request(String operator, double num1, double num2) {
        this.operator = operator;
        this.num1 = num1;
        this.num2 = num2;

    }

    //能够把这个对象转成字符串==》序列化
    public String convertToString(){
        //String.format类似于printf只是String.format把结果转到string 中了
        return String.format("%s,%f,%f\n",operator,num1,num2);
    }

    //把字符串装换为对象==》方序列化
    public static Request convertFromString(String request){
        String[] split = request.split(",");
        String operator = split[0];
        double num1 = Double.parseDouble(split[1]);
        double num2 = Double.parseDouble(split[2]);

        return new Request(operator, num1, num2);
    }

    public String getOperator() {
        return operator;
    }

    public void setOperator(String operator) {
        this.operator = operator;
    }

    public double getNum1() {
        return num1;
    }

    public void setNum1(double num1) {
        this.num1 = num1;
    }

    public double getNum2() {
        return num2;
    }

    public void setNum2(double num2) {
        this.num2 = num2;
    }
}

响应【结构化数据】

java 复制代码
package application;

/**
 * @ClassName Response
 * @Description TODO 响应结构化数据
 * @Author zhongge
 * @Date 2026-02-02 16:31
 * @Version 1.0
 */
public class Response {
    private double result;

    public Response(double result) {
        this.result = result;
    }

    //能够把这个对象转成字符串
    public String convertToString(){
        //String.format类似于printf只是String.format把结果转到string 中了
        return String.format("%f\n",result);
    }

    //把字符串装换为对象
    public static Response convertFromString(String response){
        double result = Double.parseDouble(response);
        return new Response(result);
    }

    public double getResult() {
        return result;
    }

    public void setResult(double result) {
        this.result = result;
    }
}

服务器代码

java 复制代码
package application;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @ClassName TcpCalcServer
 * @Description TODO 进行算术运算的服务器
 * @Author zhongge
 * @Date 2026-02-02 16:16
 * @Version 1.0
 */
public class TcpCalcServer {
    private ServerSocket serverSocket = null;

    public TcpCalcServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("启动服务器。。。");
        ExecutorService service = Executors.newCachedThreadPool();


        while (true){
            //1.获取请求
            Socket socket = serverSocket.accept();

            service.submit(() -> {
                processConnection(socket);
            });
        }
    }

    private void processConnection(Socket socket) {
        //注意我们一个连接中可能会涉及多组请求和响应的交互。
        // 也就是说你打一次电话,可能说的话是很多很多,做的事情都很多。

        System.out.printf("[%s:%d] 客⼾端上线!\n", socket.getInetAddress().toString(),socket.getPort());

        try(InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream()
        ){

            //读取一个请求方法二使用Scanner
            Scanner scanner = new Scanner(inputStream);

            //一次循环呢,处理一组请求和响应。
            while (true){

                if (!scanner.hasNext()){
                    //判断当前tcp的连接是否断开了,如果断开的话,那么就没有必要再读取了
                    break;
                }
                String requestString =scanner.next();

                //反序列化【字符串变为结构化数据】
                Request request = Request.convertFromString(requestString);


                //2.根据请求来计算响应
                Response response = process(request);

                //3.把响应写回到客户端
                //序列化
                String responseString = response.convertToString();
                //前面序列化String.format("%f\n",result); 已经有 \n 了 所以这里就不需要了
                outputStream.write(responseString.getBytes());//转为字节流写过去

                
                // ⽇志, 打印当前的请求详情.
                System.out.printf("[%s:%d] req: %s, resp: %s\n", socket.getInetAddress().toString(),socket.getPort(),
                        requestString, responseString);

            }
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            try {
                System.out.printf("[%s:%d] 客⼾端下线!\n", socket.getInetAddress().toString(),socket.getPort());
                socket.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

    }

    private Response process(Request request) {
        if (request.getOperator().equals("+")) {
            return new Response(request.getNum1() + request.getNum2());
        } else if (request.getOperator().equals("-")) {
            return new Response(request.getNum1() - request.getNum2());
        }else if (request.getOperator().equals("*")) {
            return new Response(request.getNum1() * request.getNum2());
        }else if (request.getOperator().equals("/")) {
            return new Response(request.getNum1() / request.getNum2());
        }else {
            throw new RuntimeException("不支持的运算符:" + request.getOperator());
        }
    }

    public static void main(String[] args) throws IOException {
        TcpCalcServer tcpCalcServer = new TcpCalcServer(9090);
        tcpCalcServer.start();
    }
}

客户端代码

java 复制代码
package application;

import net.tcp.TcpEchoClient;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

/**
 * @ClassName TcpCalcClient
 * @Description TODO 进行算数运算的客户端
 * @Author zhongge
 * @Date 2026-02-02 16:16
 * @Version 1.0
 */
public class TcpCalcClient {
    private Socket socket = null;

    public TcpCalcClient(String serverIp,int ServerPort) throws IOException {
        socket = new Socket(serverIp, ServerPort);
    }

    public void start() {
        System.out.println("客户端启动");


        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()
        ) {
            Scanner scanner = new Scanner(System.in);

            Scanner scannerNetWork = new Scanner(inputStream);
            while (true) {
                //1.从控制台读取用户输入
                System.out.print("请输入你要进行的运算(+ - * /):-> ");
                String operator = scanner.next();
                System.out.print("请输入操作数1:-> ");
                double num1 = scanner.nextDouble();
                System.out.print("请输入操作数2:-> ");
                double num2 = scanner.nextDouble();

                //2.构造请求发送给服务器

                Request request = new Request(operator, num1, num2);
                //序列化
                String requestString = request.convertToString();
//                request += "\n"; //由于加了\n了所以就不用再+\n了
                outputStream.write(requestString.getBytes());

                //3.从服务器读取响应
                if (!scannerNetWork.hasNext()) {
                    break;
                }
                String responseString = scannerNetWork.next();

                Response response = Response.convertFromString(responseString);

                //4.把响应显示到控制台
                System.out.println(response.convertToString());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public static void main(String[] args) throws IOException {
        TcpCalcClient tcpCalcClient = new TcpCalcClient("127.0.0.1",9090);
        tcpCalcClient.start();
    }
}

服务端结果

客户端结果

传输层

传输层我们说过最核心的协议是TCP和UDP,也是和Java网络编程关联最紧密的一层------我们写代码时调用的Socket,本质就是对传输层协议的封装,不用自己关心底层传输细节,只需要调用API就能实现端到端的通信,这一层只关心数据从哪个应用程序发出去,最终到哪个应用程序收,中间经过多少路由器、交换机都不管。

再谈端口号

我们说端口号是用整数表示,用来区分同一台主机上不同的应用程序。

我们前面在网络编程中每个程序的Socket创建的时候都需要关联端口号,那么对于服务器来说,端口号是程序员手动指定的;而对于我们的客户端来说,端口号是系统自动分配的。

端口号是由两个字节表示的无符号整数

  • 范围:0~65535。

虽然它的范围比较多,但是并不是所有的数都可以使用的。

  • 0~1023 这样的范围通常我们是不使用的,他们叫做知名端口号,是给一些知名的服务器预留的。

虽然现在知名的服务器协议对应的端口没有太多,但有两个知名的端口,一定要重点认识。

  • 80 ==> 这个是给HTTP服务器留的端口号。
  • 443 ==》 这个是给HTTPS服务器留的端口。

问题1一个进程是否可以绑定多个端口号?

答:这个是完全可以的,但是注意其实不是进程绑定端口号,而是我们的Socket绑定端口,我们一个进程中完全可以创建多个Socket,所以可以同时关联到多个端口号,这个是一个非常常见的情况。

举个例子:我们在开发服务器程序的时候,一般会提供至少两个端口:

  1. 业务端口: 供用户的客户端连接使用,处理正常的业务请求;
  2. 调试端口/管理端口:这个是不对外公开的,我们开发和运维人员自己使用,生产环境下服务器程序不能直接用调试器调试,可能会把整个进程阻塞住,导致用户无法访问,就可以通过这个端口做远程管理。

问题2一个端口号是否可以被多个进程绑定?

答:如果是同一种协议则不行,如果是不同协议是可以的

比如UDP的8080端口和TCP的8080端口,能被两个不同的进程分别绑定,系统会通过协议类型区分,不会混淆;但如果是两个进程都想绑定TCP的8080端口,就会报错,端口被占用。


核心协议:TCP 和 UDP

传输层就这两个核心协议,所有基于网络的应用程序,最终都会选择其中一个来传输数据,Java Socket编程也分TCP Socket和UDP Socket,就是对这两个协议的直接使用。

我们用通俗的例子来理解,把数据传输比作寄快递:

UDP 协议:快递里的「平邮普通件」

UDP的全称是用户数据报协议,核心特点就是无连接、不可靠、面向数据报、传输速度快

  • 无连接:寄件前不用和收件人确认"你是否能收",直接把包裹扔到快递站就行,不用建立任何连接;
  • 不可靠:快递站只负责发,不保证包裹一定能到收件人手里,也不保证包裹的顺序,可能丢件、可能少件、可能先发的后到;
  • 面向数据报:包裹有固定的大小限制,一次只能发一个完整的包裹,不能拆分,也不能拼接,发的时候是一个整体,收的时候也必须一次性收一个整体。

UDP协议不会做任何额外的校验、重传操作,所以开销小、速度快,适合对速度要求高,对数据可靠性要求低的场景。
常见使用场景 :直播、短视频、语音通话、网络游戏、广播消息(比如局域网设备发现)。

Java中使用DatagramSocketDatagramPacket就是基于UDP协议开发。

TCP 协议:快递里的「顺丰保价件」

TCP的全称是传输控制协议,核心特点是面向连接、可靠、面向字节流、传输速度稍慢

  • 面向连接:寄件前必须先和收件人通电话,确认"你在家、能收快递",双方建立好连接后,再开始寄件,对应TCP的「三次握手」;寄完件后,还要确认"所有包裹都收到了",再断开连接,对应TCP的「四次挥手」;
  • 可靠:快递站会全程跟踪包裹,丢件了会重新发,包裹顺序乱了会重新整理,保证收件人收到的包裹和寄件人发的完全一致,而且会确认"你已经收到了"(对应TCP的确认应答、重传机制、拥塞控制);
  • 面向字节流:没有包裹大小限制,想发多少发多少,数据会被拆成一个个小数据包传输,到了对方手里再拼接成完整的字节流,就像水管流水一样,连续不断。

TCP协议通过各种机制保证了数据的可靠传输,但是会做很多额外的操作,开销比UDP大,速度稍慢,适合对数据可靠性要求高的场景。
常见使用场景 :网页访问(HTTP/HTTPS)、文件传输、聊天软件、电商支付、数据库连接。

Java中使用ServerSocketSocket就是基于TCP协议开发,也是我们Java网络编程中最常用的方式。

一句话总结

UDP:只管发,不管到,速度快;TCP:先连好,再发货,保送到。

实际开发中,选哪个协议完全看业务需求:比如直播不能卡,偶尔掉几个帧没关系,用UDP;比如转账付款,数据不能丢、不能错,必须用TCP。


网络层

网络层是负责点到点传输、规划路径 的一层,简单说就是决定数据从A主机到B主机,要走哪条路,经过哪些路由器,这一层只关心「主机与主机之间」的通信,不关心主机上的哪个应用程序,识别主机的唯一标识就是IP地址,这也是网络层的核心标识。

站在Java开发的角度,我们只需要知道IP地址的作用,不用关心网络层的具体实现------因为这一层的所有操作,都是在操作系统内核中完成的,我们写代码时只需要指定目标主机的IP地址,系统会自动完成路径规划、数据转发,不用我们手动干预。


核心概念:IP地址

IP地址是用来在网络中唯一标识一台主机的,就像每个房子都有唯一的门牌号,网络中的每台设备(电脑、手机、路由器)都有唯一的IP地址,网络层通过IP地址,知道数据要从哪个主机发,到哪个主机收。

目前常用的有两个版本的IP地址:IPv4和IPv6:

  • IPv4:32位的二进制数,通常拆成4个8位的十进制数,用点分隔,比如127.0.0.1(本地回环地址)、192.168.1.1(路由器常用地址),范围是0.0.0.0~255.255.255.255,可用的地址数量有限,现在已经不够用了;
  • IPv6:128位的二进制数,通常拆成8个16位的十六进制数,用冒号分隔,比如::1(对应IPv4的127.0.0.1),地址数量极大,能满足未来所有设备的联网需求,是未来的主流。

注意:IP地址标识的是「主机的网络接口」,不是主机本身------一台主机如果连了多个网络(比如电脑连了有线网和无线网),就会有多个IP地址。


核心协议:IP协议、ICMP协议、ARP协议

网络层的核心协议以IP协议为核心,其他协议都是为IP协议服务的,解决IP协议的各种问题,我们还是用通俗易懂的方式介绍,不用深究细节:

1. IP协议:网络层的「核心总指挥」

IP协议的全称是网际协议,是网络层最核心的协议,作用就是定义IP地址的格式、规定数据如何在主机之间转发、规划传输路径

简单说,IP协议就是给数据打上「寄件人IP(源IP)」和「收件人IP(目标IP)」的标签,然后根据网络情况,选择一条合适的路径,把数据从源主机转发到目标主机。

但IP协议和UDP协议类似,是不可靠的,它只负责转发数据,不保证数据一定能到达,也不保证顺序,丢包、乱序的问题,会交给上层的TCP协议去解决(TCP会做重传、排序)。

2. ICMP协议:网络层的「快递物流查询+故障反馈」

ICMP协议的全称是互联网控制报文协议,是IP协议的"辅助小弟",作用就是反馈网络传输中的错误信息、检测网络连通性

我们平时在电脑上用的ping命令,就是基于ICMP协议实现的------ping某个IP地址,就是向目标主机发送ICMP请求包,目标主机收到后返回ICMP响应包,通过这个过程就能知道"目标主机是否可达""网络延迟多少""是否丢包"。

比如你ping 192.168.1.1不通,就说明你的电脑和路由器之间的网络有问题,ICMP协议会返回具体的错误信息,比如"请求超时""目标主机不可达"。

3. ARP协议:网络层的「门牌号转身份证号」

ARP协议的全称是地址解析协议,作用是将IP地址转换为MAC地址(MAC地址是数据链路层的标识,是设备网卡的唯一物理地址,烧录在网卡上,全球唯一)。

为什么需要这个转换?因为网络层用IP地址找主机,但实际在局域网内传输数据时,底层的硬件设备(交换机)不认识IP地址,只认识MAC地址,就像你知道朋友的门牌号(IP),但快递员需要知道朋友的身份证号(MAC)才能准确送件。

ARP协议会在局域网内广播查询"哪个设备的IP是XXX,你的MAC地址是多少?",对应IP的设备会回复自己的MAC地址,这样就完成了IP到MAC的转换,后续数据就能在局域网内准确传输了。

网络层的核心工作

简单总结网络层的工作:给传输层传过来的数据打上源IP、目标IP标签,通过ARP协议把IP转成MAC地址,然后通过ICMP协议检测网络,规划出一条从源主机到目标主机的最优路径,把数据转发给下一个路由器,直到数据到达目标主机所在的局域网。


数据链路层

数据链路层是负责两个相邻设备之间的传输,简单说就是「点对点的直接通信」,比如你的电脑和路由器之间、路由器和路由器之间,都是相邻的设备,数据链路层就负责把网络层传过来的数据,在这些相邻设备之间可靠地传输,这一层不关心整体的路径,只关心"我身边的这个邻居,能不能收到我发的数据"。

这一层由硬件(网卡、交换机)和驱动程序实现,和Java应用开发完全无关,我们只做泛泛介绍,知道核心作用和核心协议就行。


核心标识:MAC地址

MAC地址是数据链路层的唯一标识,全称是媒体访问控制地址,也叫物理地址,是烧录在网卡上的一串唯一的二进制数,全球唯一,不会重复,长度是48位,通常用12位的十六进制数表示,中间用冒号或横线分隔,比如00:11:22:33:44:55

MAC地址的特点是面向硬件,不管设备连到哪个网络,MAC地址都不会变,就像人的身份证号,终身不变;而IP地址是面向网络的,设备连到不同的网络,IP地址会变,就像人的门牌号,换房子就会变。

数据链路层就是通过MAC地址,识别相邻的设备,保证数据在两个相邻设备之间准确传输。


核心协议:以太网协议、CSMA/CD协议、PPP协议

数据链路层的协议和底层硬件关联紧密,核心是以太网协议,因为现在我们的有线网络、局域网,基本都是基于以太网技术的,其他协议都是为了解决以太网的使用问题,或者适配其他网络场景。

1. 以太网协议:局域网数据传输的「通用规则」

以太网协议是目前最主流的数据链路层协议,定义了数据链路层数据的传输格式、MAC地址的使用规则、数据在局域网内的传输方式

简单说,以太网协议就是给网络层传过来的数据,再打上源MAC地址、目标MAC地址的标签,然后封装成「以太网帧」(数据链路层的传输单位),在局域网内传输,相邻设备通过MAC地址识别,只接收目标MAC是自己的帧,其他帧直接丢弃。

以太网协议还规定了数据的传输介质,比如双绞线、光纤,还有传输的速率,比如百兆以太网、千兆以太网、万兆以太网。

2. CSMA/CD协议:以太网的「先听后说,冲突避让」规则

CSMA/CD协议的全称是载波监听多路访问/冲突检测,是以太网协议的「访问控制规则」,解决的是局域网内多个设备同时发数据,导致的信号冲突问题

用通俗的话讲,就是局域网内的所有设备,共用一条传输线路,就像多人用同一个话筒说话,要是同时说,就会听不清,CSMA/CD协议就是定了一个规矩:

  1. 先听后说:设备要发数据前,先监听线路上有没有其他设备在发数据,如果有,就等一会儿,不发;
  2. 边说边听:发数据的同时,继续监听线路,检测是否有其他设备也在发数据(冲突);
  3. 冲突避让:如果检测到冲突,立刻停止发数据,发送一个冲突信号,告诉其他设备"这里冲突了",然后随机等一段时间,再重新尝试发数据。

这个规则保证了局域网内的设备,能有序地使用传输线路,避免数据冲突。

3. PPP协议:广域网的「点对点通信规则」

PPP协议的全称是点对点协议,和以太网协议的使用场景不同,以太网协议用于局域网,而PPP协议用于广域网的点对点连接,比如我们早期的宽带拨号、光纤入户的光猫和运营商机房之间的连接,都是用PPP协议。

PPP协议解决的是两个远程设备之间的点对点通信问题,定义了数据的封装格式、身份验证方式(比如宽带的账号密码验证),保证广域网中两个相邻设备之间的数据可靠传输。

数据链路层的核心工作

简单总结数据链路层的工作:接收网络层传过来的数据包,打上源MAC、目标MAC标签,封装成以太网帧,按照CSMA/CD(局域网)或PPP(广域网)的规则,在两个相邻设备之间传输,同时做简单的校验,保证数据在相邻设备之间不丢包、不错包,传输完成后,再把MAC标签去掉,把数据包交给上层的网络层。


物理层

按照我们Java学习的视角,物理层和应用程序开发完全无关,它是整个网络协议栈的最底层,负责底层硬件基础设施的物理传输,简单说就是把数据链路层的二进制数据,转换成电信号、光信号、无线电信号,通过物理介质(双绞线、光纤、无线电波)传输,再把接收到的信号转换回二进制数据,交给数据链路层。

物理层不关心数据的含义,也不关心任何协议,只关心「信号的传输」,比如电压的高低、光的亮灭、无线电波的频率,对应的硬件设备有网线、光纤、网卡、交换机、路由器的物理端口等,我们只需要知道有这一层,是整个网络的物理基础就行,无需深入了解。


总结

整个网络的数据传输,就是从上到下逐层封装(加标签:应用层数据→加端口→加IP→加MAC→转物理信号),到了目标主机后,再从下到上逐层解封装(去标签:物理信号→解MAC→解IP→解端口→应用层数据),最终到达目标应用程序,这就是协议分层的核心意义------每层做好自己的事,互不干扰,简化开发和维护。


那么最后到这儿呢,我们就广泛的介绍了各个层次的一个情况。对于我们JAVA学习者来说呢,我们后续博客我们会详细的介绍应用层的HTTP和HTTPS协议,以及我们传输层的TCP和UDP的协议,那么对于网络层和数据链路层我们只做了解,物理层我们可以直接不管,感谢各位老铁,我们下期见。

相关推荐
瑞雪兆丰年兮2 小时前
[从0开始学Java|第十二天]学生管理系统升级
java·开发语言
执风挽^2 小时前
Python_func_basic
开发语言·python·算法·visual studio code
周杰伦的稻香2 小时前
Hexo搭建教程
java·node.js
倔强的石头1062 小时前
飞算JavaAI如何提升重塑Java开发体验
java·飞算javaai·ai开发工具
努力d小白2 小时前
leetcode438.找到字符串中所有字母异位词
java·javascript·算法
短剑重铸之日2 小时前
《设计模式》第九篇:三大类型之结构型模式
java·后端·设计模式·组合模式·代理模式·结构性模式
tangchao340勤奋的老年?2 小时前
ADS通信 C++ 设置通知方式读取指定变量
开发语言·c++·算法
froginwe112 小时前
SOAP 简介
开发语言
没有bug.的程序员2 小时前
RocketMQ 与 Kafka 深度对垒:分布式消息引擎内核、事务金融级实战与高可用演进指南
java·分布式·kafka·rocketmq·分布式消息·引擎内核·事务金融