UDP使用IP地址和端口号进行标识,以此将数据包发送至目标地址。端口的应用解决了多个UDP数据包发送过程中使用同一信道的冲突问题。每个UDP数据包分配了一对无符号16位端口号,端口号的范围从0到65536。源端口标识了源机器上发送数据包的特定进程或程序,而目标端口则标识了目标IP地址上进行该会话的特定应用程序。形式如下:
Source (IP : port number) → Destination (IP : port number)
端口分类:
①知名端口(0~1023):被分配给最重要、最常用的服务。如:DNS的默认端口是53,SSH
是22,FTP是21。
②注册端口(1024~49151):操作系统上无任何特别之处,
③其余的端口(49152~65535):可以随意使用的端口。
3.套接字。
网络操作背后的系统调用都是围绕着套接字进行的,套接字是一个通信端点,操作系统使用整数来标识套接字,而python使用socket.socket()对象来更方便地表示套接字。下面使用socket.socket()函数创建一个使用自环接口的UDP服务器和客户端。
代码:
#!/usr/bin/python
#coding:utf-8
import argparse,socket
from datetime import datetime
MAX_BYTES = 65535
#服务端代码
def server(port):
'''
创建空的套接字AF_INET表示协议族,SOCK_DGRAM数据报类型
SOCK_DGRAM它表示在IP网络上使用UDP协议
'''
sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
#绑定IP地址和和端口
sock.bind(('127.0.0.1',port))
#getsockname()函数获取目前绑定的IP地址和端口号的二元组
print("Listening at {}".format(sock.getsockname()))
while True:
#设置接受数据的最大字节,recvfrom()函数将返回客户端地址和数据报内容两个值
data,address = sock.recvfrom(MAX_BYTES)
text = data.decode('ascii')
print('The client at {} says {!r}'.format(address,text))
text = 'Your data was {} bytes long'.format(len(data))
data = text.encode('ascii')
sock.sendto(data,address)
#客户端代码
def client(port):
sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
text = 'The time is {}'.format(datetime.now())
data = text.encode('ascii')
#设置要发送的信息和目标地址
sock.sendto(data,('127.0.0.1',port))
print('The OS assigned me the address {}'.format(sock.getsockname()))
data,address = sock.recvfrom(MAX_BYTES)
text = data.decode('ascii')
print('The srever {} replied {!r}'.format(address,text))
if __name__ == '__main__':
#设置命令行参数
choices = {'client':client,'server':server}
parser = argparse.ArgumentParser(description='Send and receive UDP locally')
parser.add_argument('role',choices=choices,help='Which role to play')
#用-p指定端口号
parser.add_argument('-p',metavar='PORT',type=int,default=1060,help='UDP port (default 1060)')
args = parser.parse_args()
function = choices[args.role]
function(args.p)
socket() 创建一个空的套接字
bind() 绑定UDP网络地址
getsockname() 获取套接字绑定的IP地址和端口
recvfrom() 接受返回发送该数据报的客户端地址和字节表示的数据报内容
sendto() 要发送的信息和目标地址
测试结果:
4.混杂客户端与垃圾回复。
recvfrom() 函数在接收数据报时并没有检查数据报的的源地址,也没有验证该数据包是否确实是服务器发回的响应,像这样不考虑地址是否正确,接收并处理所有的数据包的网络监听客户端的技术叫做混杂客户端。下面是监听模拟过程:
①启动一个新的服务器,然后Ctrl+Z停止
②运行客户端挂起等待
③伪造服务器响应。
④客户端等待结束,将伪造的服务器响应看成是服务器响应。
5.不可靠性、退避、阻塞和超时。
模拟数据包丢失和信号故障的UDP服务。
代码:
#!/usr/bin/python
#coding:utf-8
import argparse,random,socket,sys
MAX_BYTES = 65535
def server(inerface,port):
sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
sock.bind((inerface,port))
print('Listening at',sock.getsockname())
while True:
data,address = sock.recvfrom(MAX_BYTES)
#模拟包丢弃
if random.random() < 0.5:
print('Pretending to drop packet from {}'.format(address))
continue
text = data.decode('ascii')
print('The client at {} says {!r}'.format(address,text))
meassage = 'Your data was {} bytes long'.format(len(data))
sock.sendto(meassage.encode('ascii'),address)
def client(hostname,port):
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
hostname = sys.argv[2]
sock.connect((hostname,port))
print('Client socket name is {}'.format(sock.getsockname()))
#设置等待时间
delay = 0.1
text = 'This is another message'
data = text.encode('ascii')
while True:
sock.send(data)
print('Waiting up to {} seconds for a reply'.format(delay))
sock.settimeout(delay)
try:
data = sock.recv(MAX_BYTES)
break
#超过等待时间抛出socket.timeout异常
except socket.timeout:
#将等待时间翻倍
delay *= 2
if delay > 2.0:
raise RuntimeError('I think the server is down')
print('The server says {!r}'.format(data.decode('ascii')))
if __name__ == '__main__':
choices = {'client':client,'server':server}
parser = argparse.ArgumentParser(description='Send and receive UDP,'
'pretending packets are often dropped')
parser.add_argument('role',choices=choices,help='Which role to take')
parser.add_argument('host',help='interface the server listens at;'
'host the client sends to')
parser.add_argument('-p',metavar='PORT',type=int, default=1060,
help = 'UDP port (default 1060)')
args = parser.parse_args()
function = choices[args.role]
function(args.host,args.p)
该程序中的服务器并未始终响应客户端的请求,而是随机选择,只对收到的一半客户端请求做出响应。该客户端使用了一种叫做指数退避的技术就是让每次无法收到响应时的等待时间间隔翻倍。客户端还调用了settimeout()方法,用来通知操作系统,客户端进行一个套接字操作的最长等待时间是delay秒,一旦超过等待时间delay秒,就会抛出一个socket.timeout异常,recv调用就会中断。
测试结果:
6.连接UDP套接字。
connect():使用sendto()向服务器发送消息时必须每次向服务器给出服务器的IP地址和端口。而如果使用connect()调用,这样就可以简单地把发送的数据作为参数传入send()调用,不用给出服务器地址。connect()只是简单地将连接的地址写入操作系统的内存,以供之后send()和recv()调用。
7.请求ID。
请求ID是解决重复响应的好方法。重复响应问题是我们收到所有数据包后又收到一个被认为丢失的响应,此时可能误解这个是当前请求的响应。随机选择请求ID可以预防最简单的电子欺骗(spoofing)。
8.绑定接口。
服务器bind()绑定接口时,可以使用:
127.0.0.1 本机上其他应用程序接口
空字符串或者0.0.0.0 该服务器的任意网络接口
该服务器的一个外网IP
### 最后
不知道你们用的什么环境,我一般都是用的Python3.6环境和pycharm解释器,没有软件,或者没有资料,没人解答问题,都可以免费领取(包括今天的代码),过几天我还会做个视频教程出来,有需要也可以领取~
给大家准备的学习资料包括但不限于:
Python 环境、pycharm编辑器/永久激活/翻译插件
python 零基础视频教程
Python 界面开发实战教程
Python 爬虫实战教程
Python 数据分析实战教程
python 游戏开发实战教程
Python 电子书100本
Python 学习路线规划
![](https://img-blog.csdnimg.cn/d29631674929476f9c3b30f7ff58dff0.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2ZlaTM0Nzc5NTc5MA==,size_16,color_FFFFFF,t_70)
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化学习资料的朋友,可以戳这里无偿获取](https://bbs.csdn.net/topics/618317507)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**