QuecPython 网络协议之TCP/UDP协议最祥解析
概述
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 客户端编程的接口调用流程:
-
调用
socket()接口创建 socket 对象。 -
调用
connect()接口连接服务器。 -
调用
send()接口向服务器发送数据。 -
调用
recv()接口接收服务器下发的数据。 -
循环执行第 3 步和第 4 步,业务满足一定条件或连接断开,调用
close()接口关闭 socket,释放资源。
几乎所有编程语言实现的 socket 接口,默认都是阻塞模式的,即所有涉及到网络报文收发的接口,如connect()、send()、recv()、close()等,默认都是阻塞式接口。
TCP 服务器与客户端的 socket 编程模型示意图的左侧展示了服务器编程的接口调用流程:
-
调用
socket()接口创建 socket 对象。 -
调用
bind()接口绑定本地的地址和端口。 -
调用
listen()接口监听客户端连接请求。 -
调用
accept()接口接受客户端连接请求。 -
调用
recv()接口接收客户端上行的数据。 -
调用
send()接口向客户端发送数据。 -
每一个客户端连接中,循环执行第 5 步和第 6 步,业务满足一定条件或连接断开,调用
close()接口关闭 socket,释放资源。 -
在接受客户端连接请求的线程中,循环执行第 4 步,以接受更多的客户端接入。
TCP 服务器编程调用的接口相比客户端,多了bind()、listen()、accept()三个接口。
TCP 服务器代码如下:
import usocket
import _threaddef _client_conn_proc(conn, ip_addr, port):while True:try:# Receive data sent by the clientdata = conn.recv(1024)print('[server] [client addr: %s, %s] recv data:' % (ip_addr, port), data)# Send data back to the clientconn.send(data)except:# Exception occurred and connection closedprint('[server] [client addr: %s, %s] disconnected' % (ip_addr, port))conn.close()breakdef tcp_server(address, port):# Create a socket objectsock = usocket.socket(usocket.AF_INET, usocket.SOCK_STREAM, usocket.IPPROTO_TCP_SER)print('[server] socket object created.')# Bind the server IP address and portsock.bind((address, port))print('[server] bind address: %s, %s' % (address, port))# Listen for client connection requestssock.listen(10)print('[server] started, listening ...')while True:# Accept a client connection requestcli_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 _threaddef _client_conn_proc(conn, ip_addr, port):while True:try:# Receive data sent by the clientdata = conn.recv(1024)print('[server] [client addr: %s, %s] recv data:' % (ip_addr, port), data)# Send data back to the clientconn.send(data)except:# Exception occurred and connection closedprint('[server] [client addr: %s, %s] disconnected' % (ip_addr, port))conn.close()breakdef tcp_server(address, port):# Create a socket objectsock = usocket.socket(usocket.AF_INET, usocket.SOCK_STREAM, usocket.IPPROTO_TCP_SER)print('[server] socket object created.')# Bind the server IP address and portsock.bind((address, port))print('[server] bind address: %s, %s' % (address, port))# Listen for client connection requestssock.listen(10)print('[server] started, listening ...')while True:# Accept a client connection requestcli_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 dataCallif __name__ == '__main__':stage, state = checkNet.waitNetworkReady(30)if stage == 3 and state == 1: # Network connection is normalprint('[net] Network connection successful.')# Get the IP address of the moduleserver_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 successfullyprint('sleep 3s to ensure that the server starts successfully.')utime.sleep(3)# Start the clientudp_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 编程时,第二个参数
type为usocket.SOCK_STREAM,而 UDP 编程时,第二个参数type为usocket.SOCK_DGRAM。 - TCP 编程时,第三个参数
proto为usocket.IPPROTO_TCP或usocket.IPPROTO_TCP_SER,而 UDP 编程时,第三个参数proto为usocket.IPPROTO_UDP。
- TCP 编程时,第二个参数
- 由于 UDP 是无连接的,客户端无需调用
connect()接口去连接服务器。 - 数据发送方只要直接调用
sendto()接口将数据发送出去即可。 - 数据接收方调用
recvfrom()接口接收数据。
sendto()接口是否能真正将数据发送到目的地,视网络环境而定,如果无法找到目标 IP 地址对应的主机,则数据被丢弃。
接下来,我们做一个实验:在模组中分别编写一个 UDP 服务器程序和一个 UDP 客户端程序,客户端周期性向服务器发送数据,而后等待服务器回送数据。
有了前面 TCP 编程的经验,我们直接给出实验代码
import usocket
import _thread
import utime
import checkNet
import dataCalldef udp_server(address, port):# Create a socket objectsock = usocket.socket(usocket.AF_INET, usocket.SOCK_DGRAM, usocket.IPPROTO_UDP)print('[server] socket object created.')# Bind server IP address and portsock.bind((address, port))print('[server] bind address: %s, %s' % (address, port))while True:# Read client datadata, sockaddr = sock.recvfrom(1024)print('[server] [client addr: %s] recv data: %s' % (sockaddr, data))# Send data back to the clientsock.sendto(data, sockaddr)def udp_client(address, port):# Create a socket objectsock = 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 serversock.sendto(data, (address, port))print('[client] send data:', data)# Read data sent back from the serverdata, sockaddr = sock.recvfrom(1024)print('[client] [server addr: %s] recv data: %s' % (sockaddr, data))print('[client] -------------------------')# Delay for 1 secondutime.sleep(1)if __name__ == '__main__':stage, state = checkNet.waitNetworkReady(30)if stage == 3 and state == 1: # Network connection is normalprint('[net] Network connection successful.')# Get the IP address of the moduleserver_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 successfullyprint('sleep 3s to ensure that the server starts successfully.')utime.sleep(3)# Start the clientudp_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。
相关文章:
QuecPython 网络协议之TCP/UDP协议最祥解析
概述 IP 地址与域名 IP 地址是网络中的主机地址,用于两台网络主机能够互相找到彼此,这也是网络通信能够成功进行的基础。IP 地址一般以点分十进制的字符串来表示,如192.168.1.1。 我们日常访问的网站,其所在的服务器主机都有…...
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能在机器人方面能“破圈”,但是一切皆有可能,…...
如何在jupyter notebook中使用django框架
(最好以管理员身份进入,否则在安装某些内容时会报错) 一.创建一个名为new_env虚拟环境 输入以下指令创建名为new_env的虚拟环境: conda create -n new_env python3.8 回车,出现以下内容,输入y确认安装,等待安装完毕…...
Axure RP9.0教程: 多级联动【设置选项改变时->情形->面板状态】(给动态面板元件设置相关交互事件的情形,来控制其他面板不同的状态。)
文章目录 引言I 多级联动(省、市、区)实现思路添加三省、市、区下拉列表给省下拉框添加数据源将市、区下拉框添加不同状态,分别以省、市命名给省下拉控件设置选项改变时的交互事件省下拉控件的交互事件情形市下拉交互事件的配置II 知识扩展: 展示省 → 地级市 → 区县的多级…...
速卖通vs亚马逊SP-API:接口权限、数据字段与采集成本对比测评
以下是关于速卖通和亚马逊 SP - API 在接口权限、数据字段与采集成本方面的对比测评: 接口权限 速卖通 申请流程:需在速卖通平台注册账号,创建应用后获取 API Key 和 Secret 等凭证。一般要填写企业或个人的相关信息,经过平台审…...
Rabbitmq消息被消费时抛异常,进入Unacked 状态,进而导致消费者不断尝试消费(下)
一、消费流程图 消息在消费出现异常的时候,将一直保留在消息队列,所以你会看到以下奇怪的现象: 消息队列仅有5个消息, 投递速度也非常快,结果却一直无法消费掉。 二、重试策略 重试机制的使用场景:重试机制…...
Java 基础入门代码示例解析
在 Java 编程的学习过程中,理解函数(方法)的使用以及简单系统功能的实现是非常重要的基础。本文将对一系列 Java 代码进行详细解析,这些代码涵盖了菜单驱动的功能选择、数据查询以及简单的 RBAC(基于角色的访问控制&am…...
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重磅开源了其…...
数据库:一文掌握 Neo4J 的各种指令(Neo4J指令备忘)
文章目录 入门Neo4J运行 Neo4J 使用进入管理页面 图数据库概念 Neo4j 语法读取查询结构仅写入查询结构读取-写入查询结构 Neo4j 读取数据MATCHWHERERETURNWITHUNION Neo4j 写入数据CREATESETMERGEDELETEREMOVEFOREACH调用子查询调用存储过程导入运算符nullPatternsUSESHOW FUNC…...
Java面试第十三山!《设计模式》
大家好,我是陈一。如果文章对你有帮助,请留下一个宝贵的三连哦~ 万分感谢! 一、设计模式入门指南 1. 什么是设计模式? 设计模式是可复用的解决方案模板,用于解决软件开发中常见的架构问题。如同建筑领域的…...
从 @SpringBootApplication 出发,深度剖析 Spring Boot 自动装配原理
在 Spring Boot 的开发旅程中,SpringBootApplication 注解堪称开启便捷开发之门的钥匙。它不仅是一个简单的注解,更是理解 Spring Boot 自动装配原理的重要入口。接下来,我们将以SpringBootApplication 为切入点,深入探究 Spring …...
使用vue3和vue-router实现动态添加和删除cachedViews数组
以下是一个使用 Vue 3 和 Vue Router 实现动态添加和删除 cachedViews 数组的代码示例,该示例结合 keep-alive 组件来动态控制路由组件的缓存。 src/ ├── App.vue ├── router/ │ └── index.js ├── views/ │ ├── Home.vue │ ├── About.v…...
vue 点击放大,图片预览效果
背景: 在使用vue框架element组件的背景下,我们对图片的展示需要点击放大(单张);如果是多张图片,要支持左右滑动查看多张图片(多张)。 单张图片放大,el-image图片组件,或者原生的img标签。 多张图片放大&…...
如何使用Xshell连接Linux虚拟机
在日常开发和运维工作中,远程连接Linux服务器或虚拟机是一项基本技能。Xshell 是一款功能强大的终端模拟器,支持通过 SSH 协议远程连接 Linux 系统。本文将详细介绍如何使用 Xshell 连接 Linux 虚拟机。准备工作 在开始之前,请确保你已经完成…...
笛卡尔轨迹规划之齐次变换矩阵与欧拉角、四元数的转化
一、笛卡尔轨迹规划需求 笛卡尔轨迹规划本质就是我们对机械臂的末端位置和姿态进行规划,其实也就是对末端坐标系的位姿进行规划。我们清楚末端坐标系的位姿是可以用齐次变换矩阵T来表示的,但这样表示的话,并不利于我们去做规划,所…...
1 存储过程学习: 使用DMSQL程序的优点
DMSQL程序具有以下优点: 与SQL语言的完美结合 SQL语言已成为数据库的标准语言,DMSQL程序支持所有SQL数据类型和所有SQL函数,同时支持所有DM对象类型。在DMSQL程序中可以使用SELECT、INSERT、DELETE、UPDATE数据操作语句,事务控制…...
NPU上如何使能pytorch图模式
1 Pytorch的compile技术 PyTorch 的 torch.compile 是一个强大的功能,用于优化 PyTorch 模型的性能。它通过将 PyTorch 的动态图转换为静态图,并利用 Just-In-Time(JIT)编译技术,显著提高模型的推理速度和训练效率。 …...
进制转换(c++)
由于进制转换属于基础且比较重要,所以我就写一个博客方便自己复习,过程中如有错误,还请指出。 常用的进制有二进制,八进制,十进制和十六进制。 常用的进制转换就是十进制转换成其他进制和其他进制转换成十进制 我们先…...
2025-03-24 学习记录--C/C++-PTA 习题7-7 字符串替换
合抱之木,生于毫末;九层之台,起于累土;千里之行,始于足下。💪🏻 一、题目描述 ⭐️ 习题7-7 字符串替换 本题要求编写程序,将给定字符串中的大写英文字母按以下对应规则替换&#…...
为什么TCP需要三次握手?一次不行吗?
文章目录 1. 三次握手的过程2. 为什么需要三次握手?3. 握手过程中每一步的具体作用4. 简单比喻5. 为什么是三次握手,而不是两次或四次?6. 三次握手中的序列号有什么作用?7. 总结 1. 三次握手的过程 三次握手是建立 TCP 连接的过程…...
