4
\$\begingroup\$

I'm new to both Python and socket programming and figured setting up a simple chat server would be good practice. In addition to general critiques of the code, I have specific questions:

  • What are the best practices for network programming in Python? Is the object-oriented style I went with advisable or should I structure it differently?

  • How can I write effective unit tests for a client-server program when both client and server seem to depend on each other for execution? I've heard of mocking, but I have no firsthand experience with it and don't know whether mocking would be applicable in this case.

  • Normally I would have assumed that a server with multiple clients would be multithreaded, but the Python networking tutorials I could find all used select. In socket programming should I prefer to use select over threads?

server.py

import socket
import select
import sys

class Server:

    def __init__(self, host, port, max_clients):
        self.host = host
        self.port = port
        self.max_clients = max_clients
        self.clients = {}

    def run(self):
        self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.server.bind((self.host, self.port))
        self.server.listen(self.max_clients)
        print 'Listening on %s' % ('%s:%s' % self.server.getsockname())
        self.clients[self.server] = 'server'

        while True:
            read_sockets, write_sockets, error_sockets = select.select(self.clients.keys(), [], [])

            for connection in read_sockets:
                if connection == self.server:
                    client_connection, addr = self.server.accept()
                    self.setup_user(client_connection)
                else:
                    try:
                        message = connection.recv(4096)
                        if message != '':
                            self.broadcast(connection, '\n<' + self.clients[connection] + '>' + message)
                    except:
                        self.broadcast(connection, '\n[%s has left the chat]' % self.clients[connection])
                        connection.close()
                        del self.clients[connection]
                        continue
        self.server.close()

    def setup_user(self, connection):
        try:
            name = connection.recv(1024).strip()
        except socket.error:
            return
        if name in self.clients.keys():
            connection.send('Username is already taken\n')
        else:
            self.clients[connection] = name
            self.broadcast(connection, '\n[%s has enterred the chat]' % name)

    def broadcast(self, sender, message):
        print message,
        for connection, name in self.clients.items():
            if connection != sender:
                try:
                    connection.send(message)
                except socket.error:
                    pass


if __name__ == '__main__':
    if (len(sys.argv) < 3):
        print 'Format requires: python server.py hostname portno'
        sys.exit()

    server = Server(sys.argv[1], int(sys.argv[2]), 10)
    server.run()

client.py

import socket
import select
import sys

class Client:

    def __init__(self, username):
        self.username = username

    def prompt(self):
        sys.stdout.write('<You> ')
        sys.stdout.flush()

    def connect_to(self, hostname, portno):
        server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        server_socket.settimeout(2)

        try:
            server_socket.connect((hostname, portno))
        except:
            print 'Connection error'
            sys.exit()

        server_socket.send(self.username)
        print 'Connected to host'

        self.prompt()

        while True:
            socket_list = [sys.stdin, server_socket]
            read_sockets, write_sockets, error_sockets = select.select(socket_list, [], [])

            for chosen_socket in read_sockets:
                if chosen_socket == server_socket:
                    message = chosen_socket.recv(4096)

                    if not message:
                        print 'Connection error: no data'
                        sys.exit()
                    else:
                        sys.stdout.write(message)
                        self.prompt()
                else:
                    message = sys.stdin.readline()
                    server_socket.send(message)
                    self.prompt()


if __name__ == '__main__':
    if (len(sys.argv) < 4):
        print 'Format requires: python client.py username hostname portno'
        sys.exit()

    client = Client(sys.argv[1])
    client.connect_to(sys.argv[2], int(sys.argv[3]))
\$\endgroup\$
1
  • 9
    \$\begingroup\$ First suggestion is to stop using Python 2.x because it's end of life. \$\endgroup\$
    – Kate
    Commented Nov 26, 2020 at 21:23

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.