Repository navigation
Expand file tree
/
Copy pathsocketserver.py
More file actions
165 lines (134 loc) · 6.72 KB
/
Copy pathsocketserver.py
File metadata and controls
165 lines (134 loc) · 6.72 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
"""
import socket
HOST='127.0.0.1' # standard loopback interface address
PORT=1100 # port to listen, non prvileged are above 1023
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind((HOST, PORT))
s.listen()
print("server listening...")
conn, addr = s.accept() #a succesful connection involves two pieces of information. conn (a new socket object
# which is the client specific socket you use to communicate directly with the connected client. and addr (a tuple
# containing the IP address and port number of the connected client). the original listening socket is the main
#switchboard operator waiting for the line to ring. When a call comes, the operator hands the call over to a
#dedicated extension line and notes down the caller's phone number for logging and calling back. The socket object
# the server is listening on is designed only to wait for and accept new connections. The newly created socket is
#designed for sending and receiving data with that single client.
with conn:
print(f"Connected by {addr}")
while True:
data=conn.recv(1024)
if not data:
break
conn.sendall(data)
"""
import socket
import threading
# Configuration must match the client
HOST = "0.0.0.0" #localhost refers to system configuration, 0.0.0.0 refers to all network interfaces
PORT = 1990
# List to hold all connected client sockets and their addresses
clients = []
clients_lock = threading.Lock()
def broadcast(message, sender_socket):
"""Sends a message to all connected clients except the sender."""
with clients_lock:
# Iterate over a copy of the list to avoid issues if a client disconnects during the loop
for client_socket, addr in list(clients):
if client_socket != sender_socket:
try:
full_message = f"[{addr[1]}] Note: {message}\n"
client_socket.sendall(full_message.encode('utf-8'))
# Encode and send the message
except:
# If sending fails, assume the client disconnected
print(f"Error sending to {addr}. Removing client.")
remove_client(client_socket)
def remove_client(client_socket):
"""Removes a disconnected client from the global list."""
with clients_lock:
for i, (sock, addr) in enumerate(clients):
if sock == client_socket:
clients.pop(i)
print(f"Client {addr} disconnected and removed.")
try:
client_socket.close()
except:
pass
break
def handle_client(client_socket, client_address):
"""Handles all communication with a single connected client."""
print(f"New connection established with {client_address}")
# Send a welcome message
welcome_message = f"Connected to server. Welcome, User:{client_address[1]}!\n"
try:
client_socket.sendall(welcome_message.encode('utf-8'))
except:
remove_client(client_socket)
return
# Data buffer for incomplete messages (TCP stream handling)
buffer = ""
while True:
try:
# Receive data
data = client_socket.recv(4096)
if not data:
break # Client closed connection gracefully
buffer += data.decode('utf-8')
# Process received data line by line using the newline delimiter
messages = buffer.split('\n')
buffer = messages.pop() # Keep the last (incomplete) segment in the buffer
for message in messages:
if message.strip():
print(f"Note from {client_address}: {message.strip()}")
# Broadcast the received message to all other clients
broadcast(message.strip(), sender_socket=client_socket, sender_addr=client_address)
except ConnectionResetError:
print(f"Client {client_address} forcefully disconnected.")
break
except Exception as e:
print(f"Unexpected error with client {client_address}: {e}")
break
# Cleanup after the loop breaks
remove_client(client_socket)
def start_server():
"""Initializes the server and starts listening for connections."""
try:
# Create a socket object
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Allow the port to be reused immediately after the server stops
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# Bind the socket to the host and port
server_socket.bind((HOST, PORT))
# Start listening for incoming connections (max 5 queue)
server_socket.listen(5)
print(f"Server started and listening on {HOST}:{PORT}...")
except Exception as e:
print(f"Could not start server: {e}")
return
while True:
try:
# Accept a new connection
client_socket, client_address = server_socket.accept()
# Add the new client to the list
with clients_lock:
clients.append((client_socket, client_address))
# Start a new thread to handle this client
client_handler = threading.Thread(target=handle_client, args=(client_socket, client_address))
client_handler.daemon = True
client_handler.start()
except KeyboardInterrupt:
print("\nServer shutting down.")
server_socket.close()
break
except Exception as e:
print(f"An error occurred in the server loop: {e}")
break
if __name__ == "__main__":
start_server()
#master.protocol is a method in Tkinter to interact with the window manager protocols the standard messages
#the OS sends to your window.The method is used to define a callback that runs a specific standardized message
#is sent to the app by the OS. The most common use and the one you used is to intercept the event that occurs when
#a user clicks the window's close button. This event is known as "WM_DELETE_WINDOW", without using master.protocol()
#the window would simply close immediately when the user clicked X. By implementing master.protocol() with a custom
#function like self.on_closing() you gain control to execute critical cleanup tasks before the application shuts down
#as asking the user if he really wants to quit, resource cleanup, thread termination.