Added second example.
							parent
							
								
									0c72481045
								
							
						
					
					
						commit
						09e345a85a
					
				@ -0,0 +1,138 @@
 | 
			
		||||
# Byte-compiled / optimized / DLL files
 | 
			
		||||
__pycache__/
 | 
			
		||||
*.py[cod]
 | 
			
		||||
*$py.class
 | 
			
		||||
 | 
			
		||||
# C extensions
 | 
			
		||||
*.so
 | 
			
		||||
 | 
			
		||||
# Distribution / packaging
 | 
			
		||||
.Python
 | 
			
		||||
build/
 | 
			
		||||
develop-eggs/
 | 
			
		||||
dist/
 | 
			
		||||
downloads/
 | 
			
		||||
eggs/
 | 
			
		||||
.eggs/
 | 
			
		||||
lib/
 | 
			
		||||
lib64/
 | 
			
		||||
parts/
 | 
			
		||||
sdist/
 | 
			
		||||
var/
 | 
			
		||||
wheels/
 | 
			
		||||
share/python-wheels/
 | 
			
		||||
*.egg-info/
 | 
			
		||||
.installed.cfg
 | 
			
		||||
*.egg
 | 
			
		||||
MANIFEST
 | 
			
		||||
 | 
			
		||||
# PyInstaller
 | 
			
		||||
#  Usually these files are written by a python script from a template
 | 
			
		||||
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
 | 
			
		||||
*.manifest
 | 
			
		||||
*.spec
 | 
			
		||||
 | 
			
		||||
# Installer logs
 | 
			
		||||
pip-log.txt
 | 
			
		||||
pip-delete-this-directory.txt
 | 
			
		||||
 | 
			
		||||
# Unit test / coverage reports
 | 
			
		||||
htmlcov/
 | 
			
		||||
.tox/
 | 
			
		||||
.nox/
 | 
			
		||||
.coverage
 | 
			
		||||
.coverage.*
 | 
			
		||||
.cache
 | 
			
		||||
nosetests.xml
 | 
			
		||||
coverage.xml
 | 
			
		||||
*.cover
 | 
			
		||||
*.py,cover
 | 
			
		||||
.hypothesis/
 | 
			
		||||
.pytest_cache/
 | 
			
		||||
cover/
 | 
			
		||||
 | 
			
		||||
# Translations
 | 
			
		||||
*.mo
 | 
			
		||||
*.pot
 | 
			
		||||
 | 
			
		||||
# Django stuff:
 | 
			
		||||
*.log
 | 
			
		||||
local_settings.py
 | 
			
		||||
db.sqlite3
 | 
			
		||||
db.sqlite3-journal
 | 
			
		||||
 | 
			
		||||
# Flask stuff:
 | 
			
		||||
instance/
 | 
			
		||||
.webassets-cache
 | 
			
		||||
 | 
			
		||||
# Scrapy stuff:
 | 
			
		||||
.scrapy
 | 
			
		||||
 | 
			
		||||
# Sphinx documentation
 | 
			
		||||
docs/_build/
 | 
			
		||||
 | 
			
		||||
# PyBuilder
 | 
			
		||||
.pybuilder/
 | 
			
		||||
target/
 | 
			
		||||
 | 
			
		||||
# Jupyter Notebook
 | 
			
		||||
.ipynb_checkpoints
 | 
			
		||||
 | 
			
		||||
# IPython
 | 
			
		||||
profile_default/
 | 
			
		||||
ipython_config.py
 | 
			
		||||
 | 
			
		||||
# pyenv
 | 
			
		||||
#   For a library or package, you might want to ignore these files since the code is
 | 
			
		||||
#   intended to run in multiple environments; otherwise, check them in:
 | 
			
		||||
# .python-version
 | 
			
		||||
 | 
			
		||||
# pipenv
 | 
			
		||||
#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
 | 
			
		||||
#   However, in case of collaboration, if having platform-specific dependencies or dependencies
 | 
			
		||||
#   having no cross-platform support, pipenv may install dependencies that don't work, or not
 | 
			
		||||
#   install all needed dependencies.
 | 
			
		||||
