QuecPython 网络协议之TCP/UDP协议最祥解析

news2025/3/27 15:54:47

概述

IP 地址与域名

IP 地址是网络中的主机地址,用于两台网络主机能够互相找到彼此,这也是网络通信能够成功进行的基础。IP 地址一般以点分十进制的字符串来表示,如192.168.1.1

我们日常访问的网站,其所在的服务器主机都有唯一的 IP 地址,网络中的主机不计其数,靠记 IP 地址的方式来区分不同的主机显然比较困难,并且同一个网站可能有多个不同的 IP 地址,或者 IP 地址会因为某种原因而更换。

因此,用域名表示网站地址的方式便应运而生,如我们常见的www.baidu.com比 IP 地址更容易被记住。因为实际的网络通信报文中使用的仍然是 IP 地址,所以需要使用域名解析协议去获取域名背后所对应的 IP 地址。

下文的讲解均以 IPv4 协议为基础。

OSI 七层模型

国际标准化组织(ISO)制定的一个用于计算机或通信系统的标准体系,一般被称为 OSI(Open System Interconnection)七层模型。它为网络通信协议的实现提供了一个标准,通信双方在相同的层使用相同的协议,即可进行通信;就同一台设备而言,下层协议为上层协议提供了调用接口,将上层协议打包为底层协议,最终发送到网络上进行传输。

这七层分别为:应用层、表示层、会话层、传输层、网络层、数据链路层、物理层。

为了简化协议实现或者方便理解,五层模型或者四层模型的概念也诞生了。四层模型一般被提及的比较多,包括:应用层、传输层、网络层、网络接口层。

上文中的 IP 地址则属于网络层。

网络层用于把该主机所有的网络数据转发到网卡,经由物理层电路发送到网络中去。

为了方便阐述,下文将按照四层模型来进行讲解。

传输层协议

IP 地址解决了网络中两台主机如何能够找到彼此,进而进行报文收发的问题。

试想下,一台主机上可能运行着多个应用程序,执行着不同的网络任务。这时,某台 IP 地址的主机收到了另一台主机的报文,这个报文数据要传递给哪个应用程序呢?

为了解决这个问题,人们基于网络层协议演化出了传输层协议,传输层协议为本地的网络应用分配不同的端口。收到网络层的报文后,根据不同的端口号,将数据递交给不同的应用。

为了应对不同的场景,传输层协议分为 UDP 和 TCP 协议。

UDP 协议

UDP 协议具有以下特点:

  • 无连接
  • 支持一对一、一对多和多对多通信
  • 不保证可靠交付
  • 全双工通信
  • 面向报文

根据不同的需求,基于 UDP 衍生出了一些应用层协议,不同的应用会默认指定一个端口号。端口号亦可根据实际情况更换。

TCP 协议具有以下特点:

  • 面向连接
  • 每条连接只能有两个端点,即点对点
  • 提供可靠的数据交付
  • 全双工通信
  • 面向字节流

根据不同的需求,基于 TCP 衍生出了一些应用层协议,不同的应用会默认指定一个端口号。端口号亦可根据实际情况更换

TCP 网络编程

在开始 TCP 网络编程之前,我们先通过下图,初步了解下 TCP 服务器与客户端的 socket 编程模型:

TCP 客户端网络编程

上图的右侧是最简的 TCP 客户端编程的接口调用流程:

  1. 调用socket()接口创建 socket 对象。

  2. 调用connect()接口连接服务器。

  3. 调用send()接口向服务器发送数据。

  4. 调用recv()接口接收服务器下发的数据。

  5. 循环执行第 3 步和第 4 步,业务满足一定条件或连接断开,调用close()接口关闭 socket,释放资源。

几乎所有编程语言实现的 socket 接口,默认都是阻塞模式的,即所有涉及到网络报文收发的接口,如connect()send()recv()close()等,默认都是阻塞式接口。

