Much, much simpler BGE networking

me again, finally able to get back to my attempt to learn networking & sockets with BGE. all the tutorials (totter333, goran, various others) assume that what you want is a multiplayer game, which requires a server, clients, etc. and that’s exactly what most BGE folks want when they tackle networking. so it makes sense.

but i’m actually trying to build something much simpler. right now, all i want is one computer that can send a message and one computer that can listen for a message and then act on it. so i have been line-by-line through these tutorials and tried to separate the most basic send-and-receive stuff and use just that. but so far i have not gotten it to work.

here’s the code for the computer that sends a message (this isn’t in blender, it just runs in the python shell):


import pickle
import socket


host = "10.0.0.10" # this is the sending computer
port = 4445
client = "10.0.0.11" # this is the receiving computer
serverSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
serverSocket.bind((host,port))
serverSocket.setblocking(0)


messageToSend = str(input("Message? "))
# as you'll see in the 'listening' code, right now there's only one 'messageToSend' that will do anything: 'spaceBar'


try:
    serverSocket.sendto(pickle.dumps(messageToSend), client)
except:
    pass


on the listening end is a blender file with the default cube (with dynamic physics). the cube has an ‘always’ sensor connected to a python controller connected to a motion actuator named ‘mover’. the python script goes like this:


import pickle
import bge
import socket


mainSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
mainSocket.setblocking(0)


cont = bge.logic.getCurrentController()
mover = cont.actuators["mover"]
mover.useLocalAngV = True
spin = mover.angV[2]


try:
    r, addr = mainSocket.recvfrom(100000)
    newMotion = pickle.loads(r)
    if newMotion == "spaceBar":
        spin = spin + 1
        mover.angV = [0.0, 0.0, spin]
        cont.activate(mover)
except:
    pass

and so i hit ‘p’ on the blender game (the listener) and i run the python ‘sender’ script in the python shell and type ‘spaceBar’ in response to “Message?” and . . . nothing happens. that’s my problem.

yes, i’m quite new to this, but i promise i have diligently worked through the various tutorials to try and understand what each class, function, operation, etc. does. for some reason, i can’t get it work on this super-simplified basis.

any thoughts?

Your client socket doesn’t have a port number since you never bind it to an address or send something from it. You need to do one or the other for the two sockets to communicate.

Some things that come to mind:

  • You don’t need pickling in this simple case. Not to mention, sending unsecure pickles across the net is a security risk.
  • Instead, try using loopback address to test, i.e. 127.0.0.1.
  • With your script, if would also help if you’re not creating a socket on every tic: create the socket once and store it as a property.
  • Using a huge buffer is pointless as there is a limit to how big a single packet can be; a buffer size of 512-1500 is reasonable. IIRC, for IPv4, packets over 576 bytes could get split up, and for IPv6, it’s 1500 bytes. Remember, data can get split up into smaller chunks that don’t necessarily arrive in the order that they were sent, nor are they guaranteed to arrive at all.

This provides a really simple example (updated for Python 3):


# SENDING

import socket


UDP_IP = "127.0.0.1"
UDP_PORT = 5005
MESSAGE = "Hello, World!"


print("UDP target IP:", UDP_IP)
print("UDP target port:", UDP_PORT)
print("message:", MESSAGE)


sock = socket.socket(socket.AF_INET, # Internet
                     socket.SOCK_DGRAM) # UDP
sock.sendto(MESSAGE.encode(), (UDP_IP, UDP_PORT))


# RECEIVING

import socket


UDP_IP = "127.0.0.1"
UDP_PORT = 5005


sock = socket.socket(socket.AF_INET, # Internet
                     socket.SOCK_DGRAM) # UDP
sock.bind((UDP_IP, UDP_PORT))


while True:
    data, addr = sock.recvfrom(1024) # buffer size is 1024 bytes
    print("received message:", data.decode())

thanks.

@Mahalin: “with your script it would also be helpful if you’re not creating a socket on every tic: create the socket once and store it as a property.” two questions about this:

1: if creating the socket once and storing it as a property has been a part of the tutorials i have looked at, i guess i missed it or didn’t recognize that that’s what’s happening – any sample code or general guidance on how to do that?

