i春秋作家:wasrehpic

0x00 前言

在上一篇文章「Python 绝技 —— TCP 服务器与客户端」中,介绍了传输层的核心协议 TCP ,并运用 Python 脚本的 socket 模块演示了 TCP 服务器与客户端的通信过程。

本篇将按照同样的套路,先介绍传输层的另一个核心协议 UDP,再比较 TCP 与 UDP 的特点,最后借助 Python 脚本演示 UDP 服务器与客户端的通信过程。

0x01 UDP 协议

UDP(User Datagram Protocol,用户数据报协议)是一种无连接、不可靠、基于数据报的传输层通信协议。

  • UDP 的通信过程与 TCP 相比较为简单,不需要复杂的三次握手与四次挥手,体现了无连接;
  • UDP 传输速度比 TCP 快,但容易丢包、数据到达顺序无保证、缺乏拥塞控制、秉承尽最大努力交付的原则,体现了不可靠;
  • UDP 的无连接与不可靠特性注定无法采用字节流的通信模式,由协议名中的「Datagram」与 socket 类型中的「SOCK_DGRAM」即可体现它基于数据报的通信模式。

为了更直观地比较 TCP 与 UDP 的异同,笔者将其整理成以下表格:

  TCP UDP
连接模式 面向连接(单点通信) 无连接(多点通信)
传输可靠性 可靠 不可靠
通信模式 基于字节流 基于数据报
报头结构 复杂(至少20字节) 简单(8字节)
传输速度
资源需求
到达顺序 保证 不保证
流量控制
拥塞控制
应用场合 大量数据传输 少量数据传输
支持的应用层协议 Telnet、FTP、SMTP、HTTP DNS、DHCP、TFTP、SNMP

0x02 Network Socket

Network Socket(网络套接字)是计算机网络中进程间通信的数据流端点,广义上也代表操作系统提供的一种进程间通信机制。

进程间通信(Inter-Process Communication,IPC)的根本前提是能够唯一标示每个进程。在本地主机的进程间通信中,可以用 PID(进程 ID)唯一标示每个进程,但 PID 只在本地唯一,在网络中不同主机的 PID 则可能发生冲突,因此采用「IP 地址 + 传输层协议 + 端口号」的方式唯一标示网络中的一个进程。

小贴士:网络层的 IP 地址可以唯一标示主机,传输层的 TCP/UDP 协议和端口号可以唯一标示该主机的一个进程。注意,同一主机中 TCP 协议与 UDP 协议的可以使用相同的端口号。

所有支持网络通信的编程语言都各自提供了一套 socket API,下面以 Python 3 为例,讲解服务器与客户端建立 UDP 通信连接的交互过程:

可见,UDP 的通信过程比 TCP 简单许多,服务器少了监听与接受连接的过程,而客户端也少了请求连接的过程。客户端只需要知道服务器的地址,直接向其发送数据即可,而服务器也敞开大门,接收任何发往自家地址的数据。

小贴士:由于 UDP 采用无连接模式,可知 UDP 服务器在接收到客户端发来的数据之前,是不知道客户端的地址的,因此必须是客户端先发送数据,服务器后响应数据。而 TCP 则不同,TCP 服务器接受了客户端的连接后,既可以先向客户端发送数据,也可以等待客户端发送数据后再响应。

0x03 UDP 服务器

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sockets = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(("127.0.0.1", 6000)) print("UDP bound on port 6000...") while True:     data, addr = s.recvfrom(1024)     print("Receive from %s:%s" % addr)     if data == b"exit":         s.sendto(b"Good bye!\n", addr)         continue     s.sendto(b"Hello %s!\n" % data, addr)
  • Line 5:创建 socket 对象,第一个参数为 socket.AF_INET,代表采用 IPv4 协议用于网络通信,第二个参数为 socket.SOCK_DGRAM,代表采用 UDP 协议用于无连接的网络通信。
  • Line 6:向 socket 对象绑定服务器主机地址 ("127.0.0.1", 6000),即本地主机的 UDP 6000 端口。
  • Line 9:进入与客户端交互数据的循环阶段。
  • Line 10:接收客户端发来的数据,包括 bytes 对象 data,以及客户端的 IP 地址和端口号 addr,其中 addr 为二元组 (host, port)。
  • Line 11:打印接收信息,表示从地址为 addr 的客户端接收到数据。
  • Line 12:若 bytes 对象为 b"exit",则向地址为 addr 的客户端发送结束响应信息 b"Good bye!\n"。发送完毕后,继续等待其他 UDP 客户端发来数据。
  • Line 15:若 bytes 对象不为 b"exit",则向地址为 addr 的客户端发送问候响应信息 b"Hello %s!\n",其中 %s是客户端发来的 bytes 对象。发送完毕后,继续等待任意 UDP 客户端发来数据。

