3
$\begingroup$

I have 2 Blender instances running on my machine. I want the 1st Blender instance to be a Server and the 2nd one is a client.

I just want a simple thing. Just to send a String message from the Client Blender app to the Server Blender app. Local host.

Please, share some examples of Python scripts for a server and client.

$\endgroup$
3
  • $\begingroup$ Local host? Are you talking about a web server? Why? $\endgroup$ Commented Jan 16, 2023 at 21:30
  • $\begingroup$ I have 2 apps. I want to connect them. Don't ask why. Say how! ) $\endgroup$
    – mifth
    Commented Jan 16, 2023 at 22:27
  • 1
    $\begingroup$ tbh this has NOTHING to do with blender. That's a pure python question. $\endgroup$
    – Chris
    Commented Jan 17, 2023 at 6:50

1 Answer 1

7
$\begingroup$

Here I have configured one instance of Blender to run the server script using Blender 3.4, and another instance to run the client script using Blender 3.3.1, but you can use any or the same versions of Blender. Make sure you open the System Console for each instance using menu Window > Toggle System Console if you are running Blender on Windows. If you run Blender on Unix or a Unix-like system like Linux, then make sure you run each Blender instance directly from the Terminal or Command-line interface so you can see the print outputs.

enter image description here

In this example, I first start the server script which prints out Waiting for connections... and starts to listen for client connections. Then I run the client script which connects to the server socket which triggers the server to print out Got a connection from.... The server then sends a response which the client receives and prints out as Thank you for connection. The client then sends Message from client which is well received on the server. In addition, I have configured my client script to send a message and notify the server about the specific selected object.

enter image description here

Take note that the server script is blocking the UI and thus you cannot interact with the whole Blender instance while it's running the server script. You can look into Asyncio or other modules if you need it to be non-blocking or want it to run in the background.

Server Script Version 1 (accepts only 1 client connection):

import socket

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
host = socket.gethostname() # get name of local machine
port = 55555

server_socket.bind((host, port))

while True:
    server_socket.listen(20)
    print("Waiting for connections...")
    client_socket, addr = server_socket.accept()
    print("Got a connection from %s" % str(addr))
    while True:
        message = client_socket.recv(1024).decode()
        if message:
            print("Received message: %s" % message)
            client_socket.send(b'Thank you for connecting')
        else:
            break
    print("client socket closed")
    client_socket.close()
    server_socket.close()

Server Script Version 2 (accepts multiple client connections via multi threading)

import socket
import threading

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
host = socket.gethostname() # get name of local machine
port = 55555

server_socket.bind((host, port))

def handle_client(client_socket, addr):
    while True:
        message = client_socket.recv(1024).decode()
        if message:
            print("Received message from %s: %s" % (str(addr), message))
            client_socket.send(b'Thank you for connecting')
        else:
            break
    print("Closing connection with %s" % str(addr))
    client_socket.close()

while True:
    server_socket.listen(20)
    print("Waiting for connections...")
    client_socket, addr = server_socket.accept()
    print("Got a connection from %s" % str(addr))
    client_thread = threading.Thread(target=handle_client, args=(client_socket, addr))
    client_thread.start()

Server Script Version 3 (this version is like version 2 but non-blocking, meaning you can interact with the Blender instance running the server script using a timer but it is a bit slow).

import socket
import threading
import bpy
import select

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
host = socket.gethostname() # get name of local machine
port = 55555
server_socket.bind((host, port))
server_socket.listen(20)

client_sockets = []

def handle_client(client_socket, addr):
    while True:
        if select.select([client_socket], [], [], 0.1)[0]:
            message = client_socket.recv(1024).decode()
            if message:
                print("Received message from %s: %s" % (str(addr), message))
                client_socket.send(b'Thank you for connecting')
            else:
                print("Closing connection with %s" % str(addr))
                client_socket.close()
                client_sockets.remove(client_socket)
                break

def background_server():
    if select.select([server_socket], [], [], 0.1)[0]:
        client_socket, addr = server_socket.accept()
        print("Got a connection from %s" % str(addr))
        client_sockets.append(client_socket)
        client_thread = threading.Thread(target=handle_client, args=(client_socket, addr))
        client_thread.start()
    bpy.app.timers.register(background_server)

bpy.app.timers.register(background_server, first_interval=1.0)

Client script:

import socket
import bpy
from bpy.app.handlers import persistent

client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
host = socket.gethostname() # get name of local machine
port = 55555

def connect_to_host(host, port):
    client_socket.connect((host, port))

def close_connection():
    client_socket.close()

def send_message(msg):
    print("send: ", msg)
    client_socket.send(msg.encode())

def receive_message():
    data = client_socket.recv(1024).decode()
    print("Data received from server: %s" % data)


connect_to_host(host, port)

send_message("Message from client")
receive_message()

bpy.app.handlers.depsgraph_update_post.clear()

def on_depsgraph_update_post(scene, depsgraph):
    bpy.app.handlers.depsgraph_update_post.clear()
    send_message("Select object: " + bpy.context.object.name)
    bpy.app.handlers.depsgraph_update_post.append(on_depsgraph_update_post)

bpy.app.handlers.load_post.clear()
bpy.app.handlers.save_pre.clear()

@persistent
def on_file_close(context, event):
    print("The file has been closed or a new file has been opened.")
    close_connection()

bpy.app.handlers.load_post.append(on_file_close)
bpy.app.handlers.save_pre.append(on_file_close)

bpy.app.handlers.depsgraph_update_post.append(on_depsgraph_update_post)

You can replace the host variable with the IP address of the server if the server and the client are running on different machines.

Note that the above example uses TCP sockets, which are reliable and preserve message boundaries, but also introduce some overhead. If you're sending a large number of small messages, you may want to use UDP sockets instead.

$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .