PageRenderTime 37ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/examples/networking/stacklesssocket30.py

http://stacklessexamples.googlecode.com/
Python | 582 lines | 409 code | 74 blank | 99 comment | 68 complexity | c428c32d9a6acf1fa4950f0e26ac27d8 MD5 | raw file
  1. #
  2. # Stackless compatible socket module (for Python 3.0+):
  3. #
  4. # Author: Richard Tew <richard.m.tew@gmail.com>
  5. #
  6. # This code was written to serve as an example of Stackless Python usage.
  7. # Feel free to email me with any questions, comments, or suggestions for
  8. # improvement.
  9. #
  10. # This wraps the asyncore module and the dispatcher class it provides in order
  11. # write a socket module replacement that uses channels to allow calls to it to
  12. # block until a delayed event occurs.
  13. #
  14. # Not all aspects of the socket module are provided by this file. Examples of
  15. # it in use can be seen at the bottom of this file.
  16. #
  17. # NOTE: Versions of the asyncore module from Python 2.4 or later include bug
  18. # fixes and earlier versions will not guarantee correct behaviour.
  19. # Specifically, it monitors for errors on sockets where the version in
  20. # Python 2.3.3 does not.
  21. #
  22. # Possible improvements:
  23. # - More correct error handling. When there is an error on a socket found by
  24. # poll, there is no idea what it actually is.
  25. # - Launching each bit of incoming data in its own tasklet on the recvChannel
  26. # send is a little over the top. It should be possible to add it to the
  27. # rest of the queued data
  28. import stackless
  29. import asyncore, weakref
  30. import socket as stdsocket # We need the "socket" name for the function we export.
  31. # Cache socket module entries we may monkeypatch.
  32. _old_socket = stdsocket.socket
  33. _old_SocketIO = stdsocket.SocketIO
  34. _old_realsocket = stdsocket._realsocket
  35. def install():
  36. if stdsocket.socket is _new_socket:
  37. raise RuntimeError("Still installed")
  38. stdsocket._realsocket = _old_realsocket
  39. stdsocket.socket = _new_socket
  40. stdsocket.SocketIO = _new_SocketIO
  41. def uninstall():
  42. stdsocket._realsocket = _old_realsocket
  43. stdsocket.socket = _old_socket
  44. stdsocket.SocketIO = _old_SocketIO
  45. class _new_SocketIO(_old_SocketIO):
  46. def __init__(self, sock, mode):
  47. if not isinstance(sock, _fakesocket):
  48. raise RuntimeError("Bad socket '%s'" % sock.__class__.__name__)
  49. _old_SocketIO.__init__(self, sock, mode)
  50. # If we are to masquerade as the socket module, we need to provide the constants.
  51. for k in stdsocket.__all__:
  52. globals()[k] = stdsocket.__dict__[k]
  53. stringEncoding = "utf-8"
  54. # Someone needs to invoke asyncore.poll() regularly to keep the socket
  55. # data moving. The "ManageSockets" function here is a simple example
  56. # of such a function. It is started by StartManager(), which uses the
  57. # global "managerRunning" to ensure that no more than one copy is
  58. # running.
  59. #
  60. # If you think you can do this better, register an alternative to
  61. # StartManager using stacklesssocket_manager(). Your function will be
  62. # called every time a new socket is created; it's your responsibility
  63. # to ensure it doesn't start multiple copies of itself unnecessarily.
  64. #
  65. managerRunning = False
  66. def ManageSockets():
  67. global managerRunning
  68. t = stackless.getcurrent()
  69. while len(asyncore.socket_map):
  70. # Check the sockets for activity.
  71. t.block_trap = False
  72. asyncore.poll(0.01)
  73. t.block_trap = True
  74. # Yield to give other tasklets a chance to be scheduled.
  75. stackless.schedule()
  76. managerRunning = False
  77. def StartManager():
  78. global managerRunning
  79. if not managerRunning:
  80. managerRunning = True
  81. stackless.tasklet(ManageSockets)()
  82. _manage_sockets_func = StartManager
  83. def stacklesssocket_manager(mgr):
  84. global _manage_sockets_func
  85. _manage_sockets_func = mgr
  86. def socket(*args, **kwargs):
  87. import sys
  88. if "socket" in sys.modules and sys.modules["socket"] is not stdsocket:
  89. raise RuntimeError("Use 'stacklesssocket.install' instead of replacing the 'socket' module")
  90. class _new_socket(object):
  91. _old_socket.__doc__
  92. def __init__(self, family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None):
  93. sock = _old_socket(family, type, proto, fileno)
  94. _manage_sockets_func()
  95. self.__dict__["dispatcher"] = _fakesocket(sock)
  96. def __getattr__(self, attrName):
  97. # Forward nearly everything to the dispatcher
  98. if not attrName.startswith("__"):
  99. # I don't like forwarding __repr__
  100. return getattr(self.dispatcher, attrName)
  101. def __setattr__(self, attrName, attrValue):
  102. setattr(self.dispatcher, attrName, attrValue)
  103. def __del__(self):
  104. if not hasattr(self, "dispatcher"):
  105. return
  106. # Close dispatcher if it isn't already closed
  107. if self.dispatcher._fileno is not None:
  108. try:
  109. self.dispatcher.close()
  110. finally:
  111. self.dispatcher = None
  112. def makefile(self, *args, **kwargs):
  113. return _old_socket.makefile(self.dispatcher, *args, **kwargs)
  114. class _fakesocket(asyncore.dispatcher):
  115. connectChannel = None
  116. acceptChannel = None
  117. recvChannel = None
  118. wasConnected = False
  119. def __init__(self, realSocket):
  120. # This is worth doing. I was passing in an invalid socket which
  121. # was an instance of _fakesocket and it was causing tasklet death.
  122. if not isinstance(realSocket, _old_socket):
  123. raise RuntimeError("An invalid socket (class '%s') passed to _fakesocket" % realSocket.__class__.__name__)
  124. # This will register the real socket in the internal socket map.
  125. asyncore.dispatcher.__init__(self, realSocket)
  126. self.socket = realSocket
  127. self.recvChannel = stackless.channel()
  128. self.recvChannel.preference = 0
  129. self.readBytes = bytearray()
  130. self.readIdx = 0
  131. self.sendBuffer = bytearray()
  132. self.sendToBuffers = []
  133. def __del__(self):
  134. # There are no more users (sockets or files) of this fake socket, we
  135. # are safe to close it fully. If we don't, asyncore will choke on
  136. # the weakref failures.
  137. self.close()
  138. # The asyncore version of this function depends on socket being set
  139. # which is not the case when this fake socket has been closed.
  140. def __getattr__(self, attr):
  141. if not hasattr(self, "socket"):
  142. raise AttributeError("socket attribute unset on '"+ attr +"' lookup")
  143. return getattr(self.socket, attr)
  144. def add_channel(self, map=None):
  145. if map is None:
  146. map = self._map
  147. map[self._fileno] = weakref.proxy(self)
  148. def writable(self):
  149. if self.socket.type != SOCK_DGRAM and not self.connected:
  150. return True
  151. return len(self.sendBuffer) or len(self.sendToBuffers)
  152. def accept(self):
  153. if not self.acceptChannel:
  154. self.acceptChannel = stackless.channel()
  155. return self.acceptChannel.receive()
  156. def connect(self, address):
  157. asyncore.dispatcher.connect(self, address)
  158. # UDP sockets do not connect.
  159. if self.socket.type != SOCK_DGRAM and not self.connected:
  160. if not self.connectChannel:
  161. self.connectChannel = stackless.channel()
  162. # Prefer the sender. Do not block when sending, given that
  163. # there is a tasklet known to be waiting, this will happen.
  164. self.connectChannel.preference = 1
  165. self.connectChannel.receive()
  166. def send(self, data, flags=0):
  167. if not self.connected:
  168. # The socket was never connected.
  169. if not self.wasConnected:
  170. raise error(10057, "Socket is not connected")
  171. # The socket has been closed already.
  172. raise error(EBADF, 'Bad file descriptor')
  173. self.sendBuffer.extend(data)
  174. stackless.schedule()
  175. return len(data)
  176. def sendall(self, data, flags=0):
  177. if not self.connected:
  178. # The socket was never connected.
  179. if not self.wasConnected:
  180. raise error(10057, "Socket is not connected")
  181. # The socket has been closed already.
  182. raise error(EBADF, 'Bad file descriptor')
  183. # WARNING: this will busy wait until all data is sent
  184. # It should be possible to do away with the busy wait with
  185. # the use of a channel.
  186. self.sendBuffer.extend(data)
  187. while self.sendBuffer:
  188. stackless.schedule()
  189. return len(data)
  190. def sendto(self, sendData, flags, sendAddress):
  191. waitChannel = None
  192. for idx, (data, address, channel, sentBytes) in enumerate(self.sendToBuffers):
  193. if address == sendAddress:
  194. self.sendToBuffers[idx] = (data + sendData, address, channel, sentBytes)
  195. waitChannel = channel
  196. break
  197. if waitChannel is None:
  198. waitChannel = stackless.channel()
  199. self.sendToBuffers.append((sendData, sendAddress, waitChannel, 0))
  200. return waitChannel.receive()
  201. # Read at most byteCount bytes.
  202. def recv(self, byteCount, flags=0):
  203. b = bytearray()
  204. self.recv_into(b, byteCount, flags)
  205. return b
  206. def recvfrom(self, byteCount, flags=0):
  207. if self.socket.type == SOCK_STREAM:
  208. return self.recv(byteCount), None
  209. # recvfrom() must not concatenate two or more packets.
  210. # Each call should return the first 'byteCount' part of the packet.
  211. data, address = self.recvChannel.receive()
  212. return data[:byteCount], address
  213. def recv_into(self, buffer, nbytes=0, flags=0):
  214. if len(buffer):
  215. nbytes = len(buffer)
  216. # recv() must not concatenate two or more data fragments sent with
  217. # send() on the remote side. Single fragment sent with single send()
  218. # call should be split into strings of length less than or equal
  219. # to 'byteCount', and returned by one or more recv() calls.
  220. remainingBytes = self.readIdx != len(self.readBytes)
  221. # TODO: Verify this connectivity behaviour.
  222. if not self.connected:
  223. # Sockets which have never been connected do this.
  224. if not self.wasConnected:
  225. raise error(10057, 'Socket is not connected')
  226. # Sockets which were connected, but no longer are, use
  227. # up the remaining input. Observed this with urllib.urlopen
  228. # where it closes the socket and then allows the caller to
  229. # use a file to access the body of the web page.
  230. elif not remainingBytes:
  231. self.readBytes = self.recvChannel.receive()
  232. self.readIdx = 0
  233. remainingBytes = len(self.readBytes)
  234. if nbytes == 1 and remainingBytes:
  235. buffer[:] = self.readBytes[self.readIdx]
  236. self.readIdx += 1
  237. return 1
  238. if nbytes == 0:
  239. nbytes = len(self.readBytes)
  240. if nbytes == 0:
  241. buffer[:] = []
  242. return 0
  243. if self.readIdx == 0 and nbytes >= len(self.readBytes):
  244. buffer[:] = self.readBytes
  245. self.readBytes = bytearray()
  246. return nbytes
  247. idx = self.readIdx + nbytes
  248. buffer[:] = self.readBytes[self.readIdx:idx]
  249. self.readBytes = self.readBytes[idx:]
  250. self.readIdx = 0
  251. return nbytes
  252. def close(self):
  253. asyncore.dispatcher.close(self)
  254. self.connected = False
  255. self.accepting = False
  256. self.sendBuffer = None # breaks the loop in sendall
  257. # Clear out all the channels with relevant errors.
  258. while self.acceptChannel and self.acceptChannel.balance < 0:
  259. self.acceptChannel.send_exception(error, 9, 'Bad file descriptor')
  260. while self.connectChannel and self.connectChannel.balance < 0:
  261. self.connectChannel.send_exception(error, 10061, 'Connection refused')
  262. while self.recvChannel and self.recvChannel.balance < 0:
  263. # The closing of a socket is indicted by receiving nothing. The
  264. # exception would have been sent if the server was killed, rather
  265. # than closed down gracefully.
  266. self.recvChannel.send(bytearray())
  267. #self.recvChannel.send_exception(error, 10054, 'Connection reset by peer')
  268. # asyncore doesn't support this. Why not?
  269. def fileno(self):
  270. return self.socket.fileno()
  271. def handle_accept(self):
  272. if self.acceptChannel and self.acceptChannel.balance < 0:
  273. t = asyncore.dispatcher.accept(self)
  274. if t is None:
  275. return
  276. currentSocket, clientAddress = t
  277. currentSocket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
  278. currentSocket.wasConnected = True
  279. stackless.tasklet(self.acceptChannel.send)((currentSocket, clientAddress))
  280. # Inform the blocked connect call that the connection has been made.
  281. def handle_connect(self):
  282. if self.socket.type != SOCK_DGRAM:
  283. self.wasConnected = True
  284. self.connectChannel.send(None)
  285. # Asyncore says its done but self.readBuffer may be non-empty
  286. # so can't close yet. Do nothing and let 'recv' trigger the close.
  287. def handle_close(self):
  288. pass
  289. # Some error, just close the channel and let that raise errors to
  290. # blocked calls.
  291. def handle_expt(self):
  292. self.close()
  293. def handle_read(self):
  294. try:
  295. if self.socket.type == SOCK_DGRAM:
  296. ret = self.socket.recvfrom(20000)
  297. else:
  298. ret = asyncore.dispatcher.recv(self, 20000)
  299. # Not sure this is correct, but it seems to give the
  300. # right behaviour. Namely removing the socket from
  301. # asyncore.
  302. if not ret:
  303. self.close()
  304. # Do not block.
  305. if self.recvChannel.balance < 0:
  306. # The channel prefers the sender. This means if there are waiting
  307. # receivers, the first will be scheduled with the given data.
  308. self.recvChannel.send(ret)
  309. else:
  310. # No waiting receivers. The send needs to block in a tasklet.
  311. stackless.tasklet(self.recvChannel.send)(ret)
  312. except stdsocket.error as err:
  313. # If there's a read error assume the connection is
  314. # broken and drop any pending output
  315. if self.sendBuffer:
  316. self.sendBuffer = bytearray()
  317. self.recvChannel.send_exception(stdsocket.error, err)
  318. def handle_write(self):
  319. if len(self.sendBuffer):
  320. sentBytes = asyncore.dispatcher.send(self, self.sendBuffer[:512])
  321. self.sendBuffer = self.sendBuffer[sentBytes:]
  322. elif len(self.sendToBuffers):
  323. data, address, channel, oldSentBytes = self.sendToBuffers[0]
  324. sentBytes = self.socket.sendto(data, 0, address)
  325. totalSentBytes = oldSentBytes + sentBytes
  326. if len(data) > sentBytes:
  327. self.sendToBuffers[0] = data[sentBytes:], address, channel, totalSentBytes
  328. else:
  329. del self.sendToBuffers[0]
  330. stackless.tasklet(channel.send)(totalSentBytes)
  331. if __name__ == '__main__':
  332. import sys
  333. import struct
  334. # Test code goes here.
  335. testAddress = "127.0.0.1", 3000
  336. info = -12345678
  337. data = struct.pack("i", info)
  338. dataLength = len(data)
  339. def TestTCPServer(address):
  340. global info, data, dataLength
  341. print("server listen socket creation")
  342. listenSocket = stdsocket.socket(AF_INET, SOCK_STREAM)
  343. listenSocket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
  344. listenSocket.bind(address)
  345. listenSocket.listen(5)
  346. NUM_TESTS = 2
  347. i = 1
  348. while i < NUM_TESTS + 1:
  349. # No need to schedule this tasklet as the accept should yield most
  350. # of the time on the underlying channel.
  351. print("server connection wait", i)
  352. currentSocket, clientAddress = listenSocket.accept()
  353. print("server", i, "listen socket", currentSocket.fileno(), "from", clientAddress)
  354. if i == 1:
  355. print("server closing (a)", i, "fd", currentSocket.fileno(), "id", id(currentSocket))
  356. currentSocket.close()
  357. print("server closed (a)", i)
  358. elif i == 2:
  359. print("server test", i, "send")
  360. currentSocket.send(data)
  361. print("server test", i, "recv")
  362. if currentSocket.recv(4) != "":
  363. print("server recv(1)", i, "FAIL")
  364. break
  365. try:
  366. v = currentSocket.recv(4)
  367. print("server recv(2)", i, "FAIL, expected error")
  368. except error:
  369. pass
  370. else:
  371. print("server closing (b)", i, "fd", currentSocket.fileno(), "id", id(currentSocket))
  372. currentSocket.close()
  373. print("server test", i, "OK")
  374. i += 1
  375. if i != NUM_TESTS+1:
  376. print("server: FAIL", i)
  377. else:
  378. print("server: OK", i)
  379. print("Done server")
  380. def TestTCPClient(address):
  381. global info, data, dataLength
  382. # Attempt 1:
  383. clientSocket = stdsocket.socket()
  384. clientSocket.connect(address)
  385. print("client connection (1) fd", clientSocket.fileno(), "id", id(clientSocket.socket), "waiting to recv")
  386. if clientSocket.recv(5) != "":
  387. print("client test", 1, "FAIL")
  388. else:
  389. print("client test", 1, "OK")
  390. # Attempt 2:
  391. clientSocket = stdsocket.socket()
  392. clientSocket.connect(address)
  393. print("client connection (2) fd", clientSocket.fileno(), "id", id(clientSocket.socket), "waiting to recv")
  394. s = clientSocket.recv(dataLength)
  395. if s == "":
  396. print("client test", 2, "FAIL (disconnect)")
  397. else:
  398. t = struct.unpack("i", s)
  399. if t[0] == info:
  400. print("client test", 2, "OK")
  401. else:
  402. print("client test", 2, "FAIL (wrong data)")
  403. print("client exit")
  404. def TestMonkeyPatchUrllib(uri):
  405. # replace the system socket with this module
  406. #oldSocket = sys.modules["socket"]
  407. #sys.modules["socket"] = __import__(__name__)
  408. install()
  409. try:
  410. import urllib.request # must occur after monkey-patching!
  411. f = urllib.request.urlopen(uri)
  412. if not isinstance(f.fp.fp._file._sock, _fakesocket):
  413. raise AssertionError("failed to apply monkeypatch, got %s" % f.fp._sock.__class__)
  414. s = f.read()
  415. if len(s) != 0:
  416. print("Fetched", len(s), "bytes via replaced urllib")
  417. else:
  418. raise AssertionError("no text received?")
  419. finally:
  420. #sys.modules["socket"] = oldSocket
  421. uninstall()
  422. def TestMonkeyPatchUDP(address):
  423. # replace the system socket with this module
  424. #oldSocket = sys.modules["socket"]
  425. #sys.modules["socket"] = __import__(__name__)
  426. install()
  427. try:
  428. def UDPServer(address):
  429. listenSocket = stdsocket.socket(AF_INET, SOCK_DGRAM)
  430. listenSocket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
  431. listenSocket.bind(address)
  432. # Apparently each call to recvfrom maps to an incoming
  433. # packet and if we only ask for part of that packet, the
  434. # rest is lost. We really need a proper unittest suite
  435. # which tests this module against the normal socket
  436. # module.
  437. print("waiting to receive")
  438. data, address = listenSocket.recvfrom(256)
  439. print("received", data, len(data))
  440. if len(data) != 256:
  441. raise RuntimeError("Unexpected UDP packet size")
  442. def UDPClient(address):
  443. clientSocket = stdsocket.socket(AF_INET, SOCK_DGRAM)
  444. # clientSocket.connect(address)
  445. print("sending 512 byte packet")
  446. buffer = bytearray("-"+ ("*" * 510) +"-", "utf-8")
  447. sentBytes = clientSocket.sendto(buffer, 0, address)
  448. print("sent 512 byte packet", sentBytes)
  449. stackless.tasklet(UDPServer)(address)
  450. stackless.tasklet(UDPClient)(address)
  451. stackless.run()
  452. finally:
  453. #sys.modules["socket"] = oldSocket
  454. uninstall()
  455. if len(sys.argv) == 2:
  456. if sys.argv[1] == "client":
  457. print("client started")
  458. TestTCPClient(testAddress)
  459. print("client exited")
  460. elif sys.argv[1] == "slpclient":
  461. print("client started")
  462. stackless.tasklet(TestTCPClient)(testAddress)
  463. stackless.run()
  464. print("client exited")
  465. elif sys.argv[1] == "server":
  466. print("server started")
  467. TestTCPServer(testAddress)
  468. print("server exited")
  469. elif sys.argv[1] == "slpserver":
  470. print("server started")
  471. stackless.tasklet(TestTCPServer)(testAddress)
  472. stackless.run()
  473. print("server exited")
  474. else:
  475. print("Usage:", sys.argv[0], "[client|server|slpclient|slpserver]")
  476. sys.exit(1)
  477. else:
  478. print("* Running client/server test")
  479. install()
  480. try:
  481. stackless.tasklet(TestTCPServer)(testAddress)
  482. stackless.tasklet(TestTCPClient)(testAddress)
  483. stackless.run()
  484. finally:
  485. uninstall()
  486. print("* Running urllib test")
  487. stackless.tasklet(TestMonkeyPatchUrllib)("http://python.org/")
  488. stackless.run()
  489. print("* Running udp test")
  490. TestMonkeyPatchUDP(testAddress)
  491. print("result: SUCCESS")