3

I'm trying to write Erlang server for my program but still having some problem which I can't handle with. When using this (I wrote client in erlang to test server):

send(Socket, Message) ->
BinMsg = term_to_binary(Message),
gen_tcp:send(Socket, Message).

everything seems to be ok, but when I'm trying to send something from my c# client, server sees connection and dissconnection but doesn't see message (it's even not reacting)

theStream = mySocket.GetStream();
theWriter = new StreamWriter(theStream);
String foo = theLine + "\r\n"; 
test = System.Text.Encoding.UTF8.GetBytes(foo);
theWriter.Write(test);

I realised that it's probably some issue with Erlang's

term_to_binary()

but I don't know how to fix it. I need simple:

  1. Client converts data to bytes
  2. Client sends data to server
  3. Server encodes data
  4. Server decide what to do
  5. Server generate new data, converts it to bytes and send to Client
  6. Client encodes it

And I can do it simple on strings, but I'dont think this method will be good solution and I want to send binaries instead of strings. Any suggetions?

MrKaszu
  • 71
  • 8

2 Answers2

1

You should properly encode data on client side in order to be able to use term_to_binary/binary_to_term in Erlang. Take a look at http://erlang.org/doc/apps/erts/erl_ext_dist.html it is not that hard to implement and I think there are should be existing solutions.

But from your words it looks like something else wrong with your code since Erlang should throw exception on wrong binaries. Probably something to do with {active, true|false|once|N}. You should set {active, true} on socket to be able to receive TCP packets as Erlang messages (once or Nare good too, take a look into docs: http://erlang.org/doc/man/gen_tcp.html). And if you want to receive binaries you should probably set binary option on socket.

mynameisdaniil
  • 1,106
  • 8
  • 10
  • Thank's for link but I've already read it and (maybe cause I'm pretty new to Erlang) It didn't help me. As I wrote, when I'm using client written in erlang everything seems to be fine, I have troubles only while using c#'s client. Server doesn't throw exception cause, as I tested, it looks like it doesn't even recive message. Can I write it without coding/encoding then? will it be very bad/unprofessional? – MrKaszu Dec 16 '15 at 17:56
  • You need some serialization format anyway. term_to_binary is just like, I don't know, JSON.serialize. So, if you using `term_to_binary` in Erlang client and `binary_to_term` in Erlang server you should probably use erlang-compatable serialization on C# side. Maybe you can show us the code of erlang server and client? – mynameisdaniil Dec 16 '15 at 18:07
  • 1
    And by the way, when in doubt use Wireshark! – mynameisdaniil Dec 16 '15 at 18:11
0

So here's my server/client code:

-define(TCP_OPTIONS, [binary, {packet, 2}, {active, false}, {reuseaddr, true}]).
-define(PORT, 8080).

-spec start(Port) -> pid() when
  Port::integer().
start(Port) ->
  process_flag(trap_exit, true),
  ClientDict = dict:new(),
  GamesDict = dict:new(),
  HandlerPID = spawn_link(fun() -> handler(ClientDict, GamesDict) end),
  imup_listener:listen(Port, HandlerPID).





%%------------------------------
% Internal Functions
%%------------------------------

handler(Clients, Games) ->
  receive
    {insert_client, Socket, Alias} ->
      TmpClients = dict:store(Socket, Alias, Clients),
      TmpClients2 = dict:store(Alias, Socket, TmpClients),
      handler(TmpClients2, Games);
    {get_client_id, ReceiverPID, ClientPID} ->
      {ok , CID} = dict:find(ClientPID, Clients),
      ReceiverPID ! {id, CID},
      handler(Clients, Games);
    {get_client_pid, ReceiverPID, ClientID} ->
      {ok, CPID} = dict:find(ClientID, Clients),
      ReceiverPID ! {pid, CPID},
      handler(Clients, Games);
    {host_game, HostID, GameID} ->
      TmpGames = dict:append_list(GameID, [HostID], Games),
      handler(Clients, TmpGames);
    {add_player, PlayerID, GameID} ->
      TmpGames = dict:append_list(GameID, [PlayerID], Games),
      handler(Clients, TmpGames);
    {get_host, ReceiverPID, GameID} ->
      {ok, [HID|T]} = dict:find(GameID, Games),
      {ok, HPID} = dict:find(HID, Clients),
      ReceiverPID ! {host_is, HID, HPID},
      handler(Clients, Games);

    _ ->
      {error, "I don't know what you want from me :("}
  end.

Listener:

-define(TCP_OPTIONS, [binary, {packet, 2}, {active, false}, {reuseaddr, true}]).

listen(Port, HandlerPID) ->
  {ok, LSocket} = gen_tcp:listen(Port, ?TCP_OPTIONS),
  spawn_link(fun() -> accept(LSocket, HandlerPID) end),
  LSocket.




% Wait for incoming connections and spawn a process that will process incoming packets.
accept(LSocket, HandlerPID) ->
  {ok, Socket} = gen_tcp:accept(LSocket),
  Pid = spawn(fun() ->
    io:format("Connection accepted ~n", []),
    %%DictPID ! {insert, Socket, Socket},
    loop(Socket, HandlerPID)
              end),
  gen_tcp:controlling_process(Socket, Pid),
  accept(LSocket, HandlerPID).



% Echo back whatever data we receive on Socket
loop(Sock, HandlerPID) ->

  inet:setopts(Sock, [{active, once}]),
  receive
    {tcp, Socket, Data} ->
      io:format("wchodze pakiet"),
      io:format("Got packet: ~p == ", [Data]),

      FormatedData = process_data(Socket, Data, HandlerPID),
      io:format("~p~n", [FormatedData]),
      convey_message(Socket, FormatedData),

      loop(Socket, HandlerPID);
    {tcp_closed, Socket} ->
      io:format("wchodze closed"),
      io:format("Socket ~p closed~n", [Socket]);
    {tcp_error, Socket, Reason} ->
      io:format("wchodze error"),
      io:format("Error on socket ~p reason: ~p~n", [Socket, Reason])
  end.




%%------------------------------
% Internal Functions
%%------------------------------


-spec process_data(S, D, P) -> term() when
  S::port(),
  D::binary(),
  P::pid().
process_data(Socket, Data, HandlerPID) when is_binary(Data) ->
  case binary_to_term(Data) of
    {send_host, GameID, Msg} ->
      HandlerPID ! {get_host, self(), GameID},
      receive
        {host_is, _HID, HSOCK} ->
          HSOCK;
        _ ->
          {error, nohost}
      end,
      Msg;
    {send_all, GameID, Msg} ->
      Msg;
    {send_to_id, ReceiverID, Msg} ->
      HandlerPID ! {get_client_pid, self(), ReceiverID},
      receive
        {pid, SockPID} ->
          gen_tcp:send(SockPID, term_to_binary(Msg));
        _ ->
          {error, noid}
      end,
      term_to_binary({ok, delivered});
    {host_game, GameID} ->
      GameID;
    {join_game, GameID} ->
      GameID;
    {start_game, GameID} ->
      GameID;
    {enter, SenderID} ->
      HandlerPID ! {insert_client, Socket, SenderID};
    Dat ->
      Dat
  end;
process_data(Socket, Data, DictPID) ->
  Data.


convey_message(Socket, Data) when is_binary(Data) ->
  gen_tcp:send(Socket, Data);
convey_message(Socket, Data) ->
  gen_tcp:send(Socket, term_to_binary(Data)).

Erlang CLient (working):

connect(PortNo) ->
  {ok, Socket} = gen_tcp:connect("localhost", PortNo, [binary, {active, false}, {packet, 2}]),
  spawn(fun() -> recv(Socket) end),
  Socket.

connect(IP, PortNo) ->
  {ok, Socket} = gen_tcp:connect(IP, PortNo, [binary, {active, false}, {packet, 2}]),
  spawn(fun() -> recv(Socket) end),
  Socket.


send(Socket, Message) ->
  BinMsg = unicode:characters_to_binary(Message),
  io:format(Message ++ "~n"),
  io:format(BinMsg),
  gen_tcp:send(Socket, BinMsg).


recv(Socket) ->
  {ok, A} = gen_tcp:recv(Socket, 0),
  io:format("Received: ~p~n", [A]),
  recv(Socket).


disconnect(Socket) ->
  gen_tcp:close(Socket).

C# Client:

using UnityEngine;
using System;
using System.IO;
using System.Net.Sockets;

//using System.Globalization.NumberStyles;



public class NetworkController : MonoBehaviour

{

    public string tekst;

    public Transform nek;

    public byte[] bytesW = new byte[4];
    public byte[] test;

    public float[] qq = new float[4];

    internal Boolean socketReady = false;

    TcpClient mySocket;

    NetworkStream theStream;

    StreamWriter theWriter;

    BStreamReader theReader;

    String Host = "localhost";

    public Int32 Port;



    void Update()

    {

        string receivedText = readSocket();

        if (receivedText != "")

        {
            tekst = receivedText;
        }

    }



    void OnGUI()

    {
        if (!socketReady)

        {
            if (GUILayout.Button("Connect"))
            {
                setupSocket();
                writeSocket("serverStatus:");
            }
        }
         if (GUILayout.Button("Send"))
         {
             writeSocket("ala");
         }
         if (GUILayout.Button("Close"))
         {
             closeSocket();
         }

        GUI.Label(new Rect(0, 40, 1000, 400), tekst);
        if (socketReady)
        {


        }
    }

    void OnApplicationQuit()

    {

        closeSocket();

    }



    public void setupSocket()

    {

        try

        {


            mySocket = new TcpClient(Host, Port);

            theStream = mySocket.GetStream();

            theWriter = new StreamWriter(theStream);

            theReader = new StreamReader(theStream);

            socketReady = true;

        }



        catch (Exception e)

        {

            Debug.Log("Socket error: " + e);

        }

    }



    public void writeSocket(string theLine)

    {

        if (!socketReady)

            return;

        //byte[] foo = System.Text.Encoding.UTF8.GetBytes(theLine);
        String foo = theLine + "\r\n"; 
        Debug.Log(foo);
        test = System.Text.Encoding.UTF8.GetBytes(foo);



        theWriter.Write(test);
        theWriter.Flush();

    }
/*
    public void WriteToStream(string theLine)
    {
        byte[] len_bytes = BitConverter.GetBytes(length);

        if (BitConverter.IsLittleEndian)
        {
            Array.Reverse(len_bytes);
        }
        writer.Write(len_bytes);

        writer.Write(content);
    }
    */



    public String readSocket()

    {

        if (!socketReady)

            return "";

        if (theStream.DataAvailable)

            return theReader.ReadLine();

        //return theReader.ReadToEnd();

        return "";

    }



    public void closeSocket()

    {

        if (!socketReady)

            return;

        theWriter.Close();

        theReader.Close();

        mySocket.Close();

        socketReady = false;

    }
}

It's not ready yet but it should send basic message and it doesn't. As i told, I'm pretty new to Erlang so maybe I did something stupid

MrKaszu
  • 71
  • 8
  • I have no idea on how you erlang client is actually working because I can see `binary_to_term` on server side and I don't see `term_to_binary` on client side. Also, you using `{packet, 2}` which means Erlang expects fist two bytes of packet to be packet length, then it waits for `length` bytes, then strips off 2 byte header and then delivers the message. And by the way you are not using `gen_server` and other OTP practices. Sorry, but you'd better start with http://learnyousomeerlang.com/content – mynameisdaniil Dec 17 '15 at 12:16