I am currently working on implementing a toy store server using Python, employing socket connections and a custom thread pool for handling concurrent client requests. However, I'm facing some challenges with managing concurrent requests efficiently.
Server Component: The server listens for incoming client requests over sockets. It takes the name of a toy as an argument and returns the dollar price of the item if it's in stock. If the item is not found, it returns -1, and if the item is found but not in stock, it returns 0.
A custom thread pool is implemented to handle multiple client connections concurrently.
Client Component:
The client connects to the server using a socket connection and issues random toy requests.
I've implemented a custom thread pool to manage client requests, but I'm unsure if it's handling concurrent requests optimally. The server does not receive any further requests after the first client request.
Server code:
import socket
import json
from threading import Thread, Lock
from collections import deque
class Server():
def __init__(self, host, port, num_threads):
self.items = {
"tux": {
"qty": 100,
"cost": 25.99
},
"whale": {
"qty": 100,
"cost": 19.99
}
}
self.lock = Lock()
self.request_queue = deque([])
self.thread_pool = [Thread(target=self.serve_request) for _ in range(num_threads)]
self.s = socket.socket()
self.s.bind((host, port))
print("socket binded to port", port)
self.s.listen(5)
print("socket is listening")
def get_item(self, item):
with self.lock:
if item not in self.items:
return -1
if self.items[item]['qty'] == 0:
return 0
self.items[item]['qty'] -= 1
return self.items[item]['cost']
def add_request(self, req):
data = req.recv(4096)
data = json.loads(data.decode('utf-8'))
cost = self.get_item(data['query'])
self.request_queue.append([req, cost])
def serve_request(self):
while True:
if self.request_queue:
req, cost = self.request_queue.popleft()
print("cost: ", cost)
req.send(str(cost).encode('utf-8'))
def run(self):
req, addr = self.s.accept()
for thread in self.thread_pool:
thread.start()
while True:
self.add_request(req)
print("request_queue: ", self.request_queue)
host = "127.0.0.1"
port = 12345
server = Server(host, port, 100)
server.run()
Client code:
import socket
import json
import random
def main():
host = "127.0.0.1"
port = 12345
s = socket.socket()
s.connect((host, port))
while True:
toys = ["tux", "whale"]
choice = random.choice(toys)
message = {"query": str(choice)}
serialzed_message = json.dumps(message)
print("requesting: ", choice)
s.send(serialzed_message.encode('utf-8'))
data = s.recv(4096)
print("Server replied: {}".format(str(data.decode('utf-8'))))
if __name__ == "__main__":
main()
Let's look at the client. You need to create a new socket for each request since once the server processes a request and returns a response it "forgets" the client's socket. In this case the client socket should first be closed.
Client Code
As far as the server goes, it is not handling requests correctly because you are only issuing
req, addr = self.s.accept()once. Functionrunshould be:You also have all your request-handling server threads in a CPU-bound loop checking for new requests. You could have/should have used
threading.Conditioninstances to wait for a non-empty queue. But far simpler is to just use a multithreading pool. Note that I have also allowed for termination of the server by hitting the Enter key.Server Code
Update
If you do not want to use a multithreading pool, then your original code should be modified to use a
Conditioninstance so that there is no wasted CPU cycles checking an empty queue:Server Code
Client Code
For a better test, we are multithreading this:
Prints: