Python 3: stoppable TCP server (and TCP client) with GeeXLab


GeeXLab and Python 3 - TCP server and TCP client with the socket module



Today let’s see how to code a simple TCP server (as well as a TCP client) with the socket module.

This demo requires GeeXLab 0.27.2+. This is important because this version of GeeXLab adds the support of Python’s GIL. What is the GIL? It’s Python’s Global Interpreter Lock. It’s simply a lock that allows to run python scripts in separate threads. Thanks to the GIL, you can have two Python scripts, each running in its own system thread, that work without crashing the whole application.

Why the GIL is important in this demo? Because of the TCP server. The same GeeXLab demo will start a TCP server, a TCP client and will render the user interface. Most of the time, the TCP server does nothing. It waits for an incoming connection. When it receives a connection (from the TCP client), it processes it and returns to its favorite job: waiting for a new connection… Then the TCP server must run in its own system thread otherwise, the GeeXLab demo won’t be able to draw the user interface and won’t be reactive.

In GeeXLab, there is a way to start a script in a separate system thread: just create a ZOMBIE script, and when needed, start its execution in a new system thread with the gh_utils.exe_script() function. In the demo, the exe_script() is called at the end of the init.py file.

As usual, to test the demo it’s simple. Be sure to have GeeXLab 0.27.2 or higher. Download and unzip the demopack where you want. You will find the demo in the socket/02-socket-tcp-server/ folder. Drop the main.xml in GeeXLab, that’s all.

Type any message and click on the Send Message button to send your message to the echo server.


GeeXLab and Python 3 - TCP server and TCP client with the socket module

 
Here is the script (tcp_server.py) of our echo TCP server.

import socket
import threading

bind_ip = '127.0.0.1'  # localhost
bind_port = 7777  # listen on port 7777

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((bind_ip, bind_port))
server.listen(2) # max backlog of connections

server.settimeout(4) # 4 seconds accept timeout

quit_server = 0

while (quit_server != 1):

  quit_server = gh_utils.global_array_int_get(0)

  try:
    # wait for a connection...
    client_sock, address = server.accept()
    if client_sock:
      gh_utils.trace('TCP server - accepted connection from {}:{}'.format(address[0], address[1]))
      data = client_sock.recv(1024)
      if data:
        data_str = data.decode('utf-8')
        gh_utils.trace('TCP server - recv => ' + data_str)
        if (data_str == "stop"):
          quit_server = 1
          client_sock.send(b'OK')
        else:
          client_sock.send(data)

      client_sock.close()
  except socket.timeout as e:
    gh_utils.trace('TCP server - timeout on accept')

gh_utils.trace('TCP server - stopped')

 
Here are two options to create a stoppable TCP server:

  1. a special message, sent by the client, is used to quit the while loop and stop the server.
  2. the use of a timeout on the accept function combined with a try/catch. In the demo, a 4-second timeout generates an exception (socket.timeout) every 4 seconds. At this moment, if quit_server == 1 is true, the server can quit the while loop.

 
In the demo both options are in service. When you quit the demo, the terminate.py script is executed and a stop message is sent to the server if it’s still running.

Here is the code of the TCP client that sends the stop message:

client_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_sock.connect(('127.0.0.1', 7777))
# we send a sequence of bytes (bytes literals are always prefixed with 'b' or 'B' 
client_sock.send(b'stop')
client_sock.close()

 
Here is the same code but this time with a variable for the message and the handling of the server reply:

message_to_server = 'stop'

client_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = ('127.0.0.1', 7777)
client_sock.connect(server_address)

message = message_to_server.encode()
client_sock.send(message)

data = client_sock.recv(256)
message_from_server = repr(data)

client_sock.close()

 
As you can see, coding a TCP server in Python 3 is rather easy. Ok, this server can handle only one client at the same time, but it works. To handle multiple clients, the server must create a thread per client (this time the thread can be created directly in Python). Maybe in another demo… 😉





Leave a Comment

Your email address will not be published. Required fields are marked *