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 客户端编程的接口调用流程:

  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 _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 编程时,第二个参数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 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。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

React - LineChart组件编写(用于查看每日流水图表)

一、简单版本 LineChart.tsx // src/component/LineChart/LineChart.tsx import React, {useEffect,useRef,useImperativeHandle,forwardRef,useMemo,useCallback, } from react; import * as echarts from echarts/core; import type { ComposeOption } from echarts/core; …

医学图像分割数据集肺分割数据labelme格式6299张2类别

数据集格式:labelme格式(不包含mask文件,仅仅包含jpg图片和对应的json文件) 图像分辨率:1024x1024 图片数量(jpg文件个数):6299 标注数量(json文件个数):6299 标注类别数:2 标注类别名称:["leftl…

帕金森病致生活艰难,如何缓解心理负担?

你是否留意到身边有人手部不由自主地颤抖,且肢体变得僵硬,行动也愈发迟缓?这很可能是帕金森病的症状。帕金森病是一种常见的神经系统退行性疾病,多发生于中老年人。​ 静止性震颤往往是帕金森病的首发症状,患者在安静状…

从零构建大语言模型全栈开发指南:第二部分:模型架构设计与实现-2.1.1自注意力机制(Scaled Dot-Product Attention)的逐行代码实现

👉 点击关注不迷路 👉 点击关注不迷路 👉 点击关注不迷路 文章大纲 2.1.1 自注意力机制(Scaled Dot-Product Attention)的逐行代码实现1. 自注意力机制的核心原理与数学表达1.1 注意力计算的三元组:`Q, K, V`2. 逐行代码实现与解析2.1 输入嵌入与权重矩阵初始化2.2 完…

机械臂【逆运动学】

回顾正运动学fk: IK: 几何法 代数法 六轴 456轴交再同一点 有解析解 下列公式为正运动学部分结论 a和d是长度 ,theta和alfa是角度 **疑问:alfa00? Z轴互相平行 ** 已知末端要在空间XYZ处如下 绿色项&#x…

IDEA批量替换项目下所有文件中的特定内容

文章目录 1. 问题引入2. 批量替换项目下所有文件中的特定内容2.1 右键项目的根目录,点击在文件中替换2.2 输入要替换的内容 3. 解决替换一整行文本后出现空行的问题4. 增加筛选条件提高匹配的精确度 更多 IDEA 的使用技巧可以查看 IDEA 专栏: IDEA 1. 问…

Ubuntu22.04美化MacOS主题

安装Tweaks 参考Ubuntu 22.04 桌面美化成Mac风格这篇更好点 sudo apt install gnome-tweaks gnome-shell-extensions -y安装macos主题 git clone https://github.com/vinceliuice/WhiteSur-gtk-theme.git # 进到文件目录 ./install.sh -t all -N glassy sudo ./tweaks.sh -g…

基于Python的机器学习入门指南

在当今数字化时代,机器学习(Machine Learning)已经成为科技领域中最热门的话题之一。它不仅改变了我们对数据的理解和处理方式,还在许多行业中得到了广泛应用,如金融、医疗、交通等。Python作为一门强大的编程语言&…

Python前缀和(例题:异或和,求和)

前缀和 前缀和:对于一个长度为n的列表a,前缀和为: sum[i]a[0]a[1]...a[i] 前缀和的性质: 第一条性质用于处理出前缀和: Sum[i]Sum[i-1]a[i] 第二条性质可以在O(l)的时间内求出区间和: a[l]....a[r] S…

统计矩的高阶推广:经验还是理论推导?

矩的发展既是经验总结的结果,也是数学理论推导的产物。研究者们在分析数据、描述物理现象的过程中,发现了低阶矩与日常物理概念(如质心、惯性)之间的紧密联系,而高阶矩的应用往往出现在更复杂的数学体系中,…

安宝特分享|AR智能装备赋能企业效率跃升

AR装备开启智能培训新时代 在智能制造与数字化转型浪潮下,传统培训体系正面临深度重构。安宝特基于工业级AR智能终端打造的培训系统,可助力企业构建智慧培训新生态。 AR技术在不同领域的助力 01远程指导方面 相较于传统视频教学的单向输出模式&#x…

《软件安装与使用教程》— NVIDIA CUDA在Windows的安装教程

《软件安装与使用教程》— NVIDIA CUDA在Windows的安装教程 Installed: - Nsight Monitor Not Installed: - Nsight for Visual Studio 2019 Reason: VS2019 was not found - Nsight for Visual Studio 2017 Reason: VS2017 was not found - Integrated Graphics Frame Debugge…

领域驱动设计(DDD)实践入门

文章目录 1.认识领域驱动设计1.1 简介1.2 发展历史1.3 DDD 的兴起 2.从一个简单案例2.1 转账需求2.2 设计的问题2.3 违反的设计原则 3.使用 DDD 进行重构抽象数据存储层抽象第三方服务抽象中间件封装业务逻辑重构后的架构 4.小结参考文献 1.认识领域驱动设计 1.1 简介 领域驱…

OrangePi 5B 内核开启 CONFIG_CIFS 通过 Samba 挂载 NAS 路径

文章目录 OrangePi 5B 内核开启 CONFIG_CIFS 通过 Samba 挂载 NAS 路径获取 Linux SDK 的源码从 github 下载 orangepi-build编译 linux 内核更新开发板内核上传编译好的 deb 包到开发板登录开发板,卸载旧内核安装新内核重启开发板 Ubuntu & Debian 系统下挂载 …

8662 234的和

8662 234的和 ⭐️难度:中等 🌟考点:模拟、二维前缀和 📖 📚 import java.util.Arrays; import java.util.LinkedList; import java.util.Queue; import java.util.Scanner;public class Main {static int[] a ne…

softmax回归的实现

softmax回归是logistic回归在多分类问题上的推广 原理 网络架构: 常用的方式是独热编码: 如果下面这样,会使得分类器更倾向于把奶牛和耗牛预测到一起,因为预测为海公牛惩罚更大,这样是不合理的。 损失函数&…

架构师面试(十九):IM 架构

问题 IM 系统从架构模式上包括 【介绍人模式】和 【代理人模式】。介绍人模式也叫直连模式,消息收发不需要服务端的参与,即客户端之间直连的方式;代理人模式也叫中转模式,消息收发需要服务端进行中转。 下面关于这两类模式描述的…

WSL2增加memory问题

我装的是Ubuntu24-04版本,所有的WSL2子系统默认memory为主存的一半(我的电脑是16GB,wsl是8GB),可以通过命令查看: free -h #查看ubuntu的memory和swap (改过的11GB) 前几天由于配置E…

OpenCV图像拼接(5)构建图像的拉普拉斯金字塔 (Laplacian Pyramid)

操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 cv::detail::createLaplacePyr 是 OpenCV 中的一个函数,用于构建图像的拉普拉斯金字塔 (Laplacian Pyramid)。拉普拉斯金字塔是一种多…

C++题目

1、内存管理 1.内存模型 栈:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。 堆:就是那些由new分配的内存块,其释放由程序员控制(一个new对应一个delete&#xff09…