#Pipfile.lock
 | 
			
		||||
 | 
			
		||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
 | 
			
		||||
__pypackages__/
 | 
			
		||||
 | 
			
		||||
# Celery stuff
 | 
			
		||||
celerybeat-schedule
 | 
			
		||||
celerybeat.pid
 | 
			
		||||
 | 
			
		||||
# SageMath parsed files
 | 
			
		||||
*.sage.py
 | 
			
		||||
 | 
			
		||||
# Environments
 | 
			
		||||
.env
 | 
			
		||||
.venv
 | 
			
		||||
env/
 | 
			
		||||
venv/
 | 
			
		||||
ENV/
 | 
			
		||||
env.bak/
 | 
			
		||||
venv.bak/
 | 
			
		||||
 | 
			
		||||
# Spyder project settings
 | 
			
		||||
.spyderproject
 | 
			
		||||
.spyproject
 | 
			
		||||
 | 
			
		||||
# Rope project settings
 | 
			
		||||
.ropeproject
 | 
			
		||||
 | 
			
		||||
# mkdocs documentation
 | 
			
		||||
/site
 | 
			
		||||
 | 
			
		||||
# mypy
 | 
			
		||||
.mypy_cache/
 | 
			
		||||
.dmypy.json
 | 
			
		||||
dmypy.json
 | 
			
		||||
 | 
			
		||||
# Pyre type checker
 | 
			
		||||
.pyre/
 | 
			
		||||
 | 
			
		||||
# pytype static type analyzer
 | 
			
		||||
.pytype/
 | 
			
		||||
 | 
			
		||||
# Cython debug symbols
 | 
			
		||||
cython_debug/
 | 
			
		||||
@ -0,0 +1,76 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
import json
 | 
			
		||||
import socket
 | 
			
		||||
import threading
 | 
			
		||||
import messages
 | 
			
		||||
import model
 | 
			
		||||
import view
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
BUFFER_SIZE = 2 ** 10
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Application(object):
 | 
			
		||||
 | 
			
		||||
    instance = None
 | 
			
		||||
 | 
			
		||||
    def __init__(self, args):
 | 
			
		||||
        self.args = args
 | 
			
		||||
        self.closing = False
 | 
			
		||||
        self.host = None
 | 
			
		||||
        self.port = None
 | 
			
		||||
        self.receive_worker = None
 | 
			
		||||
        self.sock = None
 | 
			
		||||
        self.username = None
 | 
			
		||||
        self.ui = view.EzChatUI(self)
 | 
			
		||||
        Application.instance = self
 | 
			
		||||
 | 
			
		||||
    def execute(self):
 | 
			
		||||
        if not self.ui.show():
 | 
			
		||||
            return
 | 
			
		||||
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 | 
			
		||||
        try:
 | 
			
		||||
            self.sock.connect((self.host, self.port))
 | 
			
		||||
        except (socket.error, OverflowError):
 | 
			
		||||
            self.ui.alert(messages.ERROR, messages.CONNECTION_ERROR)
 | 
			
		||||
            return
 | 
			
		||||
        self.receive_worker = threading.Thread(target=self.receive)
 | 
			
		||||
        self.receive_worker.start()
 | 
			
		||||
        self.ui.loop()
 | 
			
		||||
 | 
			
		||||
    def receive(self):
 | 
			
		||||
        while True:
 | 
			
		||||
            try:
 | 
			
		||||
                message = model.Message(**json.loads(self.receive_all()))
 | 
			
		||||
            except (ConnectionAbortedError, ConnectionResetError):
 | 
			
		||||
                if not self.closing:
 | 
			
		||||
                    self.ui.alert(messages.ERROR, messages.CONNECTION_ERROR)
 | 
			
		||||
                return
 | 
			
		||||
            self.ui.show_message(message)
 | 
			
		||||
 | 
			
		||||
    def receive_all(self):
 | 
			
		||||
        buffer = ""
 | 
			
		||||
        while not buffer.endswith(model.END_CHARACTER):
 | 
			
		||||
            buffer += self.sock.recv(BUFFER_SIZE).decode(model.TARGET_ENCODING)
 | 
			
		||||
        return buffer[:-1]
 | 
			
		||||
 | 
			
		||||
    def send(self, event=None):
 | 
			
		||||
        message = self.ui.message.get()
 | 
			
		||||
        if len(message) == 0:
 | 
			
		||||
            return
 | 
			
		||||
        self.ui.message.set("")
 | 
			
		||||
        message = model.Message(username=self.username, message=message, quit=False)
 | 
			
		||||
        try:
 | 
			
		||||
            self.sock.sendall(message.marshal())
 | 
			
		||||
        except (ConnectionAbortedError, ConnectionResetError):
 | 
			
		||||
            if not self.closing:
 | 
			
		||||
                self.ui.alert(messages.ERROR, messages.CONNECTION_ERROR)
 | 
			
		||||
 | 
			
		||||
    def exit(self):
 | 
			
		||||
        self.closing = True
 | 
			
		||||
        try:
 | 
			
		||||
            self.sock.sendall(model.Message(username=self.username, message="", quit=True).marshal())
 | 
			
		||||
        except (ConnectionResetError, ConnectionAbortedError, OSError):
 | 
			
		||||
            print(messages.CONNECTION_ERROR)
 | 
			
		||||
        finally:
 | 
			
		||||
            self.sock.close()
 | 
			
		||||
