-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathchat.py
More file actions
175 lines (161 loc) · 7.12 KB
/
chat.py
File metadata and controls
175 lines (161 loc) · 7.12 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
import sys
import socket
import threading
#Chat Class
class Chat:
def __init__(self):
self.connections = [] # List to keep track of (conn, addr, id) tuples
self.next_id = 1 # Unique identifier for each connection
self.lock = threading.Lock()
self.shutdown_event = threading.Event() #Event to signal shutdown
def get_ip(self):
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
#response = requests.get("https://ipinfo.io/ip")
#ip_addr = response.text
hostname = socket.gethostname()
ip = socket.gethostbyname(hostname)
s.connect((ip, 1))
IP = s.getsockname()[0]
except Exception:
IP = '127.0.0.1'
finally:
s.close()
return IP
def handle_client(self, conn: socket, addr: str, id: int):
print(f"New connection from {addr}, id: {id}")
try:
while not self.shutdown_event.is_set():
try:
msg = conn.recv(1024).decode('utf-8')
if not msg:
break
print(f"Message received from {addr[0]}")
print(f"Sender's Port: {addr[1]}")
print(f"Message: \"{msg}\"")
except socket.error:
break
except Exception as e:
print(f"Error with connection id {id}: {e}")
finally:
with self.lock:
self.connections = [(c, a, i) for c, a, i in self.connections if i != id]
conn.close()
print(f"Connection id {id} closed.")
def accept_connections(self, server_socket):
server_socket.settimeout(1) # Allows the accept call to timeout periodically
while not self.shutdown_event.is_set():
# this prevents the method from blocking indefinitely on recv() and allows threads to exit if a shutdown is signaled.
try:
conn, addr = server_socket.accept()
with self.lock:
self.connections.append((conn, addr, self.next_id))
thread = threading.Thread(target=self.handle_client, args=(conn, addr, self.next_id))
thread.start()
self.next_id += 1
except socket.timeout:
continue
except socket.error as e:
if not self.shutdown_event.is_set():
print(f"Socket error: {e}")
break
def connect_to_peer(self, dest, port):
try:
peer_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
peer_sock.connect((dest, port))
with self.lock:
self.connections.append((peer_sock, (dest, port), self.next_id))
thread = threading.Thread(target=self.handle_client, args=(peer_sock, (dest, port), self.next_id))
thread.start()
print(f"Connected to {dest}:{port} with connection id {self.next_id}")
self.next_id += 1
except Exception as e:
print(f"Failed to connect to {dest}:{port}: {e}")
def main(port):
chatter = Chat()
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
server_socket.bind(('', port))
server_socket.listen()
print(f"Listening on port {port}")
threading.Thread(target=chatter.accept_connections, args=(server_socket,), daemon=False).start()
except Exception as e:
print(f"Failed to bind socket on port {port}: {e}")
sys.exit()
my_ip = chatter.get_ip()
while True:
cmd = input().split()
if not cmd:
continue
if cmd[0] == 'exit':
print("Closing all connections and terminating process.")
chatter.shutdown_event.set() # Signal all threads to shutdown
for conn, _, _ in list(chatter.connections): # # Use a list copy for safe iteration
conn.close()
server_socket.close()
# Ensure all threads finish
main_thread = threading.current_thread()
for t in threading.enumerate():
if t is not main_thread:
t.join()
break
elif cmd[0] == 'help':
print("Available commands:\nhelp, myip, myport, connect <destination> <port>, list, terminate <id>, send <id> <message>, exit")
elif cmd[0] == 'myip':
print(f"My IP address is {my_ip}")
elif cmd[0] == 'myport':
print(f"Listening on port {port}")
elif cmd[0] == 'connect' and len(cmd) == 3:
has_error = False
if my_ip == cmd[1] and port == int(cmd[2]):
print("Error: Connecting to yourself is not allowed.")
has_error = True
for connection in chatter.connections:
if connection[1][0] == cmd[1] and connection[1][1] == int(cmd[2]):
print("Error: You are already connected to that peer.")
has_error = True
break
if (not has_error):
chatter.connect_to_peer(cmd[1], int(cmd[2]))
elif cmd[0] == 'list':
print("id: IP address Port No.")
for conn, addr, id in chatter.connections:
# Use getsockname to get the local endpoint details
local_ip, local_port = conn.getsockname()
print(f"{id}: {local_ip} {local_port}")
elif cmd[0] == 'terminate' and len(cmd) == 2:
terminate_id = int(cmd[1])
terminated = False
with chatter.lock:
# use list to safely iterate
for conn, addr, id in list(chatter.connections):
if id == terminate_id:
conn.close()
# connection list is correctly updated without trying to modify it during iteration.
chatter.connections = [(c, a, i) for c, a, i in chatter.connections if i != id]
terminated = True
print(f"Terminated connection {terminate_id}")
break
if not terminated:
print(f"No connection found with id {terminate_id}")
elif cmd[0] == 'send' and len(cmd) >= 3:
send_id = int(cmd[1])
message = " ".join(cmd[2:])
print("Sending message: " + message)
sent = False
with chatter.lock:
for conn, addr, id in chatter.connections:
if id == send_id:
conn.sendall(message.encode('utf-8'))
print(f"Message sent to {send_id} Successfully") # Modified output for success
sent = True
break
if not sent:
print(f"No connection found with id {send_id}")
else:
print(f"{cmd} is not a valid option. Type 'help' for more options.")
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: python chat.py <listening_port>")
sys.exit()
main(int(sys.argv[1]))