与 TCP 服务器相比,UDP 服务器不必使用多线程,因为它无需为每个通信过程创建独立连接,而是采用「即收即发」的模式,又一次体现了 UDP 的无连接特性。

0x04 UDP 客户端

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sockets = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
addr = ("127.0.0.1", 6000) while True:     data = input("Please input your name: ")     if not data:         continue     s.sendto(data.encode(), addr)     response, addr = s.recvfrom(1024)     print(response.decode())     if data == "exit":         print("Session is over from the server %s:%s\n" % addr)         break s.close()
  • Line 5:创建 socket 对象,第一个参数为 socket.AF_INET,代表采用 IPv4 协议用于网络通信,第二个参数为 socket.SOCK_DGRAM,代表采用 UDP 协议用于无连接的网络通信。
  • Line 6:初始化 UDP 服务器的地址 ("127.0.0.1", 6000),即本地主机的 UDP 6000 端口。
  • Line 8:进入与服务器交互数据的循环阶段。
  • Line 9:要求用户输入名字。
  • Line 10:当用户的输入为空时,则重新开始循环,要求用户重新输入。
  • Line 12:当用户的输入非空时,则将字符串转换为 bytes 对象后,发送至地址为 ("127.0.0.1", 6000) 的 UDP 服务器。
  • Line 13:接收服务器的响应数据,包括 bytes 对象 response,以及服务器的 IP 地址和端口号 addr,其中 addr 为二元组 (host, port)。
  • Line 14:将响应的 bytes 对象 response 转换为字符串后打印输出。
  • Line 15:当用户的输入为 "exit" 时,则打印会话结束信息,终止与服务器交互数据的循环阶段,即将关闭套接字。
  • Line 19:关闭套接字,不再向服务器发送数据。

0x05 UDP 进程间通信

将 UDP 服务器与客户端的脚本分别命名为 udp_server.py 与 udp_client.py,然后存至桌面,笔者将在 Windows 10 系统下用 PowerShell 进行演示。

小贴士:读者进行复现时,要确保本机已安装 Python 3,注意笔者已将默认的启动路径名 python 改为了 python3

单服务器 VS 多客户端

  1. 在其中一个 PowerShell 中运行命令 python3 ./udp_server.py,服务器绑定本地主机的 UDP 6000 端口,并打印信息 UDP bound on port 6000...,等待客户端发来数据;
  2. 在另两个 PowerShell 中分别运行命令 python3 ./udp_client.py,并向服务器发送字符串 Client1Client2
  3. 服务器打印接收信息,表示分别从 UDP 63643、63644端口接收到数据,并分别向客户端发送问候响应信息;
  4. 客户端 Client1 发送空字符串,则被要求重新输入;
  5. 客户端 Client2 先发送字符串 Alice,得到服务器的问候响应信息,再发送字符串 exit,得到服务器的结束响应信息,最后打印会话结束信息,终止与服务器的数据交互;
  6. 客户端 Client1 发送字符串 exit,得到服务器的结束响应信息,并打印会话结束信息,终止与服务器的数据交互;
  7. 服务器按照以上客户端的数据发送顺序打印接收信息,并继续等待任意 UDP 客户端发来数据。

0x06 Python API Reference

socket 模块

本节介绍上述代码中用到的内建模块 socket,是 Python 网络编程的核心模块。

socket() 函数

socket() 函数用于创建网络通信中的套接字对象。函数原型如下:

socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)
  • family 参数代表地址族(Address Family),默认值为 AF_INET,用于 IPv4 网络通信,常用的还有 AF_INET6,用于 IPv6 网络通信。family 参数的可选值取决于本机操作系统。
  • type 参数代表套接字的类型,默认值为 SOCK_STREAM,用于 TCP 协议(面向连接)的网络通信,常用的还有 SOCK_DGRAM,用于 UDP 协议(无连接)的网络通信。
  • proto 参数代表套接字的协议,默认值为 0,一般忽略该参数,除非 family 参数为 AF_CAN,则 proto 参数需设置为 CAN_RAW 或 CAN_BCM
  • fileno 参数代表套接字的文件描述符,默认值为 None,若设置了该参数,则其他三个参数将会被忽略。

创建完套接字对象后,需使用对象的内置函数完成网络通信过程。注意,以下函数原型中的「socket」是指 socket 对象,而不是上述的 socket 模块。

bind() 函数

bind() 函数用于向套接字对象绑定 IP 地址与端口号。注意,套接字对象必须未被绑定,并且端口号未被占用,否则会报错。函数原型如下:

socket.bind(address)
  • address 参数代表套接字要绑定的地址,其格式取决于套接字的 family 参数。若 family 参数为 AF_INET,则 address 参数表示为二元组 (host, port),其中 host 是用字符串表示的主机地址,port 是用整型表示的端口号。

sendto() 函数

sendto() 函数用于向远程套接字对象发送数据。注意,该函数用于 UDP 进程间的无连接通信,远程套接字的地址在参数中指定,因此使用前不需要先与远程套接字连接。相对地,TCP 进程间面向连接的通信过程需要用 send() 函数。函数原型如下:

socket.sendto(bytes[, flags], address)
  • bytes 参数代表即将发送的 bytes 对象数据。例如,对于字符串 "hello world!" 而言,需要用 encode() 函数转换为 bytes 对象 b"hello world!" 才能进行网络传输。
  • flags 可选参数用于设置 sendto() 函数的特殊功能,默认值为 0,也可由一个或多个预定义值组成,用位或操作符 | 隔开。详情可参考 Unix 函数手册中的 sendto(2),flags 参数的常见取值有 MSG_OOB、MSG_EOR、MSG_DONTROUTE 等。
  • address 参数代表远程套接字的地址,其格式取决于套接字的 family 参数。若 family 参数为 AF_INET,则 address 参数表示为二元组 (host, port),其中 host 是用字符串表示的主机地址,port 是用整型表示的端口号。

sendto() 函数的返回值是发送数据的字节数。

recvfrom() 函数

recvfrom() 函数用于从远程套接字对象接收数据。注意,与 sendto() 函数不同,recvfrom() 函数既可用于 UDP 进程间通信,也能用于 TCP 进程间通信。函数原型如下:

socket.recvfrom(bufsize[, flags])
  • bufsize 参数代表套接字可接收数据的最大字节数。注意,为了使硬件设备与网络传输更好地匹配,bufsize 参数的值最好设置为 2 的幂次方,例如 4096
  • flags 可选参数用于设置 recv() 函数的特殊功能,默认值为 0,也可由一个或多个预定义值组成,用位或操作符 |隔开。详情可参考 Unix 函数手册中的 recvfrom(2),flags 参数的常见取值有 MSG_OOB、MSG_PEEK、MSG_WAITALL 等。

recvfrom() 函数的返回值是二元组 (bytes, address),其中 bytes 是接收到的 bytes 对象数据,address 是发送方的 IP 地址与端口号,用二元组 (host, port) 表示。注意,recv() 函数的返回值只有 bytes 对象数据。

close() 函数

close() 函数用于关闭本地套接字对象,释放与该套接字连接的所有资源。

socket.close()

0x07 总结

本文介绍了 UDP 协议的基础知识,并与 TCP 协议进行对比,再用 Python 3 实现并演示了 UDP 服务器与客户端的通信过程,最后将脚本中涉及到的 Python API 做成了的参考索引,有助于读者理解实现过程。

感谢各位的阅读,笔者水平有限,若有不足或错误之处请谅解并告知,希望自己对 TCP 和 UDP 的浅薄理解,能帮助读者更好地理解传输层协议。

本文的相关参考请移步至:

TCP和UDP的最完整的区别
TCP和UDP之间的区别
UDP编程 - 廖雪峰的官方网站