@ -0,0 +1,12 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
import sys
 | 
			
		||||
import application
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main(args):
 | 
			
		||||
    app = application.Application(args)
 | 
			
		||||
    app.execute()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    main(sys.argv)
 | 
			
		||||
@ -0,0 +1,12 @@
 | 
			
		||||
    # -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
CONNECTION_ERROR = "Could not connect to server"
 | 
			
		||||
ERROR = "Error"
 | 
			
		||||
INPUT_SERVER_HOST = "Input Server Host"
 | 
			
		||||
INPUT_SERVER_PORT = "Input Server Port"
 | 
			
		||||
INPUT_USERNAME = "Input your username"
 | 
			
		||||
SEND = "Send"
 | 
			
		||||
SERVER_HOST = "Server Host"
 | 
			
		||||
SERVER_PORT = "Server Port"
 | 
			
		||||
TITLE = "ezChat"
 | 
			
		||||
USERNAME = "Username"
 | 
			
		||||
@ -0,0 +1,22 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
import json
 | 
			
		||||
 | 
			
		||||
END_CHARACTER = "\0"
 | 
			
		||||
MESSAGE_PATTERN = "{username}>{message}"
 | 
			
		||||
TARGET_ENCODING = "utf-8"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Message(object):
 | 
			
		||||
 | 
			
		||||
    def __init__(self, **kwargs):
 | 
			
		||||
        self.username = None
 | 
			
		||||
        self.message = None
 | 
			
		||||
        self.quit = False
 | 
			
		||||
        self.__dict__.update(kwargs)
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return MESSAGE_PATTERN.format(**self.__dict__)
 | 
			
		||||
 | 
			
		||||
    def marshal(self):
 | 
			
		||||
        return (json.dumps(self.__dict__) + END_CHARACTER).encode(TARGET_ENCODING)
 | 
			
		||||
@ -0,0 +1,98 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
import json
 | 
			
		||||
import socket
 | 
			
		||||
import sys
 | 
			
		||||
import threading
 | 
			
		||||
import model
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
BUFFER_SIZE = 2 ** 10
 | 
			
		||||
CLOSING = "Application closing..."
 | 
			
		||||
CONNECTION_ABORTED = "Connection aborted"
 | 
			
		||||
CONNECTED_PATTERN = "Client connected: {}:{}"
 | 
			
		||||
ERROR_ARGUMENTS = "Provide port number as the first command line argument"
 | 
			
		||||
ERROR_OCCURRED = "Error Occurred"
 | 
			
		||||
EXIT = "exit"
 | 
			
		||||
JOIN_PATTERN = "{username} has joined"
 | 
			
		||||
RUNNING = "Server is running..."
 | 
			
		||||
SERVER = "SERVER"
 | 
			
		||||
