Python 网络编程
Table of Contents
环境搭建(OS X)
准备
下载 Vagrant 和 VirtualBox 。 在 mac 下,我们可以:
brew cask install vagrant
brew cask install virtualbox
启动
使用下面的命令:
mkdir workspace
cd workspace
vagrant init brandon-rhodes/playground #这一步会下载镜像
vagrant up # 后面好像要加 --provider virtualbox, 不加也可
vagrant ssh
这时我们打开 virtualbox 会看到一个正在运行的虚拟机。
当 ssh 登录虚拟机以后,执行
./launch.sh
这个脚本会启动上图中需要的 container,然后做些配置。到这一步环境就算是可以了。
Chapter 1 客户算 服务器网络编程简介
展示了根据地址来获得经度,维度信息。
Chapter 2 UDP
套接字
套接字(socket)是一个通信端点。下面是一个简单 UDP 服务器和 UDP 客户端。
import argparse
from datetime import datetime
import socket
MAX_BYTES = 65535
def server(port):
# AF_INET 是 IPv4 网络协议的套接字类型。AF 表示地址族。SOCK_DGRAM 表示数据报类型,即在网络上使用 UDP 协议
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)
混杂客户端和垃圾回复
上面的客户端由于没有检查数据报的原地址,也没有验证数据报是否确实是服务器发回的响应。
只有使用编写良好的优秀加密算法,才可以保证程序与正确的服务器进行通信。还有两个快速解决方案。第一个方法,设计使用在请求中包含唯一标示符或请求 ID 的协议。 第二个,检查地址。
不可靠性、退避、阻塞和超时
下面模拟一下丢包的情况。
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)
sock.connect((hostname, port))
print('Client socket name is {}'.format(sock.getsockname()))
delay = 0.1 # seconds
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)
except socket.timeout as exc:
delay *= 2 # wait even longer for the next request
if delay > 2.0:
raise RuntimeError('I think the server is down') from exc
else:
break # we are done, and can stop looping
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)
使用 connect
上个例子中,我们发现使用了一个新的函数 connect()。
广播
import argparse, socket
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 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)
何时使用 UDP
实际上,只有在每次只发送一条信息然后等待响应的时候,UDP 才是高效的。
如果一口气发送多条信息,那么使用 MQ 这样的消息队列实际上效率会更高。
UDP 最强大的功能是广播。
TCP
例子
import argparse, socket
def recvall(sock, length):
data = b''
while len(data) < length:
more = sock.recv(length - len(data))
if not more:
raise EOFError('was expecting %d bytes but only received'
' %d bytes before the socket closed'
% (length, len(data)))
data += more
return data
def server(interface, port):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((interface, port))
sock.listen(1)
print('Listening at', sock.getsockname())
while True:
print('Waiting to accept a new connection')
sc, sockname = sock.accept()
print('We have accepted a connection from', sockname)
print(' Socket name:', sc.getsockname())
print(' Socket peer:', sc.getpeername())
message = recvall(sc, 16)
print(' Incoming sixteen-octet message:', repr(message))
sc.sendall(b'Farewell, client')
sc.close()
print(' Reply sent, socket closed')
def client(host, port):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host, port))
print('Client has been assigned socket name', sock.getsockname())
sock.sendall(b'Hi there, server')
reply = recvall(sock, 16)
print('The server said', repr(reply))
sock.close()
if __name__ == '__main__':
choices = {'client': client, 'server': server}
parser = argparse.ArgumentParser(description='Send and receive over TCP')
parser.add_argument('role', choices=choices, help='which role to play')
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='TCP port (default 1060)')
args = parser.parse_args()
function = choices[args.role]
function(args.host, args.p)
死锁
在 TCP 中很容易发生。