-1

My goal basically is to have this 'memcached' object i've created, which is basically a dictionary with a set of commands that resemble the memchached system, and operate them from a client perspective and actually make some changes to the 'memcached dictionary object'.

First of all, i decided to make a memcached.py script that's basically a Memcached class with some of the commands of the real Memcached system, but as methods.

'''Class built with the intention of simulating a real Memcached system'''

class Memcached:

def __init__(self):
    self.storage = {}

"""Storage Commands"""    
def set(self, key, value):
    self.storage[key] = value
    print("STORED")

def add(self,key,value):
    if key in self.storage:
        print("ERROR")
    else:
        self.storage[key] = value
        print("STORED")

"""Retrieval Commands"""
def get(self, key):
    response = print(f'VALUE {key} \n {self.storage[key]} \n END')
    return response

This works from the terminal obviously, so this is the farthest i would go as of complexity of the Memcached system(i will add some other functionalities/commands when i resolve the bigger problem)

The bigger problem:

What i'm trying to do now is actually send and receive this memcached object through a localhost socket, so(for example) the client can store and retrieve information from this memcached object that i've created.

These are basically the scripts i have made:

server.py

import socket
import json
import memcached

cache = memcached.Memcached()

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('localhost', 12345))
s.listen(1)
print('Socket is listening...')
conn, addr = s.accept()
b = b''

with conn:
    while True:
        data = conn.recv(1024)
        b += data
        if not data:
            break

d = json.loads(b.decode('utf-8'))

args = d.split(' ')

if args[0] == 'set':
    cache.set(args[1], args[2])
elif args[0] == 'add':
    cache.add(args[1], args[2])
elif args[0] == 'get':
    cache.get(args[1], args[2])
else:
    print('ERROR')    

#control print
print(d)

client.py

import socket
import json

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('localhost', 12345))

class Client:

    def __init__(self):
       self.host = 'localhost'
        self.port = 12345
 
    def command(self, command, key, value):
        cmd = command  + ' ' +  key + ' ' + value
        b = json.dumps(cmd).encode('utf-8')    
        s.sendall(b)


newClient = Client()

while True:
    print('Taking memcached commands...')
    cmd = input()
    if cmd == 'quit':
        break
    else:
        cmdList = cmd.split()
        newClient.command(cmdList[0], cmdList[1], cmdList[2])