SHUTDOWN_MESSAGE = "shutdown"
 | 
			
		||||
TYPE_EXIT = "Type 'exit' to exit>"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Server(object):
 | 
			
		||||
 | 
			
		||||
    def __init__(self, argv):
 | 
			
		||||
        self.clients = set()
 | 
			
		||||
        self.listen_thread = None
 | 
			
		||||
        self.port = None
 | 
			
		||||
        self.sock = None
 | 
			
		||||
        self.parse_args(argv)
 | 
			
		||||
 | 
			
		||||
    def listen(self):
 | 
			
		||||
        self.sock.listen(1)
 | 
			
		||||
        while True:
 | 
			
		||||
            try:
 | 
			
		||||
                client, address = self.sock.accept()
 | 
			
		||||
            except OSError:
 | 
			
		||||
                print(CONNECTION_ABORTED)
 | 
			
		||||
                return
 | 
			
		||||
            print(CONNECTED_PATTERN.format(*address))
 | 
			
		||||
            self.clients.add(client)
 | 
			
		||||
            threading.Thread(target=self.handle, args=(client,)).start()
 | 
			
		||||
 | 
			
		||||
    def handle(self, client):
 | 
			
		||||
        while True:
 | 
			
		||||
            try:
 | 
			
		||||
                message = model.Message(**json.loads(self.receive(client)))
 | 
			
		||||
            except (ConnectionAbortedError, ConnectionResetError):
 | 
			
		||||
                print(CONNECTION_ABORTED)
 | 
			
		||||
                return
 | 
			
		||||
            if message.quit:
 | 
			
		||||
                client.close()
 | 
			
		||||
                self.clients.remove(client)
 | 
			
		||||
                return
 | 
			
		||||
            print(str(message))
 | 
			
		||||
            if SHUTDOWN_MESSAGE.lower() == message.message.lower():
 | 
			
		||||
                self.exit()
 | 
			
		||||
                return
 | 
			
		||||
            self.broadcast(message)
 | 
			
		||||
 | 
			
		||||
    def broadcast(self, message):
 | 
			
		||||
        for client in self.clients:
 | 
			
		||||
            client.sendall(message.marshal())
 | 
			
		||||
 | 
			
		||||
    def receive(self, client):
 | 
			
		||||
        buffer = ""
 | 
			
		||||
        while not buffer.endswith(model.END_CHARACTER):
 | 
			
		||||
            buffer += client.recv(BUFFER_SIZE).decode(model.TARGET_ENCODING)
 | 
			
		||||
        return buffer[:-1]
 | 
			
		||||
 | 
			
		||||
    def run(self):
 | 
			
		||||
        print(RUNNING)
 | 
			
		||||
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 | 
			
		||||
        self.sock.bind(("", self.port))
 | 
			
		||||
        self.listen_thread = threading.Thread(target=self.listen)
 | 
			
		||||
        self.listen_thread.start()
 | 
			
		||||
 | 
			
		||||
    def parse_args(self, argv):
 | 
			
		||||
        if len(argv) != 2:
 | 
			
		||||
            raise RuntimeError(ERROR_ARGUMENTS)
 | 
			
		||||
        try:
 | 
			
		||||
            self.port = int(argv[1])
 | 
			
		||||
        except ValueError:
 | 
			
		||||
            raise RuntimeError(ERROR_ARGUMENTS)
 | 
			
		||||
 | 
			
		||||
    def exit(self):
 | 
			
		||||
        self.sock.close()
 | 
			
		||||
        for client in self.clients:
 | 
			
		||||
            client.close()
 | 
			
		||||
        print(CLOSING)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    try:
 | 
			
		||||
        Server(sys.argv).run()
 | 
			
		||||
    except RuntimeError as error:
 | 
			
		||||
        print(ERROR_OCCURRED)
 | 
			
		||||
        print(str(error))
 | 
			
		||||
@ -0,0 +1,76 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
import tkinter
 | 
			
		||||
import messages
 | 
			
		||||
 | 
			
		||||
from tkinter import messagebox, simpledialog
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CLOSING_PROTOCOL = "WM_DELETE_WINDOW"
 | 
			
		||||
