There is a (sort of) old technology called telnet which is basically a simple messaging protocol that works through sockets. It’s not ideal in that the information sent isn’t secure, but for a little fun application running on a server that doesn’t matter, well, it’s quite fantastic. Here is a little fun that I had around 3:00am putting together a few logical pieces to make a Pokemon “Gotta Catch Em’ All” command line thing:
The Dependencies
If you are interested in the retarded code I used to make this, look here. The first I did is install a few dependencies, including a tool supervisor
that would keep a process running, my pokemon ascii python module, and an nginx
web server to basically serve a useless index.html
giving some wandering user the correct command to use telnet
.
sudo apt-get update
sudo apt-get -y install python-pip
sudo apt-get -y install nginx
sudo apt-get -y install daemontools #supervise
sudo service nginx start
git clone https://www.github.com/vsoch/pokemon-server
cd pokemon-server
pip install pokemon
The nginx
server by default stores the index.html
in /var/www/html
, so you can programmatically generate that file on the fly with dig
:
myip="$(dig +short myip.opendns.com @resolver1.opendns.com)"
echo "<h2>telnet ${myip} 5005</h2>" >> index.html
sudo mv index.html /var/www/index.html
Before we jump into the serve r itself, I’ll briefly touch on the (super simple) way I kept it running. By installing supervise
, which is included in daemontools
, you can point the executable at any folder that has a file called run
that executes some command, and it will keep the process running. So that run
file looks like this:
#!/bin/sh
exec python server.py &
Note that I was careful to identify the running shell, and also added exec
to make sure that the process is carried forward. I got some weird error when I didn’t do that. So the last command in my setup file looks like this to start the process (and the server):
supervise $HOME/pokemon-server &
Basically, this script runs a file called server.py
using Python, and that is where the magic happens.
The Server
The basic idea here is that we are going to open up a multi-threaded socket, using Python, and send a bunch of Pokemon back to the user. So the imports look like this.
import socket
import threading
from pokemon.skills import catch_em_all
from time import sleep
The function catch_em_all
returns a nice json data structure that has ascii generated for each Pokemon, names, and other battle statistics (not included in this application). I made this a while back because… why not? Now let’s look at the server:
class ThreadedServer(object):
def __init__(self, host, port):
self.host = host
self.port = port
self.pokemons = catch_em_all()
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.sock.bind((self.host, self.port))
def listen(self):
self.sock.listen(5)
while True:
client, address = self.sock.accept()
client.settimeout(60)
threading.Thread(target = self.listenToClient,args = (client,address)).start()
def listenToClient(self, client, address):
size = 1024
while True:
try:
client.send("Would you like to see pokemon?")
data = client.recv(size)
if data:
for key,data in self.pokemons.items():
client.send(data['ascii']) # echo
client.send("\t%s" %data['name']) # echo
sleep(2)
else:
raise error('Client disconnected')
except:
client.close()
return False
if __name__ == "__main__":
port_num = 5005
ThreadedServer('',port_num).listen()
What we see is that when the file is called as an executable (the __main__
bit at the bottom) we define a port, and then generate a ThreadedServer
object to listen on that port number. Then what does the listen function do?
def listen(self):
self.sock.listen(5)
while True:
client, address = self.sock.accept()
client.settimeout(60)
threading.Thread(target = self.listenToClient,args = (client,address)).start()
It listens for clients, accepts the message, and then creates a thread to run the main function to listen to that client (meaning sending them pokemon). This is the way that we are going to allow for multiple people to use the server at once. I tried it without threading, and it only worked for one. Now let’s look at listenToClient
.
def listenToClient(self, client, address):
size = 1024
while True:
try:
client.send("Would you like to see pokemon?")
data = client.recv(size)
if data:
for key,data in self.pokemons.items():
client.send(data['ascii']) # echo
client.send("\t%s" %data['name']) # echo
sleep(2)
else:
raise error('Client disconnected')
except:
client.close()
return False
This again is pretty simple. We send them a message, asking if they want to receive pokemon. Notice that we don’t parse the response data
for anything in particular, just that it’s there. This is where we could ask them a question, or to input something specific, and choose a response based on what they say. We could write to a database, run a machine learning algorithm with their data as input, whatever! I chose to just respond by iterating through my data structure, and sending them back Pokemon Ascii’s with a 2 second delay in between each. You’ll notice in the Asciicast that the client closes after the timeout. Again, it would be nice to give the client a nice programmatic way for them to type something and then call client.close()
, but for programming at 3:00am, that’s what I came up with. What can I say :)
And this closes another excellent example of how awesome technology is. Thanks for reading!
Suggested Citation:
Sochat, Vanessa. "Pokemon Server." @vsoch (blog), 19 Jan 2017, https://vsoch.github.io/2017/pokemon-server/ (accessed 18 Nov 24).