-
Notifications
You must be signed in to change notification settings - Fork 9
/
client.py
223 lines (177 loc) · 7.25 KB
/
client.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
#!/usr/bin/env python3
import threading
import socket
import argparse
import os
import sys
import tkinter as tk
class Send(threading.Thread):
"""
Sending thread listens for user input from the command line.
Attributes:
sock (socket.socket): The connected socket object.
name (str): The username provided by the user.
"""
def __init__(self, sock, name):
super().__init__()
self.sock = sock
self.name = name
def run(self):
"""
Listens for user input from the command line only and sends it to the server.
Typing 'QUIT' will close the connection and exit the application.
"""
while True:
print('{}: '.format(self.name), end='')
sys.stdout.flush()
message = sys.stdin.readline()[:-1]
# Type 'QUIT' to leave the chatroom
if message == 'QUIT':
self.sock.sendall('Server: {} has left the chat.'.format(self.name).encode('ascii'))
break
# Send message to server for broadcasting
else:
self.sock.sendall('{}: {}'.format(self.name, message).encode('ascii'))
print('\nQuitting...')
self.sock.close()
os._exit(0)
class Receive(threading.Thread):
"""
Receiving thread listens for incoming messages from the server.
Attributes:
sock (socket.socket): The connected socket object.
name (str): The username provided by the user.
messages (tk.Listbox): The tk.Listbox object containing all messages displayed on the GUI.
"""
def __init__(self, sock, name):
super().__init__()
self.sock = sock
self.name = name
self.messages = None
def run(self):
"""
Receives data from the server and displays it in the GUI.
Always listens for incoming data until either end has closed the socket.
"""
while True:
message = self.sock.recv(1024).decode('ascii')
if message:
if self.messages:
self.messages.insert(tk.END, message)
print('hi')
print('\r{}\n{}: '.format(message, self.name), end = '')
else:
# Thread has started, but client GUI is not yet ready
print('\r{}\n{}: '.format(message, self.name), end = '')
else:
# Server has closed the socket, exit the program
print('\nOh no, we have lost connection to the server!')
print('\nQuitting...')
self.sock.close()
os._exit(0)
class Client:
"""
Supports management of client-server connections and integration with the GUI.
Attributes:
host (str): The IP address of the server's listening socket.
port (int): The port number of the server's listening socket.
sock (socket.socket): The connected socket object.
name (str): The username of the client.
messages (tk.Listbox): The tk.Listbox object containing all messages displayed on the GUI.
"""
def __init__(self, host, port):
self.host = host
self.port = port
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.name = None
self.messages = None
def start(self):
"""
Establishes the client-server connection. Gathers user input for the username,
creates and starts the Send and Receive threads, and notifies other connected clients.
Returns:
A Receive object representing the receiving thread.
"""
print('Trying to connect to {}:{}...'.format(self.host, self.port))
self.sock.connect((self.host, self.port))
print('Successfully connected to {}:{}'.format(self.host, self.port))
print()
self.name = input('Your name: ')
print()
print('Welcome, {}! Getting ready to send and receive messages...'.format(self.name))
# Create send and receive threads
send = Send(self.sock, self.name)
receive = Receive(self.sock, self.name)
# Start send and receive threads
send.start()
receive.start()
self.sock.sendall('Server: {} has joined the chat. Say hi!'.format(self.name).encode('ascii'))
print("\rAll set! Leave the chatroom anytime by typing 'QUIT'\n")
print('{}: '.format(self.name), end = '')
return receive
def send(self, text_input):
"""
Sends text_input data from the GUI. This method should be bound to text_input and
any other widgets that activate a similar function e.g. buttons.
Typing 'QUIT' will close the connection and exit the application.
Args:
text_input(tk.Entry): A tk.Entry object meant for user text input.
"""
message = text_input.get()
text_input.delete(0, tk.END)
self.messages.insert(tk.END, '{}: {}'.format(self.name, message))
# Type 'QUIT' to leave the chatroom
if message == 'QUIT':
self.sock.sendall('Server: {} has left the chat.'.format(self.name).encode('ascii'))
print('\nQuitting...')
self.sock.close()
os._exit(0)
# Send message to server for broadcasting
else:
self.sock.sendall('{}: {}'.format(self.name, message).encode('ascii'))
def main(host, port):
"""
Initializes and runs the GUI application.
Args:
host (str): The IP address of the server's listening socket.
port (int): The port number of the server's listening socket.
"""
client = Client(host, port)
receive = client.start()
window = tk.Tk()
window.title('Chatroom')
frm_messages = tk.Frame(master=window)
scrollbar = tk.Scrollbar(master=frm_messages)
messages = tk.Listbox(
master=frm_messages,
yscrollcommand=scrollbar.set
)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y, expand=False)
messages.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
client.messages = messages
receive.messages = messages
frm_messages.grid(row=0, column=0, columnspan=2, sticky="nsew")
frm_entry = tk.Frame(master=window)
text_input = tk.Entry(master=frm_entry)
text_input.pack(fill=tk.BOTH, expand=True)
text_input.bind("<Return>", lambda x: client.send(text_input))
text_input.insert(0, "Your message here.")
btn_send = tk.Button(
master=window,
text='Send',
command=lambda: client.send(text_input)
)
frm_entry.grid(row=1, column=0, padx=10, sticky="ew")
btn_send.grid(row=1, column=1, pady=10, sticky="ew")
window.rowconfigure(0, minsize=500, weight=1)
window.rowconfigure(1, minsize=50, weight=0)
window.columnconfigure(0, minsize=500, weight=1)
window.columnconfigure(1, minsize=200, weight=0)
window.mainloop()
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Chatroom Server')
parser.add_argument('host', help='Interface the server listens at')
parser.add_argument('-p', metavar='PORT', type=int, default=1060,
help='TCP port (default 1060)')
args = parser.parse_args()
main(args.host, args.p)