python网络编程——UDP

1.UDP。

UDP叫做用户数据报协议,是OSI七层参考模型中传输层使用的协议,他提供的是不可靠传输,既它在传输过程中不保证数据的完整性!

2.端口号。

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停止启动一个新的服务器,然后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

客户端:

客户端是同一台机器的自环接口127.0.0.1,不会响应
客户端是本机外网IP,可以连通
客户端是其他机器,可以连接

9.UDP分组。

由于某些数据包过大传输过程中需要将它分成一组一组的小数据包。MTU指“最大传输单元”或“最大数据包容量”。下面是模拟发送大型UDP数据包:
代码:

#!/usr/bin/python
#coding:utf-8
import argparse
import socket

#python3.7已经弃用IN模块了手动编写
class IN:
    IP_MTU = 14
    IP_MTU_DISCOVER = 10
    IP_PMTUDISC_DO = 2

if not hasattr(IN,'IP_MTU'):
    raise RuntimeError('cannot perform MTU discovery on this combination of operating '
                       'system and python distribution')

def send_big_datagram(host,port):
    sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    #获取并设置套接字选项
    sock.setsockopt(socket.IPPROTO_IP,IN.IP_MTU_DISCOVER,IN.IP_PMTUDISC_DO)
    sock.connect((host,port))
    try:
        sock.send(b'#' * 65000)
    #设置异常如果超过MTU打印出实际MTU
    except socket.error:
        print('Alas,the datagram did not make it')
        max_mtu = sock.getsockopt(socket.IPPROTO_IP,IN.IP_MTU)
        print('Actual MTU:{}'.format(max_mtu))
    else:
        print('The big datagram was sent!')
if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Send UDP packet to get MTU')
    parser.add_argument('host',help='the host to which to target the packet')
    parser.add_argument('-p',metavar='PORT',type=int,default=1060,help='UDP port (default 1060)')
    args = parser.parse_args()
    send_big_datagram(args.host,args.p)

测试结果:
在这里插入图片描述

10.套接字选项。

getsockopt()和setsockopt()来获取和设置,第一个参数为所属选项组,第二个参数为要设置的选项名
比如设置广播:
在这里插入图片描述

11.广播。

通过广播可以将数据报的目标地址设置为本机连接的整个子网,然后使用物理网卡将数据报广播,这要就无需复制该数据包并单独将其发送给所有连接至该子网的主机了。
代码:

#!/usr/bin/python
#coding:utf-8
import  argparse,socket

MAX_BYTES = 65535

def server(inerface,port):
    sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    sock.bind((inerface,port))
    print('Listening for datagrams at {}'.format(sock.getsockname()))
    while True:
        data,address = sock.recvfrom(MAX_BYTES)
        text = data.decode('ascii')
        print('The client at {} says {!r}'.format(address,text))

def client(network,port):
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    #设置允许使用广播
    sock.setsockopt(socket.SOL_SOCKET,socket.SO_BROADCAST,1)
    text = 'Broadcast datagram!'
    sock.sendto(text.encode('ascii'),(network,port))
    
if __name__ == '__main__':
    choices = {'client':client,'server':server}
    parser = argparse.ArgumentParser(description='Send,receive UDP broadcast')
    parser.add_argument('role',choices=choices,help='Which role to take')
    parser.add_argument('host',help='interface the server listens at;'
                        'network 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)

测试结果:
在这里插入图片描述


版权声明:本文为qq_40909772原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。