TCP 服务器与客户端的 socket 编程模型示意图的左侧展示了服务器编程的接口调用流程:

  1. 调用socket()接口创建 socket 对象。

  2. 调用bind()接口绑定本地的地址和端口。

  3. 调用listen()接口监听客户端连接请求。

  4. 调用accept()接口接受客户端连接请求。

  5. 调用recv()接口接收客户端上行的数据。

  6. 调用send()接口向客户端发送数据。

  7. 每一个客户端连接中,循环执行第 5 步和第 6 步,业务满足一定条件或连接断开,调用close()接口关闭 socket,释放资源。

  8. 在接受客户端连接请求的线程中,循环执行第 4 步,以接受更多的客户端接入。

TCP 服务器编程调用的接口相比客户端,多了bind()listen()accept()三个接口。

TCP 服务器代码如下:

import usocket
import _thread

def _client_conn_proc(conn, ip_addr, port):
    while True:
        try:
            # Receive data sent by the client
            data = conn.recv(1024)
            print('[server] [client addr: %s, %s] recv data:' % (ip_addr, port), data)

            # Send data back to the client
            conn.send(data)
        except:
            # Exception occurred and connection closed
            print('[server] [client addr: %s, %s] disconnected' % (ip_addr, port))
            conn.close()
            break

def tcp_server(address, port):
    # Create a socket object
    sock = usocket.socket(usocket.AF_INET, usocket.SOCK_STREAM, usocket.IPPROTO_TCP_SER)
    print('[server] socket object created.')

    # Bind the server IP address and port
    sock.bind((address, port))
    print('[server] bind address: %s, %s' % (address, port))

    # Listen for client connection requests
    sock.listen(10)
    print('[server] started, listening ...')

    while True:
        # Accept a client connection request
        cli_conn, cli_ip_addr, cli_port = sock.accept()
        print('[server] accept a client: %s, %s' % (cli_ip_addr, cli_port))

        # Create a new thread for each client connection for concurrent processing
        _thread.start_new_thread(_client_conn_proc, (cli_conn, cli_ip_addr, cli_port))

TCP客户端代码如下

import usocket
import _thread

def _client_conn_proc(conn, ip_addr, port):
    while True:
        try:
            # Receive data sent by the client
            data = conn.recv(1024)
            print('[server] [client addr: %s, %s] recv data:' % (ip_addr, port), data)

            # Send data back to the client
            conn.send(data)
        except:
            # Exception occurred and connection closed
            print('[server] [client addr: %s, %s] disconnected' % (ip_addr, port))
            conn.close()
            break

def tcp_server(address, port):
    # Create a socket object
    sock = usocket.socket(usocket.AF_INET, usocket.SOCK_STREAM, usocket.IPPROTO_TCP_SER)
    print('[server] socket object created.')

    # Bind the server IP address and port
    sock.bind((address, port))
    print('[server] bind address: %s, %s' % (address, port))

    # Listen for client connection requests
    sock.listen(10)
    print('[server] started, listening ...')

    while True:
        # Accept a client connection request
        cli_conn, cli_ip_addr, cli_port = sock.accept()
        print('[server] accept a client: %s, %s' % (cli_ip_addr, cli_port))

        # Create a new thread for each client connection for concurrent processing
        _thread.start_new_thread(_client_conn_proc, (cli_conn, cli_ip_addr, cli_port))

主流程代码如下:

import checkNet
import _thread
import utime
import dataCall

if __name__ == '__main__':
    stage, state = checkNet.waitNetworkReady(30)
    if stage == 3 and state == 1: # Network connection is normal
        print('[net] Network connection successful.')

        # Get the IP address of the module
        server_addr = dataCall.getInfo(1, 0)[2][2]
        server_port = 80

        # Start the server thread to listen for client connection requests
        _thread.start_new_thread(udp_server, (server_addr, server_port))

        # Delay for a while to ensure that the server starts successfully
        print('sleep 3s to ensure that the server starts successfully.')
        utime.sleep(3)

        # Start the client
        udp_client(server_addr, server_port)
    else:
        print('[net] Network connection failed, stage={}, state={}'.format(stage, state))

