-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathsocket_client.py
More file actions
223 lines (181 loc) · 7.18 KB
/
socket_client.py
File metadata and controls
223 lines (181 loc) · 7.18 KB
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
import socket
import json
import threading
import time
import shutil
class ChatClient:
def __init__(self, host='127.0.0.1', port=5555):
self.host = host
self.port = port
self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.connected = False
self.username = ""
self.encoding = "utf-8"
self.receiving_lock = threading.Lock()
self.input_lock = threading.Lock()
# Try to determine terminal width
try:
self.terminal_width = shutil.get_terminal_size().columns
except:
self.terminal_width = 100 # Default fallback width
def connect(self, username):
"""Connect to the server and send username"""
try:
# Connect to server
self.client_socket.connect((self.host, self.port))
self.username = username
# Send username to server in JSON format as expected by server
username_data = json.dumps({"username": username})
self.client_socket.send(username_data.encode(self.encoding))
self.connected = True
# Start thread to receive messages
threading.Thread(target=self.receive_messages, daemon=True).start()
# Start the input thread
threading.Thread(target=self.handle_input, daemon=True).start()
return True
except Exception as e:
print(f"Connection error: {e}")
return False
def send_message(self, message: dict):
"""Send a message to the server"""
if not self.connected:
print("Not connected to server")
return False
try:
# Format message as JSON as expected by the server
message_data = json.dumps(message)
self.client_socket.send(message_data.encode(self.encoding))
return True
except Exception as e:
print(f"Error sending message: {e}")
self.connected = False
return False
def clear_line(self):
"""Clear the current terminal line properly"""
# Update terminal width in case it changed (e.g., window resize)
try:
self.terminal_width = shutil.get_terminal_size().columns
except:
pass # Keep the previous value if we can't get a new one
return f"\r{' ' * self.terminal_width}\r"
def receive_messages(self):
buffer = ""
while self.connected:
try:
message = self.client_socket.recv(1024).decode(self.encoding)
if not message:
with self.receiving_lock:
print("\nConnection to server lost")
self.connected = False
break
# Add received data to buffer
buffer += message
# Process complete lines from the buffer
while '\n' in buffer or '\r\n' in buffer:
# Find the newline character
index = buffer.find('\n')
if index == -1:
index = buffer.find('\r\n')
if index != -1: # Found \r\n
line = buffer[:index]
buffer = buffer[index+2:] # Skip both \r and \n
else:
break # No complete line found
else:
line = buffer[:index]
buffer = buffer[index+1:] # Skip just \n
# Display the complete line
with self.receiving_lock:
print(f"{self.clear_line()}{line}")
print("> ", end='', flush=True) # Show prompt again
# If we have data but no newline, display it too
if buffer and not ('\n' in buffer or '\r\n' in buffer):
with self.receiving_lock:
print(f"{self.clear_line()}{buffer}")
buffer = ""
print("> ", end='', flush=True)
except Exception as e:
with self.receiving_lock:
print(f"\nError receiving message: {e}")
self.connected = False
break
def format_input_message(self, message: str) -> dict:
data: dict = dict()
if message.startswith("say "):
data["say"] = message[4:]
else:
data["cmd"] = message
return data
def handle_input(self):
"""Handle user input in a separate thread"""
time.sleep(1) # Short delay to allow initial server messages
print("Connected! Type 'quit' to exit.")
print("> ", end='', flush=True)
while self.connected:
try:
with self.input_lock:
user_input = input()
if not self.connected:
break
lower: str = user_input.lower()
if lower == 'quit':
self.connected = False
break
elif lower == "help":
print("Write: say Hello (to send a message to all connected clients).")
print("Write: help (to get the help menu).")
print("Any other inputs are considered a command to execute on the server.")
continue
if user_input: # Only send non-empty messages
self.send_message(self.format_input_message(user_input))
# Re-display prompt
print("> ", end='', flush=True)
except EOFError:
break
except KeyboardInterrupt:
self.connected = False
break
def disconnect(self):
"""Disconnect from the server"""
if hasattr(self, 'client_socket'):
try:
self.client_socket.close()
except:
pass
self.connected = False
print("\nDisconnected from server")
def main():
# Get server details
server_ip = input("Server IP (press Enter for default 127.0.0.1): ")
if not server_ip:
server_ip = '127.0.0.1'
server_port = input("Server PORT (press Enter for default 5555): ")
if not server_port:
server_port = 5555
else:
try:
server_port = int(server_port)
except ValueError:
print("Invalid port. Using default 5555.")
server_port = 5555
# Create client instance
client = ChatClient(host=server_ip, port=server_port)
# Get username
username = input("Enter your username: ")
while not username:
username = input("Username cannot be empty. Please enter a username: ")
print(f"Connecting to server at {server_ip}:{server_port} as {username}...")
# Connect to server
if not client.connect(username):
print("Failed to connect to server")
return
# Main thread will wait here until user quits
try:
while client.connected:
time.sleep(0.2)
except KeyboardInterrupt:
pass
finally:
client.disconnect()
if __name__ == "__main__":
main()