我在Erlang中编写客户端/服务器系统并且无法连接它们。他们应该使用的节点存储在配置文件中,配置文件在启动时加载。他们还使用几种分布式数据结构。问题是,我首先启动数据结构,然后是服务器(工作正常),但是当我启动客户端时,它不会连接到任何东西并抛出异常。
服务器:
-module(server).
-export([startServer/0,loopServer/7,node/0,log/0,name/0]).
-compile({no_auto_import,[node/0]}).
-import(werkzeug, [get_config_value/2,lengthSL/1,logging/2,reset_timer/3,get_config_value/2]).
-import(erlang, [append/2]).
log() ->
erlang:list_to_atom(lists:concat(["Server@", node(),".log"])).
node() ->
{ok,Config} = file:consult("configs/server.cfg"),
{ok, Node} = werkzeug:get_config_value(node,Config),
Node.
name() ->
{ok,Config} = file:consult("configs/server.cfg"),
{ok, Name} = werkzeug:get_config_value(servername,Config),
Name.
%%Startet den Server und die darin enthaltene Schleife
startServer() ->
{ok,Config} = file:consult("configs/server.cfg"),
{ok, Name} = get_config_value(servername,Config),
%%CMEM initialisieren
{ok, RemTime} = get_config_value(cmemtime,Config),
CMEM = cmem:initCMEM(RemTime, log()),
%%HBQ-Prozess erzeugen
{ok,HBQName} = get_config_value(hbqname,Config),
{ok,HBQNode} = get_config_value(node,Config),
HBQ = {HBQName,HBQNode},
{ok,Serverzeit} = get_config_value(serverzeit,Config),
%%Zeitpunkt des Timer übergeben
{ok, Timer} = timer:send_after(round(RemTime * 1000),Name,delServer),
%%Prozess registrieren, Serverschleife mit allen Infos starten, plus NNr 1
ServerPid = spawn(?MODULE, loopServer, [Name,CMEM,HBQ,Timer,Serverzeit,Config,1]),
register(Name,ServerPid),
%%HBQ initialisieren
HBQ ! {ServerPid, {request,initHBQ}},
{Config,CMEM,HBQ,ServerPid}.
loopServer(Name,CMEM,HBQ,Timer,Serverzeit,Config,NNr) ->
receive
%%Client fragt neue Nachrichten ab, dazu wird aus CMEM die aktuelle NNr für
%%den Client angefordert und mit der ClientPID an die HBQ weitergegeben
{ClientPID,getmessages} ->
NewTimer = reset_timer(Timer,Serverzeit,delServer),
ClientNNr = cmem:getClientNNr(CMEM, ClientPID),
HBQ ! {self(), {request, deliverMSG, ClientNNr, ClientPID}},
logging(log(), lists:concat(["Server: Nachricht ", NNr, " wurde zugestellt.\n"])),
loopServer(Name,CMEM,HBQ,NewTimer,Serverzeit,Config,NNr);
%%Nachricht soll in HBQ verschoben werden
{dropmessage,[INNr,Msg,TSclientout]} ->
NewTimer = reset_timer(Timer,Serverzeit,delServer),
HBQ ! {self(), {request,pushHBQ,[INNr,Msg,TSclientout]}},
receive
{reply,ok} ->
logging(log(), lists:concat(["Server: Nachricht ", INNr, " wurde in HBQ eingefuegt.\n"]))
end,
loopServer(Name,CMEM,HBQ,NewTimer,Serverzeit,Config,NNr);
%%Client fragt naechste NNr ab, diese wird dem Zustand des Server entnommen
{ClientPID,getmsgid} ->
NewTimer = reset_timer(Timer,Serverzeit,delServer),
ClientPID ! {nid, NNr},
NewNNr = NNr + 1,
logging(log(), lists:concat(["Server: Nachrichtennumer ", NNr, " an ", erlang:pid_to_list(ClientPID), "gesendet.\n"])),
loopServer(Name,CMEM,HBQ,NewTimer,Serverzeit,Config,NewNNr);
%%Server beendet sich selbst und zugleich die HBQ
delServer ->
HBQ ! {self(),{request,delHBQ}},
receive
{reply,ok} ->
logging(log(), lists:concat([lists:concat(["Server: Downtime ", werkzeug:timeMilliSecond(), " von ", name() ,"\n"])])),
ok
end
end.
客户端:
-module(client).
-export([startClients/0,loopClient/4,spawnC/1,forLoop/3,mitServerVerbinden/6,configLaden/0]).
-import(werkzeug, [get_config_value/2]).
-import(timer, [apply_after/4]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Client %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%initClient
startClients() ->
Config = configLaden(),
{ok, ClientAnzahl} = werkzeug:get_config_value(clientanzahl, Config),
{ok, ServerName} = werkzeug:get_config_value(servername, Config),
{ok, ServerNode} = werkzeug:get_config_value(servernode, Config),
ServerPid = {ServerName,ServerNode},
forLoop(ClientAnzahl, fun client:spawnC/1, ServerPid).
%%Hilfsfunktion fuer for-Schleife: Zaehlt runter,
%%ruft Funktion auf und gibt Ergebnisse als Liste zurueck
forLoop(Clients, Spawn, SPid) -> forLoop(Clients, Spawn, SPid, []).
forLoop(0, _Spawn, _SPid, ClientListe) -> ClientListe;
forLoop(Clients, Spawn, SPid, ClientListe) ->
ClientListeNew = ClientListe ++ [Spawn(SPid)],
ClientsNew = Clients - 1,
forLoop(ClientsNew, Spawn, SPid, ClientListeNew).
%%Neuen ClientProzess erzeugen
spawnC(ServerPid) ->
Config = configLaden(),
{ok, Lebenszeit} = werkzeug:get_config_value(lebenszeit, Config),
{ok, Cookie} = werkzeug:get_config_value(cookie, Config),
{ok, ServerNode} = werkzeug:get_config_value(servernode, Config),
{ok, Wartezeit} = werkzeug:get_config_value(wartezeit, Config),
ClientPid = erlang:spawn(?MODULE, mitServerVerbinden, [ServerPid, [], [], Wartezeit, ServerNode, Cookie]),
timer:kill_after(Lebenszeit, ClientPid),
ClientPid.
%%mit Server Verbinden
mitServerVerbinden(ServerPid,Datei,NNummern,Wartezeit,ServerNode,Cookie) ->
erlang:set_cookie(ServerNode,Cookie),
pong = net_adm:ping(ServerNode),
loopClient(ServerPid,NNummern,Wartezeit,Datei).
%%loopClient
loopClient(ServerPid,NNummern,Wartezeit,Datei) ->
%%Client wird zum Redakteur
{NNummernNew, WartezeitNew, DateiNew} = nachrichtenSenden(5,ServerPid,NNummern,Wartezeit,Datei),
%%Client wird zum Leser
nachrichtenLesen(false, NNummernNew, ServerPid,DateiNew),
%%Methode ruft sich selbst wieder auf
loopClient(ServerPid, NNummernNew, WartezeitNew,DateiNew).
configLaden() ->
{ok, Config} = file:consult("configs/client.cfg"),
Config.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Redakteur %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%Nachricht soll vergessen werden
nachrichtenSenden(0,ServerPid,NNummern,Wartezeit,Datei) ->
%%Naechste NNr beim Server anfragen
ServerPid ! {self(),getmsgid},
receive
%%Server sendet NNr
{nid,NNr} ->
%%Logeintrag
werkzeug:logging(Datei,lists:concat([NNr, "te Nachricht um ", werkzeug:timeMilliSecond(), " vergessen zu senden. ******\n"]))
end,
WartezeitNew = wartezeitBerechnen(Wartezeit),
%%Rückgabewerte: Liste mit Nachrichtennummern fuer leser, neue Wartezeit, Logfile
{NNummern,WartezeitNew,Datei};
%%Nachrichtennummer anfragen und erhalten, Nachricht schreiben
nachrichtenSenden(NachrichtenVorVergessen,ServerPid,NNummern,Wartezeit,Datei) ->
Config = configLaden(),
{ok, ServerNode} = werkzeug:get_config_value(servernode, Config),
%%Naechste NNr beim Server anfragen
ServerPid ! {self(),getmsgid},
receive
%%Server sendet NNr
{nid,NNr} ->
%%Nachricht schreiben
Nachricht = nachrichtSchreiben(NNr),
%%NNr zur Liste hinzufuegen fuer Leser
NNummernNew = NNummern ++[NNr],
timer:sleep(Wartezeit),
%%Nachricht an Server senden
ServerPid ! {dropmessage,[NNr,Nachricht,erlang:now()]},
%%Logeintrag schreiben
werkzeug:logging(Datei,lists:concat([Nachricht, " gesendet"])),
%%Neue Wartezeit berechnen
WartezeitNew = wartezeitBerechnen(Wartezeit),
%%Zaehler dekrementieren
NachrichtenVorVergessenNew = NachrichtenVorVergessen -1,
%%Methode neu aufrufen
nachrichtenSenden(ServerPid,NNummernNew,WartezeitNew,NachrichtenVorVergessenNew,Datei)
end.
%%nachrichtSchreiben
nachrichtSchreiben(NNr) ->
Config = configLaden(),
{ok, Rechner} = werkzeug:get_config_value(rechner, Config),
{ok, Praktikumsgruppe} = werkzeug:get_config_value(praktikumsgruppe, Config),
{ok, Teamnummer} = werkzeug:get_config_value(teamnummer, Config),
lists:concat(["client@",Rechner, "_", Praktikumsgruppe, "_", Teamnummer, ": ", integer_to_list(NNr), "_te Nachricht. Sendezeit: ", werkzeug:timeMilliSecond()]).
%%Hilfsmethode: Intervall darf nicht kleiner als zwei Sekunden werden
minimumTime() -> 2000.
%%Berechnet das neue Zeitintervall, um die Haelfte groesser oder
%%kleiner als die alte Zeit, solange sie groesser gleich 2 Sekunden ist
wartezeitBerechnen(Wartezeit) ->
GroesserKleiner = werkzeug:bool_rand(),
HaelfteZeit = trunc(max(Wartezeit * 0.5, minimumTime())),
if
GroesserKleiner ->
Wartezeit + HaelfteZeit;
true ->
max(Wartezeit - HaelfteZeit, minimumTime())
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Leser %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
nachrichtenLesen(true,NNummern,ServerPid,Datei) -> ok;
nachrichtenLesen(false,NNummern,ServerPid,Datei) ->
ServerPid ! {self(),getmessages},
receive
{reply,Message,Terminated} ->
nachrichtInLogSchreiben(Message,NNummern,Datei),
nachrichtenLesen(Terminated,NNummern,ServerPid,Datei)
end.
nachrichtInLogSchreiben([NNr,Msg,TSclientout,TShbqin,TSdlqin,TSdlqout],NNummern,Datei) ->
Now = erlang:timestamp(),
DLQInTrue = werkzeug:validTS(TSdlqin),
DLQOutTrue = werkzeug:validTS(TSdlqout),
DLQInZukunft = werkzeug:lessTS(Now, TSdlqin),
DLQOutZukunft = werkzeug:lessTS(Now, TSdlqout),
MsgVonGleichemClient = msgVonGleichemClient(NNr, Msg, NNummern),
if
DLQInTrue and DLQInZukunft ->
Zeitunterschied = werkzeug:now2stringD(werkzeug:diffTS(TSdlqin, Now)),
MsgNew = MsgVonGleichemClient ++ ", Zeitunterschied: " ++ Zeitunterschied,
werkzeug:logging(Datei, MsgNew ++ "\n");
DLQOutTrue and DLQOutZukunft ->
Zeitunterschied = werkzeug:now2stringD(werkzeug:diffTS(TSdlqout, Now)),
MsgNew = MsgVonGleichemClient ++ ", Zeitunterschied: " ++ Zeitunterschied,
werkzeug:logging(Datei, MsgNew ++ "\n");
true ->
werkzeug:logging(Datei, MsgVonGleichemClient ++ "\n")
end.
msgVonGleichemClient(NNr,Msg,NNummern) ->
MsgVonGleichemClient = lists:member(NNr, NNummern),
if
MsgVonGleichemClient -> Msg ++ "*******";
true -> Msg
end.
服务器的配置文件:
{sizedlq,400}.
{servername,'serverpa'}.
{cmemtime,2000}.
{hbqname,'hbqpa'}.
{node,'hbqnode'}.
{serverzeit,50}.
客户端的配置文件:
{wartezeit,20}.
{lebenszeit,240}.
{pratikumsgruppe,'2'}.
{teamnummer,'02'}.
{servername,'serverpa'}.
{clientanzahl,5}.
{servernode,'servernode'}.
{cookie,pa}.
{rechner,'rechner'}.
还有分布式数据结构,它们本质上是队列,似乎工作正常:
HBQ:
-module(hbq).
-export([startHBQ/0, checkTransfer/6,loophbq/4]).
%% HBQ !{self(), {request,pushHBQ,[INNr,Msg,TSclientout]}}
startHBQ() ->
startHBQ("./configs/server.cfg").
%% Used for starting the HBQ with a custom config file
startHBQ(Configfile) ->
%%Config und Namen auslesen
Config = loadConfig(Configfile),
Config = loadConfig(Configfile),
%%{ok,Config} = file:consult("./config/server.cfg"),
{ok,HBQName} = werkzeug:get_config_value(hbqname,Config),
%%Prozess der HBQ starten
HBQPid = spawn(?MODULE,loophbq,[[],[],Config, 1]),
%%Prozess registrieren
register(HBQName,HBQPid),
HBQPid.
loophbq(HBQ,DLQ,Config,CurrNumber) ->
receive
%%initialisere HBQ und DLQ, sendet ok an Server
{ServerPid,{request,initHBQ}} ->
HBQList = [],
Log = log(Config,["HBQ>>> initialisiert worden von ", ServerPid, ".\n"]),
{ok,DLQSize} = werkzeug:get_config_file(sizedlq,Config),
DLQ = dlq:initDLQ(DLQSize,Log),
ServerPid ! {reply,ok},
loophbq(HBQList, DLQ ,Config, CurrNumber);
%%fuegt der Msg einen Zeitstempel hinzu, fuegt sie in HBQ ein, sendet ok
{ServerPid,{request,pushHBQ,[NNr,Msg,TSclientout]}} ->
TShbqin = werkzeug:timeMillliSecond(),
NewMessage = {NNr,lists:concat(Msg,["HBQ in: ",TShbqin])},
Log = log(Config,["HBQ>>> Nachricht ",NNr, "in HBQ eingefügt.\n"]),
NewHBQ = [HBQ|NewMessage],
lists:sort(fun({A,_},{B,_}) -> A=<B end),
checkTransfer(NewHBQ, CurrNumber, Config, DLQ,TSclientout,TShbqin),
ServerPid ! {reply,ok},
loophbq(NewHBQ, DLQ ,Config, CurrNumber);
%%DLQ soll über HBQ Nachricht an Client senden
{ServerPid,{request,deliverMSG,NNr,ToClient}} ->
{ok, HBQName} = werkzeug:get_config_value(hbqname, Config),
Log = lists:concat(["HB-DLQ@", HBQName, ".log"]),
Datei = erlang:list_to_atom(Log),
log(Config, ["HBQ>>> dlq:delivermsg", NNr, pid_to_list(ToClient), "\n"]),
NNrDLQ = dlq:deliverMSG(NNr, ToClient, DLQ, Datei),
ServerPid ! {reply, NNrDLQ},
loophbq(HBQ, DLQ ,Config, CurrNumber);
%%Terminiert Prozess der DLQ
{ServerPid,{request,delHBQ}} ->
ServerPid ! {reply,ok},
ok
end.
%%CheckTransfer
checkTransfer(HBQ, Number, Config, [DLQ,Size],TSclientout,TShbqin) ->
Datei = log(Config, ["HBQ>>> Nachricht Nr ", Number, " wird in DLQ uebertragen\n"]),
FoundMatch = lists:keyfind(Number, 1, HBQ),
if FoundMatch =:= false ->
if(length(HBQ)>Size*0.667) ->
[{MinNNr,_}|_] = HBQ,
%wegen sort ist immer die kleinste NNr vorne in der Liste
NewMessage = {MinNNr-1,lists:concat(["Weiß noch nicht was hier reinsoll: ",werkzeug:timeMilliSecond()])},
NewHBQ = lists:append([{MinNNr-1,NewMessage}], HBQ),
log(Config,["HBQ>>> Fehlernachricht fuer Nachrichten ",Number," bis",MinNNr-1, " generiert.\n"]),
NewNumber = MinNNr-1,
checkTransfer(NewHBQ, NewNumber, Config,[DLQ,Size],TSclientout,TShbqin);
true -> ok
end;
true ->
{MatchNr,Msg} = FoundMatch,
dlq:push2DLQ([MatchNr, Msg, TSclientout, TShbqin], DLQ, Datei),
lists:delete(FoundMatch, HBQ),
checkTransfer(HBQ, Number+1, Config,[DLQ,Size],TSclientout,TShbqin)
end.
log(Config,Message) ->
{ok,HBQNode} = werkzeug:get_config_value(node,Config),
DateiName = lists:concat(["HB-DLQ@", HBQNode,".log"]),
Datei = erlang:list_to_atom(DateiName),
werkzeug:logging(Datei, lists:concat(Message)),
Datei.
%%Dummy-Nachrichten schreiben um Lücken in der HBQ zu füllen wenn sie kritische Größe erreicht
%%Methode um zu prüfen, ob Nachrichten in DLQ geschoben werden können, das dann auch machen
loadConfig(Configfile) ->
{ok, Config} = file:consult(Configfile),
Config.
DLQ:
-module(dlq).
-export([initDLQ/2, delDLQ/1, expectedNr/1, push2DLQ/3, deliverMSG/4]).
%%Initialisiert DLQ mit Kapazitaet Size, Log in Datei
initDLQ(Size, Datei) ->
werkzeug:logging(Datei,lists:concat(["DLQ>>> intitialisiert worden mit Groesse ",Size,".\n"])),
[[],Size].
%%Loescht DLQ
delDLQ(_Queue) -> ok.
%%Liefert NNr die als naechstes in DLQ gespeichert werden kann
%%expectedNr(Queue)
expectedNr([[], _Size]) -> 1;
expectedNr([[[NNr, _Msg, _TSclientout, _TShbqin, _TSdlqin] | _Rest], _Size]) -> NNr + 1.
%%Speichert Nachricht in DLQ, fuegt Zeitstempel hinzu, Rueckgabe: Modifizierte DLQ
%%push2DLQ([NNr, Msg, TSclientout, TShbqin], Queue, Datei)
%%Fehlt noch: Abfrage, ob das die passende Nummer ist!
push2DLQ([NNr, Msg, TSclientout, TShbqin], [DLQ,Size], Datei) ->
if
length(DLQ) < Size ->
werkzeug:logging(Datei, lists:concat(["DLQ>>> Nachricht ", NNr, " in DLQ eingefügt.\n"])),
[[[NNr, Msg, TSclientout, TShbqin, erlang:now()] | DLQ], Size];
length(DLQ) =:= Size ->
[LastNNr, _Msg, _TSclientout, _TShbqin, _TSdlqin] = lists:last(DLQ),
werkzeug:logging(Datei, lists:concat(["DLQ>>> Nachricht ", LastNNr, " aus DLQ entfernt.\n"])),
werkzeug:logging(Datei, lists:concat(["DLQ>>> Nachricht ", NNr, " in DLQ eingefügt.\n"])),
[[[NNr, Msg, TSclientout, TShbqin, erlang:now()] | lists:droplast(DLQ)], Size]
end.
%%Sendet Nachricht an Leser-Client
deliverMSG(MSGNr, ClientPID, Queue, Datei) ->
%%Aendern: MSGNer = -1, flag am Ende der Reply (siehe zeile 42)
[{NewestNr,Msg}|Rest] = Queue,
if MSGNr > NewestNr ->
DummyMessage = {-1,lists:concat(["DLQ in: ",werkzeug:timeMilliSecond()])}, %% -1 Flag
werkzeug:logging(Datei, lists:concat(["DLQ>>> DummyNachricht fuer ",MSGNr," an Client ",ClientPID, " ausgeliefert.\n"])),
ClientPID ! {reply,Msg,true}
end,
%%%%%%Ab hier noch aendern bzgl Flag
FoundMatch = lists:keyfind(MSGNr, 1, Queue),
if
FoundMatch =:= false ->
deliverMSG(MSGNr+1, ClientPID, Queue, Datei),
if
MSGNr =:= NewestNr ->
{Number,Msg} = FoundMatch,
NewMessage = {Number,lists:concat(Msg,["DLQ in: ",werkzeug:timeMilliSecond()],-1)},
werkzeug:logging(Datei, lists:concat(["DLQ>>> Nachricht ", Number, " an Client ",ClientPID, " ausgeliefert.\n"])),
ClientPID ! {reply,Msg,false};
true ->
{Number,Msg} = FoundMatch,
NewMessage = {Number,lists:concat(Msg,["DLQ in: ",werkzeug:timeMilliSecond()],0)},
werkzeug:logging(Datei, lists:concat(["DLQ>>> Nachricht ", Number, " an Client ",ClientPID, " ausgeliefert.\n"])),
ClientPID ! {reply,Msg,false}
end;
true ->
ok
end.
CMEM:
-module(cmem).
-export([initCMEM/2, delCMEM/1, updateClient/4, getClientNNr/2]).
-import(werkzeug, [get_config_value/2,lengthSL/1,logging/2,reset_timer/3,get_config_value/2,getUTC/0]).
%%Initialisiert CMEM
initCMEM(RemTime, Datei) -> werkzeug:logging(Datei, lists:concat(["CMEM>>> initialisiert mit ", RemTime, " Sekunden\n"])),
[[], RemTime].
%%Loescht CMEM
delCMEM(_CMEM) -> ok.
%%Speichert/aktualisiert Client und NNr im CMEM,
updateClient([CMEM, RemTime], ClientID, NNr, Datei) ->
ClientTS = getUTC(),
logging(Datei, lists:concat(["CMEM>>> Client ", ClientID, " wird aktualisiert.\n"])),
[lists:keystore(ClientID, 1, CMEM, {ClientID, NNr, ClientTS}), RemTime].
%%Gibt naechste vom Client erwartete NNr zurueck
%%Es wird geprueft ob Client in der Liste steht und dann
%%mit diesem Wissen eine Hilfsfunktion aufgerufen
getClientNNr([CMEM, RemTime], ClientID) ->
ClientBekannt = lists:keymember(ClientID, 1, CMEM),
isClientKnown([CMEM, RemTime], ClientID, ClientBekannt).
%%Client ist nicht bekannt: Naechste NNr = 1
isClientKnown(_CMEM, _ClientID, false) -> 1;
%% Der Client ist bekannt.
%%Zeit noch nicht abgelaufen: Gibt naechste Nummer aus
%%Zeit abgelaufen: Naechste NNr = 1
isClientKnown([CMEM, RemTime], ClientID, true) ->
{ClientID, NNr, ClientTS} = lists:keyfind(ClientID, 1, CMEM),
RemainingTime = ClientTS + RemTime,
Now = getUTC(),
if
RemainingTime >= Now -> NNr + 1;
true -> 1
end.
客户端应该联系服务器向其发送消息,服务器将其放入数据结构中,并在适当的情况下将其发送回客户端。 问题是,当我编译它们时,启动HBQ,然后启动服务器,然后启动客户端,我得到
=ERROR REPORT==== 18-Apr-2017::10:25:46 ===
Error in process <0.104.0> with exit value: {distribution_not_started,[{auth,set_cookie,2,[{file,"auth.erl"},{line,119}]},{client,mitServerVerbinden,6,[{file,"client.erl"},{line,42}]}]}
显然,客户端没有连接到服务器存在问题。这是我第一次使用Erlang和分布式系统,所以我不知道发生了什么。
将节点和cookie放入配置并告诉系统组件在那里看是不够的?
答案 0 :(得分:0)
当调用它的erlang VM没有名称时,auth模块会返回错误distribution_not_started
。确保在启动erlang时传递-sname
或-name
标志,例如:
erl -sname test