mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
196 lines
No EOL
8.7 KiB
Markdown
196 lines
No EOL
8.7 KiB
Markdown
# Protocols
|
|
|
|
```
|
|
Internet│ Protocol
|
|
┌─────┐ │ |
|
|
┌──────┐ │Text │ │ ┌──────────┐ ┌────────────┐ ┌─────┐
|
|
│Client◄────┤JSON ├─┼──┤outputfunc◄────┤commandtuple◄───┤msg()│
|
|
└──────┘ │etc │ │ └──────────┘ └────────────┘ └─────┘
|
|
└─────┘ │
|
|
│Evennia
|
|
```
|
|
|
|
The _Protocol_ describes how Evennia sends and receives data over the wire to the client. Each connection-type (telnet, ssh, webclient etc) has its own protocol. Some protocols may also have variations (such plain-text Telnet vs Telnet SSL).
|
|
|
|
See the [Message Path](./Messagepath.md) for the bigger picture of how data flows through Evennia.
|
|
|
|
In Evennia, the `PortalSession` represents the client connection. The session is told to use a particular protocol. When sending data out, the session must provide an "Outputfunc" to convert the generic `commandtuple` to a form the protocol understands. For ingoing data, the server must also provide suitable [Inputfuncs](../Components/Inputfuncs.md) to handle the instructions sent to the server.
|
|
|
|
## Adding a new Protocol
|
|
|
|
Evennia has a plugin-system that add the protocol as a new "service" to the application.
|
|
|
|
To add a new service of your own (for example your own custom client protocol) to the Portal or Server, expand `mygame/server/conf/server_services_plugins` and `portal_services_plugins`.
|
|
|
|
To expand where Evennia looks for plugins, use the following settings:
|
|
```python
|
|
# add to the Server
|
|
SERVER_SERVICES_PLUGIN_MODULES.append('server.conf.my_server_plugins')
|
|
# or, if you want to add to the Portal
|
|
PORTAL_SERVICES_PLUGIN_MODULES.append('server.conf.my_portal_plugins')
|
|
```
|
|
|
|
> When adding a new client connection you'll most likely only need to add new things to the Portal-plugin files.
|
|
|
|
The plugin module must contain a function `start_plugin_services(app)`, where the `app` arguments refers to the Portal/Server application itself. This is called by the Server or Portal when it starts up. It must contatin all startup code needed.
|
|
|
|
Example:
|
|
|
|
```python
|
|
# mygame/server/conf/portal_services_plugins.py
|
|
|
|
# here the new Portal Twisted protocol is defined
|
|
class MyOwnFactory( ... ):
|
|
# [...]
|
|
|
|
# some configs
|
|
MYPROC_ENABLED = True # convenient off-flag to avoid having to edit settings all the time
|
|
MY_PORT = 6666
|
|
|
|
def start_plugin_services(portal):
|
|
"This is called by the Portal during startup"
|
|
if not MYPROC_ENABLED:
|
|
return
|
|
# output to list this with the other services at startup
|
|
print(f" myproc: {MY_PORT}")
|
|
|
|
# some setup (simple example)
|
|
factory = MyOwnFactory()
|
|
my_service = internet.TCPServer(MY_PORT, factory)
|
|
# all Evennia services must be uniquely named
|
|
my_service.setName("MyService")
|
|
# add to the main portal application
|
|
portal.services.addService(my_service)
|
|
```
|
|
|
|
Once the module is defined and targeted in settings, just reload the server and your new
|
|
protocol/services should start with the others.
|
|
|
|
### Writing your own Protocol
|
|
|
|
```{important}
|
|
This is considered an advanced topic.
|
|
```
|
|
|
|
Writing a stable communication protocol from scratch is not something we'll cover here, it's no trivial task. The good news is that Twisted offers implementations of many common protocols, ready for adapting.
|
|
|
|
Writing a protocol implementation in Twisted usually involves creating a class inheriting from an already existing Twisted protocol class and from `evennia.server.session.Session` (multiple
|
|
inheritance), then overloading the methods that particular protocol uses to link them to the
|
|
Evennia-specific inputs.
|
|
|
|
Here's a example to show the concept:
|
|
|
|
```python
|
|
# In module that we'll later add to the system through PORTAL_SERVICE_PLUGIN_MODULES
|
|
|
|
# pseudo code
|
|
from twisted.something import TwistedClient
|
|
# this class is used both for Portal- and Server Sessions
|
|
from evennia.server.session import Session
|
|
|
|
from evennia.server.portal.portalsessionhandler import PORTAL_SESSIONS
|
|
|
|
class MyCustomClient(TwistedClient, Session):
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.sessionhandler = PORTAL_SESSIONS
|
|
|
|
# these are methods we must know that TwistedClient uses for
|
|
# communication. Name and arguments could vary for different Twisted protocols
|
|
def onOpen(self, *args, **kwargs):
|
|
# let's say this is called when the client first connects
|
|
|
|
# we need to init the session and connect to the sessionhandler. The .factory
|
|
# is available through the Twisted parents
|
|
|
|
client_address = self.getClientAddress() # get client address somehow
|
|
|
|
self.init_session("mycustom_protocol", client_address, self.factory.sessionhandler)
|
|
self.sessionhandler.connect(self)
|
|
|
|
def onClose(self, reason, *args, **kwargs):
|
|
# called when the client connection is dropped
|
|
# link to the Evennia equivalent
|
|
self.disconnect(reason)
|
|
|
|
def onMessage(self, indata, *args, **kwargs):
|
|
# called with incoming data
|
|
# convert as needed here
|
|
self.data_in(data=indata)
|
|
|
|
def sendMessage(self, outdata, *args, **kwargs):
|
|
# called to send data out
|
|
# modify if needed
|
|
super().sendMessage(self, outdata, *args, **kwargs)
|
|
|
|
# these are Evennia methods. They must all exist and look exactly like this
|
|
# The above twisted-methods call them and vice-versa. This connects the protocol
|
|
# the Evennia internals.
|
|
|
|
def disconnect(self, reason=None):
|
|
"""
|
|
Called when connection closes.
|
|
This can also be called directly by Evennia when manually closing the connection.
|
|
Do any cleanups here.
|
|
"""
|
|
self.sessionhandler.disconnect(self)
|
|
|
|
def at_login(self):
|
|
"""
|
|
Called when this session authenticates by the server (if applicable)
|
|
"""
|
|
|
|
def data_in(self, **kwargs):
|
|
"""
|
|
Data going into the server should go through this method. It
|
|
should pass data into `sessionhandler.data_in`. THis will be called
|
|
by the sessionhandler with the data it gets from the approrpriate
|
|
send_* method found later in this protocol.
|
|
"""
|
|
self.sessionhandler.data_in(self, text=kwargs['data'])
|
|
|
|
def data_out(self, **kwargs):
|
|
"""
|
|
Data going out from the server should go through this method. It should
|
|
hand off to the protocol's send method, whatever it's called.
|
|
"""
|
|
# we assume we have a 'text' outputfunc
|
|
self.onMessage(kwargs['text'])
|
|
|
|
# 'outputfuncs' are defined as `send_<outputfunc_name>`. From in-code, they are called
|
|
# with `msg(outfunc_name=<data>)`.
|
|
|
|
def send_text(self, txt, *args, **kwargs):
|
|
"""
|
|
Send text, used with e.g. `session.msg(text="foo")`
|
|
"""
|
|
# we make use of the
|
|
self.data_out(text=txt)
|
|
|
|
def send_default(self, cmdname, *args, **kwargs):
|
|
"""
|
|
Handles all outputfuncs without an explicit `send_*` method to handle them.
|
|
"""
|
|
self.data_out(**{cmdname: str(args)})
|
|
|
|
```
|
|
The principle here is that the Twisted-specific methods are overridden to redirect inputs/outputs to
|
|
the Evennia-specific methods.
|
|
|
|
### Sending data out
|
|
|
|
To send data out through this protocol, you'd need to get its Session and then you could e.g.
|
|
|
|
```python
|
|
session.msg(text="foo")
|
|
```
|
|
|
|
The message will pass through the system such that the sessionhandler will dig out the session and check if it has a `send_text` method (it has). It will then pass the "foo" into that method, which
|
|
in our case means sending "foo" across the network.
|
|
|
|
### Receiving data
|
|
|
|
Just because the protocol is there, does not mean Evennia knows what to do with it. An [Inputfunc](../Components/Inputfuncs.md) must exist to receive it. In the case of the `text` input exemplified above, Evennia alredy handles this input - it will parse it as a Command name followed by its inputs. So handle that you need to simply add a cmdset with commands on your receiving Session (and/or the Object/Character it is puppeting). If not you may need to add your own Inputfunc (see the [Inputfunc](../Components/Inputfuncs.md) page for how to do this.
|
|
|
|
These might not be as clear-cut in all protocols, but the principle is there. These four basic components - however they are accessed - links to the *Portal Session*, which is the actual common interface between the different low-level protocols and Evennia. |