Sign in to follow this  
  • entries
    3
  • comments
    0
  • views
    3635

Akka IO and Protocol Buffers

Sign in to follow this  

1647 views

[font='times new roman']

Fun with Akka

[/font]
[font='times new roman']

Work on the game server has proven to be a lot of fun using Akka, and even more so writing it in Scala. Let's take a closer look to what's been done so far.

[/font]

[font='times new roman']

I've decided that I'll have a separate login server that will handle user login and registration, and then a game server that will handle logged in users.

[/font]

[font='times new roman']

The login server is an Akka Actor that listens for connections on a given port. When a connection is established, the login server passes the connection to a newly created Actor which handles the messages over the connection. This frees up the login server to handle new connections immediately and will continue spawning connection handlers when more connections are made.

[/font]

[font='times new roman']

Here's a look inside LoginServer.scala which implements the process I've described above:

[/font]package com.vinctus.venatus.serversimport akka.actor.{ Actor, Props }import akka.io.{ IO, Tcp }import java.net.InetSocketAddressimport com.vinctus.venatus.handlers.LoginHandlerclass LoginServer extends Actor { import Tcp._ import context.system IO(Tcp) ! Tcp.Bind(self, new InetSocketAddress("localhost", 6666)) def receive = { case Tcp.CommandFailed(_: Bind) => context.stop(self) case c @ Tcp.Connected(remote, local) => val handler = context.actorOf(Props[LoginHandler]) val connection = sender connection ! Tcp.Register(handler) }}
[font='times new roman']

Getting a Handle on Protobuf

[/font]

[font='times new roman']

The login handler is expecting messages that are encoded using Google's protocol buffers. Currently there are two types of messages it can handle; a login message, and a register message.

[/font]


[font='times new roman']

Inside my .proto file, I have the following schema for my messages:

[/font]message WrapperMessage { oneof msg { Login login = 1; Register register = 2; }}message Login { required string email = 1; required string password = 2;}message Register { required string email = 1; required string password = 2; optional string firstName = 3; optional string lastName = 4;}

[font='times new roman']Because protobuf messages aren't self describing, I'm using the technique of having a parent message (in this case the 'WrapperMessage') with a 'oneof' field that holds the message I'll be matching against later. This way I first decode the expected 'WrapperMessage', and match against the type of inner message, and use the relevant decoder for that message.[/font]


[font='times new roman']Here's the 'LoginHandler' implementing the technique I'm describing above:[/font]

import scala.concurrent.ExecutionContext.Implicits.globalimport akka.actor.{ Actor, ActorRef, Props }import akka.io.{ IO, Tcp }import akka.util.ByteStringimport akka.io.Tcp.{ Write, Received }import org.mindrot.jbcrypt._import com.vinctus.venatus.protobuf.LoginProtos._import com.vinctus.venatus.protobuf.LoginProtos.WrapperMessage.Msgimport com.vinctus.venatus.models.Userimport com.vinctus.venatus.dao.UserDAOclass LoginHandler extends Actor { val userDAO = new UserDAO def receive: Receive = { case Tcp.Received(data) => val wrapperMessage = WrapperMessage.parseFrom(data.toArray) val loginResponse = LoginResponse() wrapperMessage.msg match { case Msg.Login(l) => userDAO.find(l.email).map(_ match { case Some(user) => if (BCrypt.checkpw(l.password, user.password)) { sender ! Tcp.Write(ByteString(loginResponse .withUserId(user.id) .withSuccess("Login successful.").toByteArray)) } else sender ! Tcp.Write(ByteString(loginResponse .withError("Invalid password for that user.").toByteArray)) case None => sender ! Tcp.Write(ByteString(loginResponse .withError("No user exists with that email address.").toByteArray)) }) case Msg.Register(r) => val user = new User( email = r.email, password = r.password, firstName = r.firstName, lastName = r.lastName) userDAO.create(user).map(_ match { case Some(user) => sender ! Tcp.Write(ByteString(loginResponse .withUserId(user.id) .withSuccess("Registration successful.").toByteArray)) case None => sender ! Tcp.Write(ByteString(loginResponse .withError("Failed to register.").toByteArray)) }) case Msg.Empty => // TODO } case _: Tcp.ConnectionClosed => context.stop(self) }}
[font='times new roman']

We now have an Akka server capable of handling protobuf messages.

[/font]

Sign in to follow this  


0 Comments


Recommended Comments

There are no comments to display.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now