一封信一封信地收

👁️ 2 人浏览 💬 0 人评论 ❤️ 添加收藏

(每道题开头都有同一段:上面的内存版 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 个包丢掉(发送方毫不知情)

一个服务端 socket,两个客户端各发一次,看服务端两次 recvfrom 各拿到什么、来源端口是否不同:

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))
a = net.socket()
b = net.socket()
a.sendto(b"from-a", ("127.0.0.1", 9000))
b.sendto(b"from-b", ("127.0.0.1", 9000))
d1, f1 = srv.recvfrom(1024)
d2, f2 = srv.recvfrom(1024)
print(d1.decode() + "/" + d2.decode() + "/" + str(f1 != f2) + "/" + str(f1[1]))
提交你的答案
请登录后提交答案。
去登录
代码编辑器
Ctrl + Enter 运行
本次输入:
输出:

                        
👩‍🏫
AI
💬 题目评论

全部评论