END_OF_LINE = "\n"
 | 
			
		||||
KEY_RETURN = "<Return>"
 | 
			
		||||
TEXT_STATE_DISABLED = "disabled"
 | 
			
		||||
TEXT_STATE_NORMAL = "normal"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EzChatUI(object):
 | 
			
		||||
 | 
			
		||||
    def __init__(self, application):
 | 
			
		||||
        self.application = application
 | 
			
		||||
        self.gui = None
 | 
			
		||||
        self.frame = None
 | 
			
		||||
        self.input_field = None
 | 
			
		||||
        self.message = None
 | 
			
		||||
        self.message_list = None
 | 
			
		||||
        self.scrollbar = None
 | 
			
		||||
        self.send_button = None
 | 
			
		||||
 | 
			
		||||
    def show(self):
 | 
			
		||||
        self.gui = tkinter.Tk()
 | 
			
		||||
        self.gui.title(messages.TITLE)
 | 
			
		||||
        self.fill_frame()
 | 
			
		||||
        self.gui.protocol(CLOSING_PROTOCOL, self.on_closing)
 | 
			
		||||
        return self.input_dialogs()
 | 
			
		||||
 | 
			
		||||
    def loop(self):
 | 
			
		||||
        self.gui.mainloop()
 | 
			
		||||
 | 
			
		||||
    def fill_frame(self):
 | 
			
		||||
        self.frame = tkinter.Frame(self.gui)
 | 
			
		||||
        self.scrollbar = tkinter.Scrollbar(self.frame)
 | 
			
		||||
        self.message_list = tkinter.Text(self.frame, state=TEXT_STATE_DISABLED)
 | 
			
		||||
        self.scrollbar.pack(side=tkinter.RIGHT, fill=tkinter.Y)
 | 
			
		||||
        self.message_list.pack(side=tkinter.LEFT, fill=tkinter.BOTH)
 | 
			
		||||
        self.message = tkinter.StringVar()
 | 
			
		||||
        self.frame.pack()
 | 
			
		||||
        self.input_field = tkinter.Entry(self.gui, textvariable=self.message)
 | 
			
		||||
        self.input_field.pack()
 | 
			
		||||
        self.input_field.bind(KEY_RETURN, self.application.send)
 | 
			
		||||
        self.send_button = tkinter.Button(self.gui, text=messages.SEND, command=self.application.send)
 | 
			
		||||
        self.send_button.pack()
 | 
			
		||||
 | 
			
		||||
    def input_dialogs(self):
 | 
			
		||||
        self.gui.lower()
 | 
			
		||||
        self.application.username = simpledialog.askstring(messages.USERNAME, messages.INPUT_USERNAME, parent=self.gui)
 | 
			
		||||
        if self.application.username is None:
 | 
			
		||||
            return False
 | 
			
		||||
        self.application.host = simpledialog.askstring(messages.SERVER_HOST, messages.INPUT_SERVER_HOST,
 | 
			
		||||
                parent=self.gui)
 | 
			
		||||
        if self.application.host is None:
 | 
			
		||||
            return False
 | 
			
		||||
        self.application.port = simpledialog.askinteger(messages.SERVER_PORT, messages.INPUT_SERVER_PORT,
 | 
			
		||||
                parent=self.gui)
 | 
			
		||||
        if self.application.port is None:
 | 
			
		||||
            return False
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def alert(self, title, message):
 | 
			
		||||
        messagebox.showerror(title, message)
 | 
			
		||||
 | 
			
		||||
    def show_message(self, message):
 | 
			
		||||
        self.message_list.configure(state=TEXT_STATE_NORMAL)
 | 
			
		||||
        self.message_list.insert(tkinter.END, str(message) + END_OF_LINE)
 | 
			
		||||
        self.message_list.configure(state=TEXT_STATE_DISABLED)
 | 
			
		||||
 | 
			
		||||
    def on_closing(self):
 | 
			
		||||
        self.application.exit()
 | 
			
		||||
        self.gui.destroy()
 | 
			
		||||
					Loading…
					
					
				
		Reference in New Issue