一次完整的来回
(每道题开头都有同一段:上面的内存版 UDP。)
贯穿全条的内存版 UDP(判题机不联网;接口和真 socket 一字不差,真机上把 net.socket() 换成 socket.socket(AF_INET, SOCK_DGRAM) 就是真的):
net = FakeNet() 一个内存版的网络:每个 (地址, 端口) 一个信箱
s = net.socket() s.bind((host, port)) 端口 0 由系统挑;已被占用报 Address already in use
s.sendto(data, addr) 超过 65507 字节报 Message too long;没 bind 就发会自动挑端口;对面没人听 → 悄悄丢(connect 过的 socket 下次 recv 报 Connection refused)
s.recvfrom(bufsize) 交回 (data, 来源地址);一次一个完整数据报;缓冲小了就截断;信箱空:设了超时报 timed out,没设就永远等(内存版抛 RuntimeError 提醒)
s.settimeout(秒) / s.connect(addr) + send / recv / s.close()
net.drop_next(k) 接下来 k 个包丢掉(发送方毫不知情)回显服务端收一个回一个(原样);客户端发 "ping"、收回信;再让客户端对着没人听的端口发一次看超时:
import collections
MAX_DATAGRAM = 65507
class FakeNet:
"""内存版的网络:每个 (地址, 端口) 一个信箱,信箱里是一个个完整的数据报。"""
def __init__(self):
self.boxes = {}
self.next_port = 40000
self.drops = 0
self.sent = 0
def socket(self):
return FakeSock(self)
def drop_next(self, k):
self.drops = k
class FakeSock:
def __init__(self, net):
self.net = net
self.addr = None
self.timeout = None
self.peer = None
self.refused = False
def bind(self, addr):
host, port = addr
if port == 0:
port = self.net.next_port
self.net.next_port += 1
if (host, port) in self.net.boxes:
raise OSError(98, "Address already in use")
self.addr = (host, port)
self.net.boxes[self.addr] = collections.deque()
def getsockname(self):
return self.addr
def settimeout(self, t):
self.timeout = t
def sendto(self, data, addr):
if len(data) > MAX_DATAGRAM:
raise OSError(90, "Message too long")
if self.addr is None:
self.bind(("127.0.0.1", 0)) # 没 bind 就发:系统自动挑一个端口
self.net.sent += 1
if self.net.drops > 0:
self.net.drops -= 1
return len(data) # 丢了——发送方毫不知情
if addr in self.net.boxes:
self.net.boxes[addr].append((bytes(data), self.addr))
else:
self.refused = True # 没人听:Linux 会回一个 ICMP 不可达,只有 connect 过的 socket 才看得到
return len(data)
def recvfrom(self, bufsize):
if self.refused and self.peer is not None:
self.refused = False
raise ConnectionRefusedError(111, "Connection refused")
box = self.net.boxes.get(self.addr)
if not box:
if self.timeout is None:
raise RuntimeError("信箱是空的又没设超时:真的 socket 会在这里永远等下去")
raise TimeoutError("timed out")
data, frm = box.popleft()
return data[:bufsize], frm # 缓冲区小了就截断,多出来的部分丢掉
def connect(self, addr):
self.peer = addr
def send(self, data):
return self.sendto(data, self.peer)
def recv(self, bufsize):
return self.recvfrom(bufsize)[0]
def close(self):
if self.addr in self.net.boxes:
del self.net.boxes[self.addr]
self.addr = None
net = FakeNet()
srv = net.socket()
srv.bind(("127.0.0.1", 9000))
c = net.socket()
c.settimeout(0.2)
c.sendto(b"ping", ("127.0.0.1", 9000))
data, frm = srv.recvfrom(65535)
srv.sendto(data, frm)
reply = c.recvfrom(65535)[0].decode()
c.sendto(b"ping", ("127.0.0.1", 9999))
try:
c.recvfrom(65535)
second = "有回"
except TimeoutError:
second = "超时"
print(reply + "/" + second)
全部评论