fsxNet Wiki

BBS Development & Resources

User Tools

Site Tools


tutorials:python_bbs:part_one

Python BBS - Part One

I'll start off by explaining each section of the code, then give a listing of the entire source at the end. Please note, you will also need a config.ini that will look something like this:

[Main]
Port = 2023

Firstly we need to import the modules we will be using.

import configparser
import sys
import socket
import threading

Next we set up the variable that will store the flags that tell us which nodes are in use.

nodes = []

Now we'll start with our main method, this is what will run when the program starts, it will come last in our python script.

The first thing the script does, is check how it was invoked. We need a config file to be passed as an argument, and so we need to check that a config file argument was passed.

Next, we read the config INI file.

Then we create an instance of the server class passing it the port number from the config file.

Finally, run the server.

if __name__ == "__main__":
 
    if len(sys.argv) < 2:
 
        print("Usage python bbs.py config.ini")
        exit(1)
 
    config = configparser.ConfigParser()
    config.read(sys.argv[1])
 
    server = bbsServer(config.getint("Main", "Port"))
 
    server.run()

Next we'll define the bbsServer class, it starts with an initialization routine.

The initialization routine sets up the variables, gets a socket, binds that socket to a port and finally listens for connections.

class bbsServer():
    def __init__(self, port, host='0.0.0.0', nodemax=5):
        self.port = port
        self.host = host
        self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.nodemax = nodemax
 
        for index in range(nodemax):
            nodes.append(False)
 
        try:
            self.server.bind((self.host, self.port))
        except socket.error:
            print("Couldn't bind %s" % (socket.error))
            sys.exit()
 
        self.server.listen(10)

The next function of the bbsServer class is the run function, this is what we call from our main function.

It starts up by displaying an informational message on the console.

Next it goes into the main loop and listens for a connection. Once it has a connection, it continues on from the self.server.accept statement. It checks if there are any free nodes, and if there are, create a thread and start it. If not, just send the text “BUSY” and disconnect.

    def run(self):
        print("Starting BBS on port %s" % (self.port))
 
        while True:
            conn, addr = self.server.accept()
 
            for index in range(self.nodemax):
                if nodes[index] == False:
 
                    nodes[index] = True
                    threading.Thread(target=self.run_thread, args=(conn, addr, index)).start()
                    break
                else:
                    conn.sendall("BUSY\r\n")
                    conn.close()

Next we'll define the function which is running as a thread. We need access to the list of available nodes, so we can update it when we disconnect.

Next we'll send a message to the user, and then get the users input indefinitely. This is where we'd begin to add our BBS logic, like logging in etc.

Notice how it is encased in try-except blocks? This is because inside the getChar function which we'll define soon, it needs to detect if a client disconnects by closing the connection.

Once this finishes, the BBS will print a message that the node is now offline, and update the list of available nodes, then exit.

    def run_thread(self, conn, addr, node):
        global nodes
 
        try:
            self.sendString(conn, "Welcome to my BBS on Node %s" % (node + 1))
 
            while True:
                self.getChar(conn)
 
 
        except RuntimeError:
            print("Node %s hung up..." % (node + 1))
 
        print("Node %s offline." % (node + 1))
        nodes[node] = False
        conn.close()
        sys.exit()

Finally we have the two helper functions to send strings and receive characters.

You will notice that in getChar() conn.recv will return nothing if the connection is dropped, so we raise an exception, which is caught in the previous code. getChar() also sends the character back to the user, this is because later when we implement telnet commands, we will be turning echo off.

sendString just converts a string into a bytearray for sending.

    def sendString(self, conn, text):
        conn.sendall(text.encode())
 
    def getChar(self, conn):
 
        c = conn.recv(1)
        if c == b'':
            print("Connection dropped")
            raise RuntimeError("Socket connection dropped")
 
        conn.send(c)
 
        return str(c, 'utf-8')

Entire source file

import configparser
import sys
import socket
import threading
 
nodes = []
 
class bbsServer():
    def __init__(self, port, host='0.0.0.0', nodemax=5):
        self.port = port
        self.host = host
        self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.nodemax = nodemax
 
        for index in range(nodemax):
            nodes.append(False)
 
        try:
            self.server.bind((self.host, self.port))
        except socket.error:
            print("Couldn't bind %s" % (socket.error))
            sys.exit()
 
        self.server.listen(10)
 
    def sendString(self, conn, text):
        conn.sendall(text.encode())
 
    def getChar(self, conn):
 
        c = conn.recv(1)
        if c == b'':
            print("Connection dropped")
            raise RuntimeError("Socket connection dropped")
 
        conn.send(c)
 
        return str(c, 'utf-8')
 
    def run_thread(self, conn, addr, node):
        global nodes
 
        try:
            self.sendString(conn, "Welcome to my BBS on Node %s" % (node + 1))
 
            while True:
                self.getChar(conn)
 
        except RuntimeError:
            print("Node %s hung up..." % (node + 1))
 
        print("Node %s offline." % (node + 1))
        nodes[node] = False
        conn.close()
        sys.exit()
 
    def run(self):
        print("Starting BBS on port %s" % (self.port))
 
        while True:
            conn, addr = self.server.accept()
 
            for index in range(self.nodemax):
                if nodes[index] == False:
 
                    nodes[index] = True
                    threading.Thread(target=self.run_thread, args=(conn, addr, index)).start()
                    break
                else:
                    conn.sendall("BUSY\r\n")
                    conn.close()
 
if __name__ == "__main__":
 
    if len(sys.argv) < 2:
 
        print("Usage python bbs.py config.ini")
        exit(1)
 
    config = configparser.ConfigParser()
    config.read(sys.argv[1])
 
    server = bbsServer(config.getint("Main", "Port"))
 
    server.run()
tutorials/python_bbs/part_one.txt · Last modified: 2017/03/10 11:17 by apam