5
\$\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
  • 7
    \$\begingroup\$ First suggestion is to stop using Python 2.x because it's end of life. \$\endgroup\$ – Anonymous Nov 26 '20 at 21:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.