EDITED: Updated the scripts with the logic that lets me accede memcached methods from the client side. However now i'm not able to send different commands from the same connection. Is threading neccesary or a simple change in the logic could solve this? For example, in the current state i'm in, i can't use the 'get' command since the dictionary is basically rebuilt after the client script ends.

  • always put code, data and full error message as text (not screenshot) in question (not comment). – furas Feb 12 '21 at 21:19
  • what commands do you means? For simple commands you can simply split text on spaces `cmd = line.split(' ')` and compare substrings `if cmd[0] == "add": add(cmd[1:])` – furas Feb 12 '21 at 21:24
  • maybe you should use something different - ie, `RPC` ([Remote Procedure Call](https://en.wikipedia.org/wiki/Remote_procedure_call)). See: [What is the current choice for doing RPC in Python?](https://stackoverflow.com/questions/1879971/what-is-the-current-choice-for-doing-rpc-in-python) – furas Feb 12 '21 at 21:28
  • Changed formatting so it's more readable. It was a pain for some reason. The commands i mean are the actual methods of the mecached class. So for example, i want, from the client, send to server a request to set(test_key, test_value), and that the server actually adds that key and value to the dictionary in question. – Marco Liguori Feb 12 '21 at 23:42
  • you can send text `"set key_name value"` and server can `split(" ")` to get list `args = ["set", "key_name", "value"]` and use `if args[0] == "set": set(args[1], args[2])` – furas Feb 13 '21 at 00:37
  • if `key name` or `value` may have spaces then you may send text with `" "` like `set "key name" "some value"` and use standard module `shlex` to split it into list `args = ["set", "key name", "some value"]` Module `shlex` will try to keep words in `" "` as single text. – furas Feb 13 '21 at 00:41
  • Thanks for you time furas, i will look into your solution and come back to report my progress. Thanks again! – Marco Liguori Feb 14 '21 at 00:07
  • @furas Hey, i got the solution you gave me working. Now i can call the Memcached object methods from the client side. However, i have a different problem now, which is that i can't send multiple commands over the same connection, it just ends the script or keeps prompting for commands which later become errors because it creates a great string of commands. I'll update the code, and any help will be greatly appreciated. Thank you – Marco Liguori Feb 16 '21 at 18:49
  • in server you need two `while`-loops - inner loop to get all data with single command, and external loop to repeate it for many commands. At this moment you have only inner loop which gets all data with single command but you still need external loop. – furas Feb 17 '21 at 15:24
  • Hmm, i'm not sure how to implement what you say. Trying to encompass in a loop all the logic beyond conn, addr = s.accept() doesn't get it to work. Any pointers in the right direction are appreciated. Thanks – Marco Liguori Feb 18 '21 at 15:24

1 Answers1

0

You have to run code in while True: to repeat it for next command.

And if you want to connect many times then you have to run it in another while True.

I don't have memcache so I created own class with set, add, get to simulate it.

I use '\n' to recognize end of command.

I don't use JSON and split but shlex.split to correctly split set X "hello world" into ['set', 'X', 'hello world']

server.py

import socket
import shlex
#import memcached

# --- classes ---

class MyMemcached():

    def __init__(self):

        self.data = dict()
        
    def set(self, key, val):
        print('[MyClass]: set', key, val)
        self.data[key] = val

    def add(self, key, val):
        print('[MyClass]: add', key, val)
        if key not in self.data:
           self.data[key] = ""
        self.data[key] += val
    
    def get(self, key):
        val = self.data.get(key)
        print('[MyClass]: get', key, val)
        return val

# --- main ---

#cache = memcached.Memcached()
cache = MyMemcached()

s = socket.socket()  # default values are `socket.AF_INET, socket.SOCK_STREAM`

s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  # solution for: `OSError: [Errno 98] Address already in use` when socket already closed

print('Bind: 0.0.0.0:12345')
s.bind(('0.0.0.0', 12345))

print('Listen: 1')
s.listen(1)  # how many clients can wait for connection

try:
    while True:  # repeat for next connection
        print('Waiting for client...')
        conn, addr = s.accept()

        print('Connected:', addr)#, conn)

        with conn:
            while True: # repeat for next command

                cmd = b''
                
                # get one command
                while True:
                    data = conn.recv(1)  # read single char until you get `\n'
                    # read until `\n'`
                    if not data or data == b'\n':
                        break

                    cmd += data
                     
                cmd = cmd.decode('utf-8')   
                
                #control print
                print('cmd:', cmd)
                        
                if cmd.lower() == 'quit':
                    break
                                           
                #args = cmd.split(' ') # it split INCORRECTLY `set a "hello world"`
                args = shlex.split(cmd)  # it split correctly `set a "hello world"`
                print('args:', args)

                if args[0] == 'set':
                    cache.set(args[1], args[2])
                elif args[0] == 'add':
                    cache.add(args[1], args[2])
                elif args[0] == 'get':
                    val = cache.get(args[1])
                    print(val)
                else:
                    print('ERROR: cmd:', cmd) 
                    break
except KeyboardInterrupt:
    print('Pressed Ctrl+C')            
finally:            
    s.close()

client.py

import socket

# --- classes ---

class Client:

    def __init__(self, host='0.0.0.0', port=8000):
        self.host = host
        self.port = port
        self.socket = socket.socket()  # default values are `socket.AF_INET, socket.SOCK_STREAM`
        self.socket.connect((self.host, self.port))
 
    def command(self, cmd):
        print('send:', cmd)
        cmd = cmd.encode('utf-8') + b'\n'    
        self.socket.sendall(cmd)

    def run(self):
        while True:
            cmd = input('cmd: ')
            self.command(cmd)

            if cmd.lower() == 'quit':
                break
    
# --- functions ---

# ... empty ...

# --- main ---

#client = Client('localhost', 12345)
client = Client(port=12345)
client.run()
furas
  • 134,197
  • 12
  • 106
  • 148
  • Excellent furas. Thanks a lot for your time and dedication in helping me sort this out, really appreciated. Cheers mate. – Marco Liguori Feb 19 '21 at 15:01