Skip to content

Instantly share code, notes, and snippets.

@jirassimok
Last active October 23, 2019 22:25
Show Gist options
  • Save jirassimok/7c6589645b77bdd12e08ad254b8d28b8 to your computer and use it in GitHub Desktop.
Save jirassimok/7c6589645b77bdd12e08ad254b8d28b8 to your computer and use it in GitHub Desktop.
Stack Overflow Sockets question
"""
Code in other all other files adapted from here:
https://stackoverflow.com/q/58424446
This is just a simple script that runs processes for the server and client.
My comments in server.py and client.py all start with '##'.
shared.py sets the global variables shared by the client and the server,
and it modifies a handful of functions they use to improve logging.
The arguments to this script are the port for the server to use
and the number of clients to test.
If the output includes the phrase "client thread completed" a number of
times equal to the number of clients, the behavior was what I believe the
asker expected (i.e. the server processed each client). However, the script
will not terminate, and will need to be closed manually.
"""
import subprocess
import sys
import time
try:
port = int(sys.argv[1]) # just make sure it's an int
port = str(port)
except IndexError:
print('usage: test.py PORT [NUMBER_OF_CLIENTS]', file=sys.stderr)
sys.exit(1)
if len(sys.argv) >= 3:
clients = int(sys.argv[2])
if clients > 26:
print("Error: can only have up to 26 clients")
sys.exit(1)
else:
clients = 3
procs = [subprocess.Popen([sys.executable, 'server.py', port, '(server)', str(clients)])]
time.sleep(1) # Give the server a moment to start
procs.extend(subprocess.Popen([sys.executable, 'client.py', port, '(client {})'.format(i)])
for i in range(clients))
for proc in procs:
proc.wait() # Note: this will never finish, as the server runs forever
import socket
from shared import CONNECT, IP, PORT, BUFFER_SIZE, print
def receive_udp_msg(destination):
msg, origin = destination.recvfrom(BUFFER_SIZE) # receiving message OK
# This is the message sent as confirmation but is never received
destination.sendto("CHECK".encode('utf8'), origin) # sent but not received
msg = msg.decode()
return msg
c = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
c.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s = (IP, PORT)
# Connecting to the server
c.sendto(CONNECT.encode('utf8'), s)
cport = receive_udp_msg(c)
print("Client done")
c.close()
import socket
import threading
from threading import Thread
from shared import CONNECT, IP, PORT, BUFFER_SIZE, print, num_clients, name_client
n_cons = 0
def send_udp_mgs(origin, destination, msg):
msg = str(msg).encode('utf8')
origin.sendto(msg, destination) # sending message
# I never receive confirmation
opt, _ = origin.recvfrom(BUFFER_SIZE) # Code stucks here
opt = opt.decode('utf8')
print(opt)
def client_thread (s, c):
print("Connection petition from", c)
global n_cons
cport = PORT + n_cons # (see below)
send_udp_mgs(s, c, cport)
## Log when client is done
print("Client thread complete")
return
## Does 'cport' imply that you plan to create a new port, _in sequential order_, for each client?
## Consider that
## (a) you can only have so many ports open,
## (b) another process many be using one of those ports,
## (c) unless n_cons never goes down, _this process_ will be using those ports,
## (d) what do you need that many ports for?
## See also https://stackoverflow.com/a/11129641
# UDP socket acting as server
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((IP, PORT))
i = -1 ## Loop index
while True:
i += 1
data, addr = s.recvfrom(BUFFER_SIZE)
if data.decode() == CONNECT:
n_cons += 1
try:
Thread(target=client_thread, args=(s,), kwargs={'c':addr},
name=name_client(i)).start()
except:
print("Error creating thread")
break
## Log when other messages arrive, too
else:
print("Connect listener got {} from {}".format(data, addr))
import builtins
import socket
import string
import sys
import threading
import time
# These probably should have better names, or better yet, be function parameters.
IP = 'localhost'
PORT = int(sys.argv[1])
BUFFER_SIZE = 20
# Why isn't this a bytes object in the first place?
# All you do is decode, compare, and encode it.
CONNECT = 'CONNECT'
# Also, "CONNECT" seems like a rather misleading name for a UDP message,
# given that there is no such thing as a UDP connection.
# Unless you're on Windows and using SO_CONDITIONAL_ACCEPT,
# Extra globals for logging
procname = sys.argv[2]
if len(sys.argv) >= 4:
num_clients = int(sys.argv[3])
def name_client(i):
if i > 26:
return '[Client {}]'.format(i)
return '[Client {}]'.format(string.ascii_lowercase[i])
# I override a few functions to improve logging
# Print now includes time, process name, and thread name
def print(*args, **kwargs):
thread = threading.current_thread()
if threading.main_thread() is thread:
threadname = ''
else:
threadname = ' ' + thread.name
t = int(time.time() * 1e9)
heading = '{} {}{}: '.format(t, procname, threadname)
builtins.print(heading, *args, **kwargs, flush=True)
# Some socket methods now print information
socket.socket.REAL_RECVFROM = socket.socket.recvfrom
def recvfrom(self, buflen):
data, addr = self.REAL_RECVFROM(buflen)
print("socket got {} from {}".format(data, addr))
return data, addr
socket.socket.recvfrom = recvfrom
socket.socket.REAL_SENDTO = socket.socket.sendto
def sendto(self, data, addr):
result = self.REAL_SENDTO(data, addr)
print("socket sent {} to {}".format(data, addr))
return result
socket.socket.sendto = sendto
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment