代码清单2-1 使用自环接口的UDP服务器和客户端

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)
    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)

  服务器首先创建了一个空套接字,这个套接字没有与任何IP地址或端口号绑定,也没有进行任何连接。但该套接字还是标记了所属的特定类别:协议族AF_INET以及数据报类型SOCK_DGRAM。AF_INET为Ipv4协议的套接字类型,SOCK_DGRAM为UDP协议。

  sock.bind()命令请求绑定一个UDP网络地址,绑定失败(如端口号被占用)则会抛出异常

  format为字符串格式化命令。{!r}对应repr()(这里不懂)。sock.getsockname()获取该套接字的二元组。

  while True:进入循环,不断运行recvfrom()接受消息。MAX_BYTES为可接受消息的最大值。decode解码,encode编码,sendto发送消息。其中address对应一个二元组,记录发送该消息的主机的ip地址和端口号,如(127.0.0.1, 1060)

  客户端创建套接字后,并没有与ip地址和端口号绑定,操作系统会随机分配一个端口号。

  

  代码清单2-2 运行在不同机器上的UDP服务器与客户端

import argparse, random, socket, sys

MAX_BYTES = 65535

def server(interface, port):
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind((interface, 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))
        message = 'Your data was {} bytes long'.format(len(data))
        sock.sendto(message.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 anthor message'
    data = text.encode('ascii')
    while True:
        sock.send(data)
        print('Wating up to {} seconds for a delay'.format(delay))
        sock.settimeout(delay)
        try:
            data = sock.recv(MAX_BYTES)
        except socket.timeout:
            delay *= 2
            if delay > 2.0:
                raise RuntimeError('I think the server is down')
        else:
            break
    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 listen 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)

  server服务器端,sock.bind((interface, port))绑定本地接口,interface为本地IP地址,设成空字符串表示任何本地接口。

  while True:循环,随机丢弃连接请求,只处理一半消息。

  sys.argv[2],获取命令行参数中的第三项(命令行 python udp_2_2.py client 127.0.0.1,第三项为127.0.0.1)。

  sock.connect((hostname, port))连接服务器端口。当客户端尝试使用套接字时,操作系统会为其随机分配一个临时端口(隐式绑定)。此外,运行connect(),如果操作系统发现传入的数据包返回地址与已连接的地址不同,则会将该数据包丢弃,解决了客户端混杂性的问题。

  指数退避技术:尝试重发数据包的频率越来越低。

  

  代码清单2-3 发送大型UDP数据包

import IN, argparse, socket

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)
    except socket.error:
        print('The datagam 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)

  本程序没有顺利执行,显示没有import IN 失败

  代码清单2-4,使用UDP广播

import socket, argparse

BUFSIZE = 65535

def server(interface, port):
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind((interface, port))
    print('Listening for datagrams at {}'.format(sock.getsockname()))
    while True:
        data, address = sock.recvfrom(BUFSIZE)
        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 listen at;''network the cllient 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)

  客户端和服务器端设置并不复杂。服务器端只负责接收数据,客户端只负责发送数据。

  客户端setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1),setsockopt为设置套接字选项,SO_BROADCAST该选项允许发送并接收UDP数据包

  服务器端运行代码后开始监听:

python udp_2_4.py server ""

  客户端运行代码

python udp_2_4.py client 192.168.1.129

  向本地192.168.1.129地址发送数据包

  运行代码

python udp_2_4.py client 192.168.1.255

  或者

python udp_2_4.py client "<broadcast>"

  发送广播。

  其中'<broadcast>'为Python特殊主机名,表示广播地址。