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

[复制链接]
发表于 2025-10-9 22:05:51 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
概述

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 服务器代码如下:
  1. import usocket
  2. import _thread
  3. def _client_conn_proc(conn, ip_addr, port):
  4.     while True:
  5.         try:
  6.             # Receive data sent by the client
  7.             data = conn.recv(1024)
  8.             print('[server] [client addr: %s, %s] recv data:' % (ip_addr, port), data)
  9.             # Send data back to the client
  10.             conn.send(data)
  11.         except:
  12.             # Exception occurred and connection closed
  13.             print('[server] [client addr: %s, %s] disconnected' % (ip_addr, port))
  14.             conn.close()
  15.             break
  16. def tcp_server(address, port):
  17.     # Create a socket object
  18.     sock = usocket.socket(usocket.AF_INET, usocket.SOCK_STREAM, usocket.IPPROTO_TCP_SER)
  19.     print('[server] socket object created.')
  20.     # Bind the server IP address and port
  21.     sock.bind((address, port))
  22.     print('[server] bind address: %s, %s' % (address, port))
  23.     # Listen for client connection requests
  24.     sock.listen(10)
  25.     print('[server] started, listening ...')
  26.     while True:
  27.         # Accept a client connection request
  28.         cli_conn, cli_ip_addr, cli_port = sock.accept()
  29.         print('[server] accept a client: %s, %s' % (cli_ip_addr, cli_port))
  30.         # Create a new thread for each client connection for concurrent processing
  31.         _thread.start_new_thread(_client_conn_proc, (cli_conn, cli_ip_addr, cli_port))
复制代码
TCP客户端代码如下
  1. import usocket
  2. import _thread
  3. def _client_conn_proc(conn, ip_addr, port):
  4.     while True:
  5.         try:
  6.             # Receive data sent by the client
  7.             data = conn.recv(1024)
  8.             print('[server] [client addr: %s, %s] recv data:' % (ip_addr, port), data)
  9.             # Send data back to the client
  10.             conn.send(data)
  11.         except:
  12.             # Exception occurred and connection closed
  13.             print('[server] [client addr: %s, %s] disconnected' % (ip_addr, port))
  14.             conn.close()
  15.             break
  16. def tcp_server(address, port):
  17.     # Create a socket object
  18.     sock = usocket.socket(usocket.AF_INET, usocket.SOCK_STREAM, usocket.IPPROTO_TCP_SER)
  19.     print('[server] socket object created.')
  20.     # Bind the server IP address and port
  21.     sock.bind((address, port))
  22.     print('[server] bind address: %s, %s' % (address, port))
  23.     # Listen for client connection requests
  24.     sock.listen(10)
  25.     print('[server] started, listening ...')
  26.     while True:
  27.         # Accept a client connection request
  28.         cli_conn, cli_ip_addr, cli_port = sock.accept()
  29.         print('[server] accept a client: %s, %s' % (cli_ip_addr, cli_port))
  30.         # Create a new thread for each client connection for concurrent processing
  31.         _thread.start_new_thread(_client_conn_proc, (cli_conn, cli_ip_addr, cli_port))
复制代码
主流程代码如下:
  1. import checkNet
  2. import _thread
  3. import utime
  4. import dataCall
  5. if __name__ == '__main__':
  6.     stage, state = checkNet.waitNetworkReady(30)
  7.     if stage == 3 and state == 1: # Network connection is normal
  8.         print('[net] Network connection successful.')
  9.         # Get the IP address of the module
  10.         server_addr = dataCall.getInfo(1, 0)[2][2]
  11.         server_port = 80
  12.         # Start the server thread to listen for client connection requests
  13.         _thread.start_new_thread(udp_server, (server_addr, server_port))
  14.         # Delay for a while to ensure that the server starts successfully
  15.         print('sleep 3s to ensure that the server starts successfully.')
  16.         utime.sleep(3)
  17.         # Start the client
  18.         udp_client(server_addr, server_port)
  19.     else:
  20.         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。
           
  • 由于 UDP 是无毗连的,客户端无需调用connect()接口去毗连服务器。
  • 数据发送方只要直接调用sendto()接口将数据发送出去即可。
  • 数据吸收方调用recvfrom()接口吸收数据。
sendto()接口是否能真正将数据发送到目的地,视网络环境而定,如果无法找到目的 IP 所在对应的主机,则数据被扬弃。
接下来,我们做一个实验:在模组中分别编写一个 UDP 服务器步伐和一个 UDP 客户端步伐,客户端周期性向服务器发送数据,而后等候服务器回送数据。
有了前面 TCP 编程的履历,我们直接给出实验代码
  1. import usocket
  2. import _thread
  3. import utime
  4. import checkNet
  5. import dataCall
  6. def udp_server(address, port):
  7.     # Create a socket object
  8.     sock = usocket.socket(usocket.AF_INET, usocket.SOCK_DGRAM, usocket.IPPROTO_UDP)
  9.     print('[server] socket object created.')
  10.     # Bind server IP address and port
  11.     sock.bind((address, port))
  12.     print('[server] bind address: %s, %s' % (address, port))
  13.     while True:
  14.         # Read client data
  15.         data, sockaddr = sock.recvfrom(1024)
  16.         print('[server] [client addr: %s] recv data: %s' % (sockaddr, data))
  17.         # Send data back to the client
  18.         sock.sendto(data, sockaddr)
  19. def udp_client(address, port):
  20.     # Create a socket object
  21.     sock = usocket.socket(usocket.AF_INET, usocket.SOCK_DGRAM, usocket.IPPROTO_UDP)
  22.     print('[client] socket object created.')
  23.     data = b'1234567890'
  24.     while True:
  25.         # Send data to the server
  26.         sock.sendto(data, (address, port))
  27.         print('[client] send data:', data)
  28.         # Read data sent back from the server
  29.         data, sockaddr = sock.recvfrom(1024)
  30.         print('[client] [server addr: %s] recv data: %s' % (sockaddr, data))
  31.         print('[client] -------------------------')
  32.         # Delay for 1 second
  33.         utime.sleep(1)
  34. if __name__ == '__main__':
  35.     stage, state = checkNet.waitNetworkReady(30)
  36.     if stage == 3 and state == 1: # Network connection is normal
  37.         print('[net] Network connection successful.')
  38.         # Get the IP address of the module
  39.         server_addr = dataCall.getInfo(1, 0)[2][2]
  40.         server_port = 80
  41.         # Start the server thread
  42.         _thread.start_new_thread(udp_server, (server_addr, server_port))
  43.         # Delay for a while to ensure that the server starts successfully
  44.         print('sleep 3s to ensure that the server starts successfully.')
  45.         utime.sleep(3)
  46.         # Start the client
  47.         udp_client(server_addr, server_port)
  48.     else:
  49.         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。
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表