Commits

Pierre-Marie de Rodat committed 6ff60e3

Client: added basic network loop routines.

  • Participants
  • Parent commits f7f336e

Comments (0)

Files changed (3)

 # -*- coding: utf-8 -*-
 
+import gevent
+import gevent.queue
 import gevent.socket as socket
 
 from packet import *
 
 
 
-class ProtocolError(Exception):
-    pass
-
 class Client(object):
     '''
     Wrap basic operations on a connection to a server.
     def __init__(self, username):
         self.username = username
         self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        self.receiving_coroutine = None
+        self.sending_coroutine = None
+        self.sending_queue = gevent.queue.Queue(maxsize=100)
 
     def connect(self, address):
         try:
         except socket.error, e:
             self.on_disconnection(e)
 
+        # Start handshake
         self.send_packet(HandshakeClient(
             username=self.username
         ))
         self.expect_packet(response, HandshakeServer)
         self.connection_hash = response.connection_hash
 
+        # Send the login request
         self.send_packet(LoginRequestClient(
             protocol_version=self.PROTOCOL_VERSION,
             username=self.username
         ))
         response = self.recv_packet()
         self.expect_packet(response, LoginRequestServer)
-        return True
+
+        # Login is successful, start network coroutines.
+        self.receiving_coroutine = gevent.Greenlet.spawn(self.receive_forever)
+        self.sending_coroutine = gevent.Greenlet.spawn(self.send_forever)
+
+    def expect_packet(self, packet, expected):
+        if isinstance(packet, Disconnect):
+            raise ConnectionClosed(packet.reason)
+        if not isinstance(packet, expected):
+            raise ProtocolError(
+                "Expected a %s packet, but server sent a %s one" % (
+                    expected.__name__,
+                    packet.__class__.__name__
+                )
+            )
 
     def send_packet(self, packet):
         try:
             self.socket.sendall(packet.serialize())
+            logging.debug('Packet sent: %s', packet)
         except socket.error, e:
+            logging.debug('Packet not sent: %s', packet)
             self.on_disconnection(e)
             raise
 
+    def put_packet(self, packet):
+        self.sending_queue.put(packet)
+
     def recv_packet(self):
         try:
-            return Packet.parse_server(self.socket)
+            packet = Packet.parse_server(self.socket)
+            logging.debug('Packet received: %s', packet)
+            return packet
         except socket.error, e:
+            logging.debug('Packet not received: %s', e)
             self.on_disconnection(e)
             raise
 
-    def expect_packet(self, packet, expected):
-        if isinstance(packet, Disconnect):
-            raise ConnectionClosed(packet.reason)
-        if not isinstance(packet, expected):
-            raise ProtocolError(
-                "Expected a %s packet, but server sent a %s one" % (
-                    expected.__name__,
-                    packet.__class__.__name__
-                )
-            )
+    def receive_forever(self):
+        while True:
+            packet = self.recv_packet()
+
+    def send_forever(self):
+        while True:
+            packet = self.sending_queue.get()
+            self.send_packet(packet)
 
     def on_disconnection(self, exception):
         '''
 class ConnectionClosed(socket.error):
     pass
 
+class ProtocolError(Exception):
+    pass
+
+
+
 def recvall(socket, size):
     '''
     Read *exactly* "size" bytes from the socket and return them. Raise a
         return '\x01' if value else '\x00'
 
 class Metadata(BaseType):
+
+    @classmethod
+    def parse_entity(cls, socket):
+        return {
+            'id': Short.parse(socket),
+            'count': Byte.parse(socket),
+            'damage': Short.pares(socket),
+        }
+
+    @classmethod
+    def parse_triplet(cls, socket):
+        return [cls.parse_int(socket) for i in xrange(3)]
+
     @classmethod
     def parse(cls, socket):
         metadata = {}
         return wrapper
 
     @classmethod
-    def parse_entity(cls, socket):
-        return {
-            'id': Short.parse(socket),
-            'count': Byte.parse(socket),
-            'damage': Short.pares(socket),
-        }
-
-    @classmethod
-    def parse_triplet(cls, socket):
-        return [cls.prse_int(socket) for i in xrange(3)]
-
-    @classmethod
     def parse(cls, register, socket):
         packet_id = UnsignedByte.parse(socket)
-        packet_class = register[packet_id]
+        try:
+            packet_class = register[packet_id]
+        except KeyError:
+            raise ProtocolError('Unhandled packet type: %s' % hex(packet_id))
         packet = packet_class()
         for name, field_type in packet.fields:
             setattr(packet, name, field_type.parse(socket))
 # -*- coding: utf-8 -*-
 
+import logging
 import sys
 
+import gevent
+
 from client import Client
+import packet
+
 
 
+main_logger = logging.getLogger('main')
+main_logger.setLevel(logging.DEBUG)
+logging.root = main_logger
 
 username, host, port = sys.argv[1:4]
 c = Client(username)
 c.connect((host, int(port)))
+gevent.sleep(1)
+c.put_packet(packet.Disconnect(reason='Goodbye!'))
+gevent.sleep(1)
 c.close()