Last active
April 29, 2021 23:55
-
-
Save tonyallan/4085503935aa4cf57ad9c025d6888518 to your computer and use it in GitHub Desktop.
Python 3 chat server example using select.select()
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!python3 | |
""" | |
To use this program, open three or more terminal/console windows | |
python3 tcp-chat-server.py | |
in all remaining windows: | |
nc localhost 5678 | |
OR | |
telnet localhost 5678 | |
To enable telnet on Windows 10 | |
https://social.technet.microsoft.com/wiki/contents/articles/38433.windows-10-enabling-telnet-client.aspx | |
Based on the example: | |
https://steelkiwi.com/blog/working-tcp-sockets/ | |
See also: | |
https://docs.python.org/3.6/library/select.html?highlight=select.select#select.select | |
https://docs.python.org/3/howto/sockets.html | |
""" | |
import select | |
import socket | |
import sys | |
import queue | |
ip_address = 'localhost' | |
port = 5678 | |
listen_queue_depth = 5 | |
welcome = b'Welcome. Type text to echo a message or *text to broadcast to all connected clients.\n' | |
def main(): | |
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | |
server.setblocking(0) | |
server.bind((ip_address, port)) | |
server.listen(listen_queue_depth) | |
print(f'[Server] server={server} fileno={server.fileno()}') | |
print(f'[Listening] ip_address={ip_address} port={port}') | |
inputs = [server] | |
outputs = [] | |
message_queues = {} | |
while inputs: | |
readable, writable, exceptional = select.select( | |
inputs, outputs, inputs) | |
for s in readable: | |
if s is server: | |
# new connection (on the server socket) | |
connection, client_address = s.accept() | |
connection.setblocking(0) | |
inputs.append(connection) | |
message_queues[connection] = queue.Queue() | |
message_queues[connection].put(welcome) | |
outputs.append(connection) | |
print(f'[New connection] socket={s} fileno={s.fileno()}') | |
else: | |
# data from an existing connection (on the socket for this connection) | |
byte_data = s.recv(1024) | |
if byte_data: | |
# Do something with out new data | |
# i.e. broadcast to all connected clients or echo to one client | |
data = str(byte_data, 'utf-8') | |
if str(data)[0] == '*': | |
print(f'[Broadcast Data] fileno={s.fileno()} data={data}') | |
for bs in message_queues: | |
message_queues[bs].put(b'broadcast=' + bytes(data[1:], 'utf-8')) | |
if bs not in outputs: | |
outputs.append(bs) | |
else: | |
print(f'[Echo Data] fileno={s.fileno()} data={data}') | |
message_queues[s].put(b'echo=' + bytes(data, 'utf-8')) | |
if s not in outputs: | |
outputs.append(s) | |
else: | |
if s in outputs: | |
outputs.remove(s) | |
inputs.remove(s) | |
s.close() | |
del message_queues[s] | |
for s in writable: | |
try: | |
next_msg = message_queues[s].get_nowait() | |
except queue.Empty: | |
outputs.remove(s) | |
else: | |
print(f'[Send next message] fileno={s.fileno()} data={next_msg}') | |
s.send(next_msg) | |
for s in exceptional: | |
print(f'[Exception] fileno={s.fileno()}') | |
inputs.remove(s) | |
if s in outputs: | |
outputs.remove(s) | |
s.close() | |
del message_queues[s] | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment