• 网络编程原理二


    TCP / IP 协议

    应用层
    传输层
    互联网层
    数据链路层
    物理层

    1

    一、应用层

    应用层是我们网络编程主要针对的地方,也是程序员进行主要交互的一层。我们来看一些应用协议有哪些,以及应用层协议的两种形式。

    1. 常用的现有应用层协议

    DNS(域名系统)
    HTTP(超文本传送协议)
    FTP(文件传输协议)
    Telnet(远程终端协议)

    2. 自定义协议

    开发之前,建立约定。开发过程中就会让客户端和服务器之间严格遵守协议约定好的格式。

    自定义协议大体分成两类:

    (1) 文本格式

    文本格式即把请求响应当成字符串来处理,处理的基本单位是字符。

    常见的文本格式有两类:xml,json
    因为文本格式较为简单,当然自己也可以约定文本格式。

    ① xml

    xml 是一种格式化组织数据的方式,它在 Java 标准库中有其对应的实现,这种格式不仅可以用于自定义协议,也可以用于网络传输。

    格式如下:
    整个 xml 是由 " 标签 " 构成的,此外,标签是成对出现的。

    比方说: <num1> 10 </num1>
    
    开始标签: <num1>	
    结束标签: </num1>
    中间值:   10
    
    • 1
    • 2
    • 3
    • 4
    • 5
    请求:								响应:
    
    <request>							<response>
    	<num1> 10 </num1>					<result> 30 </result>
    	<num2> 20 </num2>				</response>
    	<operator> + </operator>
    </request>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    ② json

    json 是一种键值对的方式,同样地,它在 Java 标准库中有其对应的实现。

    格式如下:

    键和值之间使用 [冒号] 分割,键值对之间使用 [逗号] 分割。

    请求:								响应:
    
    {									{
    	num1: 10							result: 30
    	num2: 20						}
    	operator: "+"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    (2) 二进制格式

    把请求响应当成二进制数据处理,处理的基本单位是字节

    protobuffer,thift…

    3. 理解自定义协议

    我们先来看一下以 【文本 + 分隔符】的方式,来自己设计一个协议。

    UdpCalServer 用来描述服务器
    UdpCalClient  用来描述客户端
    
    • 1
    • 2

    (1) 基于 UDP 的 socket 写一个计算器运算

    ① UdpCalServer

    package demo1;
    
    import java.io.IOException;
    import java.net.DatagramPacket;
    import java.net.DatagramSocket;
    import java.net.SocketException;
    
    public class UdpCalServer {
        private DatagramSocket socket = null;
    
        public UdpCalServer(int port) throws SocketException {
            socket = new DatagramSocket(port);
        }
    
        public void start() throws IOException {
            System.out.println("服务器启动!");
    
            while (true) {
                //2. 读取请求并解析
                DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
                socket.receive(requestPacket);
                String request = new String(requestPacket.getData(), 0, requestPacket.getLength());
    
                //3. 根据请求构造响应
                String response = process(request);
    
                //4. 构造响应并返回
                DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),
                        response.getBytes().length, requestPacket.getSocketAddress());
                socket.send(responsePacket);
    
                //顺便打印日志
                String log = String.format("[%s : %d], req: %s; resp: %s",
                        requestPacket.getAddress().toString(), requestPacket.getPort(),
                        request, response);
                System.out.println(log);
            }
        }
    
        private String process(String request) {
            String[] strings = request.split(";");
            if (strings.length != 3) {
                System.out.println("你所输入的格式不正确!");
                return null;
            }
            int num1 = Integer.parseInt(strings[0]);
            int num2 = Integer.parseInt(strings[1]);
            String key = strings[2];
            int result = 0;
    
            if (key.equals("+")){
                result = num1 + num2;
            } else if (key.equals("-")) {
                result = num1 - num2;
            } else if (key.equals("*")) {
                result = num1 * num2;
            } else {
                result = num1 / num2;
            }
    
            return result + "";
        }
    
        public static void main(String[] args) throws IOException {
            UdpCalServer server = new UdpCalServer(9090);
            server.start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68

    ② UdpCalClient

    package demo1;
    
    import java.io.IOException;
    import java.net.*;
    import java.util.Scanner;
    
    public class UdpCalClient {
        private String serverIp;
        private int serverPort;
        private DatagramSocket socket = null;
    
        public UdpCalClient(String serverIp, int serverPort) throws SocketException {
            this.serverIp = serverIp;
            this.serverPort = serverPort;
            this.socket = new DatagramSocket();
        }
    
        public void start() throws IOException {
            Scanner scanner = new Scanner(System.in);
            System.out.println("输入格式:num1; num2; +-*/ ");
            while (true) {
    
                System.out.println("输入计算的数和符号 ->");
                String request = scanner.nextLine();
                if (request.equals("exit")) {
                    System.out.println("Exit!");
                    return;
                }
                //1. 构造请求并发送
                DatagramPacket requestPacket = new DatagramPacket(
                        request.getBytes(), request.getBytes().length,
                        InetAddress.getByName(serverIp), serverPort );
                socket.send(requestPacket);
    
                //2. 尝试获取响应
                DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);
                socket.receive(responsePacket);
                String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
    
                //3,显示响应结果
                String log = String.format("req: %s  resp: %s", request, response);
                System.out.println(log);
                System.out.println();
            }
        }
    
        public static void main(String[] args) throws IOException {
            UdpCalClient client = new UdpCalClient("127.0.0.1", 9090);
            client.start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    通信结果:

    1

    在上面的例子中,我们可以看到:当计算 15+3 = 18 这个逻辑的时候,我们在服务器与客户端通信之前,就两边约定好了,将操作数放在前两位,操作符放在后一位,它们之间通过 ; 来分割。也就说,格式为:【15;3;+】
    所以说,自定义协议本质上也是一种提前的约定。

    这个和我们开头说的例子是一样的:网友A 和 网友B 见面,他们提前就要告知对方今天穿什么颜色的上衣和裤子,否则就算到达同一地点,也很难找到找到彼此。

    4. DNS

    DNS:Domain Name System ( 域名系统 )

    什么是域名?

    类似于
    www.baidu.com www.sogou.com

    可以看到,域名就是通过一串单词通过点来进行分割的,在我们传统的叫法,域名实际上就是某某网站…

    在传输数据的时候,先根据域名,再转换成对应的 IP地址,最后根据 IP地址进行传输。因为,IP地址是通过点分十进制来表示的,对于日常使用的网络用户来说,相对不好理解,而使用域名,就相当于为 IP地址 套上了一层外套,域名也就更加形象了。

    二、传输层

    传输层只负责端对端的数据传输,即只考虑起点和终点,不考虑中间过程。
    传输层是操作系统内核实现的,因此谈到的传输层协议,一般都是指现成的一些协议,很少会涉及 " 自定制 "。

    TCP / UDP 协议就是传输层中的主要协议。

    1. 深入理解端口号

    之前的博客提到:端口号其实是用来标识一个进程的。

    一个主机中的所有进程都是通过同一个网卡来传输数据的,让每个进程分别绑定到不同的端口号,此时收到的网络数据报中就会包含一个 " 目的端口 ",它就会根据目的端口找到对应端口号的进程,从而把数据交给对应的进程。

    2. 端口号和 PID 的区别

    在学习线程的时候,我们了解过 PID 的概念,PID( Process Identification ),即进程标识符,操作系统里每打开一个程序都会创建一个进程 ID,即 PID. 但我们必须明确:程序在运行的时候,PID 是不会改变标识符的,但进程终止后,PID 标识符就会被系统回收,就可能会被继续分配给新运行的程序,所以说 PID 是临时的一个标志。

    但是,端口号是固定不变的。

    所以说:在网络编程中,我们指定一个程序 / 进程 与服务器通信,需要知道 主机的 IP地址 和 进程的端口号。而不能用 PID 来代替端口号!

    举个例子:你打中国移动 10086,准备办理流量套餐,那么对面就会有人工客服接听电话,当你办理好了套餐之后,就把电话挂了。但你发现,电话套餐忘记办理了,于是就再次打电话过去,这个时候,你会发现,系统为你分配的客服和上一个客服就不再是同一个人了。那么,端口号就相当于 10086 这个固定的号码,而不同的客服就表示不同的 PID。

    3. 注意

    (1)

    通常情况下,一个进程对应一个端口号,两个进程无法绑定同一个端口。

    而比较常见的情况是:一个进程能够对应多个端口。这是通过 socket 文件和端口绑定来实现的,因为一个进程可以有多个文件,那么就可以做到让多个 socket 文件绑定到不同的端口。

    让一个进程对应多个端口,实际上是为了解决【调试问题】,通常情况下,服务器会提供客户端一个【业务端口】,这个端口为客户端提供服务,如果在提供服务的时候,出现了数据问题怎么办?这时候,就可以事先让当前进程再绑定另一个【调试端口】,而这个端口就是用来定位一些问题。

    (2)

    端口号是一个两个字节整数,那么最多也就是 16 位,所以它的范围为 0- 65535.

    知名端口号: 01023
    
    注册端口号: 102449151
    
    剩下的端口号叫动态端口号或私有端口号: 4915265535
    
    • 1
    • 2
    • 3
    • 4
    • 5

    关于传输层的 TCP / UDP 会在后两篇博客中介绍到

  • 相关阅读:
    kaist数据集体验
    基于大规模测量和多任务深度学习的电子鼻系统目标识别、浓度预测和状态判断
    Opus Security利用其AI驱动的多层次优先级引擎提升漏洞管理能力
    Vue—大文件分片上传
    第十篇 基于JSP 技术的网上购书系统——管理员后台管理主界面、订单管理、产品管理功能实现(网上商城、仿淘宝、当当、亚马逊)
    05 Mybatis官方代码生成器的使用
    基于多头注意力机制卷积神经网络结合双向门控单元CNN-BIGRU-Mutilhead-Attention实现柴油机故障诊断附matlab代码
    vue项目|在弹窗中引入uchart图表子组件不显示
    好的架构是进化来的,不是设计来的
    低代码维格云甘特视图入门教程
  • 原文地址:https://blog.csdn.net/lfm1010123/article/details/125892682