Entwickler-Ecke

Internet / Netzwerk - TCP Chat: Server kann keine Nachrichten senden


jakobwenzel - Do 01.09.05 20:22
Titel: TCP Chat: Server kann keine Nachrichten senden
Ich bin dabei mit den Indys (version 10) einen TCP-Chat (TidTCPServer und TidTCPClient) zu programmieren. Server und Client sind in einem Programm, man muss beim Starten auswählen, ob man Client oder Server ist. Die Clients können schon Nachrichten senden, auch die Meldung, dass ein neuer Benutzer hinzugekommen ist funktioniert schon. Da idTCPServer1.Contexts.LockList irgendwie das Programm lahmlegt, wenn man es z.B. in einer Button-Click-Routine aufruft, habe ich mir gedacht, ich könnte die vorhandene idTcpClient-Komponente nutzen. Aber jetzt ist es so, dass der Host nur senden kann, wenn kein Client verbunden ist. Wenn ein Client verbunden ist, kann der Host keine Nachrichten mehr senden (keine Fehlermeldung, alles wird korrekt ausgeführt, nur keine Nachricht). Hat der Host Nachrichten gesendet, bevor der Client sich anmeldete, kann der Client auch keine Nachrichten mehr senden. Warum geht das nicht?

P.S.:Ich habe mal das Projekt drangehängt...


GTA-Place - Fr 02.09.05 13:02

Kannst du bitte den Source hier reinstellen.
Ich habe keine Lust für jedes Problem das Projekt erst herunterzuladen.
Danke.


Udontknow - Fr 02.09.05 13:07

Hallo!

Schau mal in die Open-Source-Units Sparte, da gibt´s die SimpleTCP-Units nun auch für Indy10. Da hat man die Probleme mit dem Synchronisieren etc. nicht.

Cu,
Udontknow


jakobwenzel - Fr 02.09.05 15:06

@GTA-Place:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
160:
161:
162:
163:
164:
165:
166:
167:
168:
169:
170:
171:
172:
173:
174:
175:
176:
177:
178:
179:
180:
181:
182:
183:
184:
185:
186:
187:
188:
189:
190:
unit MainUnit;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Menus, IdServerIOHandler, IdServerIOHandlerSocket,
  IdServerIOHandlerStack, IdIOHandler, IdIOHandlerSocket, IdIOHandlerStack,
  IdTCPServer, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient,
  ExtCtrls, Mask, idStack, idcontext;

const
  Join    = 'Joi ';
  Msg     = 'Msg ';
  trenner :String'|';


