Skip to content

Commit

Permalink
integration Multicast + Messaging + TCP init
Browse files Browse the repository at this point in the history
  • Loading branch information
mmuravytskyi committed Jun 20, 2021
1 parent 0dfbd44 commit 2303dfb
Show file tree
Hide file tree
Showing 4 changed files with 275 additions and 233 deletions.
308 changes: 178 additions & 130 deletions client_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,153 +5,201 @@
- When a client wants to message another client, a TCP connection is opened, data is sent and the connection is closed.
"""

import socket
import threading
import sys
from typing import Dict, List
import socket
import struct
import config
import json
import threading
import random


def tcp_listener(port: int):
s = socket.socket()
# host = socket.gethostname()
host = '0.0.0.0'
s.bind((host, port))
s.listen(5)
print('TCP listener started on port', port)
class Client():
def __init__(self):
self.client_list = dict()
self.username = ''

@staticmethod
def multicast_handler(client_port: int):
# create the datagram socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('', client_port))
# set a timeout so the socket does not block indefinitely when trying to receive data.
sock.settimeout(0.2)

# Set the time-to-live for messages to 1 so they do not go past the local network segment.
ttl = struct.pack('b', 1)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl)

while True:
conn, address = s.accept()
data = conn.recv(1024)
if not data:
break
data = data.decode('utf8')
try:
data_split = data.split()
name = data_split[0]
message = " ".join(data_split[1:])
print(f'{name} says: {message}')
except IndexError:
print(f'{address} sends an empty message.')
conn.sendall(bytes('OK', encoding='utf8'))
# send request to the multicast group
print(f'CLIENT: Sending multicast message to {config.MULTICAST_IP}')

message = 'SERVER DISCOVERY'
multicast_group = (config.MULTICAST_IP, config.MULTICAST_PORT)
sock.sendto(bytes(message, encoding='utf-8'), multicast_group)
finally:
sock.close()

def tcp_handler(self, _port: int):
sock_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock_tcp.bind(('', _port))
sock_tcp.listen(5)

# empty buffer
buff = b''
while True:
print(f'CLIENT: Waiting for a TCP connection')
connection, client_address = sock_tcp.accept()
try:
print(f'CLIENT: Connection from {client_address}')

self.username = input('=== Provide Your Nickname === ')

connection.sendall(bytes(self.username, encoding='utf8'))
# receive the data in chunks and add to the buffer
while True:
print(f'CLIENT: Waiting for the server to send client base')
data = connection.recv(512)
buff += data
if not data:
break
break
finally:
print(f'CLIENT: Client base received')
client_list = json.loads(buff.decode('utf-8'))
self.client_list = client_list
print(f'CLIENT: Closing TCP connection')
# clean up the connection
connection.close()
break

@staticmethod
def tcp_listener(_port: int):
s = socket.socket()
# host = socket.gethostname()
host = '0.0.0.0'
s.bind((host, _port))
s.listen(5)
print('TCP listener started on port', _port)

while True:
conn, address = s.accept()
data = conn.recv(1024)
if not data:
break
data = data.decode('utf8')
try:
data_split = data.split()
name = data_split[0]
message = " ".join(data_split[1:])
print(f'{name} says: {message}')
except IndexError:
print(f'{address} sends an empty message.')
conn.sendall(bytes('OK', encoding='utf8'))

@staticmethod
def do_list(clients: List):
print("Available clients:")

for client in clients:
print(f'address: {client["ip"]}:{client["port"]}, nickname: {client["nickname"]}')

@staticmethod
def do_connect(address: Dict, _username: str) -> bool:
"""
address = {
'ip': "IPv4_ADDR"
'port: 1234
}
"""
msg = input('Please provide your message: ')
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((address['ip'], address['port']))

s.sendall(bytes(f'{_username} {msg}', encoding='utf8'))
resp = s.recv(1024)

def do_list(clients: List):
print("Available clients:")

for client in clients:
print(f'address: {client["ip"]}:{client["port"]}, nickname: {client["name"]}')
s.close()
except socket.error as e:
print('[ERR]', e)
return False

if resp.decode('utf8') == 'OK':
return True
else:
return False

def do_connect(address: Dict, username: str) -> bool:
"""
address = {
'ip': "IPv4_ADDR"
'port: 1234
}
"""
msg = input('Please provide your message: ')
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((address['ip'], address['port']))

s.sendall(bytes(f'{username} {msg}', encoding='utf8'))
resp = s.recv(1024)

s.close()
except socket.error as e:
print('[ERR]', e)
return False

if resp.decode('utf8') == 'OK':
return True
else:
return False


def handle_actions(clients: List, username: str):
actions = ['list', 'msg', 'exit',]

input_split = input().split()
action = input_split[0]

if action not in actions:
print('[ERR] Incorrect command')
return

if action == 'list':
do_list(clients)

if action == 'msg':
try:
client_address = input_split[1]
client = {}
# check if client_address is an IP:port string or client name
cli_filtered = list(filter(lambda c: c['name'] == client_address, clients))

if len(cli_filtered): # client name found -> assign client
client = cli_filtered[0]
else:
# split input to separate IP / port
client['ip'] = client_address.split(':')[0]
client['port'] = int(client_address.split(':')[1])

success = do_connect(client, username)
if success:
print(f'Message to {client_address} delievered.')
else:
print(f'Error sending data to {client_address}')
except (IndexError, ValueError):
print('[ERR] incorrect address / name')
def handle_actions(self, clients: List, _username: str):
actions = ['list', 'msg', 'exit']

input_split = input().split()
action = input_split[0]

if action not in actions:
print('[ERR] Incorrect command')
return

if action == 'exit':
print('Exiting...')
sys.exit(1)


# this should be replaced with a dynamic list provided by the server
client_list = [
{
'ip': '127.0.0.1',
'port': 1234,
'name': 'client1',
},
{
'ip': '127.0.0.1',
'port': 6969,
'name': 'client2',
},
{
'ip': '127.0.0.1',
'port': 8888,
'name': 'client3',
},
]

usage_str = """
Commands:
list - display available clients
msg CLIENT_NAME/CLIENT_ADDRESS - connect to one of the clients specified in <list>
wait - allow other clients to connect to you
exit - close chat client
"""
if action == 'list':
self.do_list(clients)

if action == 'msg':
try:
client_address = input_split[1]
client = {}
# check if client_address is an IP:port string or client name
cli_filtered = list(filter(lambda c: c['name'] == client_address, clients))

if len(cli_filtered): # client name found -> assign client
client = cli_filtered[0]
else:
# split input to separate IP / port
client['ip'] = client_address.split(':')[0]
client['port'] = int(client_address.split(':')[1])

success = do_connect(client, _username)
if success:
print(f'Message to {client_address} delievered.')
else:
print(f'Error sending data to {client_address}')
except (IndexError, ValueError):
print('[ERR] incorrect address / name')
return

if action == 'exit':
print('Exiting...')
sys.exit(1)




if __name__ == '__main__':
listener_port = 5000

while True:
try:
listener_port = int(input('TCP Listener port number (must be an integer): '))
break
except ValueError:
print('[ERR] Incorrect port number.')

username = input('Your Username: ')
usage_str = """
Commands:
list - display available clients
msg CLIENT_NAME/CLIENT_ADDRESS - connect to one of the clients specified in <list>
wait - allow other clients to connect to you
exit - close chat client
"""

s = Client()

port = random.randint(50_000, 65_000)
# pass selected port to the TCP thread, in order to listen on the same port
# thread in the background as daemon
th = threading.Thread(target=s.tcp_handler, args=(port,), daemon=True)
th.start()
s.multicast_handler(port)
th.join()

threading.Thread(target=tcp_listener, args=(listener_port,)).start()
print(s.client_list)

# TODO: smart port allocation
threading.Thread(target=s.tcp_listener, args=(port+1,)).start()

print(usage_str)

while True:
handle_actions(client_list, username)
s.handle_actions(s.client_list['clients'], s.username)
2 changes: 1 addition & 1 deletion config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
MULTICAST_PORT = 10001
MULTICAST_IP = '224.0.0.1'
TCP_PORT = 10001
SERVER_IP = 'localhost'
SERVER_IP = '127.0.0.1'
Loading

0 comments on commit 2303dfb

Please sign in to comment.