fsxNet Wiki

BBS Development & Resources

User Tools

Site Tools


tutorials:crystal_bbs:part_one

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
tutorials:crystal_bbs:part_one [2017/03/19 23:43]
sardaukar
tutorials:crystal_bbs:part_one [2018/03/29 01:58] (current)
Line 17: Line 17:
 This will create the skeleton inside the ''​bbs''​ subfolder of the current directory. Jump into it and have a look around at the files created. ​ This will create the skeleton inside the ''​bbs''​ subfolder of the current directory. Jump into it and have a look around at the files created. ​
  
-One thing the app creator leaves out is an easy way to compile the project - we //can// just type ''​crystal build src/bbs.cr --release -o bbs_release''​ every time we want to compile, but that would get old really ​quick. So instead we'll create a ''​Makefile''​ and use good old //make// to perform all these repetitive tasks on the project. Here's how it could look:+One thing the app creator leaves out is an easy way to compile the project - we //can// just type ''​crystal build src/bbs.cr --release -o bbs_release''​ every time we want to compile, but that would get old real quick. So instead we'll create a ''​Makefile''​ and use good old //make// to perform all these repetitive tasks on the project. Here's how it could look:
  
 <code make> <code make>
Line 136: Line 136:
 </​code>​ </​code>​
  
-This is the method called in ''​bbs.cr''​ (our main entry point for the whole project)_after ​instantiating a new instance of the ''​Main''​ class. At this stage, it just prints out the hash parsed from the ''​config.yml''​ file that we created.+This is the method called in ''​bbs.cr''​ (our main entry point for the whole project) ​after instantiating a new instance of the ''​Main''​ class. At this stage, it just prints out the hash parsed from the ''​config.yml''​ file that we created.
  
 <code ruby> <code ruby>
Line 174: Line 174:
 </​code>​ </​code>​
  
-So we start a new TCPServer bound to ''​localhost'',​ on the port stored in the ''​telnet_port''​ variable. We then print a little info message, and go into the meat of this method - the main ''​loop''​. The loop halts on the ''​server.accept?''​ call, which is blocking, until a connection is made to the server. Once a connection is accepted, the ''​socket''​ variable will be assigned all the details to it. With the details in hand, we will ''​spawn''​ a method call to ''​handle_connection''​. Spawning is a concept unique to Crystal, and you can read an intro to it in [[https://​crystal-lang.org/​docs/​guides/​concurrency.html|the official docs on concurrency]],​ but think of it as creating a new thread specifically to handle the connection.+So we start a new ''​TCPServer'' ​bound to ''​localhost'',​ on the port stored in the ''​telnet_port''​ variable. We then print a little info message, and go into the meat of this method - the main ''​loop''​. The loop halts on the ''​server.accept?'' ​blocking ​call, until a connection is made to the server. Once a connection is accepted, the ''​socket''​ variable will be assigned all the remote endpoint'​s ​details to it. With the details in hand, we will ''​spawn''​ a method call to ''​handle_connection''​. Spawning is a concept unique to Crystal, and you can read an intro to it in [[https://​crystal-lang.org/​docs/​guides/​concurrency.html|the official docs on concurrency]],​ but think of it as creating a new thread specifically to handle the connection. So the main loop here will continue immediately and be ready to accept new connections //while// the ''​handle_connection''​ method handles the connection we just got
  
 Finally, the new handler method: Finally, the new handler method:
Line 189: Line 189:
 <​code>​ <​code>​
 $ make $ make
-...+... (make output omitted)
 now listening in port 2023 now listening in port 2023
 </​code>​ </​code>​
Line 205: Line 205:
  
 Success! As expected, you can see the ''​hello world''​ message, and then the socket is closed! Success! As expected, you can see the ''​hello world''​ message, and then the socket is closed!
 +
 +This part of the tutorial is running a bit long, so let's just optimize the YAML settings code, since it looks ugly now, and ugly code is not our style.
 +
 +Right now the issue is that Crystal had no idea what type each key or value of the file can be, so we always have to convert it to a type manually. Luckily, Crystal has a notion of a [[https://​crystal-lang.org/​api/​0.21.1/​YAML.html#​mapping-macro|mapping]] that basically will do all the type converting work for us. The downside is that each new setting added will have to be present on the mapping, but think of it as documentation for your settings!
 +
 +Let's add a new class within the main module in ''​main.cr'':​
 +
 +<code ruby>
 +class Config
 +  YAML.mapping(
 +    settings: Hash(String,​ Int32)
 +  )
 +end
 +</​code>​
 +
 +This will tell Crystal the ''​Config''​ class is a mapping from YAML that features a ''​settings''​ key. That key is a ''​Hash''​ that has ''​String''​ keys and ''​Int32''​ values. If in the future we want to add settings with ''​String''​ values, we can change this to ''​Hash(String,​ Int32 | String)''​ but for now number values will suffice.
 +
 +With this in place, we need to change the type annotation for the ''​@config''​ instance variable.
 +
 +<code ruby>
 +@config : Config
 +</​code>​
 +
 +And the parser method needs to be adjusted too:
 +
 +<code ruby>
 +private def load_config
 +  Config.from_yaml(File.read("​config.yml"​))
 +end
 +</​code>​
 +
 +Finally, we can access the settings in a less ugly way:
 +
 +<code ruby>
 +telnet_port = @config.settings["​port"​]
 +</​code>​
 +
 +Much better! This concludes part one of the tutorial, tune in soon for [[tutorials:​crystal_bbs:​part_two|part two]] where we will be negotiating features with the client! For reference, here's the whole code for the ''​src/​bbs/​main.cr''​ file:
 +
 +<code ruby>
 +require "​yaml"​
 +require "​socket"​
 +
 +module BBS
 +  class Config
 +    YAML.mapping(
 +      settings: Hash(String,​ Int32)
 +    )
 +  end
 +
 +  class Main
 +    @config : Config
 +
 +    def initialize
 +      @config = load_config
 +    end
 +
 +    def go!
 +      telnet_port = @config.settings["​port"​]
 +
 +      server = TCPServer.new("​localhost",​ telnet_port)
 +      puts "now listening in port #​{telnet_port}"​
 +      loop do
 +        if socket = server.accept?​
 +          spawn handle_connection(socket)
 +        end
 +      end
 +    end
 +
 +    private def handle_connection(socket)
 +      socket << "hello world\n"​
 +      socket.close
 +    end
 +
 +    private def load_config
 +      Config.from_yaml(File.read("​config.yml"​))
 +    end
 +  end
 +end
 +
 +</​code>​
tutorials/crystal_bbs/part_one.1489966980.txt.gz ยท Last modified: 2018/03/29 01:58 (external edit)