Skip to content

Instantly share code, notes, and snippets.

@tonyallan
Last active April 29, 2021 23:55
Show Gist options
  • Save tonyallan/4085503935aa4cf57ad9c025d6888518 to your computer and use it in GitHub Desktop.
Save tonyallan/4085503935aa4cf57ad9c025d6888518 to your computer and use it in GitHub Desktop.
Python 3 chat server example using select.select()
#!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