UDP网络编程

在开始 UDP 网络编程之前,我们先通过下图,初步了解下 UDP 服务器与客户端的 socket 编程模型:

从图中可以看出,UDP 服务器也需要调用bind()接口,绑定本地的 IP 地址和端口号,这是作为服务器所必须的接口调用。

同时,UDP 编程在接口调用上也有与 TCP 编程不同之处:

  • socket()接口参数不同:
    • TCP 编程时,第二个参数typeusocket.SOCK_STREAM,而 UDP 编程时,第二个参数typeusocket.SOCK_DGRAM
    • TCP 编程时,第三个参数protousocket.IPPROTO_TCPusocket.IPPROTO_TCP_SER,而 UDP 编程时,第三个参数protousocket.IPPROTO_UDP
  • 由于 UDP 是无连接的,客户端无需调用connect()接口去连接服务器。
  • 数据发送方只要直接调用sendto()接口将数据发送出去即可。
  • 数据接收方调用recvfrom()接口接收数据。

sendto()接口是否能真正将数据发送到目的地,视网络环境而定,如果无法找到目标 IP 地址对应的主机,则数据被丢弃。

接下来,我们做一个实验:在模组中分别编写一个 UDP 服务器程序和一个 UDP 客户端程序,客户端周期性向服务器发送数据,而后等待服务器回送数据。

有了前面 TCP 编程的经验,我们直接给出实验代码

import usocket
import _thread
import utime
import checkNet
import dataCall

def udp_server(address, port):
    # Create a socket object
    sock = usocket.socket(usocket.AF_INET, usocket.SOCK_DGRAM, usocket.IPPROTO_UDP)
    print('[server] socket object created.')

    # Bind server IP address and port
    sock.bind((address, port))
    print('[server] bind address: %s, %s' % (address, port))

    while True:
        # Read client data
        data, sockaddr = sock.recvfrom(1024)
        print('[server] [client addr: %s] recv data: %s' % (sockaddr, data))

        # Send data back to the client
        sock.sendto(data, sockaddr)

def udp_client(address, port):
    # Create a socket object
    sock = usocket.socket(usocket.AF_INET, usocket.SOCK_DGRAM, usocket.IPPROTO_UDP)
    print('[client] socket object created.')

    data = b'1234567890'
    while True:
        # Send data to the server
        sock.sendto(data, (address, port))
        print('[client] send data:', data)

        # Read data sent back from the server
        data, sockaddr = sock.recvfrom(1024)
        print('[client] [server addr: %s] recv data: %s' % (sockaddr, data))
        print('[client] -------------------------')

        # Delay for 1 second
        utime.sleep(1)

if __name__ == '__main__':
    stage, state = checkNet.waitNetworkReady(30)
    if stage == 3 and state == 1: # Network connection is normal
        print('[net] Network connection successful.')

        # Get the IP address of the module
        server_addr = dataCall.getInfo(1, 0)[2][2]
        server_port = 80

        # Start the server thread
        _thread.start_new_thread(udp_server, (server_addr, server_port))

        # Delay for a while to ensure that the server starts successfully
        print('sleep 3s to ensure that the server starts successfully.')
        utime.sleep(3)

        # Start the client
        udp_client(server_addr, server_port)
    else:
        print('[net] Network connection failed, stage={}, state={}'.format(stage, state))

常见问题:

1. 为什么连接服务器会失败?

 服务器必须是公网地址(连接模组本地的 server 除外)。

使用 PC上 的 TCP/UDP 测试工具客户端、或者 mqtt.fx,连接服务器确认一下是否可以连接成功,排除服务器故障。 

2. TCP 有自动重连功能吗?

底层没有自动重连,重连机制在应用层处理。

