3

I am writing a networked number guessing game and I could do with a little help with it. Apart from handling multiple clients, my server needs to accept a connection from a Admin client. The Admin client displays a list of the ip addresses and port numbers of the player clients connected to the server. I understand that I need to implement threading in order to achieve this.

Here is my code so far. I am unsure of how many threads are needed and how to pass the information of connected player clients into the thread.

#Server
import threading
import socket
import math
import random
import ssl

def within(guess,goal,n):
    absValue = abs(guess - goal)
    if absValue <= n:
        return True
    else:
        return False

#def HandleAdmin(adminSocket):
    #(conn,addr) = adminSocket.accept()
    #ts = ssl.wrap_socket(conn, certfile="5cc515_server.crt",
                             #keyfile="5cc515_server.key",
                             #server_side=True,
                             #cert_reqs=ssl.CERT_REQUIRED,
                             #ca_certs="5cc515-root-ca.cer")

    #if ts.recv(80).decode() == 'Hello\r\n':
        #ts.send('Admin-Greetings\r\n'.encode())
    #if ts.recv(80).decode() == 'Who\r\n':
        #ts.send((c,a).encode())
    #ts.close()
    #return

def HandleClient(c,a):
    scoreCount = 0
    guess = 0
    if(c.recv(80).decode()) == 'Hello\r\n':
        c.send('Greetings\r\n'.encode())

        goal = random.randrange(1,21)

        while guess!= goal:
            guess =c.recv(80).decode()
            guess = int(guess[7:len(guess)-2])

            if guess == goal:
                c.send('Correct\r\n'.encode())
            elif within(guess, goal, 2) == True:
                c.send('Close\r\n'.encode())
            else:
                c.send('Far\r\n'.encode())
    c.close()
    return

clientSocket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
clientSocket.bind(("127.0.0.1",4000))
clientSocket.listen(5)

adminSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
adminSocket.bind(("127.0.0.1",4001))
adminSocket.listen(5)

while True:
    (c,a) = clientSocket.accept()
    #handleAdminThread = threading.Thread(target = AcceptAdminConnection,
     #                                        args = adminSocket)

    clientThread = threading.Thread(target = HandleClient, args = (c,a))
    clientThread.start()
Chris Martin
  • 30,334
  • 10
  • 78
  • 137
Pete
  • 83
  • 1
  • 7

1 Answers1

1

So here is a solution. It should work but there are to things that need to be taken care of :

  • you'll probably have to take care of your communication protocole (I don't have the client code here so i don't know how it handle the data received).
  • you'll probably have to add some loops to handle consecutive requests of the same admin/normal client.

I didn't tried to improve your code just to add what you asked for so it may not be the best solution at least it's not finished.

here is how it works: you make 2 TCPServer instances, one to handle admin connection the other for clients. Each instance have a BaseRequestHandler.
Basically the server listen for connections and start automatically a new thread calling the request handler for each one.

Now to share data you can use any variable, here a dictionary, and share it between threads. By setting the dictionary as a server member that you can access the dictionary from the request handler. To protect the accesses on the shared dict there is a Lock (I.E. a mutex), it's is shared as the dict is.

You can make use of :

Comment if needed i'll update.

import threading
import socketserver
import ssl
import random


class ThreadedTCPAdminRequestHandler(socketserver.BaseRequestHandler):
    def handle(self):

        ts = self.request # replaced the ssl connection for simpler code
        msg = ts.recv(80).decode()
        # Here should be some kind of loop to be able to handle multiple request of the same admin client
        # like 'Hello' followed by 'Who'
        if msg == 'Hello':
            ts.send('Admin-Greetings\r\n'.encode())
        elif msg == 'Who':
            with self.server.addressesLock:
                # get a local copy of the dict and quickly release the lock
                socket_addresses = self.server.addresses.copy()
            client_string=''
            # print the list of threads with their address:port
            for threadName, socket_addresse in socket_addresses.items():
                client_string += threadName + ' ' + socket_addresse + '\r\n'
                # the admin handler doesn't anything if the following line is commented
                # ts.send(client_string.encode())

            #  for simpler test, that way you can see the connexion listing is working
            # even if there are conection problems
            print(client_string)
            ts.send('plop'.encode())


