diff options
author | Christoph Egger <christoph@debian.org> | 2017-05-28 14:30:29 +0200 |
---|---|---|
committer | Christoph Egger <christoph@debian.org> | 2017-05-28 14:30:29 +0200 |
commit | fc98900adb70b6316f991fe16726dc24b568cb52 (patch) | |
tree | af00c153db0f97c5280369fdb5f23d4636f673dc | |
parent | c83668fff106e313beb66b8c09bdce1e9844fd3c (diff) | |
parent | 9f30a9497bb1e812acfdf1c98e3b5cf35ddd8e39 (diff) |
Updated version 0.0~vcs.2017.05.26.git from '0.0_vcs.2017.05.26.git'
with Debian dir e44e7e9b159453b5fa19fb0c2508a7173ffe82a3
-rw-r--r-- | .gitignore | 95 | ||||
-rw-r--r-- | README.md (renamed from README) | 37 | ||||
-rw-r--r-- | enet.pyx | 64 | ||||
-rw-r--r-- | setup.py | 18 | ||||
-rw-r--r-- | test_client.py | 59 | ||||
-rw-r--r-- | test_enet.py | 23 | ||||
-rw-r--r-- | test_server.py | 37 |
7 files changed, 299 insertions, 34 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..01bb832 --- /dev/null +++ b/.gitignore @@ -0,0 +1,95 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# dotenv +.env + +# virtualenv +.venv/ +venv/ +ENV/ + +# Spyder project settings +.spyderproject + +# Rope project settings +.ropeproject + +# Pyenet specific files +enet.c +enet/ @@ -1,26 +1,21 @@ -pyenet 1.3 - -About -===================== +#pyenet pyenet is a python wrapper for the ENet library by Lee Salzman, http://enet.bespin.org It was originally written by Scott Robinson <scott@tranzoa.com> and is -currently maintained by Andrew Resch <andrewresch@gmail.com> at - http://code.google.com/p/pyenet +currently maintained by Andrew Resch <andrewresch@gmail.com> +##License pyenet is licensed under the BSD license, see LICENSE for details. enet is licensed under the MIT license, see http://enet.bespin.org/License.html -Dependencies -===================== +##Dependencies Building pyenet requires all the same dependencies as enet plus Cython and, obviously, Python. -Installation -===================== +##Installation The first step is to download the enet sources from http://enet.bespin.org and extract it to the enet/ directory in pyenet's source directory. You could also @@ -29,30 +24,30 @@ check out the enet source code from their CVS repository. This version of pyenet requires enet 1.3. Next step is to run the setup.py build: - - $ python setup.py build - +``` +$ python setup.py build +``` Once that is complete, install the new pyenet module: +``` +# python setup.py install +``` - # python setup.py install - -Usage -===================== +##Usage Once you have installed pyenet, you only need to import the enet module to start using enet in your project. Example server: - +``` >>> import enet >>> host = enet.Host(enet.Address("localhost", 33333), 1, 0, 0) >>> event = host.service(0) - +``` Example client: - +``` >>> import enet >>> host = enet.Host(None, 1, 0, 0) >>> peer = host.connect(enet.Address("localhost", 33333), 1) - +``` More information on usage can be obtained from: http://enet.bespin.org/Tutorial.html @@ -1,6 +1,7 @@ import atexit from cpython cimport bool +from libc.stdint cimport uintptr_t cdef extern from "enet/types.h": ctypedef unsigned char enet_uint8 @@ -22,6 +23,14 @@ cdef extern from "enet/enet.h": ctypedef int ENetSocket + ctypedef struct ENetBuffer: + void *data + size_t dataLength + + ctypedef struct ENetEvent + + ctypedef int (__cdecl *ENetInterceptCallback) (ENetHost *host, ENetEvent *event) except -1 # __cdecl is standard on unix and overwriten for win32 + ctypedef struct ENetAddress: enet_uint32 host enet_uint16 port @@ -107,10 +116,14 @@ cdef extern from "enet/enet.h": ENetPeer *peers size_t peerCount size_t channelLimit + enet_uint8 *receivedData + size_t receivedDataLength + ENetAddress receivedAddress enet_uint32 totalSentData enet_uint32 totalSentPackets enet_uint32 totalReceivedData enet_uint32 totalReceivedPackets + ENetInterceptCallback intercept ctypedef enum ENetEventType: ENET_EVENT_TYPE_NONE = 0 @@ -161,6 +174,9 @@ cdef extern from "enet/enet.h": void enet_peer_disconnect_now(ENetPeer *peer, enet_uint32 data) void enet_peer_disconnect_later(ENetPeer *peer, enet_uint32 data) + # Socket functions + int enet_socket_send(ENetSocket socket, ENetAddress *address, ENetBuffer *buffer, size_t size) + cdef enum: MAXHOSTNAME = 257 @@ -185,6 +201,8 @@ PEER_STATE_DISCONNECTING = ENET_PEER_STATE_DISCONNECTING PEER_STATE_ACKNOWLEDGING_DISCONNECT = ENET_PEER_STATE_ACKNOWLEDGING_DISCONNECT PEER_STATE_ZOMBIE = ENET_PEER_STATE_ZOMBIE +cdef class Address + cdef class Socket: """ Socket (int socket) @@ -198,6 +216,17 @@ cdef class Socket: cdef ENetSocket _enet_socket + def send(self, Address address, data): + cdef ENetBuffer buffer + data = data if isinstance(data, bytes) else data.encode('cp437') + + buffer.data = <char*>data + buffer.dataLength = len(data) + + cdef int result = enet_socket_send(self._enet_socket, + &address._enet_address, &buffer, 1) + return result + def fileno(self): return self._enet_socket @@ -397,6 +426,9 @@ cdef class Peer: return self.address != obj.address raise NotImplementedError + def __hash__(self): + return <uintptr_t>self._enet_peer + def send(self, channelID, Packet packet): """ send (int channelID, Packet packet) @@ -798,6 +830,9 @@ cdef class Event: (<Packet> self._packet)._enet_packet = self._enet_event.packet return self._packet +from weakref import WeakValueDictionary +cdef host_static_instances = WeakValueDictionary() + cdef class Host: """ Host (Address address, int peerCount, int channelLimit, @@ -819,6 +854,8 @@ cdef class Host: cdef ENetHost *_enet_host cdef bool dealloc + cdef object _interceptCallback + cdef object __weakref__ def __init__ (self, Address address=None, peerCount=0, channelLimit=0, incomingBandwidth=0, outgoingBandwidth=0): @@ -832,6 +869,12 @@ cdef class Host: raise MemoryError("Unable to create host structure!") self.dealloc = True + global host_static_instances + host_static_instances[<uintptr_t>self._enet_host] = self + + def __hash__(self): + return <uintptr_t>self._enet_host + def __cinit__(self): self.dealloc = False self._enet_host = NULL @@ -1000,6 +1043,27 @@ cdef class Host: def __set__(self, value): self._enet_host.totalReceivedPackets = value + property intercept: + def __get__(self): + return self._interceptCallback + + def __set__(self, value): + if value is None: + self._enet_host.intercept = NULL + else: + self._enet_host.intercept = intercept_callback + self._interceptCallback = value + + +cdef int __cdecl intercept_callback(ENetHost *host, ENetEvent *event) except -1: + cdef Address address = Address(None, 0) + address._enet_address = host.receivedAddress + cdef object ret = None + + if <uintptr_t>host in host_static_instances: + ret = host_static_instances[<uintptr_t>host].intercept(address, (<char*>host.receivedData)[:host.receivedDataLength]) + return int(bool(ret)) + def _enet_atexit(): enet_deinitialize() @@ -7,22 +7,22 @@ import sys source_files = ["enet.pyx"] -# _enet_files = glob.glob("enet/*.c") +_enet_files = glob.glob("enet/*.c") -# if not _enet_files: -# print("You need to download and extract the enet 1.3 source to enet/") -# print("Download the source from: http://enet.bespin.org/SourceDistro.html") -# print("See the README for more instructions") -# sys.exit(1) -# -# source_files.extend(_enet_files) +if not _enet_files: + print("You need to download and extract the enet 1.3 source to enet/") + print("Download the source from: http://enet.bespin.org/SourceDistro.html") + print("See the README for more instructions") + sys.exit(1) + +source_files.extend(_enet_files) define_macros = [('HAS_POLL', None), ('HAS_FCNTL', None), ('HAS_MSGHDR_FLAGS', None), ('HAS_SOCKLEN_T', None) ] -libraries = ['enet'] +libraries = [] if sys.platform == 'win32': define_macros.extend([('WIN32', None)]) diff --git a/test_client.py b/test_client.py index 7b29101..a75bd36 100644 --- a/test_client.py +++ b/test_client.py @@ -1,5 +1,11 @@ import enet -import os +import random +import sys + +try: + random.seed(sys.argv[1]) +except IndexError: + pass SHUTDOWN_MSG = b"SHUTDOWN" MSG_NUMBER = 10 @@ -20,7 +26,7 @@ while run: elif event.type == enet.EVENT_TYPE_RECEIVE: print("%s: IN: %r" % (event.peer.address, event.packet.data)) continue - msg = os.urandom(40) + msg = bytes(bytearray([random.randint(0,255) for i in range(40)])) packet = enet.Packet(msg) peer.send(0, packet) @@ -28,7 +34,54 @@ while run: if counter >= MSG_NUMBER: msg = SHUTDOWN_MSG peer.send(0, enet.Packet(msg)) - host.service(0) + host.service(100) peer.disconnect() print("%s: OUT: %r" % (peer.address, msg)) + + +# Part of the test to do with intercept callback and socket.send +peer = host.connect(enet.Address(b"localhost", 54301), 1) +shutdown_scheduled = False +run = True + +def receive_callback(address, data): + global shutdown_scheduled + + if shutdown_scheduled: + return + + if data == b"\xff\xff\xff\xffstatusResponse\n": + # if the test gets to this point, it means it has passed. Disconnect is a clean up + shutdown_scheduled = True + else: + # error messages are not propagating + # through cython + print("data != statusResponse. Instead of expected, got %r" % data) + assert(False) + +while run: + event = host.service(1000) + if event.type == enet.EVENT_TYPE_CONNECT: + print("%s: CONNECT" % event.peer.address) + msg = bytes(bytearray([random.randint(0,255) for i in range(40)])) + packet = enet.Packet(msg) + peer.send(0, packet) + + host.intercept = receive_callback + elif event.type == enet.EVENT_TYPE_DISCONNECT: + print("%s: DISCONNECT" % event.peer.address) + run = False + continue + elif event.type == enet.EVENT_TYPE_RECEIVE: + print("%s: IN: %r" % (event.peer.address, event.packet.data)) + continue + + if shutdown_scheduled: + msg = SHUTDOWN_MSG + peer.send(0, enet.Packet(msg)) + host.service(100) + peer.disconnect() + continue + + host.socket.send(peer.address, b"\xff\xff\xff\xffgetstatus\x00") diff --git a/test_enet.py b/test_enet.py index b7a6820..b9adf0b 100644 --- a/test_enet.py +++ b/test_enet.py @@ -71,6 +71,29 @@ class TestHost(unittest.TestCase): self.assertEquals(client_connected, True) self.assertEquals(server_connected, True) + def test_socketsend(self): + + self.send_done = False + socketsend_msg = b"\xff\xff\xff\xffgetstatus\x00" + + def f(address, data): + if data != socketsend_msg: + # error messages are not propagating + # through cython + print("data != statusResponse") + assert(False) + self.send_done = True + + while not self.send_done: + + self.client.service(0) + self.client.socket.send(self.server.address, socketsend_msg) + + event = self.server.service(0) + if event.type == enet.EVENT_TYPE_CONNECT: + self.server.intercept = f + + def test_broadcast(self): broadcast_done = False broadcast_msg = b"foo\0bar\n baz!" diff --git a/test_server.py b/test_server.py index f147553..fbeee2a 100644 --- a/test_server.py +++ b/test_server.py @@ -25,5 +25,40 @@ while run: print("%s: Error sending echo packet!" % event.peer.address) else: print("%s: OUT: %r" % (event.peer.address, msg)) - if event.packet.data == "SHUTDOWN": + if event.packet.data == b"SHUTDOWN": + shutdown_recv = True + +# Part of the test to do with intercept callback and socket.send + +connect_count = 0 +run = True +shutdown_recv = False + +def receive_callback(address, data): + if data and data == b"\xff\xff\xff\xffgetstatus\x00": + host.socket.send(address, b"\xff\xff\xff\xffstatusResponse\n") + +host.intercept = receive_callback + +while run: + # Wait 1 second for an event + event = host.service(1000) + if event.type == enet.EVENT_TYPE_CONNECT: + print("%s: CONNECT" % event.peer.address) + connect_count += 1 + elif event.type == enet.EVENT_TYPE_DISCONNECT: + print("%s: DISCONNECT" % event.peer.address) + connect_count -= 1 + if connect_count <= 0 and shutdown_recv: + run = False + elif event.type == enet.EVENT_TYPE_RECEIVE: + print("%s: IN: %r" % (event.peer.address, event.packet.data)) + + # This packet echo is used to mimick the usual use case when packets are going back&forth while the intercept callback is used + msg = event.packet.data + if event.peer.send(0, enet.Packet(msg)) < 0: + print("%s: Error sending echo packet!" % event.peer.address) + else: + print("%s: OUT: %r" % (event.peer.address, msg)) + if event.packet.data == b"SHUTDOWN": shutdown_recv = True |