3.为什么我一包数据只有不到 50B,一天消耗的流量要远远大于实际传输值

如果使用的是 TCP 协议,需要三次握手四次挥手才算完成了一次数据交互,原始数据不多但是由于 TCP 协议决定的一包数据必须要加包头包尾帧校验等,所以实际消耗的流量不止 50B,部分运营商有限制每一包数据必须 1KB 起发,不足 1KB 也会加各种校验凑足 1KB。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2321750.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ISIS-2 邻居建立关系

上一章我们介绍了ISIS的基础概念以及报文内容和作用在什么样的场景下面的 这一章我们来介绍IS-IS的邻居建立关系 一、概念 IS-IS中路由器的角色可分为L1、L2、L1/2这三种类型其中的L1/L2有点类似与我们OSPF中的ABR IS-IS中的邻居关系分为L1与L2邻居关系,其中所有建立L2邻居关…

Nature Machine Intelligence 嵌入式大语言模型使机器人能够在不可预测的环境中完成复杂的任务

近期英国爱丁堡大学发表Nature Machine Intelligence研究工作,提出了一种名为ELLMER(具身大型语言模型支持机器人)的创新框架,通过整合大型语言模型(如GPT-4)、检索增强生成(RAG)、视…

Springboot整合elasticsearch详解 封装模版 仓库方法 如何在linux里安装elasticsearch

目录 版本 下载地址 ElasticSearch频繁报503错误 开放 9300 和 9200 两个端口 测试联通性 改动包装类 elasticsearchTemplate getAllRespRepository 封装elasticsearchService 业务逻辑 版本 首先要对应版本 这是我在官网找到的版本信息 一定要 springboot 和 es 相…

【矩阵快速幂】P6601 「EZEC-2」机器|普及+

本文涉及知识点 【矩阵快速幂】封装类及测试用例及样例 P6601 「EZEC-2」机器 题目背景 tlx 喜欢科幻小说。 小宇宙中只剩下漂流瓶和生态球。漂流瓶隐没于黑暗里,在一千米见方的宇宙中,只有生态球里的小太阳发出一点光芒。在这个小小的生命世界中,几只清澈的水球在零重力环…

FPGA助力智能机器人应用

今年开年AI机器人引爆科技圈,都说FPGA是“万能芯”,在AI方向上已经挣扎了几年,仍旧不能“破圈”,那么在机器人方向呢? 个人观点我是不太看好目前FPGA能在机器人方面能“破圈”,但是一切皆有可能&#xff0c…

如何在jupyter notebook中使用django框架

(最好以管理员身份进入,否则在安装某些内容时会报错) 一.创建一个名为new_env虚拟环境 输入以下指令创建名为new_env的虚拟环境: conda create -n new_env python3.8 回车,出现以下内容,输入y确认安装,等待安装完毕…

Axure RP9.0教程: 多级联动【设置选项改变时->情形->面板状态】(给动态面板元件设置相关交互事件的情形,来控制其他面板不同的状态。)

文章目录 引言I 多级联动(省、市、区)实现思路添加三省、市、区下拉列表给省下拉框添加数据源将市、区下拉框添加不同状态,分别以省、市命名给省下拉控件设置选项改变时的交互事件省下拉控件的交互事件情形市下拉交互事件的配置II 知识扩展: 展示省 → 地级市 → 区县的多级…

Rabbitmq消息被消费时抛异常,进入Unacked 状态,进而导致消费者不断尝试消费(下)

一、消费流程图 消息在消费出现异常的时候,将一直保留在消息队列,所以你会看到以下奇怪的现象: 消息队列仅有5个消息, 投递速度也非常快,结果却一直无法消费掉。 二、重试策略 重试机制的使用场景:重试机制…

AI 的“幻觉”现象:深入解析 Hallucination 的成因与应对之道

