Python网络编程学习笔记(一)——UDP
代码清单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特殊主机名,表示广播地址。