Skip to content

Commit 6c9a177

Browse files
authored
Add files via upload
1 parent 2a2edb1 commit 6c9a177

1 file changed

Lines changed: 136 additions & 0 deletions

File tree

pywwwgetadv_udpseq.py

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
2+
# pywwwgetadv_udpseq.py
3+
# Lightweight sequence-numbered UDP transfer with optional retransmit
4+
# Compatible with Python 2 & 3, same high-level API style as pywwwgetadv
5+
6+
from __future__ import absolute_import, division, print_function
7+
import socket, struct, time, sys, os
8+
9+
# ---- UDP SEQ PROTOCOL ----
10+
# Header: magic(4) ver(1) flags(1) seq(u32) total(u32)
11+
# flags: 0x01=data, 0x02=done, 0x04=ack
12+
_MAGIC = b"PWGS"
13+
_VER = 1
14+
_HDR = "!4sBBII"
15+
_HDR_LEN = struct.calcsize(_HDR)
16+
17+
FLAG_DATA = 0x01
18+
FLAG_DONE = 0x02
19+
FLAG_ACK = 0x04
20+
21+
DEFAULT_CHUNK = 1024
22+
23+
def _now():
24+
return time.time()
25+
26+
def _send(sock, pkt, addr=None):
27+
if addr:
28+
sock.sendto(pkt, addr)
29+
else:
30+
sock.send(pkt)
31+
32+
def udp_send_fileobj(fileobj, host, port, chunk=DEFAULT_CHUNK, retries=5, window=8, timeout=0.5, progress=False):
33+
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
34+
sock.settimeout(timeout)
35+
addr = (host, int(port))
36+
seq = 0
37+
inflight = {}
38+
eof = False
39+
sent_bytes = 0
40+
41+
try:
42+
while not eof or inflight:
43+
# fill window
44+
while not eof and len(inflight) < window:
45+
data = fileobj.read(chunk)
46+
if not data:
47+
eof = True
48+
break
49+
hdr = struct.pack(_HDR, _MAGIC, _VER, FLAG_DATA, seq, 0)
50+
pkt = hdr + data
51+
_send(sock, pkt, addr)
52+
inflight[seq] = (_now(), pkt, 0)
53+
sent_bytes += len(data)
54+
seq += 1
55+
56+
# send DONE when eof and nothing new to queue
57+
if eof and not inflight:
58+
done = struct.pack(_HDR, _MAGIC, _VER, FLAG_DONE, seq, sent_bytes)
59+
for _ in range(3):
60+
_send(sock, done, addr)
61+
break
62+
63+
# wait for ACKs
64+
try:
65+
data, _ = sock.recvfrom(1024)
66+
except socket.timeout:
67+
# retransmit timed-out packets
68+
for s,(t,p,c) in list(inflight.items()):
69+
if _now() - t > timeout:
70+
if c < retries:
71+
_send(sock, p, addr)
72+
inflight[s] = (_now(), p, c+1)
73+
else:
74+
raise IOError("UDP retransmit limit reached")
75+
continue
76+
77+
if len(data) >= _HDR_LEN:
78+
magic, ver, flags, aseq, total = struct.unpack(_HDR, data[:_HDR_LEN])
79+
if magic == _MAGIC and flags & FLAG_ACK:
80+
inflight.pop(aseq, None)
81+
if progress:
82+
sys.stdout.write("\rSent %d bytes" % sent_bytes)
83+
sys.stdout.flush()
84+
finally:
85+
sock.close()
86+
return sent_bytes
87+
88+
def udp_recv_to_file(bind, port, outfile, timeout=1.0, progress=False):
89+
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
90+
sock.bind((bind, int(port)))
91+
sock.settimeout(timeout)
92+
93+
expected = 0
94+
received = {}
95+
total = None
96+
97+
try:
98+
while True:
99+
try:
100+
data, addr = sock.recvfrom(65535)
101+
except socket.timeout:
102+
if total is not None and expected >= total:
103+
break
104+
continue
105+
106+
if len(data) < _HDR_LEN:
107+
continue
108+
magic, ver, flags, seq, ttotal = struct.unpack(_HDR, data[:_HDR_LEN])
109+
if magic != _MAGIC:
110+
continue
111+
112+
if flags & FLAG_DATA:
113+
payload = data[_HDR_LEN:]
114+
if seq not in received:
115+
received[seq] = payload
116+
# send ACK
117+
ack = struct.pack(_HDR, _MAGIC, _VER, FLAG_ACK, seq, 0)
118+
sock.sendto(ack, addr)
119+
120+
# write in order
121+
while expected in received:
122+
chunk = received.pop(expected)
123+
outfile.write(chunk)
124+
expected += 1
125+
if progress:
126+
sys.stdout.write("\rRecv chunks: %d" % expected)
127+
sys.stdout.flush()
128+
129+
elif flags & FLAG_DONE:
130+
total = expected
131+
break
132+
finally:
133+
sock.close()
134+
return expected
135+
136+
__all__ = ["udp_send_fileobj", "udp_recv_to_file"]

0 commit comments

Comments
 (0)