MrKaszu
MrKaszu

Reputation: 71

Communicating Erlang server with c# program

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?

Upvotes: 3

Views: 1314

Answers (2)

MrKaszu
MrKaszu

Reputation: 71

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

Upvotes: 0

mynameisdaniil
mynameisdaniil

Reputation: 1156

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.

Upvotes: 1

Related Questions