class ThreadedTCPClientRequestHandler(socketserver.BaseRequestHandler):
    def handle(self):
        cur_thread = threading.current_thread().name
        with self.server.addressesLock:  # use a lock to protect the access to the shared dict
            # add name of the thread ad key and the address:port as value in the global dict
            self.server.addresses[cur_thread] = self.client_address[0] + ':' + str(self.client_address[1])

        score_count = 0
        guess = 0
        if (self.request.recv(80).decode()) == 'Hello':
            self.request.send('Greetings\r\n'.encode())

            goal = random.randrange(1, 21)

            while guess != goal:
                guess = self.request.recv(80).decode()
                guess = int(guess[7:len(guess) - 2])

                if guess == goal:
                    self.request.send('Correct\r\n'.encode())
                elif guess in range(goal-2,goal +2):
                    self.request.send('Close\r\n'.encode())
                else:
                    self.request.send('Far\r\n'.encode())

        with self.server.addressesLock:  # use a lock to protect the access to the shared dict
            del self.server.addresses[cur_thread]  # delete the thread entry in the dict


class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
    pass


if __name__ == "__main__":
    C_HOST, C_PORT = 'localhost', 4000
    A_HOST, A_PORT = 'localhost', 4001

    clientServer = ThreadedTCPServer((C_HOST, C_PORT), ThreadedTCPClientRequestHandler)
    adminServer = ThreadedTCPServer((C_HOST, A_PORT), ThreadedTCPAdminRequestHandler)

    # Start a thread for each the server (client and admin) -- those threads will then start one
    # more thread for each request
    clientServer_thread = threading.Thread(target=clientServer.serve_forever)
    adminServer_thread = threading.Thread(target=adminServer.serve_forever)

    addresses = {}  # create a dict to store the addresses of connected clients
    addressesLock0 = threading.Lock()  # create a lock to protect access to the dict

    clientServer_thread.daemon = True  # client will terminate when main terminate
    clientServer.addresses = addresses  # share the addreses dict
    clientServer.addressesLock = addressesLock0  # share the lock
    adminServer_thread.daemon = True
    adminServer.addresses = addresses
    adminServer.addressesLock = addressesLock0

    try:
        # strart servers
        clientServer_thread.start()
        print("clientServer loop running in thread:", clientServer_thread.name)
        adminServer_thread.start()
        print("adminServer loop running in thread:", adminServer_thread.name)

        input('Press Enter to exit...\r\n')  # wait input to end the program

    finally:
        # stop servers
        clientServer.shutdown()
        clientServer.server_close()
        adminServer.shutdown()
        adminServer.server_close()
    exit(0)
Rbtnk
  • 163
  • 1
  • 2
  • 11
  • 1
    Thanks dude. I will try to make sense of this and work with it tomorrow. It may be a little complex for my poor programming skills hehe – Pete Nov 20 '15 at 18:19
  • I really wish I was as smart as you guys but I have no idea how this code works. I am sorry. Thank you so much for your time – Pete Nov 21 '15 at 11:23
  • If you can give us you whole code and tell us what you don't understand we could help you more. – Rbtnk Nov 21 '15 at 12:01
  • I am currently trying to figure out what self means but can't find documentation that explains it clear enough. If you could help with that I would be grateful. Thank you – Pete Nov 21 '15 at 12:27
  • It is a reference on the current object you are using the method on. here is an [explanation](http://stackoverflow.com/a/2709832/5545635). – Rbtnk Nov 21 '15 at 12:34
  • I fixed the code, there were some bugs due to the fact that I couldn't test it. Maybe you can make a better use of it now. – Rbtnk Nov 23 '15 at 21:05