Hello and a quick'n'dirty publisher/subscriber system

Hello,

i’m new to programming and panda, but some inner force is driving me here to realize some of my game-design ideas.
so i will study and learn…

i started playing with python ~ a week ago and jumped into a script i know i will use often in prototyping and game-design-evaluation:

as many of my ideas make use of networking (and also communication between several processes that run the game) i wanted a very quick-and-simple to use messaging system. i choose a publisher-subscriber-system based on raknet (using pyraknet).
it works so far for testing purposes, but is nowhere near application-ready…
i’ll work on it :wink:

i know, this is dirty programming, but it works and is of great use for me, so i want to share it. (also i hope to get some feedback from you guys who DO know how to program ,-) )

if you have any questions or suggestion pls post :slight_smile:

thanks for now,

trux

##    intercom.py by Trux
##    rough pre-version, yet to be cleaned and fixed!!!
##
##    intercom.py provides networking as easy as it gets:
##    perfectly for prototyping and development. future versions to be application-ready! 
##
##    to run the server: simply run this script standalone.
##    commandline-arguments: port (default = 5555), max allowed peers/clients (default = 100), thread-sleep-time (default = 1 ms)
##
##    in your application simply instance Channel('name','format').
##    'name' is the channels name, 'format' (default = 'B') is a string defining the user-message-format (according to struct.py documentation).
##    optionally you can set priority[1..3], reliability[1..5] and ordering-channel[1..32], (according to raknet documentation) and host/port.
##    if a channel with the given 'name' is already running at the server, all arguments will be ignored. the active 'format' (and other settings) will be used
##    by the Channel() instance.
##    methods of Channel():
##    broadcast(msg): sends msg to all other members of the channel, according to 'format'
##    receive(): receives one message at a time
##    leave(): leaves the channel. currently the only way to leave the server without traces.
##
##    ToDo:   cleaning, testing, comments, doc, fixing traces from brute disconnects, extend functionality, etc..
##
##    Thanks to raknet and pyraknet :-)
##
##    have fun!!

    



import sys
import time
import struct 

import pyraknet
from pyraknet import PlayerAddress


### SERVERCODE:

class Server():
    def __init__(self, port = 5555, maxpeers = 100, sleep = 1):
        self.port = port
        self.net = pyraknet.Peer()#
        self.net.init(peers=maxpeers, port=self.port, thread_sleep_timer=sleep)#
        self.net.set_max_connections(maxpeers)#
        self.priority = 2
        self.reliability = 3
        self.orderCh = 0
        print 'Running, Waiting for connections..'
        self.channelNames = {} # {name:ID}
        self.channels = {} #{ID:(name, fmt, [memberIDs], pri, rel, och}
        
        

    def loop(self):
        while True:
            time.sleep(0.01)
            packet = self.net.receive()
            if packet:
                print 'received packet, type: ', ord(packet.data[0])
                self.handlePacket(packet)

    def handlePacket(self, packet):
        packetType = ord(packet.data[0])
        
        if packetType == 90: # channel request (90, name, fmt)
            response = self.onChRequest(packet)
            if(response):
                self.net.send(response, len(response), self.priority, self.reliability, self.orderCh, packet.address)

        elif packetType == 100: #userMsg (100, chID, (tuple from channel fmt))
            response = self.onChMsg(packet)
            if(response):
                self.net.send(response, len(response), self.priority, self.reliability, self.orderCh, packet.address)

        elif packetType == 98: #disconnect notification
            response = self.onLeave(packet)
            if(response):
                self.net.send(response, len(response), self.priority, self.reliability, self.orderCh, packet.address)

        elif packetType == 21:
            print packet.address
            print self.net.get_id_from_address(packet.address)
            print 'BRUTE DISCONECTED USERS AND POLLUTED CHANNELS WILL NOT BE DELETED IN THIS VERSION!!!'
            print

    def onLeave(self, packet):
        print 'Leave channel request by ', self.net.get_id_from_address(packet.address)
        message = struct.unpack('B B',packet.data)
        listIndex = self.channels[message[1]][2].index(self.net.get_id_from_address(packet.address))
        del self.channels[message[1]][2][listIndex]             
        print 'member removed'
        if not self.channels[message[1]][2]:
            print 'Channel empty..'
            ID = message[1]
            name = self.channels[ID][0]
            del self.channels[ID]
            del self.channelNames[name]
            print 'Channel deleted'
        

    def onChMsg(self, packet):
        channel = packet.data[1]
        print packet.address
        ID = struct.unpack('B', channel)[0]
        for i in self.channels[ID][2]:
            print i
            if (i) != self.net.get_id_from_address(packet.address):
                self.net.send(packet.data, len(packet.data), self.channels[ID][3], self.channels[ID][4], self.channels[ID][5], self.net.get_address_from_id(i))
        
            

    def onChRequest(self, packet):
        message = struct.unpack('B 16s 16s B B B', packet.data)
        memberID = self.net.get_id_from_address(packet.address)
        print memberID
        if message[1] not in self.channelNames:
            self.newChannel(message[1],message[2], message[3], message[4], message[5])
        ID = self.channelNames[message[1]]
        fmt = self.channels[ID][1]
        pri = self.channels[ID][3]
        rel = self.channels[ID][4]
        och = self.channels[ID][5]
        self.channels[ID][2].append(memberID)
        response = struct.pack('B B 16s B B B', 91, ID, fmt, pri, rel, och)
        return(response)
        
    def newChannel(self, name, fmt, pri, rel, och):
        IDs = self.channels.keys()
        for i in range(len(IDs)+2):
            if i not in IDs:
                newID = i
                break
        self.channelNames[name] = newID
        self.channels[newID] = (name, fmt, [], pri, rel, och)
        print 'New Channel: ', self.channelNames[name], self.channels[newID]
               