type
  TForm2 = class(TForm)
    IdTCPClient: TIdTCPClient;
    IdTCPServer: TIdTCPServer;
    IdIOHandlerStack: TIdIOHandlerStack;
    IdServerIOHandlerStack: TIdServerIOHandlerStack;
    Port: TLabel;
    EditPort: TEdit;
    RadioGroupType: TRadioGroup;
    LabelNick: TLabel;
    EditNick: TEdit;
    LabelNachricht: TLabel;
    EditMessage: TEdit;
    ButtonLogIn: TButton;
    ButtonSend: TButton;
    ListBox1: TListBox;
    Label1: TLabel;
    MaskEditHostIp: TMaskEdit;
    Timer1: TTimer;
    procedure ButtonSendClick(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
    procedure IdTCPServerExecute(AContext: TIdContext);
    procedure ButtonLogInClick(Sender: TObject);
    procedure RadioGroupTypeClick(Sender: TObject);
    procedure EditPortChange(Sender: TObject);
    procedure EditPortKeyPress(Sender: TObject; var Key: Char);
    procedure DisableLoginButtons;
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

var
  Form2: TForm2;

implementation

{$R *.dfm}

procedure TForm2.DisableLoginButtons;
begin
  ButtonLogIn.Enabled:=false;
  EditPort.Enabled:=false;
  EditNick.Enabled:=false;
  MaskEditHostIp.Enabled:=false;
  RadioGroupType.Enabled:=false;
end;

procedure TForm2.EditPortKeyPress(Sender: TObject; var Key: Char);
begin
  if not (Key in ['0'..'9',#8]) then begin
    Beep;
    Key:=#0;
  end;
end;

procedure TForm2.EditPortChange(Sender: TObject);
begin
  IdTCPClient.Port:=StrToInt(EditPort.Text);
  IdTCPServer.DefaultPort:=StrToInt(EditPort.Text);
end;

procedure TForm2.RadioGroupTypeClick(Sender: TObject);
begin
  if RadioGroupType.ItemIndex=1 then begin
    MaskEditHostIp.Enabled:=true;
    MaskEditHostIp.Color:=clWindow;
  end else begin
    MaskEditHostIp.Enabled:=false;
    MaskEditHostIp.Color:=clBtnFace;
  end;
end;

procedure TForm2.ButtonLogInClick(Sender: TObject);
var
  str:String;
begin
  (Sender as TButton).Caption:='Verbinde...';
  try
    try
      if RadioGroupType.ItemIndex=0 Then begin //Host
        IdTCPServer.Active:=true;
        DisableLoginButtons;
        IdTCPClient.Host:='127.0.0.1';
        IdTcpClient.Connect;
      end else begin                           //Client
        IdTCPClient.Host:=MaskEditHostIp.Text;
        IdTCPClient.Connect;
        DisableLoginButtons;
        IdIOHandlerStack.WriteLn(Join+EditNick.Text);
        Timer1.Enabled:=true;
      end;
    except
      on E: EidSocketError do begin
        case E.LastError of
          10061: str:='Auf der Angegebenen Ip läuft unter dem angegebenen Port kein Server!';
          10060: str:='Die angegebene IP exisitiert nicht.';
        else str:='Konnte nicht verbinden.'
        end;
        MessageDlg(str,mtError,[mbOK],0);
      end;
    end;
  finally
    (Sender as TButton).Caption:='Einloggen';
  end;
end;

function Cut(s:String;start,count:Integer):String;
var
  i:Integer;
  nstr:String;
begin
  if start>Length(s) then result:=s;
  if (start+count>Length(s)) or (count<=0Then
   count:=Length(s)-start;
  nstr:='';
  for i:=start To (start+count) do begin
    nstr:=nstr+s[i];
  end;
  result:=nstr;
end;

procedure TForm2.IdTCPServerExecute(AContext: TIdContext);
var
  lRow, lCmd:String;
  list:TList;
  i:Integer;
  Row,nick,nachricht:String;
begin
  lRow:=Trim(AContext.Connection.IOHandler.ReadLn);
  lCmd:=lRow;
  SetLength(lCmd,4);
  If AnsiSameText(lCmd, Join) Then begin
    List := idTcpServer.Contexts.LockList;
    row:= (Cut(lRow,5,-1))+' hat den Chat betreten.';
    for i:=0 to List.Count-1 do
      tIdContext(List.Items[i]).Connection.IOHandler.WriteLn(row);
    ListBox1.AddItem(row,nil);
  end else if AnsiSameText(lCmd, Msg) then begin
    nick:=Cut(lRow, 5, (Pos(Trenner,lRow)-6));
    nachricht:=Cut(lRow,(Pos(Trenner,lRow)+1),-1);
    List := idTcpServer.Contexts.LockList;
    row:= nick+': '+nachricht;
    for i:=0 to List.Count-1 do
      tIdContext(List.Items[i]).Connection.IOHandler.WriteLn(row);
    ListBox1.AddItem(row,nil);
  end;
end;

procedure TForm2.Timer1Timer(Sender: TObject);
begin
  while IdIOHandlerStack.Readable(0do ListBox1.AddItem(IdIOHandlerStack.ReadLn,nil);
end;

procedure TForm2.ButtonSendClick(Sender: TObject);
var
  list:TList;
  row:String;
  i:Integer;
begin
//  if RadioGroupType.ItemIndex=1 Then
    IdIOHandlerStack.WriteLn(Msg+EditNick.Text+Trenner+EditMessage.Text);
{  else begin
    row:= EditNick.Text+': '+EditMessage.Text;
    list := idTcpServer.Contexts.LockList;
    for i:=0 to List.Count-1 do
      tIdContext(List.Items[i]).Connection.IOHandler.WriteLn(row);
    ListBox1.AddItem(row,nil);
  end;}

end;

end.


@Udontknow: Ich schau mal...


Udontknow - Fr 02.09.05 15:12

Hallo!

Habe kurz den Code überflogen:

In deiner Execute-Routine rufst du gar nicht Unlocklist auf. Also bleibt die Liste auf ewig gesperrt für andere Threads wie die TCP-Input-Threads...

[Werbung]Das Problem hättest du mit den SimpleTCP-Sachen nicht... :wink: [/Werbung]

Cu,
Udontknow


jakobwenzel - Fr 02.09.05 15:41

Danke!! Das war der Fehler!!! :D :D :D
Ich hab die Indys erst seit zwei Tagen und kann halt noch nicht so gut damit umgehen. Deshalb will ich es auch selber machen und nicht die SimpleTcpSachen nutzen.


Udontknow - Fr 02.09.05 15:48

Deine Vorgehensweise ist aber trotzdem nicht threadsicher. Du erreichst zwar mit Locklist/Unlocklist eine Synchronisation mit den anderen InputThreads, nicht aber mit dem VCL-Hauptthread. Trotzdem greifst du in der Execute-Routine auf Steuerelemente zu. Alle Steuerelemente sind nicht von sich aus threadsicher, nur der VCL-Hauptthread darf sie manipulieren! Ansonsten kann alles passieren, von Zugriffsschutzverletzungen bis zum kompletten Programmabbruch.

Schau mal in der Delphi-Hilfe unter Synchronize, wie man das hinbekommt. Alternativ kannst du natürlich (Achtung, wieder Werbung!) den Code der SimpleTCP-Komponenten begutachten, die das ja berücksichtigen...

Cu,
Udontknow