代码清单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特殊主机名,表示广播地址。