This article is all about writing your own TCP server from the scratch. You will see the GenServer implementation for receiving and sending packets inside the TCP server. We go with a very basic step of creating a mix project .

Let’s dive into action.

On more thing that I am reminding you is, this is not all about TCP basics. If you are expecting about the TCP then it is not for you and I guess you already knew how to create a mix project in elixir, if not then look down else skip the para.

Mix Project Creation

1
mix new gen_tcp --module TcpServer

The above command will generate a mix project with a file gen_tcp.ex where you can see the module name will be TcpServer instead of GenTcp . If you want you can also change the filename gen_tcp.ex to tcp_server.ex that really will be a sense to keep the module name and file name same.

Now I am renaming the file /lib/gen_tcp.ex to /lib/tcp_server.ex as my module is TcpServer

We are using the Eralng module called gen_tcp here. This module comprises of functions for TCP/IP protocol communication with sockets.

GenServer Behaviour Implementation

Now open the file lib/tcp_server.ex and you will see the default code. Remove everything inside the module and add the following line

1
use GenServer

With that line, we made our module to behave like the **GenServer. **You can read more about the GenServer Here

Configuration

Here we go with small configurations for setting up the IP and Port numbers.

Open your config/config.ex and add the following line

1
config :gen_tcp, ip: {127,0,0,1},port: 6666

Here, you have to set the value of the ip to a tuple with four integers as I did in above line and port number is also an Integer. You can give your own values. We are going to use these values inside our tcp_server.ex for creating a socket. That is about the configuration.

We are defining this function as an entry point for initiating the server.

1
2
3
4
5
def start_link() do
    ip = Application.get_env :gen_tcp, :ip, {127,0,0,1}
    port = Application.get_env :gen_tcp, :port, 6666
    GenServer.start_link(__MODULE__,[ip,port],[])
  end

Here Applicaion.gen_env :gen_tcp,:ip,{127,0,0,1} will fetch the values from the configuration file config/config.ex if the key is not found, then the defaults are used.

Suppose, if you override the configuration files with config/dev.ex or config/prod.ex then, the values from the respective environment will be loaded. Here we are not going to do that because I want it to be simple.

The third line GenServer.start_link.... will callback the server init function with a list of parameters [ip,port] .

init()

1
2
3
4
5
def init [ip,port] do
    {:ok,listen_socket}= :gen_tcp.listen(port,[:binary,{:packet, 0},{:active,true},{:ip,ip}])
    {:ok,socket } = :gen_tcp.accept listen_socket
    {:ok, %{ip: ip,port: port,socket: socket}}
  end

Code Understanding

The **:gen_tcp.listen(Port, Options) ::{ok, ListenSocket} {error, Reason}** will set the socket on Port
  • :binary **— The received packet will be delivered as a binary . We also have a **list option as an alternative for receiving the packets. Here I prfere :binary for demo purposes.
  • {ip, Address} — If the host has many network interfaces, this option specifies which one to listen on.

{:active, true}

Here, I am just using very few options which serve our needs. The another important option is {:active, true}

If the value is true, which is the default, everything received from the socket is sent as messages to the receiving process.

If the value is false (passive mode), the process must explicitly receive incoming data by calling gen_tcp:recv/2,3,

:gen_tcp.accept() will accept the connection on listening socket.

Receiving Packets

To receive the packets inside the GenServer, we have to define a handle_info/2 function. We receive the messages from the external processes.

So, we are implementing the handle_info()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def handle_info({:tcp,socket,packet},state) do
    IO.inspect packet, label: "incoming packet"
    :gen_tcp.send socket,"Hi Blackode \n"
    {:noreply,state}
  end   

def handle_info({:tcp_closed,socket},state) do
    IO.inspect "Socket has been closed"
    {:noreply,state}
  end   

def handle_info({:tcp_error,socket,reason},state) do
    IO.inspect socket,label: "connection closed dut to #{reason}"    {:noreply,state}
  end

Here we are defining three handle_info/2 functions and differentiating them with pattern matching the actual message.

The first one is for receiving the actual messages with packets. Using :gen_tcp.send we are sending the return message to the client

The second one is for catching the socket close

The last one is to catch the errors in sockets.

If everything is good, the overall file should like the following one…

Running and Live Execution

Now be inside your project directory and open the terminal in the project directory and run the following commands.

1
iex -S mix

This compiles the project and provides you with command line interface where you can interact with project. As soon as you type that, you are now inside iex interactive elixir shell and It also loads the module TcpServer

1
iex> TcpServer.start_link

with the above line, we are starting the server and now you are blocked inside the shell for accepting the connection. Once the connection is accepted its pid is returned as a response.

Now open another terminal or press **Ctrl+Shift+t **to open the another terminal in a new tab. Now run the following command

1
$ telnet 127.0.0.1 6666

You will be notified with connection information… If the connection is success you will see success message and it will let you enter some message…

No sooner you send the message from telnet, it returns the message with ‘hi blackode’. You can inspect the incoming packet in another terminal.

Live Execution

Hope this helps you apart in understanding and leveling up your elixir skills to next.