2: i thought that the BGE pretty much runs every script on every frame (or at least if tied to an ‘always’ sensor). would the socket-creating script be tied to an always sensor with ‘tap’ enabled – would that prevent it from running on every tic?

also, i tried something like the code you provide from python.org, but the “while True” part caused an immediate freeze, i think because of the way BGE runs its scripts on every frame (basically, an infinite loop, as best i understand). that’s why i used the “try” method instead.

You can use while loops inside a Blender Python controller, provided that there is a guarantee that the loop will break. Given that a socket may receive more than one packet in a single frame, this is an important means to retrieve all packets from the socket buffer:


from socket import error as SOCK_ERROR #, ...

while True:
    try:
        data, address = sock.recvfrom(BUFFER_SIZE)
    except SOCK_ERROR:
        break

    # handle message


The script appears to freeze because recvfrom() blocks until it receives data. If used within the BGE, follow what agoose said.

Creating a socket every frame is unecessary since it can be reused, thus create it once and store it as a property. You’d only create a new socket if it closes or you’re changing addresses/ports every frame which isn’t likely.

Untested:


import socket

UDP_IP = '127.0.0.1'
UDP_PORT = 1337
BUFFER_SIZE = 1500

#################
# SERVER/RECEIVER

# Have an Always sensor without TRUE level triggering call this function
#
# NOTE: Make sure this sensor is before the sensor below that calls 
# receive_data
def initialize_socket(controller):

    owner = controller.owner

    sock = socket.sock(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind((UDP_IP, UDP_PORT))    # only server/receiver needs to bind
    sock.setblocking(0)

    owner['socket'] = sock


# Have an Always sensor with TRUE level triggering call this function
def receive_data(controller):

    owner = controller.owner
    sock = owner['socket']

    try:

        data, clientAddress = sock.recvfrom(BUFFER_SIZE)
        
        try:

            # In case data sent is invalid, not a str, etc.
            dataStr = data.decode()

        except:
            
            pass

        if dataStr == 'spacebar':
            
            # Do whatever...
            #
            # Send response back:
            # sock.sendto('received'.encode(), clientAddress)
            pass

    except:

        # Should properly close socket or whatnot
        pass


########
# CLIENT

# Have an Always sensor without TRUE level triggering call this function
#
# NOTE: Make sure this sensor is before the sensor below that calls 
# send_data
def initialize_socket(controller):

    owner = controller.owner

    sock = socket.sock(socket.AF_INET, socket.SOCK_DGRAM)
    sock.setblocking(0)

    owner['socket'] = sock

        
def send_data(controller):

    owner = controller.owner

    owner = controller.owner
    sock = owner['socket']
       
    message = 'Message'

    try:
        
        sock.sendto(message.encode(), (UDP_IP, UDP_PORT))
        
    except:
        
        pass        

    # Could also do sock.recvfrom to read response back, etc.

thanks everyone. i will try these. thanks for your patience with a noob learning to swim by jumping into the deep end.

Or learning how to drown.

As a beginner, you should really start with something less involved.

fair enough, but if you want to know why i’m taking this approach . . . .

i have long wanted to program a 3d game where two players control pieces of a tank (one is the driver, one is the gunner) with both players looking at a single main screen to see their environment. so i want one computer for the driver’s controls and one computer for the gunner’s controls (these will probably be python-built, but not programmed through blender) and one computer connected to the main screen that receives data from both the driver and the gunner and changes the environment accordingly.

so . . . the most important thing in that setup is the ability for the gunner and driver computers to send data to the main screen computer. doesn’t do me any good to program an awesome tank or a great environment or a really cool driver or gunner station unless i know i can make everything communicate. so that’s why i’m trying to learn this first. sink or swim, i want to learn this. and i appreciate the help everyone has offered.

Goran’s method of networking seems pretty good to me. I changed it to meet my own needs I basically only kept the fact that it uses UDP sockets, I don’t read keyboard input. And I copy pasted new Python socket code into 2.62 for it to work but it’s not beyond your reach. I didn’t even know what I was doing, I went by instinct and it functions great. No lag so far. Basically the server calculates everything but the server version is only blocks and simple wireframe geometry. I don’t remember what else I changed, I made it even simpler, went with what he said to keep the client as dumb as I could. I think you should have fun.

Then again, my game is only mellee and knives, maybe an FPS that goes POWPOWPOW all the time is more intensive.