《Python网络编程》学习笔记--UDP协议

第二章中主要介绍了UDP协议

  • UDP协议的定义(转自百度百科)  

  UDP是OSI参考模型中一种无连接的传输层协议,它主要用于不要求分组顺序到达的传输中,分组传输顺序的检查与排序由应用层完成,提供面向事务的简单不可靠信息传送服务。UDP 协议基本上是IP协议与上层协议的接口。UDP协议适用端口分别运行在同一台设备上的多个应用程序。
  UDP提供了无连接通信,且不对传送数据包进行可靠性保证,适合于一次传输少量数据,UDP传输的可靠性由应用层负责。常用的UDP端口号有:
  DNS(53) TFTP(69) SNMP(161)
  UDP报文没有可靠性保证、顺序保证和流量控制字段等,可靠性较差。但是正因为UDP协议的控制选项较少,在数据传输过程中延迟小、数据传输效率高,适合对可靠性要求不高的应用程序,或者可以保障可靠性的应用程序,如DNS、TFTP、SNMP等。
  UDP在IP报文中的位置如图所示。

  • 端口号

  无符号16位数(0-65536)其中知名端口(0-1023)、注册端口(1024-49151)、其余端口(49152-65535)

  Source(IP:port number) ->Destination(IP:port number)

  在python中可以使用socket模块的getservbyname()函数获取知名端口名对应的端口号例如

import socket

socket.getservbyname('domain') Out[4]: 53 socket.getservbyname('http') Out[5]: 80
  •  套接字

  在设计网络编程API时,Python标准库在底层对兼容POSIX操作系统(注:可移植操作系统接口(Portable Operating System Interface of UNIX,缩写为 POSIX ),如LInux和MacOS)网络操作的底层系统调用进行了封装,并未所有普通的原始调用提供了一个简单的基于对象的系统。封装后的Python函数名与原始系统调用名相同。Python的这种设计使开发者可以使用早已熟知的方法来调用传统系统。

  无论对于WIndows还是POSIX系统,网络操作背后的系统调用斗神围绕着套接字(Socket)这一概念进行的。

  套接字是一个通信端点,操作系统使用整数标识套接字,而Python使用socket.socket对象来更方便的表示套接字,该对象内部维护了操作系统标识套接字的整数(可以调用它的fileno()方法来查看),每当调用socket.socket对象的方法请求使用该套接字的系统调用时,该对象都会自动使用内部维护的套接字整数标识符。

  根据书上的代码,我们可以建立一个简单的UDP客户端

#!/usr/bin/env python3
# Foundations of Python Network Programming, Third Edition
# https://github.com/brandon-rhodes/fopnp/blob/m/py3/chapter02/udp_local.py
# UDP client and server on localhost

import argparse, socket
from datetime import datetime

MAX_BYTES = 65535


def server(port):
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind(('127.0.0.1', port))
    print('Listening 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))
        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)  # Danger! See Chapter 2
    text = data.decode('ascii')
    print('The server {} 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')
    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)

  使用powshell(Windows下(目录有空格转义居然是用`,试了半天0.0)定位到当前目录

  输入 

 python udp_local.py server  #udp_local.py是本文件名

  当然如果端口被占用(例如本例子中的1060)会提示

Traceback (most recent call last):
  File "udp_local.py", line 44, in <module>
    function(args.p)
  File "udp_local.py", line 14, in server
    sock.bind(('127.0.0.1', port))
OSError: [WinError 10048] 通常每个套接字地址(协议/网络地址/端口)只允许使用一次。

  之后再打开一个shell定位到当前页面输入

python udp_local.py client

  会显示

The OS assigned me the address ('0.0.0.0', 59457)
The server ('127.0.0.1', 1060) replied 'Your data was 38 bytes long'

  而server窗口同时显示

The client at ('127.0.0.1', 59457) says 'The time is 2018-01-15 16:24:19.498818'

  而如果server已关闭本条指令会在powershell显示

The OS assigned me the address ('0.0.0.0', 50859)
Traceback (most recent call last):
  File "udp_local.py", line 44, in <module>
    function(args.p)
  File "udp_local.py", line 31, in client
    data, address = sock.recvfrom(MAX_BYTES)  # Danger! See Chapter 2
ConnectionResetError: [WinError 10054] 远程主机强迫关闭了一个现有的连接。

   程序中使用了sock.sendto()函数(需要指定地址和端口)

     使用了sock.getsockname()调用查看IP地址和端口号,在输出中我们可以看出在这里分配的是59457端口
  • 混杂客户端与垃圾回复

  从上面的程序中我们也能明显看出,这样的程序实际上是相当危险的,在客户端等待服务器的响应时(在windows下可以通过发送响应中添加time.sleep()实现)我们可以向客户端程序发送一个‘伪造的信息’,如

>>>import socket
>>>sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
>>>sock.sendto('FAKE'.encode('ascii'),('127.0.0.1',59457))

  客户端会立即结束等待,并将此响应看为服务器的响应

  像这样不考虑地址是否正确,接受并处理所有收到的数据包的网络监听客户端在技术上叫做混杂(promiscuous)客户端,在这里是一种问题。但是当我们进行网络监控时,需要监控到达某一接口的所有数据包时会故意使用这种客户端。

  优秀的加密方法,才可以保证程序与正确的服务器进行通信。但是当无法做到时,可以使用以下两种方案

  1. 设计或使用在请求中包含唯一标识符或请求ID的协议
  2. 检查响应数据包的地址与请求数据包的地址是否相同,也可以使用connect()来阻止其他地址向客户端发送数据包

  

原文地址:https://www.cnblogs.com/take-fetter/p/8283235.html