### CLIENTCODE:

class Channel():
    def __init__(self, name, format = 'B', pri = 2, rel = 4, och = 0, host  = 'localhost', port = 5555):
        self.name = name
        self.format = format
        self.ID = None
        self.priority = pri
        self.reliability = rel
        self.orderCh = och
        self.host = host
        self.port = port
        self.connect(self.host, self.port)
        self.requestServerChannel()
                

    def connect(self, host, port):
        self.net = pyraknet.Peer()      
        self.net.init(thread_sleep_timer=1)
        self.net.connect(host, port) 
        connected = False
        while not connected:
            print 'Trying to connect to server.. ', host, port
            packet = self.net.receive()
            if packet:
                print 'received packet, type: ', ord(packet.data[0])
                if ord(packet.data[0]) == 16:
                    self.serverAddress = packet.address
                    connected = True
            time.sleep(1)
        print 'Connection to server established'
        print

    def requestServerChannel(self):
        print 'rsc'
        chName = self.name
        chFormat = self.format
        chPri = self.priority
        chRel = self.reliability
        chOch = self.orderCh
        packet = struct.pack('B 16s 16s B B B', 90, chName, chFormat, chPri, chRel, chOch) # 90: channel request
        self.net.send(packet, len(packet), 2, 3, 0, self.serverAddress)
        access = False
        while not access:
            print 'requesting channel.. '
            packet = self.net.receive()
            if packet:
                print 'received packet, type: ', ord(packet.data[0])
                if ord(packet.data[0]) == 91:
                    access = True
            time.sleep(0.1)
        message = struct.unpack('B B 16s B B B', packet.data)
        self.ID = message[1]
        self.format = message[2]
        self.priority = message[3]
        self.reliability = message[4]
        self.orderCh = message[5]
        print 'Access to channel ', message[1], '. Format: ', message[2]
        print
        

    def leave(self):
        packet = struct.pack('B B',98, self.ID) # 98: disconnect
        self.net.send(packet, len(packet), 3, 3, 0, self.serverAddress)
        time.sleep(3)
        self.net.disconnect()
        print 'Disconnected..'
        print

    def broadcast(self, msg, msgTyp = 100): # msg is tuple, must fit chFormat
        chMsg = struct.pack(self.format, msg)
        msgType = struct.pack('B', msgTyp)
        chID = struct.pack('B', self.ID)
        packet = msgType+chID+chMsg
        if self.serverAddress:
            self.net.send(packet, len(packet), self.priority, self.reliability, self.orderCh, self.serverAddress)
        else:
            print 'Missing Server Address'
    

    def receive(self):
        while True:
            packet = self.net.receive()
            if packet:
                print 'got something'
                packetType = ord(packet.data[0])

                if packetType == 13: # 13: raknet-system: connection accepted
                    self.serverAddress = packet.address
                    
                elif packetType == 100: # 100: channel-message
                    chID = ord(packet.data[1])
                    chMsg = struct.unpack(self.format, packet.data[2:len(packet.data)])
                    return(chMsg)

                else:
                    print 'Unknown Packet Type! :', ord(packet.data[0]) # see raknet documentation for raknet system types..
                    return(packetType, None, None)
            else:
                break
                    
            

### commandline args: port, maximum number of peers allowed, threadsleeptime (ms)
if __name__ == '__main__':

    a = []
    for i in sys.argv[1:len(sys.argv)]:
        a.append(int(i))
    args = tuple(a)
    
    s = Server(*args)
    s.loop()

first i hate threads so i use select when possible
second panda3d provides a networking layer already why not use that?