Python 绝技 —— UDP 服务器与客户端相关推荐

  1. Python核心编程(第3版)第2章网络编程中关于tcp/udp服务器和客户端实现代码的运行出错的修正

    在Python核心编程(第3版)第2章网络编程中, 关于tcp/udp服务器和客户端实现代码的运行会出现 ['str' does not support the buffer interface]之类 ...

  2. 【python网络编程】创建TCP/UDP服务器进行客户端/服务器间通信

    客户端/服务器网络编程介绍 套接字:通信端点 实例:客户端发送数据,接收服务器返回的时间戳 用Python 编写FTP 客户端程序 客户端/服务器网络编程介绍 软件服务器也运行在一块硬件之上,但是没有 ...

  3. 【Java 网络编程】UDP 服务器 与 客户端持续交互 案例

    文章目录 I UDP 交互原理 II UDP 服务器端代码示例 III UDP 客户端代码示例 IV 服务器 客户端 运行结果 I UDP 交互原理 1. UDP 单播传输流程 : A 给 B 发送数 ...

  4. c#基于socket的UDP服务器和客户端实例

    基于Udp协议是无连接模式通讯,占用资源少,响应速度快,延时低.至于可靠性,可通过应用层的控制来满足.(不可靠连接) 使用Udp协议通讯需要具备以下几个条件: (1).建立一个套接字(Socket) ...

  5. Python实现FTP服务器和客户端

    基础知识 FTP只通过TCP连接,FTP不同于其他服务的是它使用了两个端口, 一个数据端口和一个命令端口(或称为控制端口). 通常21端口是命令端口,20端口是数据端口.当混入主动/被动模式的概念时, ...

  6. UDP服务器和客户端

    1  服务器端 代码: '''GUI MAIN''' import tkinter from tkinter import END, messagebox import os import time ...

  7. TCP与UDP服务器和客户端的搭建

    udp服务器 #include <stdio.h> #include <string.h> #include <sys/types.h> #include < ...

  8. python web.py服务器与客户端

    web.py是python中一个相对容易上手的web服务器搭建工具. 1 安装方式 web.py可以直接通过pip install 的方式安装即可,即: pip install web.py 2 服务 ...

  9. python 练习 tcp 服务器与客户端发、接信息,pycharm

    背景: win8.1, pycharm 2021.3.1, python 3.9.7 自带idle, vs code 1.64.2 现象: 1. 代码不知道如何在idle 和 vs code中运行,总 ...

  10. UDP服务器判断客户端是否掉线

    client.cpp #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include<stdlib.h> #includ ...

最新文章

  1. python3.x下 smtp发送html邮件和附件
  2. python网络爬虫实战 吕文翔_实战Python网络爬虫
  3. 回调函数这个是什么鬼?
  4. tomcat lifecyclelistener_大公司程序员带你死磕Tomcat系列(五)——容器
  5. ccsa安学网小程序_【大检修】“CCSA安学网”掀起指尖上的学习热潮,助力1#大检修...
  6. Python运算符is与==的区别
  7. 时至 2018 年,还有必要学 Vim 吗?
  8. 里约奥运会的五项技术创新
  9. 博弈论(二)完全信息静态博弈
  10. Linux中vi命令用法
  11. Android Studio 占C盘空间是什么原因?
  12. php pwuj 挂马,网站挂马原理及实战
  13. php 防挂马,织梦dedecms安全设置防挂马教程
  14. FATAL Exited too quickly (process log may have details)
  15. HTML5网页设计制作基础大二dreamweaver作业、使用HTML+CSS技术制作博客网站(5个页面)...
  16. 九江高考2021成绩查询,2021九江市地区高考成绩排名查询,九江市高考各高中成绩喜报榜单...
  17. 提问的智慧 - 艾瑞克.史蒂文.雷蒙德(Eric Steven Raymond)
  18. 日本全新超级计算机ABCI向“全球最快”目标冲击
  19. 企业云化应用(SaaS)是未来绝对的趋势
  20. Linux系统使用rsync命令进行本地备份还原详解

热门文章

  1. lucene 分词实现
  2. 7.29~8.2 广州软件所-实习工作日记
  3. winsock 收发广播包
  4. 敏捷测试与普通测试的区别
  5. asterisk app命令中文翻译
  6. 大公司面试c语言收集(6)
  7. Kafka学习笔记之Kafka三款监控工具
  8. java中级面试题 之基础篇
  9. POJ3494Largest Submatrix of All 1’s[单调栈]
  10. UIImageView三种方式 和 位置分布