文章目录 一、啥是 AI 的 Hallucination?二、啥时候容易出现幻觉?1. 知识边界之外的问题2. 模糊或不明确的输入3. 生成长篇内容4. 多模态任务中的误解5. 过度自信的语气要求 三、幻觉为啥会出现?原理是啥?1. 概率预测的本质2. 训练…

核心知识——论文详解

引入 在2010年,来自Berkeley的博士生 Matei Zaharia 发表了一篇论文《Resilient Distributed Datasets: A Fault-Tolerant Abstraction for In-Memory Cluster Computing》。伴随着这篇论文的,是一个开源系统,也就是 Spark。在之后的几年里&…

LeetCode hot 100 每日一题(15)——48.旋转图像

这是一道难度为中等的题目,让我们来看看题目描述: 给定一个 n n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。 你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。 提示…

屏幕后处理Post-Processing安装及使用

一、安装Post-Processing插件 在 Window --- PackageManager 中搜索并安装 Post-Processing 二、添加后处理效果步骤 给场景中的相机Camera添加 Post - process Layer,只有添加了该组件的相机才会进行相应的后处理,此组件允许您为该后处理层配置抗锯齿…

探索Halo:不止是博客,更是创作新宇宙

开篇:邂逅 Halo 作为一名热爱写作与分享的博主,拥有一个称手的博客系统一直是我的追求。在探索博客系统的旅程中,我尝试过不少平台,从 WordPress 到 Hexo ,每一次转换都是为了寻找那个最完美的 “写作伴侣”。 WordP…

吐血整理:Air8201如何使用LuatOS进行电源管理功能!

在物联网应用场景中,设备续航能力直接影响其部署成本与运维效率。LuatOS操作系统通过软件层面的精细化控制,为Air8201提供了灵活且高效的电源管理策略。本文将从系统架构、API接口、实战配置三个维度,解析如何利用LuatOS实现Air8201的智能电源…

开源视觉语言模型MiniMax-VL-01:动态分辨率+4M超长文本,性能比肩GPT-4o

在人工智能领域,构建能够像人类一样理解、思考和行动的智能体(AI Agent)一直是研究人员的终极目标之一。而实现这一目标的关键在于模型是否具备足够强大的感知能力、记忆能力和推理能力。近期,国内人工智能公司MiniMax重磅开源了其…

Java面试第十三山!《设计模式》

大家好,我是陈一。如果文章对你有帮助,请留下一个宝贵的三连哦~ 万分感谢! 一、设计模式入门指南 1. 什么是设计模式? 设计模式是可复用的解决方案模板,用于解决软件开发中常见的架构问题。如同建筑领域的…

vue 点击放大,图片预览效果

背景: 在使用vue框架element组件的背景下,我们对图片的展示需要点击放大(单张);如果是多张图片,要支持左右滑动查看多张图片(多张)。 单张图片放大,el-image图片组件,或者原生的img标签。 多张图片放大&…

笛卡尔轨迹规划之齐次变换矩阵与欧拉角、四元数的转化

一、笛卡尔轨迹规划需求 笛卡尔轨迹规划本质就是我们对机械臂的末端位置和姿态进行规划,其实也就是对末端坐标系的位姿进行规划。我们清楚末端坐标系的位姿是可以用齐次变换矩阵T来表示的,但这样表示的话,并不利于我们去做规划,所…

NPU上如何使能pytorch图模式

1 Pytorch的compile技术 PyTorch 的 torch.compile 是一个强大的功能,用于优化 PyTorch 模型的性能。它通过将 PyTorch 的动态图转换为静态图,并利用 Just-In-Time(JIT)编译技术,显著提高模型的推理速度和训练效率。 …

进制转换(c++)

由于进制转换属于基础且比较重要,所以我就写一个博客方便自己复习,过程中如有错误,还请指出。 常用的进制有二进制,八进制,十进制和十六进制。 常用的进制转换就是十进制转换成其他进制和其他进制转换成十进制 我们先…